Commit 139f9114 authored by catch's avatar catch
Browse files

Issue #838992 by alexpott, Charlie ChX Negyesi, daffie, Damien Tournoud, sun,...

Issue #838992 by alexpott, Charlie ChX Negyesi, daffie, Damien Tournoud, sun, quietone, longwave: Change the uid field from integer to serial by leveraging NO_AUTO_VALUE_ON_ZERO on MySQL
parent bd272a4c
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -410,9 +410,15 @@ protected function getTemplate() {
use Drupal\Core\Database\Database;

$connection = Database::getConnection();
// Ensure any tables with a serial column with a value of 0 are created as
// expected.
$sql_mode = $connection->query("SELECT @@sql_mode;")->fetchField();
$connection->query("SET sql_mode = '$sql_mode,NO_AUTO_VALUE_ON_ZERO'");

{{TABLES}}

// Reset the SQL mode.
$connection->query("SET sql_mode = '$sql_mode'");
ENDOFSCRIPT;
    return $script;
  }
+8 −1
Original line number Diff line number Diff line
@@ -1057,7 +1057,14 @@ protected function mapToStorageRecord(ContentEntityInterface $entity, $table_nam
        // SQL database drivers.
        // @see https://www.drupal.org/node/2279395
        $value = SqlContentEntityStorageSchema::castValue($definition->getSchema()['columns'][$column_name], $value);
        if (!(empty($value) && $this->isColumnSerial($table_name, $schema_name))) {
        $empty_serial = empty($value) && $this->isColumnSerial($table_name, $schema_name);
        // The user entity is a very special case where the ID field is a serial
        // but we need to insert a row with an ID of 0 to represent the
        // anonymous user.
        // @todo https://drupal.org/i/3222123 implement a generic fix for all
        //   entity types.
        $user_zero = $this->entityTypeId === 'user' && $value === 0;
        if (!$empty_serial || $user_zero) {
          $record->$schema_name = $value;
        }
      }
+16 −13
Original line number Diff line number Diff line
@@ -18,21 +18,24 @@ class UserStorage extends SqlContentEntityStorage implements UserStorageInterfac
   * {@inheritdoc}
   */
  protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
    // The anonymous user account is saved with the fixed user ID of 0.
    // Therefore we need to check for NULL explicitly.
    if ($entity->id() === NULL) {
      $entity->uid->value = $this->database->nextId($this->database->query('SELECT MAX([uid]) FROM {' . $this->getBaseTable() . '}')->fetchField());
      $entity->enforceIsNew();
    // The anonymous user account is saved with the fixed user ID of 0. MySQL
    // does not support inserting an ID of 0 into serial field unless the SQL
    // mode is set to NO_AUTO_VALUE_ON_ZERO.
    // @todo https://drupal.org/i/3222123 implement a generic fix for all entity
    //   types.
    if ($entity->id() === 0) {
      $database = \Drupal::database();
      if ($database->databaseType() === 'mysql') {
        $sql_mode = $database->query("SELECT @@sql_mode;")->fetchField();
        $database->query("SET sql_mode = '$sql_mode,NO_AUTO_VALUE_ON_ZERO'");
      }
    return parent::doSaveFieldItems($entity, $names);
    }
    parent::doSaveFieldItems($entity, $names);

  /**
   * {@inheritdoc}
   */
  protected function isColumnSerial($table_name, $schema_name) {
    // User storage does not use a serial column for the user id.
    return $table_name == $this->revisionTable && $schema_name == $this->revisionKey;
    // Reset the SQL mode if we've changed it.
    if (isset($sql_mode, $database)) {
      $database->query("SET sql_mode = '$sql_mode'");
    }
  }

  /**
+0 −10
Original line number Diff line number Diff line
@@ -26,16 +26,6 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
    return $schema;
  }

  /**
   * {@inheritdoc}
   */
  protected function processIdentifierSchema(&$schema, $key) {
    // The "users" table does not use serial identifiers.
    if ($key != $this->entityType->getKey('id')) {
      parent::processIdentifierSchema($schema, $key);
    }
  }

  /**
   * {@inheritdoc}
   */
+56 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\user\Functional;

use Drupal\FunctionalTests\Update\UpdatePathTestBase;

// cSpell:ignore refobjid regclass attname attrelid attnum refobjsubid objid
// cSpell:ignore classid

/**
 * Tests user_update_9301().
 *
 * @group user
 */
class UidUpdateToSerialTest extends UpdatePathTestBase {

  /**
   * {@inheritdoc}
   */
  protected function setDatabaseDumpFiles() {
    $this->databaseDumpFiles[] = __DIR__ . '/../../../../system/tests/fixtures/update/drupal-9.0.0.bare.standard.php.gz';
  }

  /**
   * Tests user_update_9301().
   */
  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('int', $id_schema['users']['fields']['uid']['type']);

    $this->runUpdates();

    $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);
    }
  }

}
Loading