Issue #3255419 by alexpott, Bart Vanhoutte, daffie, catch, sam-elayyoub, roberto.muzzano, quietone, tstoeckler: Updating to Drupal 9.3 fails when sql_require_primary_key MySQL system variable is ON

merge-requests/2535/head
catch 2022-02-24 11:40:43 +00:00
parent fb93414ac0
commit 95a9e5ebda
4 changed files with 141 additions and 2 deletions

View File

@ -634,6 +634,11 @@ class Schema extends DatabaseSchema {
$sql .= ', ADD ' . implode(', ADD ', $keys_sql); $sql .= ', ADD ' . implode(', ADD ', $keys_sql);
} }
$this->connection->query($sql); $this->connection->query($sql);
if ($spec['type'] === 'serial') {
$max = $this->connection->query('SELECT MAX(`' . $field_new . '`) FROM {' . $table . '}')->fetchField();
$this->connection->query("ALTER TABLE {" . $table . "} AUTO_INCREMENT = " . ($max + 1));
}
} }
/** /**

View File

@ -0,0 +1,65 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Core\Database\Database;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests user_update_9301() on MySQL 8 when sql_require_primary_key is on.
*
* @group user
*/
class Mysql8RequirePrimaryKeyUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function runDbTasks() {
parent::runDbTasks();
$database = Database::getConnection();
$is_maria = method_exists($database, 'isMariaDb') && $database->isMariaDb();
if ($database->databaseType() !== 'mysql' || $is_maria || version_compare($database->version(), '8.0.13', '<')) {
$this->markTestSkipped('This test only runs on MySQL 8.0.13 and above');
}
$database->query("SET sql_require_primary_key = 1;")->execute();
}
/**
* {@inheritdoc}
*/
protected function prepareSettings() {
parent::prepareSettings();
// Set sql_require_primary_key for any future connections.
$settings['databases']['default']['default']['init_commands'] = (object) [
'value' => ['sql_require_primary_key' => 'SET sql_require_primary_key = 1;'],
'required' => TRUE,
];
$this->writeSettings($settings);
}
/**
* {@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']);
}
}

View File

@ -113,12 +113,16 @@ function user_update_9301(&$sandbox) {
return t('The Microsoft SQL Server does not support user_update_9301() because it causes data loss.'); return t('The Microsoft SQL Server does not support user_update_9301() because it causes data loss.');
} }
$connection->schema()->dropPrimaryKey('users');
if ($connection->databaseType() === 'mysql') { if ($connection->databaseType() === 'mysql') {
$sql_mode = $connection->query("SELECT @@sql_mode;")->fetchField(); $sql_mode = $connection->query("SELECT @@sql_mode;")->fetchField();
$connection->query("SET sql_mode = '$sql_mode,NO_AUTO_VALUE_ON_ZERO'"); $connection->query("SET sql_mode = '$sql_mode,NO_AUTO_VALUE_ON_ZERO'");
$new_keys = [];
} }
$connection->schema()->changeField('users', 'uid', 'uid', ['type' => 'serial', 'not null' => TRUE], ['primary key' => ['uid']]); else {
$new_keys = ['primary key' => ['uid']];
$connection->schema()->dropPrimaryKey('users');
}
$connection->schema()->changeField('users', 'uid', 'uid', ['type' => 'serial', 'not null' => TRUE], $new_keys);
if (isset($sql_mode)) { if (isset($sql_mode)) {
$connection->query("SET sql_mode = '$sql_mode'"); $connection->query("SET sql_mode = '$sql_mode'");
} }

View File

@ -4,6 +4,7 @@ namespace Drupal\KernelTests\Core\Database;
use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Database\Database; use Drupal\Core\Database\Database;
use Drupal\Core\Database\IntegrityConstraintViolationException;
use Drupal\Core\Database\SchemaException; use Drupal\Core\Database\SchemaException;
use Drupal\Core\Database\SchemaObjectDoesNotExistException; use Drupal\Core\Database\SchemaObjectDoesNotExistException;
use Drupal\Core\Database\SchemaObjectExistsException; use Drupal\Core\Database\SchemaObjectExistsException;
@ -924,6 +925,70 @@ class SchemaTest extends KernelTestBase {
$this->schema->createTable($table_name, $table_spec); $this->schema->createTable($table_name, $table_spec);
} }
/**
* Tests converting an int to a serial when the int column has data.
*/
public function testChangePrimaryKeyToSerial() {
// Test making an invalid field the primary key of the table upon creation.
$table_name = 'test_table';
$table_spec = [
'fields' => [
'test_field' => ['type' => 'int', 'not null' => TRUE],
'test_field_string' => ['type' => 'varchar', 'length' => 20],
],
'primary key' => ['test_field'],
];
$this->schema->createTable($table_name, $table_spec);
if ($this->connection->databaseType() !== 'sqlite') {
try {
$this->connection
->insert($table_name)
->fields(['test_field_string' => 'test'])
->execute();
$this->fail('Expected IntegrityConstraintViolationException not thrown');
}
catch (IntegrityConstraintViolationException $e) {
}
}
// @todo https://www.drupal.org/project/drupal/issues/3222127 Change the
// first item to 0 to test changing a field with 0 to a serial.
// Create 8 rows in the table. Note that the 5 value is deliberately
// omitted.
foreach ([1, 2, 3, 4, 6, 7, 8, 9] as $value) {
$this->connection
->insert($table_name)
->fields(['test_field' => $value])
->execute();
}
$this->schema->changeField($table_name, 'test_field', 'test_field', ['type' => 'serial', 'not null' => TRUE]);
$data = $this->connection
->select($table_name)
->fields($table_name, ['test_field'])
->execute()
->fetchCol();
$this->assertEquals([1, 2, 3, 4, 6, 7, 8, 9], array_values($data));
try {
$this->connection
->insert($table_name)
->fields(['test_field' => 1])
->execute();
$this->fail('Expected IntegrityConstraintViolationException not thrown');
}
catch (IntegrityConstraintViolationException $e) {
}
// Ensure auto numbering now works.
$id = $this->connection
->insert($table_name)
->fields(['test_field_string' => 'test'])
->execute();
$this->assertEquals(10, $id);
}
/** /**
* Tests adding an invalid field specification as a primary key. * Tests adding an invalid field specification as a primary key.
*/ */