From 251a94657ab5cee0d1ac4acb41066ab885118b5b Mon Sep 17 00:00:00 2001 From: Lee Rowlands Date: Tue, 28 Mar 2023 12:44:23 +1000 Subject: [PATCH] Issue #3191391 by Arantxio, larowlan, _utsavsharma, daffie: Schema::changeField() has bug when changing regular serial field to big serial field (cherry picked from commit 6367bb87ee6efc0c41bda07a16551ddcf7210802) --- .../src/Driver/Database/pgsql/Schema.php | 39 ++++++++++++++--- .../Database/DriverSpecificSchemaTestBase.php | 42 +++++++++++++++++++ 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php index 9bb86554ac7..b2fc2aa1571 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php @@ -893,12 +893,11 @@ EOD; // Type 'serial' is known to PostgreSQL, but only during table creation, // not when altering. Because of that, we create it here as an 'int'. After // we create it we manually re-apply the sequence. - if (in_array($spec['pgsql_type'], ['serial', 'bigserial'])) { - $field_def = 'int'; - } - else { - $field_def = $spec['pgsql_type']; - } + $field_def = match($spec['pgsql_type']) { + 'serial' => 'int', + 'bigserial' => 'bigint', + default => $spec['pgsql_type'], + }; if (in_array($spec['pgsql_type'], ['varchar', 'character', 'text']) && isset($spec['length'])) { $field_def .= '(' . $spec['length'] . ')'; @@ -910,6 +909,14 @@ EOD; // Remove old check constraints. $field_info = $this->queryFieldInformation($table, $field); + // Remove old sequence. + $seq_name = $this->getSequenceName($table, $field); + if (!empty($seq_name)) { + // We need to add CASCADE otherwise we cannot alter the sequence because + // the table depends on it. + $this->connection->query('DROP SEQUENCE IF EXISTS ' . $seq_name . ' CASCADE'); + } + foreach ($field_info as $check) { $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT [' . $check . ']'); } @@ -1062,6 +1069,26 @@ EOD; ])->fetchField(); } + /** + * Retrieves a sequence name that is owned by the table and column.. + * + * @param string $table + * A table name that is not prefixed or quoted. + * @param string $column + * The column name. + * + * @return string|null + * The name of the sequence or NULL if it does not exist. + */ + protected function getSequenceName(string $table, string $column): ?string { + return $this->connection + ->query("SELECT pg_get_serial_sequence(:table, :column)", [ + ':table' => $this->connection->getPrefix() . $table, + ':column' => $column, + ]) + ->fetchField(); + } + } /** diff --git a/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificSchemaTestBase.php b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificSchemaTestBase.php index de74ae5437f..8581ea2222a 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificSchemaTestBase.php +++ b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificSchemaTestBase.php @@ -1308,4 +1308,46 @@ abstract class DriverSpecificSchemaTestBase extends DriverSpecificKernelTestBase $this->assertFalse($this->schema->tableExists($table_name_new)); } + /** + * Tests changing a field length. + */ + public function testChangeSerialFieldLength(): void { + $specification = [ + 'fields' => [ + 'id' => [ + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique ID.', + ], + 'text' => [ + 'type' => 'text', + 'description' => 'A text field', + ], + ], + 'primary key' => ['id'], + ]; + $this->schema->createTable('change_serial_to_big', $specification); + + // Increase the size of the field. + $new_specification = [ + 'size' => 'big', + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique ID.', + ]; + $this->schema->changeField('change_serial_to_big', 'id', 'id', $new_specification); + $this->assertTrue($this->schema->fieldExists('change_serial_to_big', 'id')); + + // Test if we can actually add a big int. + $id = $this->connection->insert('change_serial_to_big')->fields([ + 'id' => 21474836470, + ])->execute(); + + $id_two = $this->connection->insert('change_serial_to_big')->fields([ + 'text' => 'Testing for ID generation', + ])->execute(); + + $this->assertEquals($id + 1, $id_two); + } + }