Commit 431b5b4c authored by plach's avatar plach
Browse files

Issue #3031479 by amateescu, jibran, mondrake:...

Issue #3031479 by amateescu, jibran, mondrake: TaxonomyVocabularyHierarchyUpdateTest::testTaxonomyUpdateParents() is prone to breaking on PostgreSQL
parent aa003662
......@@ -612,6 +612,36 @@ public function dropIndex($table, $name) {
return TRUE;
}
/**
* {@inheritdoc}
*/
protected function introspectIndexSchema($table) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException("The table $table doesn't exist.");
}
$index_schema = [
'primary key' => [],
'unique keys' => [],
'indexes' => [],
];
$result = $this->connection->query('SHOW INDEX FROM {' . $table . '}')->fetchAll();
foreach ($result as $row) {
if ($row->Key_name === 'PRIMARY') {
$index_schema['primary key'][] = $row->Column_name;
}
elseif ($row->Non_unique == 0) {
$index_schema['unique keys'][$row->Key_name][] = $row->Column_name;
}
else {
$index_schema['indexes'][$row->Key_name][] = $row->Column_name;
}
}
return $index_schema;
}
/**
* {@inheritdoc}
*/
......
......@@ -881,6 +881,38 @@ public function dropIndex($table, $name) {
return TRUE;
}
/**
* {@inheritdoc}
*/
protected function introspectIndexSchema($table) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException("The table $table doesn't exist.");
}
$index_schema = [
'primary key' => [],
'unique keys' => [],
'indexes' => [],
];
$result = $this->connection->query("SELECT i.relname AS index_name, a.attname AS column_name FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) AND t.relkind = 'r' AND t.relname = :table_name ORDER BY index_name ASC, column_name ASC", [
':table_name' => $this->connection->prefixTables('{' . $table . '}'),
])->fetchAll();
foreach ($result as $row) {
if (preg_match('/_pkey$/', $row->index_name)) {
$index_schema['primary key'][] = $row->column_name;
}
elseif (preg_match('/_key$/', $row->index_name)) {
$index_schema['unique keys'][$row->index_name][] = $row->column_name;
}
elseif (preg_match('/_idx$/', $row->index_name)) {
$index_schema['indexes'][$row->index_name][] = $row->column_name;
}
}
return $index_schema;
}
/**
* {@inheritdoc}
*/
......
......@@ -774,6 +774,18 @@ protected function findPrimaryKeyColumns($table) {
return $schema['primary key'];
}
/**
* {@inheritdoc}
*/
protected function introspectIndexSchema($table) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException("The table $table doesn't exist.");
}
$schema = $this->introspectSchema($table);
unset($schema['fields']);
return $schema;
}
/**
* {@inheritdoc}
*/
......
......@@ -545,6 +545,28 @@ abstract public function addIndex($table, $name, $fields, array $spec);
*/
abstract public function dropIndex($table, $name);
/**
* Finds the columns for the primary key, unique keys and indexes of a table.
*
* @param string $table
* The name of the table.
*
* @return array
* A schema array with the following keys: 'primary key', 'unique keys' and
* 'indexes', and values as arrays of database columns.
*
* @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
* If the specified table doesn't exist.
* @throws \RuntimeException
* If the driver does not implement this method.
*/
protected function introspectIndexSchema($table) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException("The table $table doesn't exist.");
}
throw new \RuntimeException("The '{$this->connection->driver()}' database driver does not implement " . __METHOD__);
}
/**
* Change a field definition.
*
......
......@@ -3,6 +3,7 @@
namespace Drupal\Tests\taxonomy\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
use Drupal\Tests\Core\Database\SchemaIntrospectionTestTrait;
/**
* Tests that the 'hierarchy' property is removed from vocabularies.
......@@ -13,6 +14,8 @@
*/
class TaxonomyVocabularyHierarchyUpdateTest extends UpdatePathTestBase {
use SchemaIntrospectionTestTrait;
/**
* {@inheritdoc}
*/
......@@ -32,15 +35,18 @@ public function testTaxonomyUpdateParents() {
$hierarchy = \Drupal::config('taxonomy.vocabulary.test_vocabulary')->get('hierarchy');
$this->assertSame(1, $hierarchy);
// We can not test whether an index on the 'bundle' column existed before
// running the updates because the 'taxonomy_term__parent' table itself is
// created by an update function.
// Run updates.
$this->runUpdates();
$hierarchy = \Drupal::config('taxonomy.vocabulary.test_vocabulary')->get('hierarchy');
$this->assertNull($hierarchy);
$database = \Drupal::database();
$this->assertFalse($database->schema()->indexExists('taxonomy_term__parent', 'bundle'));
$this->assertTrue($database->schema()->indexExists('taxonomy_term__parent', 'bundle_delta_target_id'));
$this->assertNoIndexOnColumns('taxonomy_term__parent', ['bundle']);
$this->assertIndexOnColumns('taxonomy_term__parent', ['bundle', 'delta', 'parent_target_id']);
}
}
......@@ -8,6 +8,7 @@
use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Component\Utility\Unicode;
use Drupal\Tests\Core\Database\SchemaIntrospectionTestTrait;
/**
* Tests table creation and modification via the schema API.
......@@ -18,6 +19,8 @@
*/
class SchemaTest extends KernelTestBase {
use SchemaIntrospectionTestTrait;
/**
* A global counter for table and field creation.
*
......@@ -222,53 +225,15 @@ public function testSchema() {
$table_name = strtolower($this->getRandomGenerator()->name(63 - strlen($this->getDatabasePrefix())));
$this->schema->createTable($table_name, $table_specification);
// Tests for indexes are Database specific.
$db_type = $this->connection->databaseType();
// Test for existing primary and unique keys.
switch ($db_type) {
case 'pgsql':
$primary_key_exists = (bool) $this->schema->queryFieldInformation($table_name, 'id', 'p');
$unique_key_exists = (bool) $this->schema->queryFieldInformation($table_name, 'test_field', 'u');
break;
case 'sqlite':
// SQLite does not create a standalone index for primary keys.
$primary_key_exists = TRUE;
$unique_key_exists = $this->schema->indexExists($table_name, 'test_field');
break;
default:
$primary_key_exists = $this->schema->indexExists($table_name, 'PRIMARY');
$unique_key_exists = $this->schema->indexExists($table_name, 'test_field');
break;
}
$this->assertIdentical($primary_key_exists, TRUE, 'Primary key created.');
$this->assertIdentical($unique_key_exists, TRUE, 'Unique key created.');
$this->assertIndexOnColumns($table_name, ['id'], 'primary');
$this->assertIndexOnColumns($table_name, ['test_field'], 'unique');
$new_table_name = strtolower($this->getRandomGenerator()->name(63 - strlen($this->getDatabasePrefix())));
$this->assertNull($this->schema->renameTable($table_name, $new_table_name));
// Test for renamed primary and unique keys.
switch ($db_type) {
case 'pgsql':
$renamed_primary_key_exists = (bool) $this->schema->queryFieldInformation($new_table_name, 'id', 'p');
$renamed_unique_key_exists = (bool) $this->schema->queryFieldInformation($new_table_name, 'test_field', 'u');
break;
case 'sqlite':
// SQLite does not create a standalone index for primary keys.
$renamed_primary_key_exists = TRUE;
$renamed_unique_key_exists = $this->schema->indexExists($new_table_name, 'test_field');
break;
default:
$renamed_primary_key_exists = $this->schema->indexExists($new_table_name, 'PRIMARY');
$renamed_unique_key_exists = $this->schema->indexExists($new_table_name, 'test_field');
break;
}
$this->assertIdentical($renamed_primary_key_exists, TRUE, 'Primary key was renamed.');
$this->assertIdentical($renamed_unique_key_exists, TRUE, 'Unique key was renamed.');
$this->assertIndexOnColumns($new_table_name, ['id'], 'primary');
$this->assertIndexOnColumns($new_table_name, ['test_field'], 'unique');
// For PostgreSQL, we also need to check that the sequence has been renamed.
// The initial name of the sequence has been generated automatically by
......@@ -276,7 +241,7 @@ public function testSchema() {
// renames the name is generated by Drupal and can not be easily
// re-constructed. Hence we can only check that we still have a sequence on
// the new table name.
if ($db_type == 'pgsql') {
if ($this->connection->databaseType() == 'pgsql') {
$sequence_exists = (bool) $this->connection->query("SELECT pg_get_serial_sequence('{" . $new_table_name . "}', 'id')")->fetchField();
$this->assertTrue($sequence_exists, 'Sequence was renamed.');
......@@ -309,6 +274,83 @@ public function testSchema() {
$this->assertTrue($this->schema->tableExists('test_timestamp'), 'Table with database specific datatype was created.');
}
/**
* @covers \Drupal\Core\Database\Driver\mysql\Schema::introspectIndexSchema
* @covers \Drupal\Core\Database\Driver\pgsql\Schema::introspectIndexSchema
* @covers \Drupal\Core\Database\Driver\sqlite\Schema::introspectIndexSchema
*/
public function testIntrospectIndexSchema() {
$table_specification = [
'fields' => [
'id' => [
'type' => 'int',
'not null' => TRUE,
'default' => 0,
],
'test_field_1' => [
'type' => 'int',
'not null' => TRUE,
'default' => 0,
],
'test_field_2' => [
'type' => 'int',
'default' => 0,
],
'test_field_3' => [
'type' => 'int',
'default' => 0,
],
'test_field_4' => [
'type' => 'int',
'default' => 0,
],
'test_field_5' => [
'type' => 'int',
'default' => 0,
],
],
'primary key' => ['id', 'test_field_1'],
'unique keys' => [
'2' => ['test_field_2'],
'3_4' => ['test_field_3', 'test_field_4'],
],
'indexes' => [
'4' => ['test_field_4'],
'4_5' => ['test_field_4', 'test_field_5'],
],
];
$table_name = strtolower($this->getRandomGenerator()->name());
$this->schema->createTable($table_name, $table_specification);
unset($table_specification['fields']);
$introspect_index_schema = new \ReflectionMethod(get_class($this->schema), 'introspectIndexSchema');
$introspect_index_schema->setAccessible(TRUE);
$index_schema = $introspect_index_schema->invoke($this->schema, $table_name);
// The PostgreSQL driver is using a custom naming scheme for its indexes, so
// we need to adjust the initial table specification.
if ($this->connection->databaseType() === 'pgsql') {
$ensure_identifier_length = new \ReflectionMethod(get_class($this->schema), 'ensureIdentifiersLength');
$ensure_identifier_length->setAccessible(TRUE);
foreach ($table_specification['unique keys'] as $original_index_name => $columns) {
unset($table_specification['unique keys'][$original_index_name]);
$new_index_name = $ensure_identifier_length->invoke($this->schema, $table_name, $original_index_name, 'key');
$table_specification['unique keys'][$new_index_name] = $columns;
}
foreach ($table_specification['indexes'] as $original_index_name => $columns) {
unset($table_specification['indexes'][$original_index_name]);
$new_index_name = $ensure_identifier_length->invoke($this->schema, $table_name, $original_index_name, 'idx');
$table_specification['indexes'][$new_index_name] = $columns;
}
}
$this->assertEquals($table_specification, $index_schema);
}
/**
* Tests that indexes on string fields are limited to 191 characters on MySQL.
*
......
<?php
namespace Drupal\Tests\Core\Database;
/**
* Provides methods for testing database schema characteristics.
*/
trait SchemaIntrospectionTestTrait {
/**
* Checks that an index covering exactly the given column names exists.
*
* @param string $table_name
* A non-prefixed table name.
* @param array $column_names
* An array of column names
* @param string $index_type
* (optional) The type of the index. Can be one of 'index', 'unique' or
* 'primary'. Defaults to 'index'.
*/
protected function assertIndexOnColumns($table_name, array $column_names, $index_type = 'index') {
foreach ($this->getIndexColumnNames($table_name, $index_type) as $index_columns) {
if ($column_names == $index_columns) {
$this->assertTrue(TRUE);
return;
}
}
$this->assertTrue(FALSE);
}
/**
* Checks that an index covering exactly the given column names doesn't exist.
*
* @param string $table_name
* A non-prefixed table name.
* @param array $column_names
* An array of column names
* @param string $index_type
* (optional) The type of the index. Can be one of 'index', 'unique' or
* 'primary'. Defaults to 'index'.
*/
protected function assertNoIndexOnColumns($table_name, array $column_names, $index_type = 'index') {
foreach ($this->getIndexColumnNames($table_name, $index_type) as $index_columns) {
if ($column_names == $index_columns) {
$this->assertTrue(FALSE);
}
}
$this->assertTrue(TRUE);
}
/**
* Returns the column names used by the indexes of a table.
*
* @param string $table_name
* A table name.
* @param string $index_type
* The type of the index. Can be one of 'index', 'unique' or 'primary'.
*
* @return array
* A multi-dimensional array containing the column names for each index of
* the given type.
*/
protected function getIndexColumnNames($table_name, $index_type) {
assert(in_array($index_type, ['index', 'unique', 'primary'], TRUE));
$schema = \Drupal::database()->schema();
$introspect_index_schema = new \ReflectionMethod(get_class($schema), 'introspectIndexSchema');
$introspect_index_schema->setAccessible(TRUE);
$index_schema = $introspect_index_schema->invoke($schema, $table_name);
// Filter the indexes by type.
if ($index_type === 'primary') {
$indexes = [$index_schema['primary key']];
}
elseif ($index_type === 'unique') {
$indexes = array_values($index_schema['unique keys']);
}
else {
$indexes = array_values($index_schema['indexes']);
}
return $indexes;
}
}
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