diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php index c6642afdbe..c6a9bb08b3 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php @@ -23,11 +23,18 @@ public function execute() { return NULL; } - $stmt = $this->connection->prepareStatement((string) $this, $this->queryOptions); - // Fetch the list of blobs and sequences used on that table. $table_information = $this->connection->schema()->queryTableInformation($this->table); + // @todo This ugly hack is necessary to make the change for the uid field in + // the users table from an integer type to a serial type possible. It can be + // removed in D11. See https://www.drupal.org/node/3221993. + if ($this->table == 'users' && empty($table_information->serial_fields)) { + $table_information = $this->connection->schema()->queryTableInformation($this->table, TRUE); + } + + $stmt = $this->connection->prepareStatement((string) $this, $this->queryOptions); + $max_placeholder = 0; $blobs = []; $blob_count = 0; diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php index 106add49ef..f144da9eaf 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php @@ -10,7 +10,7 @@ // cSpell:ignore attrelid atttypid atttypmod bigserial conkey conname conrelid // cSpell:ignore contype fillfactor indexname indexrelid indisprimary indkey // cSpell:ignore indrelid nextval nspname regclass relkind relname relnamespace -// cSpell:ignore schemaname setval +// cSpell:ignore schemaname setval requery /** * @addtogroup schemaapi @@ -105,6 +105,8 @@ protected function ensureIdentifiersLength($table_identifier_part, $column_ident * * @param string $table * The non-prefixed name of the table. + * @param bool $requery + * (optional) Whether to re-query the table information. Defaults to FALSE. * * @return mixed|object * An object with two member variables: @@ -114,7 +116,7 @@ protected function ensureIdentifiersLength($table_identifier_part, $column_ident * @throws \Exception * Exception thrown when the query for the table information fails. */ - public function queryTableInformation($table) { + public function queryTableInformation($table, $requery = FALSE) { // Generate a key to reference this table's information on. $key = $this->connection->prefixTables('{' . $table . '}'); @@ -128,6 +130,10 @@ public function queryTableInformation($table) { $key = $this->getTempNamespaceName() . '.' . $key; } + if ($requery) { + unset($this->tableInformation[$key]); + } + if (!isset($this->tableInformation[$key])) { $table_information = (object) [ 'blob_fields' => [], diff --git a/core/modules/user/tests/src/Functional/UidUpdateToSerialTest.php b/core/modules/user/tests/src/Functional/UidUpdateToSerialTest.php index 0923317e66..7a5e55fedf 100644 --- a/core/modules/user/tests/src/Functional/UidUpdateToSerialTest.php +++ b/core/modules/user/tests/src/Functional/UidUpdateToSerialTest.php @@ -4,6 +4,9 @@ use Drupal\FunctionalTests\Update\UpdatePathTestBase; +// cSpell:ignore refobjid regclass attname attrelid attnum refobjsubid objid +// cSpell:ignore classid + /** * Tests user_update_9001(). * @@ -31,6 +34,23 @@ public function testDatabaseLoaded() { $key_value_store = \Drupal::keyValue('entity.storage_schema.sql'); $id_schema = $key_value_store->get('user.field_schema_data.uid', []); $this->assertSame('serial', $id_schema['users']['fields']['uid']['type']); + + $connection = \Drupal::database(); + if ($connection->driver() == 'pgsql') { + $seq_name = $connection->makeSequenceName('users', 'uid'); + $seq_owner = $connection->query("SELECT d.refobjid::regclass as table_name, a.attname as field_name + FROM pg_depend d + JOIN pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid + WHERE d.objid = :seq_name::regclass + AND d.refobjsubid > 0 + AND d.classid = 'pg_class'::regclass", [':seq_name' => 'public.' . $seq_name])->fetchObject(); + $this->assertEquals($connection->tablePrefix('users') . 'users', $seq_owner->table_name); + $this->assertEquals('uid', $seq_owner->field_name); + + $seq_last_value = $connection->query("SELECT last_value FROM $seq_name")->fetchField(); + $maximum_uid = $connection->query('SELECT MAX([uid]) FROM {users}')->fetchField(); + $this->assertEquals($maximum_uid + 1, $seq_last_value); + } } } diff --git a/core/modules/user/user.install b/core/modules/user/user.install index 62db201210..cc46b44148 100644 --- a/core/modules/user/user.install +++ b/core/modules/user/user.install @@ -117,4 +117,12 @@ function user_update_9001(&$sandbox) { $field_schema_data = $installed_storage_schema->get('user.field_schema_data.uid'); $field_schema_data['users']['fields']['uid']['type'] = 'serial'; $installed_storage_schema->set('user.field_schema_data.uid', $field_schema_data); + + // The new PostgreSQL sequence for the uid field needs to start with the last + // used user ID + 1 and the sequence must be owned by uid field. + if ($connection->driver() == 'pgsql') { + $maximum_uid = $connection->query('SELECT MAX([uid]) FROM {users}')->fetchField(); + $seq = $connection->makeSequenceName('users', 'uid'); + $connection->query("ALTER SEQUENCE " . $seq . " RESTART WITH " . ($maximum_uid + 1) . " OWNED BY {users}.uid"); + } } diff --git a/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php index 827e59aa5c..7393dc4d73 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php @@ -714,10 +714,18 @@ protected function assertFieldAdditionRemoval($field_spec) { } // Add another row with no value for the 'test_nullable_field' column. - $this->connection - ->insert($table_name) - ->fields(['serial_column' => NULL]) - ->execute(); + if ($this->connection->driver() == 'mysql') { + $this->connection + ->insert($table_name) + ->fields(['serial_column' => NULL]) + ->execute(); + } + else { + $this->connection + ->insert($table_name) + ->useDefaults(['serial_column']) + ->execute(); + } $this->schema->addField($table_name, 'test_field', $field_spec); @@ -779,10 +787,18 @@ protected function assertFieldCharacteristics($table_name, $field_name, $field_s // Check that the default value has been registered. if (isset($field_spec['default'])) { // Try inserting a row, and check the resulting value of the new column. - $id = $this->connection - ->insert($table_name) - ->fields(['serial_column' => NULL]) - ->execute(); + if ($this->connection->driver() == 'mysql') { + $id = $this->connection + ->insert($table_name) + ->fields(['serial_column' => NULL]) + ->execute(); + } + else { + $id = $this->connection + ->insert($table_name) + ->useDefaults(['serial_column']) + ->execute(); + } $field_value = $this->connection ->select($table_name) ->fields($table_name, [$field_name])