Unverified Commit 3edde1fc authored by alexpott's avatar alexpott

Issue #2881522 by mondrake, tstoeckler, daffie, alexpott, amateescu, catch:...

Issue #2881522 by mondrake, tstoeckler, daffie, alexpott, amateescu, catch: Add a Schema::findPrimaryKeyColumns method to remove database specific logic from test
parent f9f81a30
......@@ -522,6 +522,17 @@ public function dropPrimaryKey($table) {
return TRUE;
}
/**
* {@inheritdoc}
*/
protected function findPrimaryKeyColumns($table) {
if (!$this->tableExists($table)) {
return FALSE;
}
$result = $this->connection->query("SHOW KEYS FROM {" . $table . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
return array_keys($result);
}
/**
* {@inheritdoc}
*/
......
......@@ -715,6 +715,29 @@ public function dropPrimaryKey($table) {
return TRUE;
}
/**
* {@inheritdoc}
*/
protected function findPrimaryKeyColumns($table) {
if (!$this->tableExists($table)) {
return FALSE;
}
// Fetch the 'indkey' column from 'pg_index' to figure out the order of the
// primary key.
// @todo Use 'array_position()' to be able to perform the ordering in SQL
// directly when 9.5 is the minimum PostgreSQL version.
$result = $this->connection->query("SELECT a.attname, i.indkey FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = '{" . $table . "}'::regclass AND i.indisprimary")->fetchAllKeyed();
if (!$result) {
return [];
}
$order = explode(' ', reset($result));
$columns = array_combine($order, array_keys($result));
ksort($columns);
return array_values($columns);
}
/**
* {@inheritdoc}
*/
......
......@@ -501,14 +501,21 @@ protected function introspectSchema($table) {
if ($length) {
$schema['fields'][$row->name]['length'] = $length;
}
// $row->pk contains a number that reflects the primary key order. We
// use that as the key and sort (by key) below to return the primary key
// in the same order that it is stored in.
if ($row->pk) {
$schema['primary key'][] = $row->name;
$schema['primary key'][$row->pk] = $row->name;
}
}
else {
throw new \Exception("Unable to parse the column type " . $row->type);
}
}
ksort($schema['primary key']);
// Re-key the array because $row->pk starts counting at 1.
$schema['primary key'] = array_values($schema['primary key']);
$indexes = [];
$result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_list(' . $info['table'] . ')');
foreach ($result as $row) {
......@@ -741,6 +748,17 @@ public function dropPrimaryKey($table) {
return TRUE;
}
/**
* {@inheritdoc}
*/
protected function findPrimaryKeyColumns($table) {
if (!$this->tableExists($table)) {
return FALSE;
}
$schema = $this->introspectSchema($table);
return $schema['primary key'];
}
/**
* {@inheritdoc}
*/
......
......@@ -408,6 +408,26 @@ public function fieldExists($table, $column) {
*/
abstract public function dropPrimaryKey($table);
/**
* Finds the primary key columns of a table, from the database.
*
* @param string $table
* The name of the table.
*
* @return string[]|false
* A simple array with the names of the columns composing the table's
* primary key, or FALSE if the table does not exist.
*
* @throws \RuntimeException
* If the driver does not override this method.
*/
protected function findPrimaryKeyColumns($table) {
if (!$this->tableExists($table)) {
return FALSE;
}
throw new \RuntimeException("The '" . $this->connection->driver() . "' database driver does not implement " . __METHOD__);
}
/**
* Add a unique key.
*
......
......@@ -12,6 +12,8 @@
/**
* Tests table creation and modification via the schema API.
*
* @coversDefaultClass \Drupal\Core\Database\Schema
*
* @group Database
*/
class SchemaTest extends KernelTestBase {
......@@ -25,6 +27,8 @@ class SchemaTest extends KernelTestBase {
* Tests database interactions.
*/
public function testSchema() {
$schema = Database::getConnection()->schema();
// Try creating a table.
$table_specification = [
'description' => 'Schema table description may contain "quotes" and could be long—very long indeed.',
......@@ -92,19 +96,19 @@ public function testSchema() {
$this->assertFalse($this->tryInsert(), 'Insert without a default failed.');
// Test for fake index and test for the boolean result of indexExists().
$index_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
$index_exists = $schema->indexExists('test_table', 'test_field');
$this->assertIdentical($index_exists, FALSE, 'Fake index does not exist');
// Add index.
db_add_index('test_table', 'test_field', ['test_field'], $table_specification);
// Test for created index and test for the boolean result of indexExists().
$index_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
$index_exists = $schema->indexExists('test_table', 'test_field');
$this->assertIdentical($index_exists, TRUE, 'Index created.');
// Rename the table.
db_rename_table('test_table', 'test_table2');
// Index should be renamed.
$index_exists = Database::getConnection()->schema()->indexExists('test_table2', 'test_field');
$index_exists = $schema->indexExists('test_table2', 'test_field');
$this->assertTrue($index_exists, 'Index was renamed.');
// We need the default so that we can insert after the rename.
......@@ -149,7 +153,10 @@ public function testSchema() {
db_field_set_default('test_table', 'test_field', 0);
db_add_field('test_table', 'test_serial', ['type' => 'serial', 'not null' => TRUE], ['primary key' => ['test_serial']]);
$this->assertPrimaryKeyColumns('test_table', ['test_serial']);
// Test the primary key columns.
$method = new \ReflectionMethod(get_class($schema), 'findPrimaryKeyColumns');
$method->setAccessible(TRUE);
$this->assertSame(['test_serial'], $method->invoke($schema, 'test_table'));
$this->assertTrue($this->tryInsert(), 'Insert with a serial succeeded.');
$max1 = db_query('SELECT MAX(test_serial) FROM {test_table}')->fetchField();
......@@ -163,7 +170,8 @@ public function testSchema() {
// Test adding a new column and form a composite primary key with it.
db_add_field('test_table', 'test_composite_primary_key', ['type' => 'int', 'not null' => TRUE, 'default' => 0], ['primary key' => ['test_serial', 'test_composite_primary_key']]);
$this->assertPrimaryKeyColumns('test_table', ['test_serial', 'test_composite_primary_key']);
// Test the primary key columns.
$this->assertSame(['test_serial', 'test_composite_primary_key'], $method->invoke($schema, 'test_table'));
// Test renaming of keys and constraints.
db_drop_table('test_table');
......@@ -191,17 +199,17 @@ public function testSchema() {
// Test for existing primary and unique keys.
switch ($db_type) {
case 'pgsql':
$primary_key_exists = Database::getConnection()->schema()->constraintExists('test_table', '__pkey');
$unique_key_exists = Database::getConnection()->schema()->constraintExists('test_table', 'test_field' . '__key');
$primary_key_exists = $schema->constraintExists('test_table', '__pkey');
$unique_key_exists = $schema->constraintExists('test_table', 'test_field' . '__key');
break;
case 'sqlite':
// SQLite does not create a standalone index for primary keys.
$primary_key_exists = TRUE;
$unique_key_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
$unique_key_exists = $schema->indexExists('test_table', 'test_field');
break;
default:
$primary_key_exists = Database::getConnection()->schema()->indexExists('test_table', 'PRIMARY');
$unique_key_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
$primary_key_exists = $schema->indexExists('test_table', 'PRIMARY');
$unique_key_exists = $schema->indexExists('test_table', 'test_field');
break;
}
$this->assertIdentical($primary_key_exists, TRUE, 'Primary key created.');
......@@ -212,17 +220,17 @@ public function testSchema() {
// Test for renamed primary and unique keys.
switch ($db_type) {
case 'pgsql':
$renamed_primary_key_exists = Database::getConnection()->schema()->constraintExists('test_table2', '__pkey');
$renamed_unique_key_exists = Database::getConnection()->schema()->constraintExists('test_table2', 'test_field' . '__key');
$renamed_primary_key_exists = $schema->constraintExists('test_table2', '__pkey');
$renamed_unique_key_exists = $schema->constraintExists('test_table2', 'test_field' . '__key');
break;
case 'sqlite':
// SQLite does not create a standalone index for primary keys.
$renamed_primary_key_exists = TRUE;
$renamed_unique_key_exists = Database::getConnection()->schema()->indexExists('test_table2', 'test_field');
$renamed_unique_key_exists = $schema->indexExists('test_table2', 'test_field');
break;
default:
$renamed_primary_key_exists = Database::getConnection()->schema()->indexExists('test_table2', 'PRIMARY');
$renamed_unique_key_exists = Database::getConnection()->schema()->indexExists('test_table2', 'test_field');
$renamed_primary_key_exists = $schema->indexExists('test_table2', 'PRIMARY');
$renamed_unique_key_exists = $schema->indexExists('test_table2', 'test_field');
break;
}
$this->assertIdentical($renamed_primary_key_exists, TRUE, 'Primary key was renamed.');
......@@ -231,8 +239,8 @@ public function testSchema() {
// For PostgreSQL check in addition that sequence was renamed.
if ($db_type == 'pgsql') {
// Get information about new table.
$info = Database::getConnection()->schema()->queryTableInformation('test_table2');
$sequence_name = Database::getConnection()->schema()->prefixNonTable('test_table2', 'id', 'seq');
$info = $schema->queryTableInformation('test_table2');
$sequence_name = $schema->prefixNonTable('test_table2', 'id', 'seq');
$this->assertEqual($sequence_name, current($info->sequences), 'Sequence was renamed.');
}
......@@ -791,6 +799,125 @@ protected function assertFieldChange($old_spec, $new_spec, $test_data = NULL) {
db_drop_table($table_name);
}
/**
* @covers ::findPrimaryKeyColumns
*/
public function testFindPrimaryKeyColumns() {
$schema = Database::getConnection()->schema();
$method = new \ReflectionMethod(get_class($schema), 'findPrimaryKeyColumns');
$method->setAccessible(TRUE);
// Test with single column primary key.
$schema->createTable('table_with_pk_0', [
'description' => 'Table with primary key.',
'fields' => [
'id' => [
'type' => 'int',
'not null' => TRUE,
],
'test_field' => [
'type' => 'int',
'not null' => TRUE,
],
],
'primary key' => ['id'],
]);
$this->assertSame(['id'], $method->invoke($schema, 'table_with_pk_0'));
// Test with multiple column primary key.
$schema->createTable('table_with_pk_1', [
'description' => 'Table with primary key with multiple columns.',
'fields' => [
'id0' => [
'type' => 'int',
'not null' => TRUE,
],
'id1' => [
'type' => 'int',
'not null' => TRUE,
],
'test_field' => [
'type' => 'int',
'not null' => TRUE,
],
],
'primary key' => ['id0', 'id1'],
]);
$this->assertSame(['id0', 'id1'], $method->invoke($schema, 'table_with_pk_1'));
// Test with multiple column primary key and not being the first column of
// the table definition.
$schema->createTable('table_with_pk_2', [
'description' => 'Table with primary key with multiple columns at the end and in reverted sequence.',
'fields' => [
'test_field_1' => [
'type' => 'int',
'not null' => TRUE,
],
'test_field_2' => [
'type' => 'int',
'not null' => TRUE,
],
'id3' => [
'type' => 'int',
'not null' => TRUE,
],
'id4' => [
'type' => 'int',
'not null' => TRUE,
],
],
'primary key' => ['id4', 'id3'],
]);
$this->assertSame(['id4', 'id3'], $method->invoke($schema, 'table_with_pk_2'));
// Test with multiple column primary key in a different order. For the
// PostgreSQL and the SQLite drivers is sorting used to get the primary key
// columns in the right order.
$schema->createTable('table_with_pk_3', [
'description' => 'Table with primary key with multiple columns at the end and in reverted sequence.',
'fields' => [
'test_field_1' => [
'type' => 'int',
'not null' => TRUE,
],
'test_field_2' => [
'type' => 'int',
'not null' => TRUE,
],
'id3' => [
'type' => 'int',
'not null' => TRUE,
],
'id4' => [
'type' => 'int',
'not null' => TRUE,
],
],
'primary key' => ['id3', 'test_field_2', 'id4'],
]);
$this->assertSame(['id3', 'test_field_2', 'id4'], $method->invoke($schema, 'table_with_pk_3'));
// Test with table without a primary key.
$schema->createTable('table_without_pk', [
'description' => 'Table without primary key.',
'fields' => [
'id' => [
'type' => 'int',
'not null' => TRUE,
],
'test_field' => [
'type' => 'int',
'not null' => TRUE,
],
],
]);
$this->assertSame([], $method->invoke($schema, 'table_without_pk'));
// Test with non existing table.
$this->assertFalse($method->invoke($schema, 'non_existing_table'));
}
/**
* Tests the findTables() method.
*/
......@@ -849,46 +976,4 @@ public function testFindTables() {
Database::setActiveConnection('default');
}
/**
* Tests the primary keys of a table.
*
* @param string $table_name
* The name of the table to check.
* @param array $primary_key
* The expected key column specifier for a table's primary key.
*/
protected function assertPrimaryKeyColumns($table_name, array $primary_key = []) {
$db_type = Database::getConnection()->databaseType();
switch ($db_type) {
case 'mysql':
$result = Database::getConnection()->query("SHOW KEYS FROM {" . $table_name . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
$this->assertSame($primary_key, array_keys($result));
break;
case 'pgsql':
$result = Database::getConnection()->query("SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS data_type
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = '{" . $table_name . "}'::regclass AND i.indisprimary")
->fetchAllAssoc('attname');
$this->assertSame($primary_key, array_keys($result));
break;
case 'sqlite':
// For SQLite we need access to the protected
// \Drupal\Core\Database\Driver\sqlite\Schema::introspectSchema() method
// because we have no other way of getting the table prefixes needed for
// running a straight PRAGMA query.
$schema_object = Database::getConnection()->schema();
$reflection = new \ReflectionMethod($schema_object, 'introspectSchema');
$reflection->setAccessible(TRUE);
$table_info = $reflection->invoke($schema_object, $table_name);
$this->assertSame($primary_key, $table_info['primary key']);
break;
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment