From fdacae95f33c1504f93fb2f592d649adcbe80b59 Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Fri, 22 Dec 2023 13:31:17 +1000 Subject: [PATCH] Issue #3159113 by daffie, saxenaakansha30, quietone: PostgreSQL Duplicate table error for alter index queries --- core/lib/Drupal/Core/Database/Schema.php | 6 +- .../Unit/MigrationConfigurationTraitTest.php | 36 ++++++------ .../src/Driver/Database/pgsql/Schema.php | 10 +++- .../tests/src/Kernel/pgsql/SchemaTest.php | 56 +++++++++++++++++++ .../src/Driver/Database/sqlite/Schema.php | 4 +- 5 files changed, 87 insertions(+), 25 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 9153cd9ef8f0..9530c8fbf86b 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -161,12 +161,14 @@ protected function buildTableNameCondition($table_name, $operator = '=', $add_pr * * @param $table * The name of the table in drupal (no prefixing). + * @param bool $add_prefix + * Boolean to indicate whether the table name needs to be prefixed. * * @return bool * TRUE if the given table exists, otherwise FALSE. */ - public function tableExists($table) { - $condition = $this->buildTableNameCondition($table); + public function tableExists($table, bool $add_prefix = TRUE) { + $condition = $this->buildTableNameCondition($table, '=', $add_prefix); $condition->compile($this->connection, $this); // Normally, we would heartily discourage the use of string // concatenation for conditionals like this however, we diff --git a/core/modules/migrate_drupal/tests/src/Unit/MigrationConfigurationTraitTest.php b/core/modules/migrate_drupal/tests/src/Unit/MigrationConfigurationTraitTest.php index 4ebbfc47d196..3e6e052147f1 100644 --- a/core/modules/migrate_drupal/tests/src/Unit/MigrationConfigurationTraitTest.php +++ b/core/modules/migrate_drupal/tests/src/Unit/MigrationConfigurationTraitTest.php @@ -62,8 +62,8 @@ public function providerTestGetLegacyDrupalVersion() { 'schema_version' => '1678', 'exception' => NULL, 'table_map' => [ - ['system', TRUE], - ['key_value', FALSE], + ['system', TRUE, TRUE], + ['key_value', TRUE, FALSE], ], ], 'D6' => [ @@ -71,8 +71,8 @@ public function providerTestGetLegacyDrupalVersion() { 'schema_version' => '6057', 'exception' => NULL, 'table_map' => [ - ['system', TRUE], - ['key_value', FALSE], + ['system', TRUE, TRUE], + ['key_value', TRUE, FALSE], ], ], 'D7' => [ @@ -80,8 +80,8 @@ public function providerTestGetLegacyDrupalVersion() { 'schema_version' => '7065', 'exception' => NULL, 'table_map' => [ - ['system', TRUE], - ['key_value', FALSE], + ['system', TRUE, TRUE], + ['key_value', TRUE, FALSE], ], ], 'D8' => [ @@ -89,8 +89,8 @@ public function providerTestGetLegacyDrupalVersion() { 'schema_version' => serialize('8976'), 'exception' => NULL, 'table_map' => [ - ['system', FALSE], - ['key_value', TRUE], + ['system', TRUE, FALSE], + ['key_value', TRUE, TRUE], ], ], 'D9' => [ @@ -98,8 +98,8 @@ public function providerTestGetLegacyDrupalVersion() { 'schema_version' => serialize('9270'), 'exception' => NULL, 'table_map' => [ - ['system', FALSE], - ['key_value', TRUE], + ['system', TRUE, FALSE], + ['key_value', TRUE, TRUE], ], ], 'Not drupal' => [ @@ -107,8 +107,8 @@ public function providerTestGetLegacyDrupalVersion() { 'schema_version' => "not drupal I guess", 'exception' => NULL, 'table_map' => [ - ['system', FALSE], - ['key_value', FALSE], + ['system', TRUE, FALSE], + ['key_value', TRUE, FALSE], ], ], 'D5 almost' => [ @@ -116,8 +116,8 @@ public function providerTestGetLegacyDrupalVersion() { 'schema_version' => '123', 'exception' => NULL, 'table_map' => [ - ['system', TRUE], - ['key_value', FALSE], + ['system', TRUE, TRUE], + ['key_value', TRUE, FALSE], ], ], 'D5/6/7 Exception' => [ @@ -125,8 +125,8 @@ public function providerTestGetLegacyDrupalVersion() { 'schema_version' => NULL, 'exception' => new DatabaseExceptionWrapper(), 'table_map' => [ - ['system', TRUE], - ['key_value', FALSE], + ['system', TRUE, TRUE], + ['key_value', TRUE, FALSE], ], ], 'D8/9 Exception' => [ @@ -134,8 +134,8 @@ public function providerTestGetLegacyDrupalVersion() { 'schema_version' => NULL, 'exception' => new DatabaseExceptionWrapper(), 'table_map' => [ - ['system', FALSE], - ['key_value', TRUE], + ['system', TRUE, FALSE], + ['key_value', TRUE, TRUE], ], ], ]; diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php index 62515e8caff6..b7e05f1cc18d 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php @@ -506,8 +506,8 @@ protected function createPrimaryKeySql($fields) { /** * {@inheritdoc} */ - public function tableExists($table) { - $prefixInfo = $this->getPrefixInfo($table, TRUE); + public function tableExists($table, $add_prefix = TRUE) { + $prefixInfo = $this->getPrefixInfo($table, $add_prefix); return (bool) $this->connection->query("SELECT 1 FROM pg_tables WHERE schemaname = :schema AND tablename = :table", [':schema' => $prefixInfo['schema'], ':table' => $prefixInfo['table']])->fetchField(); } @@ -586,7 +586,11 @@ public function renameTable($table, $new_name) { preg_match('/^' . preg_quote($table_name) . '__(.*)__' . preg_quote($index_type) . '/', $index->indexname, $matches); $index_name = $matches[1]; } - $this->connection->query('ALTER INDEX "' . $this->defaultSchema . '"."' . $index->indexname . '" RENAME TO ' . $this->ensureIdentifiersLength($new_name, $index_name, $index_type)); + // The renaming of an index will fail when the there exists an table with + // the same name as the renamed index. + if (!$this->tableExists($this->ensureIdentifiersLength($new_name, $index_name, $index_type), FALSE)) { + $this->connection->query('ALTER INDEX "' . $this->defaultSchema . '"."' . $index->indexname . '" RENAME TO ' . $this->ensureIdentifiersLength($new_name, $index_name, $index_type)); + } } // Ensure the new table name does not include schema syntax. diff --git a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php index bbf05ab0abf8..2de31456cbe8 100644 --- a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php +++ b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php @@ -307,4 +307,60 @@ public function testPgsqlSequences(): void { } + /** + * Tests the method tableExists(). + */ + public function testTableExists() { + $table_name = 'test_table'; + $table_specification = [ + 'fields' => [ + 'id' => [ + 'type' => 'int', + 'default' => NULL, + ], + ], + ]; + $this->schema->createTable($table_name, $table_specification); + $prefixed_table_name = $this->connection->getPrefix($table_name) . $table_name; + + // Three different calls to the method Schema::tableExists() with an + // unprefixed table name. + $this->assertTrue($this->schema->tableExists($table_name)); + $this->assertTrue($this->schema->tableExists($table_name, TRUE)); + $this->assertFalse($this->schema->tableExists($table_name, FALSE)); + + // Three different calls to the method Schema::tableExists() with a + // prefixed table name. + $this->assertFalse($this->schema->tableExists($prefixed_table_name)); + $this->assertFalse($this->schema->tableExists($prefixed_table_name, TRUE)); + $this->assertTrue($this->schema->tableExists($prefixed_table_name, FALSE)); + } + + /** + * Tests renaming a table where the new index name is equal to the table name. + */ + public function testRenameTableWithNewIndexNameEqualsTableName() { + // Special table names for colliding with the PostgreSQL new index name. + $table_name_old = 'some_new_table_name__id__idx'; + $table_name_new = 'some_new_table_name'; + $table_specification = [ + 'fields' => [ + 'id' => [ + 'type' => 'int', + 'default' => NULL, + ], + ], + 'indexes' => [ + 'id' => ['id'], + ], + ]; + $this->schema->createTable($table_name_old, $table_specification); + + // Renaming the table can fail for PostgreSQL, when a new index name is + // equal to the old table name. + $this->schema->renameTable($table_name_old, $table_name_new); + + $this->assertTrue($this->schema->tableExists($table_name_new)); + } + } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php index eb836eb45363..64bdffa8c804 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php @@ -28,8 +28,8 @@ class Schema extends DatabaseSchema { /** * {@inheritdoc} */ - public function tableExists($table) { - $info = $this->getPrefixInfo($table); + public function tableExists($table, $add_prefix = TRUE) { + $info = $this->getPrefixInfo($table, $add_prefix); // Don't use {} around sqlite_master table. return (bool) $this->connection->query('SELECT 1 FROM [' . $info['schema'] . '].sqlite_master WHERE type = :type AND name = :name', [':type' => 'table', ':name' => $info['table']])->fetchField(); -- GitLab