From d0e05541f5b3c49935bfd4f94bad253876c7e199 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Wed, 5 Mar 2025 20:21:28 +0100 Subject: [PATCH 01/66] rebase --- core/lib/Drupal/Core/Database/Connection.php | 178 +++++++++++------- .../Database/Identifier/IdentifierHandler.php | 178 ++++++++++++++++++ .../Database/Identifier/IdentifierType.php | 25 +++ core/lib/Drupal/Core/Database/Schema.php | 10 +- .../tests/src/Unit/MigrateSqlIdMapTest.php | 4 +- .../src/Driver/Database/mysql/Connection.php | 13 +- .../mysql/tests/src/Unit/ConnectionTest.php | 1 - .../src/Driver/Database/pgsql/Connection.php | 45 +---- .../src/Driver/Database/sqlite/Connection.php | 15 +- .../Tests/Core/Database/ConnectionTest.php | 67 ------- .../Core/Database/Stub/StubConnection.php | 4 +- 11 files changed, 337 insertions(+), 203 deletions(-) create mode 100644 core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php create mode 100644 core/lib/Drupal/Core/Database/Identifier/IdentifierType.php diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 41bd043c5c8f..d515fbde3f11 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -2,9 +2,9 @@ namespace Drupal\Core\Database; -use Drupal\Component\Assertion\Inspector; use Drupal\Core\Database\Event\DatabaseEvent; use Drupal\Core\Database\Exception\EventException; +use Drupal\Core\Database\Identifier\IdentifierHandler; use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\Query\Delete; use Drupal\Core\Database\Query\Insert; @@ -101,13 +101,6 @@ abstract class Connection { */ protected $schema = NULL; - /** - * The prefix used by this database connection. - * - * @var string - */ - protected string $prefix; - /** * Replacements to fully qualify {table} placeholders in SQL strings. * @@ -119,39 +112,16 @@ abstract class Connection { protected array $tablePlaceholderReplacements; /** - * List of escaped table names, keyed by unescaped names. - * - * @var array - */ - protected $escapedTables = []; - - /** - * List of escaped field names, keyed by unescaped names. - * - * There are cases in which escapeField() is called on an empty string. In - * this case it should always return an empty string. - * - * @var array - */ - protected $escapedFields = ["" => ""]; - - /** - * List of escaped aliases names, keyed by unescaped aliases. - * - * @var array + * @todo */ - protected $escapedAliases = []; + protected IdentifierHandler $identifierHandler; /** - * The identifier quote characters for the database type. - * - * An array containing the start and end identifier quote characters for the - * database type. The ANSI SQL standard identifier quote character is a double - * quotation mark. + * Post-root (non-nested) transaction commit callbacks. * - * @var string[] + * @var callable[] */ - protected $identifierQuotes; + protected $rootTransactionEndCallbacks = []; /** * Tracks the database API events to be dispatched. @@ -178,11 +148,8 @@ abstract class Connection { * - Other driver-specific options. */ public function __construct(object $connection, array $connection_options) { - assert(count($this->identifierQuotes) === 2 && Inspector::assertAllStrings($this->identifierQuotes), '\Drupal\Core\Database\Connection::$identifierQuotes must contain 2 string values'); - // Manage the table prefix. $connection_options['prefix'] = $connection_options['prefix'] ?? ''; - $this->setPrefix($connection_options['prefix']); // Work out the database driver namespace if none is provided. This normally // written to setting.php by installer or set by @@ -195,6 +162,18 @@ public function __construct(object $connection, array $connection_options) { $this->connectionOptions = $connection_options; } + /** + * Implements the magic __get() method. + * + * @todo Remove the method in Drupal 1x. + */ + public function __get($name) { + if (in_array($name, ['prefix', 'escapedTables', 'escapedFields', 'escapedAliases', 'identifierQuotes'])) { + @trigger_error("Connection::\${$name} should not be accessed in drupal:9.x.0 and is removed from drupal:10.0.0. This is no longer used. See https://www.drupal.org/node/1234567", E_USER_DEPRECATED); + return []; + } + } + /** * Opens a client connection. * @@ -329,7 +308,8 @@ public function attachDatabase(string $database): void { * The table prefix. */ public function getPrefix(): string { - return $this->prefix; + // @trigger_error(__METHOD__ . '() is deprecated in drupal:9.x.0 and is removed from drupal:10.0.0. @todo. See https://www.drupal.org/node/1234567', E_USER_DEPRECATED); + return $this->identifierHandler->getTablePrefix(); } /** @@ -339,12 +319,7 @@ public function getPrefix(): string { * A single prefix. */ protected function setPrefix($prefix) { - assert(is_string($prefix), 'The \'$prefix\' argument to ' . __METHOD__ . '() must be a string'); - $this->prefix = $prefix; - $this->tablePlaceholderReplacements = [ - $this->identifierQuotes[0] . str_replace('.', $this->identifierQuotes[1] . '.' . $this->identifierQuotes[0], $prefix), - $this->identifierQuotes[1], - ]; + @trigger_error(__METHOD__ . '() is deprecated in drupal:9.x.0 and is removed from drupal:10.0.0. @todo. See https://www.drupal.org/node/1234567', E_USER_DEPRECATED); } /** @@ -362,7 +337,12 @@ protected function setPrefix($prefix) { * The properly-prefixed string. */ public function prefixTables($sql) { - return str_replace(['{', '}'], $this->tablePlaceholderReplacements, $sql); + $replacements = $tables = []; + preg_match_all('/(\{(\S*)\})/', $sql, $tables, PREG_SET_ORDER, 0); + foreach ($tables as $table) { + $replacements[$table[1]] = $this->identifierHandler->getPlatformTableName($table[2], TRUE, TRUE); + } + return str_replace(array_keys($replacements), array_values($replacements), $sql); } /** @@ -386,7 +366,50 @@ public function prefixTables($sql) { * This method should only be called by database API code. */ public function quoteIdentifiers($sql) { - return str_replace(['[', ']'], $this->identifierQuotes, $sql); + preg_match_all('/(\[(.+?)\])/', $sql, $matches); + $identifiers = []; + $i = 0; + foreach ($matches[1] as $match) { + $identifiers[$match] = $this->identifierHandler->getPlatformIdentifierName($matches[2][$i]); + $i++; + } + return strtr($sql, $identifiers); + } + + /** + * Find the prefix for a table. + * + * This function is for when you want to know the prefix of a table. This + * is not used in prefixTables due to performance reasons. + * + * @param string $table + * (optional) The table to find the prefix for. + * + * @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. + * Instead, you should just use Connection::getPrefix(). + * + * @see https://www.drupal.org/node/3260849 + */ + public function tablePrefix($table = 'default') { + @trigger_error(__METHOD__ . '() is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, you should just use Connection::getPrefix(). See https://www.drupal.org/node/3260849', E_USER_DEPRECATED); + return $this->identifierHandler->getTablePrefix(); + } + + /** + * Gets a list of individually prefixed table names. + * + * @return array + * An array of un-prefixed table names, keyed by their fully qualified table + * names (i.e. prefix + table_name). + * + * @deprecated in drupal:10.0.0 and is removed from drupal:11.0.0. There is + * no replacement. + * + * @see https://www.drupal.org/node/3257198 + */ + public function getUnprefixedTablesMap() { + @trigger_error(__METHOD__ . '() is deprecated in drupal:10.0.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3257198', E_USER_DEPRECATED); + return $this->unprefixedTablesMap; } /** @@ -399,9 +422,7 @@ public function quoteIdentifiers($sql) { * The fully qualified table name. */ public function getFullQualifiedTableName($table) { - $options = $this->getConnectionOptions(); - $prefix = $this->getPrefix(); - return $options['database'] . '.' . $prefix . $table; + return $this->identifierHandler->getPlatformDatabaseName($this->getConnectionOptions()['database']) . '.' . $this->identifierHandler->getPlatformTableName($table, TRUE, TRUE); } /** @@ -563,6 +584,30 @@ public function getLogger() { return $this->logger; } + /** + * Creates the appropriate sequence name for a given table and serial field. + * + * This information is exposed to all database drivers, although it is only + * useful on some of them. This method is table prefix-aware. + * + * Note that if a sequence was generated automatically by the database, its + * name might not match the one returned by this function. Therefore, in those + * cases, it is generally advised to use a database-specific way of retrieving + * the name of an auto-created sequence. For example, PostgreSQL provides a + * dedicated function for this purpose: pg_get_serial_sequence(). + * + * @param string $table + * The table name to use for the sequence. + * @param string $field + * The field name to use for the sequence. + * + * @return string + * A table prefix-parsed string for the sequence name. + */ + public function makeSequenceName($table, $field) { + return $this->identifierHandler->getPlatformTableName($table, TRUE, FALSE) . "_{$field}_seq"; + } + /** * Flatten an array of query comments into a single comment string. * @@ -780,6 +825,13 @@ public function exceptionHandler() { return new ExceptionHandler(); } + /** + * @todo + */ + public function getIdentifierHandler(): IdentifierHandler { + return $this->identifierHandler; + } + /** * Prepares and returns a SELECT query object. * @@ -985,9 +1037,7 @@ public function condition($conjunction) { * The sanitized database name. */ public function escapeDatabase($database) { - $database = preg_replace('/[^A-Za-z0-9_]+/', '', $database); - [$start_quote, $end_quote] = $this->identifierQuotes; - return $start_quote . $database . $end_quote; + return $this->identifierHandler->getPlatformDatabaseName($database); } /** @@ -1008,10 +1058,7 @@ public function escapeDatabase($database) { * @see \Drupal\Core\Database\Connection::setPrefix() */ public function escapeTable($table) { - if (!isset($this->escapedTables[$table])) { - $this->escapedTables[$table] = preg_replace('/[^A-Za-z0-9_.]+/', '', $table); - } - return $this->escapedTables[$table]; + return $this->identifierHandler->getPlatformTableName($table); } /** @@ -1028,14 +1075,7 @@ public function escapeTable($table) { * The sanitized field name. */ public function escapeField($field) { - if (!isset($this->escapedFields[$field])) { - $escaped = preg_replace('/[^A-Za-z0-9_.]+/', '', $field); - [$start_quote, $end_quote] = $this->identifierQuotes; - // Sometimes fields have the format table_alias.field. In such cases - // both identifiers should be quoted, for example, "table_alias"."field". - $this->escapedFields[$field] = $start_quote . str_replace('.', $end_quote . '.' . $start_quote, $escaped) . $end_quote; - } - return $this->escapedFields[$field]; + return $this->identifierHandler->getPlatformColumnName($field); } /** @@ -1053,11 +1093,7 @@ public function escapeField($field) { * The sanitized alias name. */ public function escapeAlias($field) { - if (!isset($this->escapedAliases[$field])) { - [$start_quote, $end_quote] = $this->identifierQuotes; - $this->escapedAliases[$field] = $start_quote . preg_replace('/[^A-Za-z0-9_]+/', '', $field) . $end_quote; - } - return $this->escapedAliases[$field]; + return $this->identifierHandler->getPlatformAliasName($field); } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php new file mode 100644 index 000000000000..4d9aeb4c4e55 --- /dev/null +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -0,0 +1,178 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Database\Identifier; + +/** + * @todo + */ +class IdentifierHandler { + + /** + * @var array{'identifier':array<string,array<int,string>>,'platform':array<string,array<int,string>>} + */ + protected array $identifiers; + + /** + * @var array{'identifier':array<string,array<int,string>>,'platform':array<string,array<int,string>>} + */ + protected array $aliases; + + /** + * Constructs an IdentifierHandler object. + * + * @param string $tablePrefix + * The table prefix to be used by the database connection. + * @param array{0:string, 1:string} $identifierQuotes + * The identifier quote characters for the database type. An array + * containing the start and end identifier quote characters for the + * database type. The ANSI SQL standard identifier quote character is a + * double quotation mark. + */ + public function __construct( + protected string $tablePrefix, + protected array $identifierQuotes = ['"', '"'], + ) { + } + + /** + * @todo + */ + public function getTablePrefix(): string { + return $this->tablePrefix; + } + + /** + * @todo + */ + protected function setIdentifier(string $identifier, string $platform_identifier, IdentifierType $type, bool $isAlias): void { + if (!$isAlias) { + $this->identifiers['identifier'][$identifier][$type->value] = $platform_identifier; + $this->identifiers['platform'][$platform_identifier][$type->value] = $identifier; + } + else { + $this->aliases['identifier'][$identifier][$type->value] = $platform_identifier; + $this->aliases['platform'][$platform_identifier][$type->value] = $identifier; + } + } + + /** + * @todo + */ + protected function hasIdentifier(string $identifier, IdentifierType $type): bool { + return isset($this->identifiers['identifier'][$identifier][$type->value]); + } + + /** + * @todo + */ + public function getPlatformIdentifierName(string $original_name, bool $quoted = TRUE): string { + if (!$this->hasIdentifier($original_name, IdentifierType::Generic)) { + $this->setIdentifier($original_name, $this->resolvePlatformGenericIdentifier($original_name), IdentifierType::Generic, FALSE); + } + [$start_quote, $end_quote] = $this->identifierQuotes; + $identifier = $this->identifiers['identifier'][$original_name][IdentifierType::Generic->value]; + return $quoted ? $start_quote . $identifier . $end_quote : $identifier; + } + + /** + * @todo + */ + public function getPlatformDatabaseName(string $original_name, bool $quoted = TRUE): string { + $original_name = (string) preg_replace('/[^A-Za-z0-9_]+/', '', $original_name); + if (!$this->hasIdentifier($original_name, IdentifierType::Database)) { + $this->setIdentifier($original_name, $this->resolvePlatformDatabaseIdentifier($original_name), IdentifierType::Database, FALSE); + } + [$start_quote, $end_quote] = $this->identifierQuotes; + return $quoted ? + $start_quote . $this->identifiers['identifier'][$original_name][IdentifierType::Database->value] . $end_quote : + $this->identifiers['identifier'][$original_name][IdentifierType::Database->value]; + } + + /** + * @todo + */ + public function getPlatformTableName(string $original_name, bool $prefixed = FALSE, bool $quoted = FALSE): string { + $original_name = (string) preg_replace('/[^A-Za-z0-9_.]+/', '', $original_name); + if (!$this->hasIdentifier($original_name, IdentifierType::Table)) { + $table_name = $this->resolvePlatformTableIdentifier($original_name); + $this->setIdentifier($original_name, $table_name, IdentifierType::Table, FALSE); + $this->setIdentifier($original_name, $this->getTablePrefix() . $table_name, IdentifierType::PrefixedTable, FALSE); + } + [$start_quote, $end_quote] = $this->identifierQuotes; + $table = $prefixed ? $this->identifiers['identifier'][$original_name][IdentifierType::PrefixedTable->value] : $this->identifiers['identifier'][$original_name][IdentifierType::Table->value]; + return $quoted ? $start_quote . str_replace(".", "$end_quote.$start_quote", $table) . $end_quote : $table; + } + + /** + * @todo + */ + public function getPlatformColumnName(string $original_name, bool $quoted = TRUE): string { + if ($original_name === '') { + return ''; + } + $original_name = (string) preg_replace('/[^A-Za-z0-9_.]+/', '', $original_name); + if (!$this->hasIdentifier($original_name, IdentifierType::Column)) { + $this->setIdentifier($original_name, $this->resolvePlatformColumnIdentifier($original_name), IdentifierType::Column, FALSE); + } + // Sometimes fields have the format table_alias.field. In such cases + // both identifiers should be quoted, for example, "table_alias"."field". + [$start_quote, $end_quote] = $this->identifierQuotes; + return $quoted ? + $start_quote . str_replace(".", "$end_quote.$start_quote", $this->identifiers['identifier'][$original_name][IdentifierType::Column->value]) . $end_quote : + $this->identifiers['identifier'][$original_name][IdentifierType::Column->value]; + } + + /** + * @todo + */ + public function getPlatformAliasName(string $original_name, IdentifierType $type = IdentifierType::Generic, bool $quoted = TRUE): string { + $original_name = (string) preg_replace('/[^A-Za-z0-9_]+/', '', $original_name); + if ($original_name[0] === $this->identifierQuotes[0]) { + $original_name = substr($original_name, 1, -1); + } + if (!$this->hasIdentifier($original_name, IdentifierType::Alias)) { + $this->setIdentifier($original_name, $this->resolvePlatformGenericIdentifier($original_name), $type, TRUE); + } + [$start_quote, $end_quote] = $this->identifierQuotes; + $alias = $this->aliases['identifier'][$original_name][$type->value] ?? $this->identifiers['identifier'][$original_name][0]; + return $quoted ? $start_quote . $alias . $end_quote : $alias; + } + + /** + * @todo + */ + protected function resolvePlatformGenericIdentifier(string $identifier): string { + return $identifier; + } + + /** + * @todo + */ + protected function resolvePlatformDatabaseIdentifier(string $identifier): string { + return $identifier; + } + + /** + * @todo + */ + protected function resolvePlatformTableIdentifier(string $identifier): string { + return $identifier; + } + + /** + * @todo + */ + protected function resolvePlatformColumnIdentifier(string $identifier): string { + return $identifier; + } + + /** + * @todo + */ + protected function resolvePlatformAliasIdentifier(string $identifier, int $type = 0): string { + return $identifier; + } + +} diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php new file mode 100644 index 000000000000..d957767fce5e --- /dev/null +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php @@ -0,0 +1,25 @@ +<?php + +/** + * @file + * Enum for database identifier types. + */ + +declare(strict_types=1); + +namespace Drupal\Core\Database\Identifier; + +/** + * Enum for database identifier types. + */ +enum IdentifierType: int { + case Generic = 0x0; + case Database = 0x4; + case Sequence = 0x5; + case Table = 0x7; + case PrefixedTable = 0x8; + case Column = 0xC; + case Index = 0xD; + + case Alias = 0x100; +} diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 190f8816fa31..03345eab6384 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -82,12 +82,16 @@ public function nextPlaceholder() { * A keyed array with information about the schema, table name and prefix. */ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { + $prefix = $this->connection->getPrefix(); $info = [ 'schema' => $this->defaultSchema, - 'prefix' => $this->connection->getPrefix(), + 'prefix' => $prefix, ]; - if ($add_prefix) { - $table = $info['prefix'] . $table; + if (strpos($table, '%') !== FALSE) { + $table = ($add_prefix ? $prefix : '') . $table; + } + else { + $table = $this->connection->getIdentifierHandler()->getPlatformTableName($table, $add_prefix); } // If the prefix contains a period in it, then that means the prefix also // contains a schema reference in which case we will change the schema key diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php index e8fc47637ea7..d5a2c6375f6b 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php @@ -1011,8 +1011,8 @@ public function testGetQualifiedMapTablePrefix(): void { $qualified_map_table = $this->getIdMap()->getQualifiedMapTableName(); // The SQLite driver is a special flower. It will prefix tables with // PREFIX.TABLE, instead of the standard PREFIXTABLE. - // @see \Drupal\sqlite\Driver\Database\sqlite\Connection::__construct() - $this->assertEquals('prefix.migrate_map_sql_idmap_test', $qualified_map_table); + // @see \Drupal\Core\Database\Driver\sqlite\Connection::__construct() + $this->assertEquals('"prefix"."migrate_map_sql_idmap_test"', $qualified_map_table); } /** diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php index bc4a360d4ae1..4039deac05e1 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php @@ -7,6 +7,8 @@ use Drupal\Core\Database\DatabaseAccessDeniedException; use Drupal\Core\Database\DatabaseConnectionRefusedException; use Drupal\Core\Database\DatabaseNotFoundException; +use Drupal\Core\Database\Identifier\IdentifierHandler; +use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\StatementWrapperIterator; use Drupal\Core\Database\SupportsTemporaryTablesInterface; use Drupal\Core\Database\Transaction\TransactionManagerInterface; @@ -60,11 +62,6 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ const MIN_MAX_ALLOWED_PACKET = 1024; - /** - * {@inheritdoc} - */ - protected $identifierQuotes = ['"', '"']; - /** * {@inheritdoc} */ @@ -88,10 +85,10 @@ public function __construct(\PDO $connection, array $connection_options) { } } - if ($this->identifierQuotes === ['"', '"'] && !$is_ansi_quotes_mode) { - $this->identifierQuotes = ['`', '`']; - } parent::__construct($connection, $connection_options); + + // Initialize the identifier handler. + $this->identifierHandler = new IdentifierHandler($this->connectionOptions['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`']); } /** diff --git a/core/modules/mysql/tests/src/Unit/ConnectionTest.php b/core/modules/mysql/tests/src/Unit/ConnectionTest.php index 8865c764e193..6e69193726e7 100644 --- a/core/modules/mysql/tests/src/Unit/ConnectionTest.php +++ b/core/modules/mysql/tests/src/Unit/ConnectionTest.php @@ -69,7 +69,6 @@ private function createConnection(): Connection { public function __construct(\PDO $connection) { $this->connection = $connection; - $this->setPrefix(''); } }; diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index 40b2de75cf05..b3749474a25e 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -7,6 +7,8 @@ use Drupal\Core\Database\DatabaseAccessDeniedException; use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\ExceptionHandler; +use Drupal\Core\Database\Identifier\IdentifierHandler; +use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Database\StatementWrapperIterator; use Drupal\Core\Database\SupportsTemporaryTablesInterface; @@ -68,26 +70,6 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ protected $transactionalDDLSupport = TRUE; - /** - * {@inheritdoc} - */ - protected $identifierQuotes = ['"', '"']; - - /** - * An array of transaction savepoints. - * - * The main use for this array is to store information about transaction - * savepoints opened to to mimic MySql's InnoDB functionality, which provides - * an inherent savepoint before any query in a transaction. - * - * @var array<string,Transaction> - * - * @see ::addSavepoint() - * @see ::releaseSavepoint() - * @see ::rollbackSavepoint() - */ - protected array $savepoints = []; - /** * Constructs a connection object. */ @@ -111,26 +93,9 @@ public function __construct(\PDO $connection, array $connection_options) { if (isset($connection_options['init_commands'])) { $this->connection->exec(implode('; ', $connection_options['init_commands'])); } - } - - /** - * {@inheritdoc} - */ - protected function setPrefix($prefix) { - assert(is_string($prefix), 'The \'$prefix\' argument to ' . __METHOD__ . '() must be a string'); - $this->prefix = $prefix; - - // Add the schema name if it is not set to public, otherwise it will use the - // default schema name. - $quoted_schema = ''; - if (isset($this->connectionOptions['schema']) && ($this->connectionOptions['schema'] !== 'public')) { - $quoted_schema = $this->identifierQuotes[0] . $this->connectionOptions['schema'] . $this->identifierQuotes[1] . '.'; - } - $this->tablePlaceholderReplacements = [ - $quoted_schema . $this->identifierQuotes[0] . str_replace('.', $this->identifierQuotes[1] . '.' . $this->identifierQuotes[0], $prefix), - $this->identifierQuotes[1], - ]; + // Initialize the identifier handler. + $this->identifierHandler = new IdentifierHandler($connection_options['prefix']); } /** @@ -366,7 +331,7 @@ public function getFullQualifiedTableName($table) { // The fully qualified table name in PostgreSQL is in the form of // <database>.<schema>.<table>. - return $options['database'] . '.' . $schema . '.' . $this->getPrefix() . $table; + return $options['database'] . '.' . $schema . '.' . $this->identifierHandler->getPlatformTableName($table, TRUE, FALSE); } /** diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 0a353dceed05..383d777bab4b 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -6,6 +6,8 @@ use Drupal\Core\Database\Connection as DatabaseConnection; use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\ExceptionHandler; +use Drupal\Core\Database\Identifier\IdentifierHandler; +use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Database\SupportsTemporaryTablesInterface; use Drupal\Core\Database\Transaction\TransactionManagerInterface; @@ -67,11 +69,6 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ protected $transactionalDDLSupport = TRUE; - /** - * {@inheritdoc} - */ - protected $identifierQuotes = ['"', '"']; - /** * Constructs a \Drupal\sqlite\Driver\Database\sqlite\Connection object. */ @@ -87,8 +84,8 @@ public function __construct(\PDO $connection, array $connection_options) { $prefix .= '.'; } - // Regenerate the prefix. - $this->setPrefix($prefix); + // Initialize the identifier handler. + $this->identifierHandler = new IdentifierHandler($prefix); } /** @@ -432,10 +429,8 @@ public function prepareStatement(string $query, array $options, bool $allow_row_ * {@inheritdoc} */ public function getFullQualifiedTableName($table) { - $prefix = $this->getPrefix(); - // Don't include the SQLite database file name as part of the table name. - return $prefix . $table; + return $this->identifierHandler->getPlatformTableName($table, TRUE, TRUE); } /** diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index f9b25e8e7072..89fb1d056b13 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -22,53 +22,6 @@ */ class ConnectionTest extends UnitTestCase { - /** - * Data provider for testPrefixRoundTrip(). - * - * @return array - * Array of arrays with the following elements: - * - Arguments to pass to Connection::setPrefix(). - * - Expected result from Connection::getPrefix(). - */ - public static function providerPrefixRoundTrip() { - return [ - [ - [ - '' => 'test_', - ], - 'test_', - ], - [ - [ - 'fooTable' => 'foo_', - 'barTable' => 'foo_', - ], - 'foo_', - ], - ]; - } - - /** - * Exercise setPrefix() and getPrefix(). - * - * @dataProvider providerPrefixRoundTrip - */ - public function testPrefixRoundTrip($expected, $prefix_info): void { - $mock_pdo = $this->createMock('Drupal\Tests\Core\Database\Stub\StubPDO'); - $connection = new StubConnection($mock_pdo, []); - - // setPrefix() is protected, so we make it accessible with reflection. - $reflection = new \ReflectionClass('Drupal\Tests\Core\Database\Stub\StubConnection'); - $set_prefix = $reflection->getMethod('setPrefix'); - - // Set the prefix data. - $set_prefix->invokeArgs($connection, [$prefix_info]); - // Check the round-trip. - foreach ($expected as $prefix) { - $this->assertEquals($prefix, $connection->getPrefix()); - } - } - /** * Data provider for testPrefixTables(). * @@ -579,26 +532,6 @@ public function testEscapeDatabase($expected, $name, array $identifier_quote = [ $this->assertEquals($expected, $connection->escapeDatabase($name)); } - /** - * @covers ::__construct - */ - public function testIdentifierQuotesAssertCount(): void { - $this->expectException(\AssertionError::class); - $this->expectExceptionMessage('\Drupal\Core\Database\Connection::$identifierQuotes must contain 2 string values'); - $mock_pdo = $this->createMock(StubPDO::class); - new StubConnection($mock_pdo, [], ['"']); - } - - /** - * @covers ::__construct - */ - public function testIdentifierQuotesAssertString(): void { - $this->expectException(\AssertionError::class); - $this->expectExceptionMessage('\Drupal\Core\Database\Connection::$identifierQuotes must contain 2 string values'); - $mock_pdo = $this->createMock(StubPDO::class); - new StubConnection($mock_pdo, [], [0, '1']); - } - /** * @covers ::__construct */ diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php index 4b31c06c2e67..bbb0f63d4fba 100644 --- a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php +++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php @@ -6,6 +6,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Database\ExceptionHandler; +use Drupal\Core\Database\Identifier\IdentifierHandler; use Drupal\Core\Database\Log; use Drupal\Core\Database\StatementWrapperIterator; use Drupal\Tests\Core\Database\Stub\Driver\Schema; @@ -41,8 +42,9 @@ class StubConnection extends Connection { * The identifier quote characters. Defaults to an empty strings. */ public function __construct(\PDO $connection, array $connection_options, $identifier_quotes = ['', '']) { - $this->identifierQuotes = $identifier_quotes; parent::__construct($connection, $connection_options); + // Initialize the identifier handler. + $this->identifierHandler = new IdentifierHandler($connection_options['prefix'] ?? '', $identifier_quotes); } /** -- GitLab From e173a78a444eae0a6e2a1c6cdda5d985445faabe Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Wed, 5 Mar 2025 20:36:33 +0100 Subject: [PATCH 02/66] updates --- core/.phpstan-baseline.php | 6 --- core/lib/Drupal/Core/Database/Connection.php | 46 ++----------------- .../Database/Identifier/IdentifierHandler.php | 28 +++++------ .../Database/Identifier/IdentifierType.php | 5 -- .../src/Driver/Database/mysql/Connection.php | 1 - .../src/Driver/Database/pgsql/Connection.php | 16 ++++++- .../src/Driver/Database/sqlite/Connection.php | 1 - 7 files changed, 34 insertions(+), 69 deletions(-) diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php index fd88ed9bf005..b9c7f11dd58f 100644 --- a/core/.phpstan-baseline.php +++ b/core/.phpstan-baseline.php @@ -29839,12 +29839,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/pgsql/src/Driver/Database/pgsql/Connection.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\pgsql\\\\Driver\\\\Database\\\\pgsql\\\\Connection\\:\\:setPrefix\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/pgsql/src/Driver/Database/pgsql/Connection.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\pgsql\\\\Driver\\\\Database\\\\pgsql\\\\Install\\\\Tasks\\:\\:checkBinaryOutput\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index d515fbde3f11..f44ac7e5ec57 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -112,7 +112,7 @@ abstract class Connection { protected array $tablePlaceholderReplacements; /** - * @todo + * @todo fill in. */ protected IdentifierHandler $identifierHandler; @@ -167,9 +167,9 @@ public function __construct(object $connection, array $connection_options) { * * @todo Remove the method in Drupal 1x. */ - public function __get($name) { + public function __get($name): mixed { if (in_array($name, ['prefix', 'escapedTables', 'escapedFields', 'escapedAliases', 'identifierQuotes'])) { - @trigger_error("Connection::\${$name} should not be accessed in drupal:9.x.0 and is removed from drupal:10.0.0. This is no longer used. See https://www.drupal.org/node/1234567", E_USER_DEPRECATED); + @trigger_error("Connection::\${$name} should not be accessed in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/1234567", E_USER_DEPRECATED); return []; } } @@ -319,7 +319,7 @@ public function getPrefix(): string { * A single prefix. */ protected function setPrefix($prefix) { - @trigger_error(__METHOD__ . '() is deprecated in drupal:9.x.0 and is removed from drupal:10.0.0. @todo. See https://www.drupal.org/node/1234567', E_USER_DEPRECATED); + @trigger_error(__METHOD__ . '() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. @todo. See https://www.drupal.org/node/1234567', E_USER_DEPRECATED); } /** @@ -376,42 +376,6 @@ public function quoteIdentifiers($sql) { return strtr($sql, $identifiers); } - /** - * Find the prefix for a table. - * - * This function is for when you want to know the prefix of a table. This - * is not used in prefixTables due to performance reasons. - * - * @param string $table - * (optional) The table to find the prefix for. - * - * @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. - * Instead, you should just use Connection::getPrefix(). - * - * @see https://www.drupal.org/node/3260849 - */ - public function tablePrefix($table = 'default') { - @trigger_error(__METHOD__ . '() is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, you should just use Connection::getPrefix(). See https://www.drupal.org/node/3260849', E_USER_DEPRECATED); - return $this->identifierHandler->getTablePrefix(); - } - - /** - * Gets a list of individually prefixed table names. - * - * @return array - * An array of un-prefixed table names, keyed by their fully qualified table - * names (i.e. prefix + table_name). - * - * @deprecated in drupal:10.0.0 and is removed from drupal:11.0.0. There is - * no replacement. - * - * @see https://www.drupal.org/node/3257198 - */ - public function getUnprefixedTablesMap() { - @trigger_error(__METHOD__ . '() is deprecated in drupal:10.0.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3257198', E_USER_DEPRECATED); - return $this->unprefixedTablesMap; - } - /** * Get a fully qualified table name. * @@ -826,7 +790,7 @@ public function exceptionHandler() { } /** - * @todo + * @todo fill in. */ public function getIdentifierHandler(): IdentifierHandler { return $this->identifierHandler; diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index 4d9aeb4c4e55..a1ec211e35dc 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -5,7 +5,7 @@ namespace Drupal\Core\Database\Identifier; /** - * @todo + * @todo fill in. */ class IdentifierHandler { @@ -37,14 +37,14 @@ public function __construct( } /** - * @todo + * @todo fill in. */ public function getTablePrefix(): string { return $this->tablePrefix; } /** - * @todo + * @todo fill in. */ protected function setIdentifier(string $identifier, string $platform_identifier, IdentifierType $type, bool $isAlias): void { if (!$isAlias) { @@ -58,14 +58,14 @@ protected function setIdentifier(string $identifier, string $platform_identifier } /** - * @todo + * @todo fill in. */ protected function hasIdentifier(string $identifier, IdentifierType $type): bool { return isset($this->identifiers['identifier'][$identifier][$type->value]); } /** - * @todo + * @todo fill in. */ public function getPlatformIdentifierName(string $original_name, bool $quoted = TRUE): string { if (!$this->hasIdentifier($original_name, IdentifierType::Generic)) { @@ -77,7 +77,7 @@ public function getPlatformIdentifierName(string $original_name, bool $quoted = } /** - * @todo + * @todo fill in. */ public function getPlatformDatabaseName(string $original_name, bool $quoted = TRUE): string { $original_name = (string) preg_replace('/[^A-Za-z0-9_]+/', '', $original_name); @@ -91,7 +91,7 @@ public function getPlatformDatabaseName(string $original_name, bool $quoted = TR } /** - * @todo + * @todo fill in. */ public function getPlatformTableName(string $original_name, bool $prefixed = FALSE, bool $quoted = FALSE): string { $original_name = (string) preg_replace('/[^A-Za-z0-9_.]+/', '', $original_name); @@ -106,7 +106,7 @@ public function getPlatformTableName(string $original_name, bool $prefixed = FAL } /** - * @todo + * @todo fill in. */ public function getPlatformColumnName(string $original_name, bool $quoted = TRUE): string { if ($original_name === '') { @@ -125,7 +125,7 @@ public function getPlatformColumnName(string $original_name, bool $quoted = TRUE } /** - * @todo + * @todo fill in. */ public function getPlatformAliasName(string $original_name, IdentifierType $type = IdentifierType::Generic, bool $quoted = TRUE): string { $original_name = (string) preg_replace('/[^A-Za-z0-9_]+/', '', $original_name); @@ -141,35 +141,35 @@ public function getPlatformAliasName(string $original_name, IdentifierType $type } /** - * @todo + * @todo fill in. */ protected function resolvePlatformGenericIdentifier(string $identifier): string { return $identifier; } /** - * @todo + * @todo fill in. */ protected function resolvePlatformDatabaseIdentifier(string $identifier): string { return $identifier; } /** - * @todo + * @todo fill in. */ protected function resolvePlatformTableIdentifier(string $identifier): string { return $identifier; } /** - * @todo + * @todo fill in. */ protected function resolvePlatformColumnIdentifier(string $identifier): string { return $identifier; } /** - * @todo + * @todo fill in. */ protected function resolvePlatformAliasIdentifier(string $identifier, int $type = 0): string { return $identifier; diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php index d957767fce5e..d891f7396a7c 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php @@ -1,10 +1,5 @@ <?php -/** - * @file - * Enum for database identifier types. - */ - declare(strict_types=1); namespace Drupal\Core\Database\Identifier; diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php index 4039deac05e1..a65e5686055d 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php @@ -8,7 +8,6 @@ use Drupal\Core\Database\DatabaseConnectionRefusedException; use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\Identifier\IdentifierHandler; -use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\StatementWrapperIterator; use Drupal\Core\Database\SupportsTemporaryTablesInterface; use Drupal\Core\Database\Transaction\TransactionManagerInterface; diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index b3749474a25e..39b3ab2708f2 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -8,7 +8,6 @@ use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\ExceptionHandler; use Drupal\Core\Database\Identifier\IdentifierHandler; -use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Database\StatementWrapperIterator; use Drupal\Core\Database\SupportsTemporaryTablesInterface; @@ -70,6 +69,21 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ protected $transactionalDDLSupport = TRUE; + /** + * An array of transaction savepoints. + * + * The main use for this array is to store information about transaction + * savepoints opened to to mimic MySql's InnoDB functionality, which provides + * an inherent savepoint before any query in a transaction. + * + * @var array<string,Transaction> + * + * @see ::addSavepoint() + * @see ::releaseSavepoint() + * @see ::rollbackSavepoint() + */ + protected array $savepoints = []; + /** * Constructs a connection object. */ diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 383d777bab4b..2154b5632946 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -7,7 +7,6 @@ use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\ExceptionHandler; use Drupal\Core\Database\Identifier\IdentifierHandler; -use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Database\SupportsTemporaryTablesInterface; use Drupal\Core\Database\Transaction\TransactionManagerInterface; -- GitLab From 7321b956f6881e232152d2de965234a427ab3a51 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Wed, 5 Mar 2025 20:54:33 +0100 Subject: [PATCH 03/66] update --- core/lib/Drupal/Core/Database/Connection.php | 5 +++-- .../Core/Database/Identifier/IdentifierHandler.php | 13 +++---------- .../pgsql/src/Driver/Database/pgsql/Connection.php | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index f44ac7e5ec57..b7e2fbcf4689 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -169,9 +169,10 @@ public function __construct(object $connection, array $connection_options) { */ public function __get($name): mixed { if (in_array($name, ['prefix', 'escapedTables', 'escapedFields', 'escapedAliases', 'identifierQuotes'])) { - @trigger_error("Connection::\${$name} should not be accessed in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/1234567", E_USER_DEPRECATED); + @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.9.0 and the property is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/1234567", E_USER_DEPRECATED); return []; } + return NULL; } /** @@ -309,7 +310,7 @@ public function attachDatabase(string $database): void { */ public function getPrefix(): string { // @trigger_error(__METHOD__ . '() is deprecated in drupal:9.x.0 and is removed from drupal:10.0.0. @todo. See https://www.drupal.org/node/1234567', E_USER_DEPRECATED); - return $this->identifierHandler->getTablePrefix(); + return $this->identifierHandler->tablePrefix; } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index a1ec211e35dc..bef96a89eeb3 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -31,18 +31,11 @@ class IdentifierHandler { * double quotation mark. */ public function __construct( - protected string $tablePrefix, - protected array $identifierQuotes = ['"', '"'], + public readonly string $tablePrefix, + public readonly array $identifierQuotes = ['"', '"'], ) { } - /** - * @todo fill in. - */ - public function getTablePrefix(): string { - return $this->tablePrefix; - } - /** * @todo fill in. */ @@ -98,7 +91,7 @@ public function getPlatformTableName(string $original_name, bool $prefixed = FAL if (!$this->hasIdentifier($original_name, IdentifierType::Table)) { $table_name = $this->resolvePlatformTableIdentifier($original_name); $this->setIdentifier($original_name, $table_name, IdentifierType::Table, FALSE); - $this->setIdentifier($original_name, $this->getTablePrefix() . $table_name, IdentifierType::PrefixedTable, FALSE); + $this->setIdentifier($original_name, $this->tablePrefix . $table_name, IdentifierType::PrefixedTable, FALSE); } [$start_quote, $end_quote] = $this->identifierQuotes; $table = $prefixed ? $this->identifiers['identifier'][$original_name][IdentifierType::PrefixedTable->value] : $this->identifiers['identifier'][$original_name][IdentifierType::Table->value]; diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index 39b3ab2708f2..b19ade74a0f7 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -333,7 +333,7 @@ public function makeSequenceName($table, $field) { $sequence_name = $this->prefixTables('{' . $table . '}_' . $field . '_seq'); // Remove identifier quotes as we are constructing a new name from a // prefixed and quoted table name. - return str_replace($this->identifierQuotes, '', $sequence_name); + return str_replace($this->getIdentifierHandler()->identifierQuotes, '', $sequence_name); } /** -- GitLab From 416e7ef280a907e40c341d23e1ae8f76b2a9ba2a Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Thu, 6 Mar 2025 22:39:47 +0100 Subject: [PATCH 04/66] wip --- .../lib/Drupal/Core/Cache/DatabaseBackend.php | 2 +- core/lib/Drupal/Core/Database/Connection.php | 43 +++++-------------- .../Database/Identifier/IdentifierBase.php | 11 +++++ .../Database/Identifier/IdentifierHandler.php | 40 ++++++++++------- .../Drupal/Core/Database/Identifier/Table.php | 37 ++++++++++++++++ .../lib/Drupal/Core/Database/Query/Select.php | 2 +- core/lib/Drupal/Core/Database/Schema.php | 2 +- .../src/Driver/Database/pgsql/Connection.php | 4 +- .../src/Driver/Database/sqlite/Connection.php | 2 +- 9 files changed, 89 insertions(+), 54 deletions(-) create mode 100644 core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php create mode 100644 core/lib/Drupal/Core/Database/Identifier/Table.php diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php index efae811b7679..e41ffee90697 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php @@ -128,7 +128,7 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) { // ::select() is a much smaller proportion of the request. $result = []; try { - $result = $this->connection->query('SELECT [cid], [data], [created], [expire], [serialized], [tags], [checksum] FROM {' . $this->connection->escapeTable($this->bin) . '} WHERE [cid] IN ( :cids[] ) ORDER BY [cid]', [':cids[]' => array_keys($cid_mapping)]); + $result = $this->connection->query('SELECT [cid], [data], [created], [expire], [serialized], [tags], [checksum] FROM {' . $this->bin . '} WHERE [cid] IN ( :cids[] ) ORDER BY [cid]', [':cids[]' => array_keys($cid_mapping)]); } catch (\Exception) { // Nothing to do. diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index b7e2fbcf4689..ff2456b40f17 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -310,7 +310,7 @@ public function attachDatabase(string $database): void { */ public function getPrefix(): string { // @trigger_error(__METHOD__ . '() is deprecated in drupal:9.x.0 and is removed from drupal:10.0.0. @todo. See https://www.drupal.org/node/1234567', E_USER_DEPRECATED); - return $this->identifierHandler->tablePrefix; + return $this->identifiers()->tablePrefix; } /** @@ -341,7 +341,7 @@ public function prefixTables($sql) { $replacements = $tables = []; preg_match_all('/(\{(\S*)\})/', $sql, $tables, PREG_SET_ORDER, 0); foreach ($tables as $table) { - $replacements[$table[1]] = $this->identifierHandler->getPlatformTableName($table[2], TRUE, TRUE); + $replacements[$table[1]] = $this->identifiers()->table($table[2])->machineName(); } return str_replace(array_keys($replacements), array_values($replacements), $sql); } @@ -371,7 +371,7 @@ public function quoteIdentifiers($sql) { $identifiers = []; $i = 0; foreach ($matches[1] as $match) { - $identifiers[$match] = $this->identifierHandler->getPlatformIdentifierName($matches[2][$i]); + $identifiers[$match] = $this->identifiers()->getPlatformIdentifierName($matches[2][$i]); $i++; } return strtr($sql, $identifiers); @@ -387,7 +387,7 @@ public function quoteIdentifiers($sql) { * The fully qualified table name. */ public function getFullQualifiedTableName($table) { - return $this->identifierHandler->getPlatformDatabaseName($this->getConnectionOptions()['database']) . '.' . $this->identifierHandler->getPlatformTableName($table, TRUE, TRUE); + return $this->identifiers()->getPlatformDatabaseName($this->getConnectionOptions()['database']) . '.' . $this->identifiers()->table($table)->machineName(); } /** @@ -549,30 +549,6 @@ public function getLogger() { return $this->logger; } - /** - * Creates the appropriate sequence name for a given table and serial field. - * - * This information is exposed to all database drivers, although it is only - * useful on some of them. This method is table prefix-aware. - * - * Note that if a sequence was generated automatically by the database, its - * name might not match the one returned by this function. Therefore, in those - * cases, it is generally advised to use a database-specific way of retrieving - * the name of an auto-created sequence. For example, PostgreSQL provides a - * dedicated function for this purpose: pg_get_serial_sequence(). - * - * @param string $table - * The table name to use for the sequence. - * @param string $field - * The field name to use for the sequence. - * - * @return string - * A table prefix-parsed string for the sequence name. - */ - public function makeSequenceName($table, $field) { - return $this->identifierHandler->getPlatformTableName($table, TRUE, FALSE) . "_{$field}_seq"; - } - /** * Flatten an array of query comments into a single comment string. * @@ -793,7 +769,7 @@ public function exceptionHandler() { /** * @todo fill in. */ - public function getIdentifierHandler(): IdentifierHandler { + public function identifiers(): IdentifierHandler { return $this->identifierHandler; } @@ -1002,7 +978,7 @@ public function condition($conjunction) { * The sanitized database name. */ public function escapeDatabase($database) { - return $this->identifierHandler->getPlatformDatabaseName($database); + return $this->identifiers()->getPlatformDatabaseName($database); } /** @@ -1023,7 +999,8 @@ public function escapeDatabase($database) { * @see \Drupal\Core\Database\Connection::setPrefix() */ public function escapeTable($table) { - return $this->identifierHandler->getPlatformTableName($table); + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); + return $this->identifiers()->table($table)->escapedName(); } /** @@ -1040,7 +1017,7 @@ public function escapeTable($table) { * The sanitized field name. */ public function escapeField($field) { - return $this->identifierHandler->getPlatformColumnName($field); + return $this->identifiers()->getPlatformColumnName($field); } /** @@ -1058,7 +1035,7 @@ public function escapeField($field) { * The sanitized alias name. */ public function escapeAlias($field) { - return $this->identifierHandler->getPlatformAliasName($field); + return $this->identifiers()->getPlatformAliasName($field); } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php new file mode 100644 index 000000000000..a111c1b13aad --- /dev/null +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Database\Identifier; + +/** + * @todo fill in. + */ +abstract class IdentifierBase { +} diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index bef96a89eeb3..5fda1b4f432b 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -36,6 +36,31 @@ public function __construct( ) { } + /** + * @todo fill in. + */ + public function table(string|Table $name): Table { + if (is_string($name)) { + $table = new Table($this, $name); + return $table; + } + return $name; + } + + /** + * @todo fill in. + */ + public function tableEscapeName(Table $table): string { + return preg_replace('/[^A-Za-z0-9_.]+/', '', $table->name); + } + + /** + * @todo fill in. + */ + public function tableMachineName(Table $table): string { + return $table->escapedName(); + } + /** * @todo fill in. */ @@ -83,21 +108,6 @@ public function getPlatformDatabaseName(string $original_name, bool $quoted = TR $this->identifiers['identifier'][$original_name][IdentifierType::Database->value]; } - /** - * @todo fill in. - */ - public function getPlatformTableName(string $original_name, bool $prefixed = FALSE, bool $quoted = FALSE): string { - $original_name = (string) preg_replace('/[^A-Za-z0-9_.]+/', '', $original_name); - if (!$this->hasIdentifier($original_name, IdentifierType::Table)) { - $table_name = $this->resolvePlatformTableIdentifier($original_name); - $this->setIdentifier($original_name, $table_name, IdentifierType::Table, FALSE); - $this->setIdentifier($original_name, $this->tablePrefix . $table_name, IdentifierType::PrefixedTable, FALSE); - } - [$start_quote, $end_quote] = $this->identifierQuotes; - $table = $prefixed ? $this->identifiers['identifier'][$original_name][IdentifierType::PrefixedTable->value] : $this->identifiers['identifier'][$original_name][IdentifierType::Table->value]; - return $quoted ? $start_quote . str_replace(".", "$end_quote.$start_quote", $table) . $end_quote : $table; - } - /** * @todo fill in. */ diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php new file mode 100644 index 000000000000..958ca22148e2 --- /dev/null +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Database\Identifier; + +/** + * @todo fill in. + */ +class Table extends IdentifierBase { + + protected string $escapedName; + protected string $machineName; + + public function __construct( + protected readonly IdentifierHandler $identifierHandler, + public readonly string $name, + ) { + } + + public function escapedName(): string { + if (!isset($this->escapedName)) { + $this->escapedName = $this->identifierHandler->tableEscapeName($this); + } + return $this->escapedName; + } + + public function machineName(bool $prefixed = TRUE, bool $quoted = TRUE): string { + if (!isset($this->machineName)) { + $this->machineName = $this->identifierHandler->tableMachineName($this); + } + [$start_quote, $end_quote] = $this->identifierHandler->identifierQuotes; + $unquotedMachineName = $prefixed ?$this->identifierHandler->tablePrefix . $this->machineName : $this->machineName; + return $quoted ? $start_quote . str_replace(".", "$end_quote.$start_quote", $unquotedMachineName) . $end_quote : $unquotedMachineName; + } + +} diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php index d284ea285a5b..263703383199 100644 --- a/core/lib/Drupal/Core/Database/Query/Select.php +++ b/core/lib/Drupal/Core/Database/Query/Select.php @@ -854,7 +854,7 @@ public function __toString() { $table_string = '(' . (string) $subquery . ')'; } else { - $table_string = $this->connection->escapeTable($table['table']); + $table_string = $this->connection->identifiers()->table($table['table'])->escapedName(); // Do not attempt prefixing cross database / schema queries. if (!str_contains($table_string, '.')) { $table_string = '{' . $table_string . '}'; diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 03345eab6384..44e2fcb75869 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -91,7 +91,7 @@ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { $table = ($add_prefix ? $prefix : '') . $table; } else { - $table = $this->connection->getIdentifierHandler()->getPlatformTableName($table, $add_prefix); + $table = $this->connection->identifiers()->table($table)->machineName(quoted: FALSE, prefixed: $add_prefix); } // If the prefix contains a period in it, then that means the prefix also // contains a schema reference in which case we will change the schema key diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index b19ade74a0f7..568c44b2ee6f 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -333,7 +333,7 @@ public function makeSequenceName($table, $field) { $sequence_name = $this->prefixTables('{' . $table . '}_' . $field . '_seq'); // Remove identifier quotes as we are constructing a new name from a // prefixed and quoted table name. - return str_replace($this->getIdentifierHandler()->identifierQuotes, '', $sequence_name); + return str_replace($this->identifiers()->identifierQuotes, '', $sequence_name); } /** @@ -345,7 +345,7 @@ public function getFullQualifiedTableName($table) { // The fully qualified table name in PostgreSQL is in the form of // <database>.<schema>.<table>. - return $options['database'] . '.' . $schema . '.' . $this->identifierHandler->getPlatformTableName($table, TRUE, FALSE); + return $options['database'] . '.' . $schema . '.' . $this->identifiers()->table($table)->machineName(quoted: FALSE); } /** diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 2154b5632946..c78365e0f145 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -429,7 +429,7 @@ public function prepareStatement(string $query, array $options, bool $allow_row_ */ public function getFullQualifiedTableName($table) { // Don't include the SQLite database file name as part of the table name. - return $this->identifierHandler->getPlatformTableName($table, TRUE, TRUE); + return $this->identifiers()->table($table)->machineName(); } /** -- GitLab From 3cc760f379e6055c6fcdc2c0078c79b76b1622a0 Mon Sep 17 00:00:00 2001 From: mondrake <28163-mondrake@users.noreply.drupalcode.org> Date: Fri, 7 Mar 2025 08:10:02 +0000 Subject: [PATCH 05/66] wip --- core/lib/Drupal/Core/Database/Connection.php | 7 ------- .../lib/Drupal/Core/Database/Identifier/Table.php | 15 ++++++++++++++- core/lib/Drupal/Core/Database/Query/Delete.php | 2 +- core/lib/Drupal/Core/Database/Query/Insert.php | 2 +- core/lib/Drupal/Core/Database/Query/Select.php | 6 +----- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index ff2456b40f17..39a999593b2a 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -116,13 +116,6 @@ abstract class Connection { */ protected IdentifierHandler $identifierHandler; - /** - * Post-root (non-nested) transaction commit callbacks. - * - * @var callable[] - */ - protected $rootTransactionEndCallbacks = []; - /** * Tracks the database API events to be dispatched. * diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 958ca22148e2..ce619e2aff16 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -9,7 +9,14 @@ */ class Table extends IdentifierBase { + /** + * @todo fill in. + */ protected string $escapedName; + + /** + * @todo fill in. + */ protected string $machineName; public function __construct( @@ -18,6 +25,9 @@ public function __construct( ) { } + /** + * @todo fill in. + */ public function escapedName(): string { if (!isset($this->escapedName)) { $this->escapedName = $this->identifierHandler->tableEscapeName($this); @@ -25,12 +35,15 @@ public function escapedName(): string { return $this->escapedName; } + /** + * @todo fill in. + */ public function machineName(bool $prefixed = TRUE, bool $quoted = TRUE): string { if (!isset($this->machineName)) { $this->machineName = $this->identifierHandler->tableMachineName($this); } [$start_quote, $end_quote] = $this->identifierHandler->identifierQuotes; - $unquotedMachineName = $prefixed ?$this->identifierHandler->tablePrefix . $this->machineName : $this->machineName; + $unquotedMachineName = $prefixed ? $this->identifierHandler->tablePrefix . $this->machineName : $this->machineName; return $quoted ? $start_quote . str_replace(".", "$end_quote.$start_quote", $unquotedMachineName) . $end_quote : $unquotedMachineName; } diff --git a/core/lib/Drupal/Core/Database/Query/Delete.php b/core/lib/Drupal/Core/Database/Query/Delete.php index 946f91240e1f..b10972a083bd 100644 --- a/core/lib/Drupal/Core/Database/Query/Delete.php +++ b/core/lib/Drupal/Core/Database/Query/Delete.php @@ -70,7 +70,7 @@ public function __toString() { // Create a sanitized comment string to prepend to the query. $comments = $this->connection->makeComment($this->comments); - $query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; + $query = $comments . 'DELETE FROM ' . $this->connection->identifiers()->table($this->table)->machineName(); if (count($this->condition)) { diff --git a/core/lib/Drupal/Core/Database/Query/Insert.php b/core/lib/Drupal/Core/Database/Query/Insert.php index 4657e5c3e5e8..d1a2dd5612d3 100644 --- a/core/lib/Drupal/Core/Database/Query/Insert.php +++ b/core/lib/Drupal/Core/Database/Query/Insert.php @@ -116,7 +116,7 @@ public function __toString() { $insert_fields = array_merge($this->defaultFields, $this->insertFields); if (!empty($this->fromQuery)) { - return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; + return $comments . 'INSERT INTO ' . $this->connection->identifiers()->table($this->table)->machineName() . ' (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; } // For simplicity, we will use the $placeholders array to inject diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php index 263703383199..4ccd9074fcfc 100644 --- a/core/lib/Drupal/Core/Database/Query/Select.php +++ b/core/lib/Drupal/Core/Database/Query/Select.php @@ -854,11 +854,7 @@ public function __toString() { $table_string = '(' . (string) $subquery . ')'; } else { - $table_string = $this->connection->identifiers()->table($table['table'])->escapedName(); - // Do not attempt prefixing cross database / schema queries. - if (!str_contains($table_string, '.')) { - $table_string = '{' . $table_string . '}'; - } + $table_string = $this->connection->identifiers()->table($table['table'])->machineName(); } // Don't use the AS keyword for table aliases, as some -- GitLab From 7db894f5842a329c8a95ace2d8f4d9a7045dae47 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Fri, 7 Mar 2025 21:14:41 +0100 Subject: [PATCH 06/66] wip --- .../Drupal/Core/Config/DatabaseStorage.php | 8 ++--- core/lib/Drupal/Core/Database/Connection.php | 29 +++++++------------ .../Database/Identifier/IdentifierHandler.php | 2 +- .../Drupal/Core/Database/Identifier/Table.php | 8 +++++ .../lib/Drupal/Core/Database/Query/Delete.php | 2 +- .../lib/Drupal/Core/Database/Query/Insert.php | 2 +- .../lib/Drupal/Core/Database/Query/Select.php | 2 +- .../Drupal/Core/Database/Query/Truncate.php | 4 +-- .../lib/Drupal/Core/Database/Query/Update.php | 2 +- core/lib/Drupal/Core/Database/Schema.php | 2 +- .../Core/KeyValueStore/DatabaseStorage.php | 6 ++-- .../DatabaseStorageExpirable.php | 6 ++-- .../lib/Drupal/Core/Routing/RouteProvider.php | 4 +-- .../src/Driver/Database/mysql/Connection.php | 7 ++++- .../src/Driver/Database/pgsql/Connection.php | 11 +++++-- .../src/Driver/Database/sqlite/Connection.php | 9 ++++-- .../src/Driver/Database/sqlite/Truncate.php | 2 +- .../Tests/Core/Database/ConnectionTest.php | 3 ++ .../Core/Database/Stub/StubConnection.php | 7 ++++- 19 files changed, 70 insertions(+), 46 deletions(-) diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index 60da9de12470..53bb9584f34a 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -65,7 +65,7 @@ public function __construct(Connection $connection, $table, array $options = [], */ public function exists($name) { try { - return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :name', 0, 1, [ + return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->table . '} WHERE [collection] = :collection AND [name] = :name', 0, 1, [ ':collection' => $this->collection, ':name' => $name, ], $this->options)->fetchField(); @@ -86,7 +86,7 @@ public function exists($name) { public function read($name) { $data = FALSE; try { - $raw = $this->connection->query('SELECT [data] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :name', [ + $raw = $this->connection->query('SELECT [data] FROM {' . $this->table . '} WHERE [collection] = :collection AND [name] = :name', [ ':collection' => $this->collection, ':name' => $name, ], $this->options)->fetchField(); @@ -115,7 +115,7 @@ public function readMultiple(array $names) { $list = []; try { $list = $this->connection - ->query('SELECT [name], [data] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] IN ( :names[] )', [ + ->query('SELECT [name], [data] FROM {' . $this->table . '} WHERE [collection] = :collection AND [name] IN ( :names[] )', [ ':collection' => $this->collection, ':names[]' => $names, ], $this->options) @@ -342,7 +342,7 @@ public function getCollectionName() { */ public function getAllCollectionNames() { try { - return $this->connection->query('SELECT DISTINCT [collection] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] <> :collection ORDER by [collection]', [ + return $this->connection->query('SELECT DISTINCT [collection] FROM {' . $this->table . '} WHERE [collection] <> :collection ORDER by [collection]', [ ':collection' => StorageInterface::DEFAULT_COLLECTION, ])->fetchCol(); } diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 39a999593b2a..d677cbb38e03 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -114,7 +114,7 @@ abstract class Connection { /** * @todo fill in. */ - protected IdentifierHandler $identifierHandler; + public readonly IdentifierHandler $identifiers; /** * Tracks the database API events to be dispatched. @@ -302,8 +302,8 @@ public function attachDatabase(string $database): void { * The table prefix. */ public function getPrefix(): string { - // @trigger_error(__METHOD__ . '() is deprecated in drupal:9.x.0 and is removed from drupal:10.0.0. @todo. See https://www.drupal.org/node/1234567', E_USER_DEPRECATED); - return $this->identifiers()->tablePrefix; + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); + return $this->identifiers->tablePrefix; } /** @@ -313,7 +313,7 @@ public function getPrefix(): string { * A single prefix. */ protected function setPrefix($prefix) { - @trigger_error(__METHOD__ . '() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. @todo. See https://www.drupal.org/node/1234567', E_USER_DEPRECATED); + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); } /** @@ -334,7 +334,7 @@ public function prefixTables($sql) { $replacements = $tables = []; preg_match_all('/(\{(\S*)\})/', $sql, $tables, PREG_SET_ORDER, 0); foreach ($tables as $table) { - $replacements[$table[1]] = $this->identifiers()->table($table[2])->machineName(); + $replacements[$table[1]] = $this->identifiers->table($table[2])->machineName(); } return str_replace(array_keys($replacements), array_values($replacements), $sql); } @@ -364,7 +364,7 @@ public function quoteIdentifiers($sql) { $identifiers = []; $i = 0; foreach ($matches[1] as $match) { - $identifiers[$match] = $this->identifiers()->getPlatformIdentifierName($matches[2][$i]); + $identifiers[$match] = $this->identifiers->getPlatformIdentifierName($matches[2][$i]); $i++; } return strtr($sql, $identifiers); @@ -380,7 +380,7 @@ public function quoteIdentifiers($sql) { * The fully qualified table name. */ public function getFullQualifiedTableName($table) { - return $this->identifiers()->getPlatformDatabaseName($this->getConnectionOptions()['database']) . '.' . $this->identifiers()->table($table)->machineName(); + return $this->identifiers->getPlatformDatabaseName($this->getConnectionOptions()['database']) . '.' . $this->identifiers->table($table)->machineName(); } /** @@ -759,13 +759,6 @@ public function exceptionHandler() { return new ExceptionHandler(); } - /** - * @todo fill in. - */ - public function identifiers(): IdentifierHandler { - return $this->identifierHandler; - } - /** * Prepares and returns a SELECT query object. * @@ -971,7 +964,7 @@ public function condition($conjunction) { * The sanitized database name. */ public function escapeDatabase($database) { - return $this->identifiers()->getPlatformDatabaseName($database); + return $this->identifiers->getPlatformDatabaseName($database); } /** @@ -993,7 +986,7 @@ public function escapeDatabase($database) { */ public function escapeTable($table) { @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); - return $this->identifiers()->table($table)->escapedName(); + return $this->identifiers->table($table)->escapedName(); } /** @@ -1010,7 +1003,7 @@ public function escapeTable($table) { * The sanitized field name. */ public function escapeField($field) { - return $this->identifiers()->getPlatformColumnName($field); + return $this->identifiers->getPlatformColumnName($field); } /** @@ -1028,7 +1021,7 @@ public function escapeField($field) { * The sanitized alias name. */ public function escapeAlias($field) { - return $this->identifiers()->getPlatformAliasName($field); + return $this->identifiers->getPlatformAliasName($field); } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index 5fda1b4f432b..8ed9176bfa4b 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -58,7 +58,7 @@ public function tableEscapeName(Table $table): string { * @todo fill in. */ public function tableMachineName(Table $table): string { - return $table->escapedName(); + return $table->escape(); } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index ce619e2aff16..d395f308d488 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -29,6 +29,14 @@ public function __construct( * @todo fill in. */ public function escapedName(): string { + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); + return $this->escape(); + } + + /** + * @todo fill in. + */ + public function escape(): string { if (!isset($this->escapedName)) { $this->escapedName = $this->identifierHandler->tableEscapeName($this); } diff --git a/core/lib/Drupal/Core/Database/Query/Delete.php b/core/lib/Drupal/Core/Database/Query/Delete.php index b10972a083bd..a3213c2b45eb 100644 --- a/core/lib/Drupal/Core/Database/Query/Delete.php +++ b/core/lib/Drupal/Core/Database/Query/Delete.php @@ -70,7 +70,7 @@ public function __toString() { // Create a sanitized comment string to prepend to the query. $comments = $this->connection->makeComment($this->comments); - $query = $comments . 'DELETE FROM ' . $this->connection->identifiers()->table($this->table)->machineName(); + $query = $comments . 'DELETE FROM ' . $this->connection->identifiers->table($this->table)->machineName(); if (count($this->condition)) { diff --git a/core/lib/Drupal/Core/Database/Query/Insert.php b/core/lib/Drupal/Core/Database/Query/Insert.php index d1a2dd5612d3..33ff994af21f 100644 --- a/core/lib/Drupal/Core/Database/Query/Insert.php +++ b/core/lib/Drupal/Core/Database/Query/Insert.php @@ -116,7 +116,7 @@ public function __toString() { $insert_fields = array_merge($this->defaultFields, $this->insertFields); if (!empty($this->fromQuery)) { - return $comments . 'INSERT INTO ' . $this->connection->identifiers()->table($this->table)->machineName() . ' (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; + return $comments . 'INSERT INTO ' . $this->connection->identifiers->table($this->table)->machineName() . ' (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; } // For simplicity, we will use the $placeholders array to inject diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php index 4ccd9074fcfc..61f4bcba476f 100644 --- a/core/lib/Drupal/Core/Database/Query/Select.php +++ b/core/lib/Drupal/Core/Database/Query/Select.php @@ -854,7 +854,7 @@ public function __toString() { $table_string = '(' . (string) $subquery . ')'; } else { - $table_string = $this->connection->identifiers()->table($table['table'])->machineName(); + $table_string = $this->connection->identifiers->table($table['table'])->machineName(); } // Don't use the AS keyword for table aliases, as some diff --git a/core/lib/Drupal/Core/Database/Query/Truncate.php b/core/lib/Drupal/Core/Database/Query/Truncate.php index 33676e7534af..5f491cb5aaba 100644 --- a/core/lib/Drupal/Core/Database/Query/Truncate.php +++ b/core/lib/Drupal/Core/Database/Query/Truncate.php @@ -75,10 +75,10 @@ public function __toString() { // The statement actually built depends on whether a transaction is active. // @see ::execute() if ($this->connection->inTransaction()) { - return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}'; + return $comments . 'DELETE FROM ' . $this->connection->identifiers->table($this->table)->machineName(); } else { - return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} '; + return $comments . 'TRUNCATE ' . $this->connection->identifiers->table($this->table)->machineName(); } } diff --git a/core/lib/Drupal/Core/Database/Query/Update.php b/core/lib/Drupal/Core/Database/Query/Update.php index 651bb674e755..d2080a0c2d5b 100644 --- a/core/lib/Drupal/Core/Database/Query/Update.php +++ b/core/lib/Drupal/Core/Database/Query/Update.php @@ -166,7 +166,7 @@ public function __toString() { $update_fields[] = $this->connection->escapeField($field) . '=' . $placeholders[$max_placeholder++]; } - $query = $comments . 'UPDATE {' . $this->connection->escapeTable($this->table) . '} SET ' . implode(', ', $update_fields); + $query = $comments . 'UPDATE ' . $this->connection->identifiers->table($this->table)->machineName() . ' SET ' . implode(', ', $update_fields); if (count($this->condition)) { $this->condition->compile($this->connection, $this); diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 44e2fcb75869..fad9da81356a 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -91,7 +91,7 @@ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { $table = ($add_prefix ? $prefix : '') . $table; } else { - $table = $this->connection->identifiers()->table($table)->machineName(quoted: FALSE, prefixed: $add_prefix); + $table = $this->connection->identifiers->table($table)->machineName(quoted: FALSE, prefixed: $add_prefix); } // If the prefix contains a period in it, then that means the prefix also // contains a schema reference in which case we will change the schema key diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php index ea5247bc750b..97c84d8f704e 100644 --- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php +++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php @@ -63,7 +63,7 @@ public function __construct($collection, SerializationInterface $serializer, Con */ public function has($key) { try { - return (bool) $this->connection->query('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :key', [ + return (bool) $this->connection->query('SELECT 1 FROM {' . $this->table . '} WHERE [collection] = :collection AND [name] = :key', [ ':collection' => $this->collection, ':key' => $key, ])->fetchField(); @@ -81,7 +81,7 @@ public function getMultiple(array $keys) { $values = []; try { $result = $this->connection - ->query('SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [name] IN ( :keys[] ) AND [collection] = :collection', [ + ->query('SELECT [name], [value] FROM {' . $this->table . '} WHERE [name] IN ( :keys[] ) AND [collection] = :collection', [ ':keys[]' => $keys, ':collection' => $this->collection, ]) @@ -105,7 +105,7 @@ public function getMultiple(array $keys) { */ public function getAll() { try { - $result = $this->connection->query('SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection', [':collection' => $this->collection]); + $result = $this->connection->query('SELECT [name], [value] FROM {' . $this->table . '} WHERE [collection] = :collection', [':collection' => $this->collection]); } catch (\Exception $e) { $this->catchException($e); diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php index 8813d9e2abe6..aa0c2b3ccc6c 100644 --- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php +++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php @@ -43,7 +43,7 @@ public function __construct( */ public function has($key) { try { - return (bool) $this->connection->query('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [name] = :key AND [expire] > :now', [ + return (bool) $this->connection->query('SELECT 1 FROM {' . $this->table . '} WHERE [collection] = :collection AND [name] = :key AND [expire] > :now', [ ':collection' => $this->collection, ':key' => $key, ':now' => $this->time->getRequestTime(), @@ -61,7 +61,7 @@ public function has($key) { public function getMultiple(array $keys) { try { $values = $this->connection->query( - 'SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [expire] > :now AND [name] IN ( :keys[] ) AND [collection] = :collection', + 'SELECT [name], [value] FROM {' . $this->table . '} WHERE [expire] > :now AND [name] IN ( :keys[] ) AND [collection] = :collection', [ ':now' => $this->time->getRequestTime(), ':keys[]' => $keys, @@ -85,7 +85,7 @@ public function getMultiple(array $keys) { public function getAll() { try { $values = $this->connection->query( - 'SELECT [name], [value] FROM {' . $this->connection->escapeTable($this->table) . '} WHERE [collection] = :collection AND [expire] > :now', + 'SELECT [name], [value] FROM {' . $this->table . '} WHERE [collection] = :collection AND [expire] > :now', [ ':collection' => $this->collection, ':now' => $this->time->getRequestTime(), diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php index ab06f6ea274d..aeb047c79c70 100644 --- a/core/lib/Drupal/Core/Routing/RouteProvider.php +++ b/core/lib/Drupal/Core/Routing/RouteProvider.php @@ -243,7 +243,7 @@ public function preLoadRoutes($names) { } else { try { - $result = $this->connection->query('SELECT [name], [route] FROM {' . $this->connection->escapeTable($this->tableName) . '} WHERE [name] IN ( :names[] )', [':names[]' => $routes_to_load]); + $result = $this->connection->query('SELECT [name], [route] FROM {' . $this->tableName . '} WHERE [name] IN ( :names[] )', [':names[]' => $routes_to_load]); $routes = $result->fetchAllKeyed(); $this->cache->set($cid, $routes, Cache::PERMANENT, ['routes']); @@ -380,7 +380,7 @@ protected function getRoutesByPath($path) { // trailing wildcard parts as long as the pattern matches, since we // dump the route pattern without those optional parts. try { - $routes = $this->connection->query("SELECT [name], [route], [fit] FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE [pattern_outline] IN ( :patterns[] ) AND [number_parts] >= :count_parts", [ + $routes = $this->connection->query("SELECT [name], [route], [fit] FROM {" . $this->tableName . "} WHERE [pattern_outline] IN ( :patterns[] ) AND [number_parts] >= :count_parts", [ ':patterns[]' => $ancestors, ':count_parts' => count($parts), ]) diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php index a65e5686055d..dd41fc93c505 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php @@ -61,6 +61,11 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ const MIN_MAX_ALLOWED_PACKET = 1024; + /** + * @todo fill in. + */ + public readonly IdentifierHandler $identifiers; + /** * {@inheritdoc} */ @@ -87,7 +92,7 @@ public function __construct(\PDO $connection, array $connection_options) { parent::__construct($connection, $connection_options); // Initialize the identifier handler. - $this->identifierHandler = new IdentifierHandler($this->connectionOptions['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`']); + $this->identifiers = new IdentifierHandler($this->connectionOptions['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`']); } /** diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index 568c44b2ee6f..8d2a4aa7e7c9 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -84,6 +84,11 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ protected array $savepoints = []; + /** + * @todo fill in. + */ + public readonly IdentifierHandler $identifiers; + /** * Constructs a connection object. */ @@ -109,7 +114,7 @@ public function __construct(\PDO $connection, array $connection_options) { } // Initialize the identifier handler. - $this->identifierHandler = new IdentifierHandler($connection_options['prefix']); + $this->identifiers = new IdentifierHandler($connection_options['prefix']); } /** @@ -333,7 +338,7 @@ public function makeSequenceName($table, $field) { $sequence_name = $this->prefixTables('{' . $table . '}_' . $field . '_seq'); // Remove identifier quotes as we are constructing a new name from a // prefixed and quoted table name. - return str_replace($this->identifiers()->identifierQuotes, '', $sequence_name); + return str_replace($this->identifiers->identifierQuotes, '', $sequence_name); } /** @@ -345,7 +350,7 @@ public function getFullQualifiedTableName($table) { // The fully qualified table name in PostgreSQL is in the form of // <database>.<schema>.<table>. - return $options['database'] . '.' . $schema . '.' . $this->identifiers()->table($table)->machineName(quoted: FALSE); + return $options['database'] . '.' . $schema . '.' . $this->identifiers->table($table)->machineName(quoted: FALSE); } /** diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index c78365e0f145..347090925603 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -68,6 +68,11 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ protected $transactionalDDLSupport = TRUE; + /** + * @todo fill in. + */ + public readonly IdentifierHandler $identifiers; + /** * Constructs a \Drupal\sqlite\Driver\Database\sqlite\Connection object. */ @@ -84,7 +89,7 @@ public function __construct(\PDO $connection, array $connection_options) { } // Initialize the identifier handler. - $this->identifierHandler = new IdentifierHandler($prefix); + $this->identifiers = new IdentifierHandler($prefix); } /** @@ -429,7 +434,7 @@ public function prepareStatement(string $query, array $options, bool $allow_row_ */ public function getFullQualifiedTableName($table) { // Don't include the SQLite database file name as part of the table name. - return $this->identifiers()->table($table)->machineName(); + return $this->identifiers->table($table)->machineName(); } /** diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Truncate.php b/core/modules/sqlite/src/Driver/Database/sqlite/Truncate.php index 29d82f477921..e5222023eb23 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Truncate.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Truncate.php @@ -19,7 +19,7 @@ public function __toString() { // Create a sanitized comment string to prepend to the query. $comments = $this->connection->makeComment($this->comments); - return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; + return $comments . 'DELETE FROM ' . $this->connection->identifiers->table($this->table)->machineName(); } } diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index 89fb1d056b13..5b9b4f1bb27f 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -430,8 +430,11 @@ public static function providerEscapeTables() { /** * @covers ::escapeTable * @dataProvider providerEscapeTables + * @group legacy */ public function testEscapeTable($expected, $name, array $identifier_quote = ['"', '"']): void { + $this->expectDeprecation('Drupal\Core\Database\Connection::escapeTable() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312'); + $this->expectDeprecation('Drupal\Core\Database\Identifier\Table::escapedName() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312'); $mock_pdo = $this->createMock(StubPDO::class); $connection = new StubConnection($mock_pdo, [], $identifier_quote); diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php index bbb0f63d4fba..2c076d2f894c 100644 --- a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php +++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php @@ -31,6 +31,11 @@ class StubConnection extends Connection { */ public $driver = 'stub'; + /** + * @todo fill in. + */ + public readonly IdentifierHandler $identifiers; + /** * Constructs a Connection object. * @@ -44,7 +49,7 @@ class StubConnection extends Connection { public function __construct(\PDO $connection, array $connection_options, $identifier_quotes = ['', '']) { parent::__construct($connection, $connection_options); // Initialize the identifier handler. - $this->identifierHandler = new IdentifierHandler($connection_options['prefix'] ?? '', $identifier_quotes); + $this->identifiers = new IdentifierHandler($connection_options['prefix'] ?? '', $identifier_quotes); } /** -- GitLab From dd6354ca8df574b0d178954587eb8c6de8f16c36 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Fri, 7 Mar 2025 21:53:25 +0100 Subject: [PATCH 07/66] wip --- core/lib/Drupal/Core/Database/Connection.php | 15 ++++++++------- core/lib/Drupal/Core/Database/Schema.php | 12 ++++-------- .../src/Driver/Database/mysql/Connection.php | 14 +++++--------- .../mysql/src/Driver/Database/mysql/Schema.php | 2 +- .../Kernel/mysql/Console/DbDumpCommandTest.php | 2 +- .../src/Driver/Database/pgsql/Connection.php | 10 +--------- .../src/Driver/Database/sqlite/Connection.php | 11 +---------- .../Tests/Core/Database/Stub/StubConnection.php | 13 +++++-------- 8 files changed, 26 insertions(+), 53 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index d677cbb38e03..71c889503518 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -111,11 +111,6 @@ abstract class Connection { */ protected array $tablePlaceholderReplacements; - /** - * @todo fill in. - */ - public readonly IdentifierHandler $identifiers; - /** * Tracks the database API events to be dispatched. * @@ -139,8 +134,14 @@ abstract class Connection { * - prefix * - namespace * - Other driver-specific options. - */ - public function __construct(object $connection, array $connection_options) { + * @param \Drupal\Core\Database\Identifier\IdentifierHandler|null $identifiers + * The identifiers handler. + */ + public function __construct( + object $connection, + array $connection_options, + public readonly ?IdentifierHandler $identifiers, + ) { // Manage the table prefix. $connection_options['prefix'] = $connection_options['prefix'] ?? ''; diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index fad9da81356a..6193c3a83bac 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -82,16 +82,12 @@ public function nextPlaceholder() { * A keyed array with information about the schema, table name and prefix. */ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { - $prefix = $this->connection->getPrefix(); $info = [ 'schema' => $this->defaultSchema, - 'prefix' => $prefix, + 'prefix' => $this->connection->identifiers->tablePrefix, ]; - if (strpos($table, '%') !== FALSE) { - $table = ($add_prefix ? $prefix : '') . $table; - } - else { - $table = $this->connection->identifiers->table($table)->machineName(quoted: FALSE, prefixed: $add_prefix); + if ($add_prefix) { + $table = $info['prefix'] . $table; } // If the prefix contains a period in it, then that means the prefix also // contains a schema reference in which case we will change the schema key @@ -227,7 +223,7 @@ public function findTables($table_expression) { $condition = $this->buildTableNameCondition('%', 'LIKE'); $condition->compile($this->connection, $this); - $prefix = $this->connection->getPrefix(); + $prefix = $this->connection->identifiers->tablePrefix; $prefix_length = strlen($prefix); $tables = []; // Normally, we would heartily discourage the use of string diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php index dd41fc93c505..a7605aa12ede 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php @@ -61,11 +61,6 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ const MIN_MAX_ALLOWED_PACKET = 1024; - /** - * @todo fill in. - */ - public readonly IdentifierHandler $identifiers; - /** * {@inheritdoc} */ @@ -89,10 +84,11 @@ public function __construct(\PDO $connection, array $connection_options) { } } - parent::__construct($connection, $connection_options); - - // Initialize the identifier handler. - $this->identifiers = new IdentifierHandler($this->connectionOptions['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`']); + parent::__construct( + $connection, + $connection_options, + new IdentifierHandler($this->connectionOptions['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`']), + ); } /** diff --git a/core/modules/mysql/src/Driver/Database/mysql/Schema.php b/core/modules/mysql/src/Driver/Database/mysql/Schema.php index 85e71af18d12..2cc75a078ab9 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Schema.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Schema.php @@ -51,7 +51,7 @@ class Schema extends DatabaseSchema { * A keyed array with information about the database, table name and prefix. */ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { - $info = ['prefix' => $this->connection->getPrefix()]; + $info = ['prefix' => $this->connection->identifiers->tablePrefix]; if ($add_prefix) { $table = $info['prefix'] . $table; } diff --git a/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php b/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php index 66d781b5eb95..d6ab61ec886c 100644 --- a/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php +++ b/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php @@ -35,7 +35,7 @@ protected function setUp(): void { // Create a table with a field type not defined in // \Drupal\Core\Database\Schema::getFieldTypeMap. - $table_name = $connection->getPrefix() . 'foo'; + $table_name = $connection->identifiers->tablePrefix . 'foo'; $sql = "create table if not exists `$table_name` (`test` datetime NOT NULL);"; $connection->query($sql)->execute(); } diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index 8d2a4aa7e7c9..e869522a4071 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -84,11 +84,6 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ protected array $savepoints = []; - /** - * @todo fill in. - */ - public readonly IdentifierHandler $identifiers; - /** * Constructs a connection object. */ @@ -103,7 +98,7 @@ public function __construct(\PDO $connection, array $connection_options) { // needs this. $this->connectionOptions = $connection_options; - parent::__construct($connection, $connection_options); + parent::__construct($connection, $connection_options, new IdentifierHandler($connection_options['prefix'])); // Force PostgreSQL to use the UTF-8 character set by default. $this->connection->exec("SET NAMES 'UTF8'"); @@ -112,9 +107,6 @@ public function __construct(\PDO $connection, array $connection_options) { if (isset($connection_options['init_commands'])) { $this->connection->exec(implode('; ', $connection_options['init_commands'])); } - - // Initialize the identifier handler. - $this->identifiers = new IdentifierHandler($connection_options['prefix']); } /** diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 347090925603..13ecc4da66b0 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -68,17 +68,10 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ protected $transactionalDDLSupport = TRUE; - /** - * @todo fill in. - */ - public readonly IdentifierHandler $identifiers; - /** * Constructs a \Drupal\sqlite\Driver\Database\sqlite\Connection object. */ public function __construct(\PDO $connection, array $connection_options) { - parent::__construct($connection, $connection_options); - // Empty prefix means query the main database -- no need to attach anything. $prefix = $this->connectionOptions['prefix'] ?? ''; if ($prefix !== '') { @@ -87,9 +80,7 @@ public function __construct(\PDO $connection, array $connection_options) { // querying an attached database. $prefix .= '.'; } - - // Initialize the identifier handler. - $this->identifiers = new IdentifierHandler($prefix); + parent::__construct($connection, $connection_options, new IdentifierHandler($prefix)); } /** diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php index 2c076d2f894c..99bcae961835 100644 --- a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php +++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php @@ -31,11 +31,6 @@ class StubConnection extends Connection { */ public $driver = 'stub'; - /** - * @todo fill in. - */ - public readonly IdentifierHandler $identifiers; - /** * Constructs a Connection object. * @@ -47,9 +42,11 @@ class StubConnection extends Connection { * The identifier quote characters. Defaults to an empty strings. */ public function __construct(\PDO $connection, array $connection_options, $identifier_quotes = ['', '']) { - parent::__construct($connection, $connection_options); - // Initialize the identifier handler. - $this->identifiers = new IdentifierHandler($connection_options['prefix'] ?? '', $identifier_quotes); + parent::__construct( + $connection, + $connection_options, + new IdentifierHandler($connection_options['prefix'] ?? '', $identifier_quotes), + ); } /** -- GitLab From be12a401dfb016e2c30f57121e199dc6b7344bd2 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Fri, 7 Mar 2025 22:27:01 +0100 Subject: [PATCH 08/66] wip --- .../sqlite/src/Driver/Database/sqlite/Connection.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 13ecc4da66b0..fa3dfc01e439 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -73,14 +73,17 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn */ public function __construct(\PDO $connection, array $connection_options) { // Empty prefix means query the main database -- no need to attach anything. - $prefix = $this->connectionOptions['prefix'] ?? ''; + $prefix = $connection_options['prefix'] ?? ''; if ($prefix !== '') { - $this->attachDatabase($prefix); + $attachedDatabaseName = $prefix; // Add a ., so queries become prefix.table, which is proper syntax for // querying an attached database. $prefix .= '.'; } parent::__construct($connection, $connection_options, new IdentifierHandler($prefix)); + if (isset($attachedDatabaseName)) { + $this->attachDatabase($attachedDatabaseName); + } } /** -- GitLab From 3cbc4591910d7f65881f130536ab2a0e0399b722 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Fri, 7 Mar 2025 23:11:43 +0100 Subject: [PATCH 09/66] fixes --- core/modules/migrate/tests/src/Kernel/MigrateTestBase.php | 2 +- core/modules/mysql/src/Driver/Database/mysql/Connection.php | 2 +- .../tests/Drupal/KernelTests/Core/Database/ConnectionTest.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/modules/migrate/tests/src/Kernel/MigrateTestBase.php b/core/modules/migrate/tests/src/Kernel/MigrateTestBase.php index 4f4089b3ac37..6cc3c5634d33 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateTestBase.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateTestBase.php @@ -203,7 +203,7 @@ public function display($message, $type = 'status') { $this->migrateMessages[$type][] = $message; } else { - $this->assertEquals('status', $type, $message); + $this->assertEquals('status', $type, (string) $message); } } diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php index a7605aa12ede..83f0995498b5 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php @@ -87,7 +87,7 @@ public function __construct(\PDO $connection, array $connection_options) { parent::__construct( $connection, $connection_options, - new IdentifierHandler($this->connectionOptions['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`']), + new IdentifierHandler($connection_options['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`']), ); } diff --git a/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php b/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php index 2799dd9a740e..590777892b02 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php @@ -130,7 +130,7 @@ public function testPerTablePrefixOption(): void { 'test_table' => $connection_info['default']['prefix'] . '_bar', ]; Database::addConnectionInfo('default', 'foo', $new_connection_info); - $this->expectException(\AssertionError::class); + $this->expectException(\TypeError::class); Database::getConnection('foo', 'default'); } @@ -144,7 +144,7 @@ public function testPrefixArrayOption(): void { 'default' => $connection_info['default']['prefix'], ]; Database::addConnectionInfo('default', 'foo', $new_connection_info); - $this->expectException(\AssertionError::class); + $this->expectException(\TypeError::class); Database::getConnection('foo', 'default'); } -- GitLab From 68f02e1e60e6bcd3f7d412b0abe1a87917332a89 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Fri, 7 Mar 2025 23:45:14 +0100 Subject: [PATCH 10/66] wip --- .../Migrate/d7/MigrateLanguageContentCommentSettingsTest.php | 2 +- core/modules/migrate/src/Plugin/migrate/id_map/Sql.php | 2 +- .../system/tests/src/Functional/System/ErrorHandlerTest.php | 2 +- core/modules/views_ui/tests/src/Functional/PreviewTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php index 16923949a2e1..8f66d9bda709 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php @@ -43,7 +43,7 @@ protected function setUp(): void { */ public function testLanguageCommentSettings(): void { // Confirm there is no message about a missing bundle. - $this->assertEmpty($this->migrateMessages, $this->migrateMessages['error'][0] ?? ''); + $this->assertEmpty($this->migrateMessages, (string) $this->migrateMessages['error'][0] ?? ''); // Article and Blog content type have multilingual settings of 'Enabled, // with Translation'. Assert that comments are translatable and the default diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index faec03c72cc4..4e37191e5a03 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -183,7 +183,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition // Default generated table names, limited to 63 characters. $machine_name = str_replace(':', '__', $this->migration->id()); - $prefix_length = strlen($this->database->getPrefix()); + $prefix_length = strlen($this->database->identifiers->tablePrefix); $this->mapTableName = 'migrate_map_' . mb_strtolower($machine_name); $this->mapTableName = mb_substr($this->mapTableName, 0, 63 - $prefix_length); $this->messageTableName = 'migrate_message_' . mb_strtolower($machine_name); diff --git a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php index 82ba0c8896a1..291ad98bf3d6 100644 --- a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php +++ b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php @@ -134,7 +134,7 @@ public function testExceptionHandler(): void { '%type' => 'DatabaseExceptionWrapper', '@message' => PHP_VERSION_ID >= 80400 ? $message : - 'SELECT "b".* FROM {bananas_are_awesome} "b"', + 'SELECT "b".* FROM "bananas_are_awesome" "b"', '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerPDOException()', '%line' => 64, '%file' => $this->getModulePath('error_test') . '/error_test.module', diff --git a/core/modules/views_ui/tests/src/Functional/PreviewTest.php b/core/modules/views_ui/tests/src/Functional/PreviewTest.php index e0baf9193c05..cc27b383b613 100644 --- a/core/modules/views_ui/tests/src/Functional/PreviewTest.php +++ b/core/modules/views_ui/tests/src/Functional/PreviewTest.php @@ -126,7 +126,7 @@ public function testPreviewUI(): void { $query_string = <<<SQL SELECT "views_test_data"."name" AS "views_test_data_name" FROM -{views_test_data} "views_test_data" +"views_test_data" "views_test_data" WHERE (views_test_data.id = '100') SQL; $this->assertSession()->assertEscaped($query_string); -- GitLab From 29cffec9e0e3509704aafe7af916997d2e450a8e Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sat, 8 Mar 2025 11:43:50 +0100 Subject: [PATCH 11/66] wip --- core/lib/Drupal/Core/Database/Connection.php | 2 +- .../Database/Identifier/IdentifierHandler.php | 13 ++++- .../Drupal/Core/Database/Identifier/Table.php | 56 ++++++++++++------- .../Core/Database/ConnectionTest.php | 29 ---------- .../Tests/Core/Database/ConnectionTest.php | 1 - 5 files changed, 48 insertions(+), 53 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 71c889503518..f4dda32d648a 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -987,7 +987,7 @@ public function escapeDatabase($database) { */ public function escapeTable($table) { @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); - return $this->identifiers->table($table)->escapedName(); + return $this->identifiers->table($table)->table; } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index 8ed9176bfa4b..20416e534c36 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -50,15 +50,22 @@ public function table(string|Table $name): Table { /** * @todo fill in. */ - public function tableEscapeName(Table $table): string { - return preg_replace('/[^A-Za-z0-9_.]+/', '', $table->name); + public function tableEscapeName(string $table): string { + return preg_replace('/[^A-Za-z0-9_.]+/', '', $table); } /** * @todo fill in. */ public function tableMachineName(Table $table): string { - return $table->escape(); + $tableName = $table->needsPrefix ? $this->tablePrefix . $table->table : $table->table; + if ($table->schema) { + return implode('.', [$table->schema, $table->database, $tableName]); + } + elseif ($table->database) { + return implode('.', [$table->database, $tableName]); + } + return $tableName; } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index d395f308d488..f6e9511aa8b5 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -12,47 +12,65 @@ class Table extends IdentifierBase { /** * @todo fill in. */ - protected string $escapedName; + public readonly ?string $schema; /** * @todo fill in. */ - protected string $machineName; + public readonly ?string $database; - public function __construct( - protected readonly IdentifierHandler $identifierHandler, - public readonly string $name, - ) { - } + /** + * @todo fill in. + */ + public readonly string $table; /** * @todo fill in. */ - public function escapedName(): string { - @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); - return $this->escape(); - } + public readonly bool $needsPrefix; /** * @todo fill in. */ - public function escape(): string { - if (!isset($this->escapedName)) { - $this->escapedName = $this->identifierHandler->tableEscapeName($this); - } - return $this->escapedName; + protected string $machineName; + + public function __construct( + protected readonly IdentifierHandler $identifierHandler, + public readonly string $identifier, + ) { + $parts = explode(".", $identifier); + [$this->schema, $this->database, $this->table] = match (count($parts)) { + 1 => [ + NULL, + NULL, + $this->identifierHandler->tableEscapeName($parts[0]), + ], + 2 => [ + NULL, + $this->identifierHandler->tableEscapeName($parts[0]), + $this->identifierHandler->tableEscapeName($parts[1]), + ], + 3 => [ + $this->identifierHandler->tableEscapeName($parts[0]), + $this->identifierHandler->tableEscapeName($parts[1]), + $this->identifierHandler->tableEscapeName($parts[2]), + ], + }; + $this->needsPrefix = match (count($parts)) { + 1 => TRUE, + default => FALSE, + }; } /** * @todo fill in. */ - public function machineName(bool $prefixed = TRUE, bool $quoted = TRUE): string { + public function machineName(bool $quoted = TRUE): string { if (!isset($this->machineName)) { $this->machineName = $this->identifierHandler->tableMachineName($this); } [$start_quote, $end_quote] = $this->identifierHandler->identifierQuotes; - $unquotedMachineName = $prefixed ? $this->identifierHandler->tablePrefix . $this->machineName : $this->machineName; - return $quoted ? $start_quote . str_replace(".", "$end_quote.$start_quote", $unquotedMachineName) . $end_quote : $unquotedMachineName; + return $quoted ? $start_quote . str_replace(".", "$end_quote.$start_quote", $this->machineName) . $end_quote : $this->machineName; } } diff --git a/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php b/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php index 590777892b02..a16f52e27cc6 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php @@ -119,35 +119,6 @@ public function testConnectionOptions(): void { $this->assertNotEquals($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.'); } - /** - * Tests per-table prefix connection option. - */ - public function testPerTablePrefixOption(): void { - $connection_info = Database::getConnectionInfo('default'); - $new_connection_info = $connection_info['default']; - $new_connection_info['prefix'] = [ - 'default' => $connection_info['default']['prefix'], - 'test_table' => $connection_info['default']['prefix'] . '_bar', - ]; - Database::addConnectionInfo('default', 'foo', $new_connection_info); - $this->expectException(\TypeError::class); - Database::getConnection('foo', 'default'); - } - - /** - * Tests the prefix connection option in array form. - */ - public function testPrefixArrayOption(): void { - $connection_info = Database::getConnectionInfo('default'); - $new_connection_info = $connection_info['default']; - $new_connection_info['prefix'] = [ - 'default' => $connection_info['default']['prefix'], - ]; - Database::addConnectionInfo('default', 'foo', $new_connection_info); - $this->expectException(\TypeError::class); - Database::getConnection('foo', 'default'); - } - /** * Ensure that you cannot execute multiple statements in a query. */ diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index 5b9b4f1bb27f..239e88ba6e2a 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -434,7 +434,6 @@ public static function providerEscapeTables() { */ public function testEscapeTable($expected, $name, array $identifier_quote = ['"', '"']): void { $this->expectDeprecation('Drupal\Core\Database\Connection::escapeTable() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312'); - $this->expectDeprecation('Drupal\Core\Database\Identifier\Table::escapedName() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312'); $mock_pdo = $this->createMock(StubPDO::class); $connection = new StubConnection($mock_pdo, [], $identifier_quote); -- GitLab From 08377e211c7310c55d403b886407f1578ca67ba1 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sat, 8 Mar 2025 11:55:10 +0100 Subject: [PATCH 12/66] wip --- .../system/tests/src/Functional/System/ErrorHandlerTest.php | 2 +- core/modules/views_ui/tests/src/Functional/PreviewTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php index 291ad98bf3d6..82ba0c8896a1 100644 --- a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php +++ b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php @@ -134,7 +134,7 @@ public function testExceptionHandler(): void { '%type' => 'DatabaseExceptionWrapper', '@message' => PHP_VERSION_ID >= 80400 ? $message : - 'SELECT "b".* FROM "bananas_are_awesome" "b"', + 'SELECT "b".* FROM {bananas_are_awesome} "b"', '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerPDOException()', '%line' => 64, '%file' => $this->getModulePath('error_test') . '/error_test.module', diff --git a/core/modules/views_ui/tests/src/Functional/PreviewTest.php b/core/modules/views_ui/tests/src/Functional/PreviewTest.php index cc27b383b613..e0baf9193c05 100644 --- a/core/modules/views_ui/tests/src/Functional/PreviewTest.php +++ b/core/modules/views_ui/tests/src/Functional/PreviewTest.php @@ -126,7 +126,7 @@ public function testPreviewUI(): void { $query_string = <<<SQL SELECT "views_test_data"."name" AS "views_test_data_name" FROM -"views_test_data" "views_test_data" +{views_test_data} "views_test_data" WHERE (views_test_data.id = '100') SQL; $this->assertSession()->assertEscaped($query_string); -- GitLab From df579b085416ccb22844fd1d23d6feaa807aafe1 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sat, 8 Mar 2025 12:48:42 +0100 Subject: [PATCH 13/66] wip --- .../Database/Identifier/IdentifierHandler.php | 38 ++++++++++++++----- .../Database/Identifier/IdentifierType.php | 18 ++++----- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index 20416e534c36..2d929d295cf3 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -10,12 +10,12 @@ class IdentifierHandler { /** - * @var array{'identifier':array<string,array<int,string>>,'platform':array<string,array<int,string>>} + * @var array{'identifier':array<string,array<string,string>>,'platform':array<string,array<string,string>>} */ protected array $identifiers; /** - * @var array{'identifier':array<string,array<int,string>>,'platform':array<string,array<int,string>>} + * @var array{'identifier':array<string,array<string,string>>,'platform':array<string,array<string,string>>} */ protected array $aliases; @@ -39,12 +39,18 @@ public function __construct( /** * @todo fill in. */ - public function table(string|Table $name): Table { - if (is_string($name)) { - $table = new Table($this, $name); - return $table; + public function table(string|Table $tableIdentifier): Table { + if ($tableIdentifier instanceof Table) { + $tableIdentifier = $tableIdentifier->identifier; } - return $name; + if ($this->hasIdentifier($tableIdentifier, IdentifierType::Table)) { + $table = $this->getIdentifier($tableIdentifier, IdentifierType::Table); + } + else { + $table = new Table($this, $tableIdentifier); + $this->setIdentifier1($tableIdentifier, IdentifierType::Table, $table); + } + return $table; } /** @@ -85,8 +91,22 @@ protected function setIdentifier(string $identifier, string $platform_identifier /** * @todo fill in. */ - protected function hasIdentifier(string $identifier, IdentifierType $type): bool { - return isset($this->identifiers['identifier'][$identifier][$type->value]); + protected function setIdentifier1(string $id, IdentifierType $type, IdentifierBase $identifier): void { + $this->identifiers['identifier'][$id][$type->value] = $identifier; + } + + /** + * @todo fill in. + */ + protected function hasIdentifier(string $id, IdentifierType $type): bool { + return isset($this->identifiers['identifier'][$id][$type->value]); + } + + /** + * @todo fill in. + */ + protected function getIdentifier(string $id, IdentifierType $type): IdentifierBase { + return $this->identifiers['identifier'][$id][$type->value]; } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php index d891f7396a7c..d6605218ce3c 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php @@ -7,14 +7,14 @@ /** * Enum for database identifier types. */ -enum IdentifierType: int { - case Generic = 0x0; - case Database = 0x4; - case Sequence = 0x5; - case Table = 0x7; - case PrefixedTable = 0x8; - case Column = 0xC; - case Index = 0xD; +enum IdentifierType: string { + case Generic = 'unknown'; + case Database = 'database'; + case Schema = 'schema'; + case Sequence = 'sequence'; + case Table = 'table'; + case Column = 'column'; + case Index = 'index'; - case Alias = 0x100; + case Alias = 'alias'; } -- GitLab From 161954cf9623382755e7a5af35f48aa33fdfa019 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sat, 8 Mar 2025 15:13:52 +0100 Subject: [PATCH 14/66] wip --- core/lib/Drupal/Core/Database/Connection.php | 50 +++++-- .../Database/Identifier/IdentifierBase.php | 16 ++- .../Database/Identifier/IdentifierHandler.php | 124 +----------------- .../Drupal/Core/Database/Identifier/Table.php | 5 +- .../Core/Database/ConnectionTest.php | 29 ++++ 5 files changed, 90 insertions(+), 134 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index f4dda32d648a..20e17b65a026 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -111,6 +111,23 @@ abstract class Connection { */ protected array $tablePlaceholderReplacements; + /** + * List of escaped field names, keyed by unescaped names. + * + * There are cases in which escapeField() is called on an empty string. In + * this case it should always return an empty string. + * + * @var array + */ + protected $escapedFields = ["" => ""]; + + /** + * List of escaped aliases names, keyed by unescaped aliases. + * + * @var array + */ + protected $escapedAliases = []; + /** * Tracks the database API events to be dispatched. * @@ -162,7 +179,7 @@ public function __construct( * @todo Remove the method in Drupal 1x. */ public function __get($name): mixed { - if (in_array($name, ['prefix', 'escapedTables', 'escapedFields', 'escapedAliases', 'identifierQuotes'])) { + if (in_array($name, ['prefix', 'escapedTables', 'identifierQuotes'])) { @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.9.0 and the property is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/1234567", E_USER_DEPRECATED); return []; } @@ -361,14 +378,7 @@ public function prefixTables($sql) { * This method should only be called by database API code. */ public function quoteIdentifiers($sql) { - preg_match_all('/(\[(.+?)\])/', $sql, $matches); - $identifiers = []; - $i = 0; - foreach ($matches[1] as $match) { - $identifiers[$match] = $this->identifiers->getPlatformIdentifierName($matches[2][$i]); - $i++; - } - return strtr($sql, $identifiers); + return str_replace(['[', ']'], $this->identifiers->identifierQuotes, $sql); } /** @@ -381,7 +391,8 @@ public function quoteIdentifiers($sql) { * The fully qualified table name. */ public function getFullQualifiedTableName($table) { - return $this->identifiers->getPlatformDatabaseName($this->getConnectionOptions()['database']) . '.' . $this->identifiers->table($table)->machineName(); + $options = $this->getConnectionOptions(); + return $options['database'] . '.' . $this->identifiers->table($table)->machineName(); } /** @@ -965,7 +976,9 @@ public function condition($conjunction) { * The sanitized database name. */ public function escapeDatabase($database) { - return $this->identifiers->getPlatformDatabaseName($database); + $database = preg_replace('/[^A-Za-z0-9_]+/', '', $database); + [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; + return $start_quote . $database . $end_quote; } /** @@ -1004,7 +1017,14 @@ public function escapeTable($table) { * The sanitized field name. */ public function escapeField($field) { - return $this->identifiers->getPlatformColumnName($field); + if (!isset($this->escapedFields[$field])) { + $escaped = preg_replace('/[^A-Za-z0-9_.]+/', '', $field); + [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; + // Sometimes fields have the format table_alias.field. In such cases + // both identifiers should be quoted, for example, "table_alias"."field". + $this->escapedFields[$field] = $start_quote . str_replace('.', $end_quote . '.' . $start_quote, $escaped) . $end_quote; + } + return $this->escapedFields[$field]; } /** @@ -1022,7 +1042,11 @@ public function escapeField($field) { * The sanitized alias name. */ public function escapeAlias($field) { - return $this->identifiers->getPlatformAliasName($field); + if (!isset($this->escapedAliases[$field])) { + [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; + $this->escapedAliases[$field] = $start_quote . preg_replace('/[^A-Za-z0-9_]+/', '', $field) . $end_quote; + } + return $this->escapedAliases[$field]; } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php index a111c1b13aad..54e363684701 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -7,5 +7,19 @@ /** * @todo fill in. */ -abstract class IdentifierBase { +abstract class IdentifierBase implements \Stringable { + + public function __construct( + protected readonly IdentifierHandler $identifierHandler, + public readonly string $identifier, + ) { + } + + /** + * @todo fill in. + */ + public function __toString(): string { + return $this->machineName(); + } + } diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index 2d929d295cf3..d1c4a8bb1d7b 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -4,21 +4,18 @@ namespace Drupal\Core\Database\Identifier; +use Drupal\Component\Assertion\Inspector; + /** * @todo fill in. */ class IdentifierHandler { /** - * @var array{'identifier':array<string,array<string,string>>,'platform':array<string,array<string,string>>} + * @var array{'identifier':array<string,array<string,string>>,'machine':array<string,array<string,string>>} */ protected array $identifiers; - /** - * @var array{'identifier':array<string,array<string,string>>,'platform':array<string,array<string,string>>} - */ - protected array $aliases; - /** * Constructs an IdentifierHandler object. * @@ -34,6 +31,7 @@ public function __construct( public readonly string $tablePrefix, public readonly array $identifierQuotes = ['"', '"'], ) { + assert(count($this->identifierQuotes) === 2 && Inspector::assertAllStrings($this->identifierQuotes), __CLASS__ . '::$identifierQuotes must contain 2 string values'); } /** @@ -48,7 +46,7 @@ public function table(string|Table $tableIdentifier): Table { } else { $table = new Table($this, $tableIdentifier); - $this->setIdentifier1($tableIdentifier, IdentifierType::Table, $table); + $this->setIdentifier($tableIdentifier, IdentifierType::Table, $table); } return $table; } @@ -77,21 +75,7 @@ public function tableMachineName(Table $table): string { /** * @todo fill in. */ - protected function setIdentifier(string $identifier, string $platform_identifier, IdentifierType $type, bool $isAlias): void { - if (!$isAlias) { - $this->identifiers['identifier'][$identifier][$type->value] = $platform_identifier; - $this->identifiers['platform'][$platform_identifier][$type->value] = $identifier; - } - else { - $this->aliases['identifier'][$identifier][$type->value] = $platform_identifier; - $this->aliases['platform'][$platform_identifier][$type->value] = $identifier; - } - } - - /** - * @todo fill in. - */ - protected function setIdentifier1(string $id, IdentifierType $type, IdentifierBase $identifier): void { + protected function setIdentifier(string $id, IdentifierType $type, IdentifierBase $identifier): void { $this->identifiers['identifier'][$id][$type->value] = $identifier; } @@ -109,100 +93,4 @@ protected function getIdentifier(string $id, IdentifierType $type): IdentifierBa return $this->identifiers['identifier'][$id][$type->value]; } - /** - * @todo fill in. - */ - public function getPlatformIdentifierName(string $original_name, bool $quoted = TRUE): string { - if (!$this->hasIdentifier($original_name, IdentifierType::Generic)) { - $this->setIdentifier($original_name, $this->resolvePlatformGenericIdentifier($original_name), IdentifierType::Generic, FALSE); - } - [$start_quote, $end_quote] = $this->identifierQuotes; - $identifier = $this->identifiers['identifier'][$original_name][IdentifierType::Generic->value]; - return $quoted ? $start_quote . $identifier . $end_quote : $identifier; - } - - /** - * @todo fill in. - */ - public function getPlatformDatabaseName(string $original_name, bool $quoted = TRUE): string { - $original_name = (string) preg_replace('/[^A-Za-z0-9_]+/', '', $original_name); - if (!$this->hasIdentifier($original_name, IdentifierType::Database)) { - $this->setIdentifier($original_name, $this->resolvePlatformDatabaseIdentifier($original_name), IdentifierType::Database, FALSE); - } - [$start_quote, $end_quote] = $this->identifierQuotes; - return $quoted ? - $start_quote . $this->identifiers['identifier'][$original_name][IdentifierType::Database->value] . $end_quote : - $this->identifiers['identifier'][$original_name][IdentifierType::Database->value]; - } - - /** - * @todo fill in. - */ - public function getPlatformColumnName(string $original_name, bool $quoted = TRUE): string { - if ($original_name === '') { - return ''; - } - $original_name = (string) preg_replace('/[^A-Za-z0-9_.]+/', '', $original_name); - if (!$this->hasIdentifier($original_name, IdentifierType::Column)) { - $this->setIdentifier($original_name, $this->resolvePlatformColumnIdentifier($original_name), IdentifierType::Column, FALSE); - } - // Sometimes fields have the format table_alias.field. In such cases - // both identifiers should be quoted, for example, "table_alias"."field". - [$start_quote, $end_quote] = $this->identifierQuotes; - return $quoted ? - $start_quote . str_replace(".", "$end_quote.$start_quote", $this->identifiers['identifier'][$original_name][IdentifierType::Column->value]) . $end_quote : - $this->identifiers['identifier'][$original_name][IdentifierType::Column->value]; - } - - /** - * @todo fill in. - */ - public function getPlatformAliasName(string $original_name, IdentifierType $type = IdentifierType::Generic, bool $quoted = TRUE): string { - $original_name = (string) preg_replace('/[^A-Za-z0-9_]+/', '', $original_name); - if ($original_name[0] === $this->identifierQuotes[0]) { - $original_name = substr($original_name, 1, -1); - } - if (!$this->hasIdentifier($original_name, IdentifierType::Alias)) { - $this->setIdentifier($original_name, $this->resolvePlatformGenericIdentifier($original_name), $type, TRUE); - } - [$start_quote, $end_quote] = $this->identifierQuotes; - $alias = $this->aliases['identifier'][$original_name][$type->value] ?? $this->identifiers['identifier'][$original_name][0]; - return $quoted ? $start_quote . $alias . $end_quote : $alias; - } - - /** - * @todo fill in. - */ - protected function resolvePlatformGenericIdentifier(string $identifier): string { - return $identifier; - } - - /** - * @todo fill in. - */ - protected function resolvePlatformDatabaseIdentifier(string $identifier): string { - return $identifier; - } - - /** - * @todo fill in. - */ - protected function resolvePlatformTableIdentifier(string $identifier): string { - return $identifier; - } - - /** - * @todo fill in. - */ - protected function resolvePlatformColumnIdentifier(string $identifier): string { - return $identifier; - } - - /** - * @todo fill in. - */ - protected function resolvePlatformAliasIdentifier(string $identifier, int $type = 0): string { - return $identifier; - } - } diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index f6e9511aa8b5..36d6db35b2ff 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -35,9 +35,10 @@ class Table extends IdentifierBase { protected string $machineName; public function __construct( - protected readonly IdentifierHandler $identifierHandler, - public readonly string $identifier, + IdentifierHandler $identifierHandler, + string $identifier, ) { + parent::__construct($identifierHandler, $identifier); $parts = explode(".", $identifier); [$this->schema, $this->database, $this->table] = match (count($parts)) { 1 => [ diff --git a/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php b/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php index a16f52e27cc6..2799dd9a740e 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php @@ -119,6 +119,35 @@ public function testConnectionOptions(): void { $this->assertNotEquals($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.'); } + /** + * Tests per-table prefix connection option. + */ + public function testPerTablePrefixOption(): void { + $connection_info = Database::getConnectionInfo('default'); + $new_connection_info = $connection_info['default']; + $new_connection_info['prefix'] = [ + 'default' => $connection_info['default']['prefix'], + 'test_table' => $connection_info['default']['prefix'] . '_bar', + ]; + Database::addConnectionInfo('default', 'foo', $new_connection_info); + $this->expectException(\AssertionError::class); + Database::getConnection('foo', 'default'); + } + + /** + * Tests the prefix connection option in array form. + */ + public function testPrefixArrayOption(): void { + $connection_info = Database::getConnectionInfo('default'); + $new_connection_info = $connection_info['default']; + $new_connection_info['prefix'] = [ + 'default' => $connection_info['default']['prefix'], + ]; + Database::addConnectionInfo('default', 'foo', $new_connection_info); + $this->expectException(\AssertionError::class); + Database::getConnection('foo', 'default'); + } + /** * Ensure that you cannot execute multiple statements in a query. */ -- GitLab From 63e04d8912b980fbd0eab7983e64ed490c2587d1 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sat, 8 Mar 2025 17:03:38 +0100 Subject: [PATCH 15/66] wip --- core/lib/Drupal/Core/Database/Connection.php | 21 +++++++------------ .../Database/Identifier/IdentifierBase.php | 5 +++++ .../tests/src/Unit/MigrateSqlIdMapTest.php | 2 +- .../src/Driver/Database/sqlite/Connection.php | 4 ++++ .../Functional/System/ErrorHandlerTest.php | 4 ++-- .../tests/src/Functional/PreviewTest.php | 8 +++---- .../Tests/Core/Database/ConnectionTest.php | 20 ++++++++++++++++++ 7 files changed, 43 insertions(+), 21 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 20e17b65a026..3e15808aa920 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -101,16 +101,6 @@ abstract class Connection { */ protected $schema = NULL; - /** - * Replacements to fully qualify {table} placeholders in SQL strings. - * - * An array of two strings, the first being the replacement for opening curly - * brace '{', the second for closing curly brace '}'. - * - * @var string[] - */ - protected array $tablePlaceholderReplacements; - /** * List of escaped field names, keyed by unescaped names. * @@ -161,6 +151,10 @@ public function __construct( ) { // Manage the table prefix. $connection_options['prefix'] = $connection_options['prefix'] ?? ''; + assert(is_string($connection_options['prefix']), 'The \'prefix\' connection option to ' . __METHOD__ . '() must be a string.'); + if (is_array($connection_options['prefix'])) { + $connection_options['prefix'] = $connection_options['prefix'][0] ?? ''; + } // Work out the database driver namespace if none is provided. This normally // written to setting.php by installer or set by @@ -179,7 +173,7 @@ public function __construct( * @todo Remove the method in Drupal 1x. */ public function __get($name): mixed { - if (in_array($name, ['prefix', 'escapedTables', 'identifierQuotes'])) { + if (in_array($name, ['prefix', 'escapedTables', 'identifierQuotes', 'tablePlaceholderReplacements'])) { @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.9.0 and the property is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/1234567", E_USER_DEPRECATED); return []; } @@ -391,8 +385,7 @@ public function quoteIdentifiers($sql) { * The fully qualified table name. */ public function getFullQualifiedTableName($table) { - $options = $this->getConnectionOptions(); - return $options['database'] . '.' . $this->identifiers->table($table)->machineName(); + return $this->identifiers->table($this->getConnectionOptions()['database'] . '.' . $table)->machineName(); } /** @@ -1000,7 +993,7 @@ public function escapeDatabase($database) { */ public function escapeTable($table) { @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); - return $this->identifiers->table($table)->table; + return $this->identifiers->table($table)->machineName(quoted: FALSE); } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php index 54e363684701..ce18df7e2b6a 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -15,6 +15,11 @@ public function __construct( ) { } + /** + * @todo fill in. + */ + abstract public function machineName(bool $quoted = TRUE): string; + /** * @todo fill in. */ diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php index d5a2c6375f6b..9e5f02481a9f 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php @@ -1011,7 +1011,7 @@ public function testGetQualifiedMapTablePrefix(): void { $qualified_map_table = $this->getIdMap()->getQualifiedMapTableName(); // The SQLite driver is a special flower. It will prefix tables with // PREFIX.TABLE, instead of the standard PREFIXTABLE. - // @see \Drupal\Core\Database\Driver\sqlite\Connection::__construct() + // @see \Drupal\sqlite\Driver\Database\sqlite\Connection::__construct() $this->assertEquals('"prefix"."migrate_map_sql_idmap_test"', $qualified_map_table); } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index fa3dfc01e439..b74c084dda4f 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -74,6 +74,10 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn public function __construct(\PDO $connection, array $connection_options) { // Empty prefix means query the main database -- no need to attach anything. $prefix = $connection_options['prefix'] ?? ''; + assert(is_string($connection_options['prefix']), 'The \'prefix\' connection option to ' . __METHOD__ . '() must be a string.'); + if (is_array($connection_options['prefix'])) { + $connection_options['prefix'] = $connection_options['prefix'][0] ?? ''; + } if ($prefix !== '') { $attachedDatabaseName = $prefix; // Add a ., so queries become prefix.table, which is proper syntax for diff --git a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php index 82ba0c8896a1..dc39f89606ec 100644 --- a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php +++ b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php @@ -134,7 +134,7 @@ public function testExceptionHandler(): void { '%type' => 'DatabaseExceptionWrapper', '@message' => PHP_VERSION_ID >= 80400 ? $message : - 'SELECT "b".* FROM {bananas_are_awesome} "b"', + 'SELECT "b"\.\* FROM .*bananas_are_awesome. "b"', '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerPDOException()', '%line' => 64, '%file' => $this->getModulePath('error_test') . '/error_test.module', @@ -160,7 +160,7 @@ public function testExceptionHandler(): void { $this->assertSession()->pageTextContains($error_pdo_exception['%type']); // Assert statement improved since static queries adds table alias in the // error message. - $this->assertSession()->pageTextContains($error_pdo_exception['@message']); + $this->assertSession()->pageTextMatches($error_pdo_exception['@message']); $error_details = new FormattableMarkup('in %function (line ', $error_pdo_exception); $this->assertSession()->responseContains($error_details); $this->drupalGet('error-test/trigger-renderer-exception'); diff --git a/core/modules/views_ui/tests/src/Functional/PreviewTest.php b/core/modules/views_ui/tests/src/Functional/PreviewTest.php index e0baf9193c05..fde2034eadf3 100644 --- a/core/modules/views_ui/tests/src/Functional/PreviewTest.php +++ b/core/modules/views_ui/tests/src/Functional/PreviewTest.php @@ -124,12 +124,12 @@ public function testPreviewUI(): void { $this->assertSession()->pageTextContains('View render time'); $this->assertSession()->responseContains('<strong>Query</strong>'); $query_string = <<<SQL -SELECT "views_test_data"."name" AS "views_test_data_name" +SELECT \"views_test_data\"\.\"name\" AS \"views_test_data_name\" FROM -{views_test_data} "views_test_data" -WHERE (views_test_data.id = '100') +.*views_test_data.* \"views_test_data\" +WHERE \(views_test_data\.id \= '100'\) SQL; - $this->assertSession()->assertEscaped($query_string); + $this->assertSession()->pageTextMatches($query_string); // Test that the statistics and query are rendered above the preview. $this->assertLessThan(strpos($this->getSession()->getPage()->getContent(), 'js-view-dom-id'), strpos($this->getSession()->getPage()->getContent(), 'views-query-info')); diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index 239e88ba6e2a..d93bdaedf1a5 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -534,6 +534,26 @@ public function testEscapeDatabase($expected, $name, array $identifier_quote = [ $this->assertEquals($expected, $connection->escapeDatabase($name)); } + /** + * Tests identifier quotes. + */ + public function testIdentifierQuotesAssertCount(): void { + $this->expectException(\AssertionError::class); + $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierHandler::$identifierQuotes must contain 2 string values'); + $mock_pdo = $this->createMock(StubPDO::class); + new StubConnection($mock_pdo, [], ['"']); + } + + /** + * Tests identifier quotes. + */ + public function testIdentifierQuotesAssertString(): void { + $this->expectException(\AssertionError::class); + $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierHandler::$identifierQuotes must contain 2 string values'); + $mock_pdo = $this->createMock(StubPDO::class); + new StubConnection($mock_pdo, [], [0, '1']); + } + /** * @covers ::__construct */ -- GitLab From 7c5845a63e6ec85db07ce54d6941dc057a733572 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sat, 8 Mar 2025 17:24:48 +0100 Subject: [PATCH 16/66] wip --- core/modules/migrate/src/Plugin/migrate/id_map/Sql.php | 2 +- .../sqlite/src/Driver/Database/sqlite/Connection.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index 4e37191e5a03..95c3e16dea06 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -183,7 +183,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition // Default generated table names, limited to 63 characters. $machine_name = str_replace(':', '__', $this->migration->id()); - $prefix_length = strlen($this->database->identifiers->tablePrefix); + $prefix_length = isset($this->database->identifiers) ? strlen($this->database->identifiers->tablePrefix) : 0 ; $this->mapTableName = 'migrate_map_' . mb_strtolower($machine_name); $this->mapTableName = mb_substr($this->mapTableName, 0, 63 - $prefix_length); $this->messageTableName = 'migrate_message_' . mb_strtolower($machine_name); diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index b74c084dda4f..46b787e283bf 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -74,9 +74,9 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn public function __construct(\PDO $connection, array $connection_options) { // Empty prefix means query the main database -- no need to attach anything. $prefix = $connection_options['prefix'] ?? ''; - assert(is_string($connection_options['prefix']), 'The \'prefix\' connection option to ' . __METHOD__ . '() must be a string.'); - if (is_array($connection_options['prefix'])) { - $connection_options['prefix'] = $connection_options['prefix'][0] ?? ''; + assert(is_string($prefix), 'The \'prefix\' connection option to ' . __METHOD__ . '() must be a string.'); + if (is_array($prefix)) { + $prefix = $prefix[0] ?? ''; } if ($prefix !== '') { $attachedDatabaseName = $prefix; -- GitLab From 64f1e9305c7e6bedb8902279dc3b78fd5fa14390 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sat, 8 Mar 2025 17:26:52 +0100 Subject: [PATCH 17/66] wip --- core/modules/migrate/src/Plugin/migrate/id_map/Sql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index 95c3e16dea06..53b598bb14db 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -183,7 +183,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition // Default generated table names, limited to 63 characters. $machine_name = str_replace(':', '__', $this->migration->id()); - $prefix_length = isset($this->database->identifiers) ? strlen($this->database->identifiers->tablePrefix) : 0 ; + $prefix_length = isset($this->database->identifiers) ? strlen($this->database->identifiers->tablePrefix) : 0; $this->mapTableName = 'migrate_map_' . mb_strtolower($machine_name); $this->mapTableName = mb_substr($this->mapTableName, 0, 63 - $prefix_length); $this->messageTableName = 'migrate_message_' . mb_strtolower($machine_name); -- GitLab From 06373b4f2d9a75d6fe8f38bfa06460451bc3bb17 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sat, 8 Mar 2025 19:00:50 +0100 Subject: [PATCH 18/66] wip --- .../Core/Database/Identifier/IdentifierHandler.php | 8 ++++---- core/lib/Drupal/Core/Database/Identifier/Table.php | 6 +++--- core/lib/Drupal/Core/Database/Schema.php | 2 +- .../mysql/tests/src/Unit/ConnectionTest.php | 2 +- .../pgsql/src/Driver/Database/pgsql/Schema.php | 12 ++++++------ .../tests/src/Kernel/pgsql/NonPublicSchemaTest.php | 14 +++++++------- .../pgsql/tests/src/Kernel/pgsql/SchemaTest.php | 8 ++++---- core/modules/pgsql/tests/src/Unit/SchemaTest.php | 1 - 8 files changed, 26 insertions(+), 27 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index d1c4a8bb1d7b..641cc498a344 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -63,11 +63,11 @@ public function tableEscapeName(string $table): string { */ public function tableMachineName(Table $table): string { $tableName = $table->needsPrefix ? $this->tablePrefix . $table->table : $table->table; - if ($table->schema) { - return implode('.', [$table->schema, $table->database, $tableName]); + if ($table->database) { + return implode('.', [$table->database, $table->schema, $tableName]); } - elseif ($table->database) { - return implode('.', [$table->database, $tableName]); + elseif ($table->schema) { + return implode('.', [$table->schema, $tableName]); } return $tableName; } diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 36d6db35b2ff..1733d6329949 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -12,12 +12,12 @@ class Table extends IdentifierBase { /** * @todo fill in. */ - public readonly ?string $schema; + public readonly ?string $database; /** * @todo fill in. */ - public readonly ?string $database; + public readonly ?string $schema; /** * @todo fill in. @@ -40,7 +40,7 @@ public function __construct( ) { parent::__construct($identifierHandler, $identifier); $parts = explode(".", $identifier); - [$this->schema, $this->database, $this->table] = match (count($parts)) { + [$this->database, $this->schema, $this->table] = match (count($parts)) { 1 => [ NULL, NULL, diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 6193c3a83bac..6504b70619cd 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -84,7 +84,7 @@ public function nextPlaceholder() { protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { $info = [ 'schema' => $this->defaultSchema, - 'prefix' => $this->connection->identifiers->tablePrefix, + 'prefix' => isset($this->connection->identifiers) ? $this->connection->identifiers->tablePrefix : '', ]; if ($add_prefix) { $table = $info['prefix'] . $table; diff --git a/core/modules/mysql/tests/src/Unit/ConnectionTest.php b/core/modules/mysql/tests/src/Unit/ConnectionTest.php index 6e69193726e7..a839c4c1f6e8 100644 --- a/core/modules/mysql/tests/src/Unit/ConnectionTest.php +++ b/core/modules/mysql/tests/src/Unit/ConnectionTest.php @@ -68,7 +68,7 @@ private function createConnection(): Connection { return new class($pdo_connection) extends Connection { public function __construct(\PDO $connection) { - $this->connection = $connection; + parent::__construct($connection, ['prefix' => '']); } }; diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php index b6f669d24ba6..cb9ac264c4af 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php @@ -128,7 +128,7 @@ protected function ensureIdentifiersLength($table_identifier_part, $column_ident */ public function queryTableInformation($table) { // Generate a key to reference this table's information on. - $prefixed_table = $this->connection->getPrefix() . $table; + $prefixed_table = $this->connection->identifiers->tablePrefix . $table; $key = $this->connection->prefixTables('{' . $table . '}'); // Take into account that temporary tables are stored in a different schema. @@ -220,7 +220,7 @@ protected function getTempNamespaceName() { * The non-prefixed name of the table. */ protected function resetTableInformation($table) { - $key = $this->defaultSchema . '.' . $this->connection->getPrefix() . $table; + $key = $this->defaultSchema . '.' . $this->connection->identifiers->tablePrefix . $table; unset($this->tableInformation[$key]); } @@ -521,7 +521,7 @@ public function tableExists($table, $add_prefix = TRUE) { * {@inheritdoc} */ public function findTables($table_expression) { - $prefix = $this->connection->getPrefix(); + $prefix = $this->connection->identifiers->tablePrefix; $prefix_length = strlen($prefix); $tables = []; @@ -567,7 +567,7 @@ public function renameTable($table, $new_name) { } // Get the schema and tablename for the old table. - $table_name = $this->connection->getPrefix() . $table; + $table_name = $this->connection->identifiers->tablePrefix . $table; // Index names and constraint names are global in PostgreSQL, so we need to // rename them when renaming the table. $indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', [':schema' => $this->defaultSchema, ':table' => $table_name]); @@ -734,7 +734,7 @@ public function indexExists($table, $name) { $sql_params = [ ':schema' => $this->defaultSchema, - ':table' => $this->connection->getPrefix() . $table, + ':table' => $this->connection->identifiers->tablePrefix . $table, ':index' => $index_name, ]; return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE schemaname = :schema AND tablename = :table AND indexname = :index", $sql_params)->fetchField(); @@ -1118,7 +1118,7 @@ public function extensionExists($name): bool { protected function getSequenceName(string $table, string $column): ?string { return $this->connection ->query("SELECT pg_get_serial_sequence(:table, :column)", [ - ':table' => $this->defaultSchema . '.' . $this->connection->getPrefix() . $table, + ':table' => $this->defaultSchema . '.' . $this->connection->identifiers->tablePrefix . $table, ':column' => $column, ]) ->fetchField(); diff --git a/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php b/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php index 73f861d0b66a..dfd6b73601bd 100644 --- a/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php +++ b/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php @@ -118,7 +118,7 @@ public function testExtensionExists(): void { $this->assertTrue($this->testingFakeConnection->schema()->tableExists('faking_table')); // Hardcoded assertion that we created the table in the non-public schema. - $this->assertCount(1, $this->testingFakeConnection->query("SELECT * FROM pg_tables WHERE schemaname = 'testing_fake' AND tablename = :prefixedTable", [':prefixedTable' => $this->testingFakeConnection->getPrefix() . "faking_table"])->fetchAll()); + $this->assertCount(1, $this->testingFakeConnection->query("SELECT * FROM pg_tables WHERE schemaname = 'testing_fake' AND tablename = :prefixedTable", [':prefixedTable' => $this->testingFakeConnection->identifiers->tablePrefix . "faking_table"])->fetchAll()); } /** @@ -273,11 +273,11 @@ public function testIndex(): void { $this->assertTrue($this->testingFakeConnection->schema()->indexExists('faking_table', 'test_field')); - $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->getPrefix() . 'faking_table__test_field__idx'])->fetchAll(); + $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->tablePrefix . 'faking_table__test_field__idx'])->fetchAll(); $this->assertCount(1, $results); $this->assertSame('testing_fake', $results[0]->schemaname); - $this->assertSame($this->testingFakeConnection->getPrefix() . 'faking_table', $results[0]->tablename); + $this->assertSame($this->testingFakeConnection->identifiers->tablePrefix . 'faking_table', $results[0]->tablename); $this->assertStringContainsString('USING btree (test_field)', $results[0]->indexdef); $this->testingFakeConnection->schema()->dropIndex('faking_table', 'test_field'); @@ -300,12 +300,12 @@ public function testUniqueKey(): void { // phpcs:ignore // $this->assertTrue($this->testingFakeConnection->schema()->indexExists('faking_table', 'test_field')); - $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->getPrefix() . 'faking_table__test_field__key'])->fetchAll(); + $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->tablePrefix . 'faking_table__test_field__key'])->fetchAll(); // Check the unique key columns. $this->assertCount(1, $results); $this->assertSame('testing_fake', $results[0]->schemaname); - $this->assertSame($this->testingFakeConnection->getPrefix() . 'faking_table', $results[0]->tablename); + $this->assertSame($this->testingFakeConnection->identifiers->tablePrefix . 'faking_table', $results[0]->tablename); $this->assertStringContainsString('USING btree (test_field)', $results[0]->indexdef); $this->testingFakeConnection->schema()->dropUniqueKey('faking_table', 'test_field'); @@ -333,7 +333,7 @@ public function testPrimaryKey(): void { $this->assertCount(1, $results); $this->assertSame('testing_fake', $results[0]->schemaname); - $this->assertSame($this->testingFakeConnection->getPrefix() . 'faking_table', $results[0]->tablename); + $this->assertSame($this->testingFakeConnection->identifiers->tablePrefix . 'faking_table', $results[0]->tablename); $this->assertStringContainsString('USING btree (id)', $results[0]->indexdef); $find_primary_keys_columns = new \ReflectionMethod(get_class($this->testingFakeConnection->schema()), 'findPrimaryKeyColumns'); @@ -356,7 +356,7 @@ public function testTable(): void { $result = $this->testingFakeConnection->query("SELECT * FROM information_schema.tables WHERE table_schema = 'testing_fake'")->fetchAll(); $this->assertFalse($this->testingFakeConnection->schema()->tableExists('faking_table')); $this->assertTrue($this->testingFakeConnection->schema()->tableExists('new_faking_table')); - $this->assertEquals($this->testingFakeConnection->getPrefix() . 'new_faking_table', $result[0]->table_name); + $this->assertEquals($this->testingFakeConnection->identifiers->tablePrefix . 'new_faking_table', $result[0]->table_name); $this->assertEquals('testing_fake', $result[0]->table_schema); sort($tables); $this->assertEquals(['new_faking_table'], $tables); diff --git a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php index 6894968a9552..c5483be42693 100644 --- a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php +++ b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php @@ -281,18 +281,18 @@ public function testPgsqlSequences(): void { // Retrieves a sequence name that is owned by the table and column. $sequence_name = $this->connection ->query("SELECT pg_get_serial_sequence(:table, :column)", [ - ':table' => $this->connection->getPrefix() . 'sequence_test', + ':table' => $this->connection->identifiers->tablePrefix . 'sequence_test', ':column' => 'uid', ]) ->fetchField(); $schema = $this->connection->getConnectionOptions()['schema'] ?? 'public'; - $this->assertEquals($schema . '.' . $this->connection->getPrefix() . 'sequence_test_uid_seq', $sequence_name); + $this->assertEquals($schema . '.' . $this->connection->identifiers->tablePrefix . 'sequence_test_uid_seq', $sequence_name); // Checks if the sequence exists. $this->assertTrue((bool) \Drupal::database() ->query("SELECT c.relname FROM pg_class as c WHERE c.relkind = 'S' AND c.relname = :name", [ - ':name' => $this->connection->getPrefix() . 'sequence_test_uid_seq', + ':name' => $this->connection->identifiers->tablePrefix . 'sequence_test_uid_seq', ]) ->fetchField()); @@ -304,7 +304,7 @@ public function testPgsqlSequences(): void { AND d.refobjsubid > 0 AND d.classid = 'pg_class'::regclass", [':seq_name' => $sequence_name])->fetchObject(); - $this->assertEquals($this->connection->getPrefix() . 'sequence_test', $sequence_owner->table_name); + $this->assertEquals($this->connection->identifiers->tablePrefix . 'sequence_test', $sequence_owner->table_name); $this->assertEquals('uid', $sequence_owner->field_name, 'New sequence is owned by its table.'); } diff --git a/core/modules/pgsql/tests/src/Unit/SchemaTest.php b/core/modules/pgsql/tests/src/Unit/SchemaTest.php index 9880bdbb4fb6..4ac29a6395e6 100644 --- a/core/modules/pgsql/tests/src/Unit/SchemaTest.php +++ b/core/modules/pgsql/tests/src/Unit/SchemaTest.php @@ -32,7 +32,6 @@ public function testComputedConstraintName($table_name, $name, $expected): void $connection = $this->prophesize('\Drupal\pgsql\Driver\Database\pgsql\Connection'); $connection->getConnectionOptions()->willReturn([]); - $connection->getPrefix()->willReturn(''); $statement = $this->prophesize('\Drupal\Core\Database\StatementInterface'); $statement->fetchField()->willReturn($max_identifier_length); -- GitLab From 72c4eb023054ee0cd4ce2237bd1fcdeb478f7eac Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 10:08:12 +0100 Subject: [PATCH 19/66] wip --- core/lib/Drupal/Core/Database/Connection.php | 10 ++-- .../Database/Exception/EventException.php | 4 +- .../Exception/IdentifierException.php | 11 ++++ .../Database/Identifier/IdentifierHandler.php | 38 +----------- .../Identifier/IdentifierProcessorBase.php | 58 +++++++++++++++++++ .../Drupal/Core/Database/Identifier/Table.php | 26 ++++++--- core/lib/Drupal/Core/Database/Schema.php | 4 +- .../migrate/src/Plugin/migrate/id_map/Sql.php | 2 +- .../src/Driver/Database/mysql/Connection.php | 2 +- .../Database/mysql/IdentifierProcessor.php | 24 ++++++++ .../src/Driver/Database/mysql/Schema.php | 2 +- .../mysql/Console/DbDumpCommandTest.php | 2 +- .../src/Driver/Database/pgsql/Connection.php | 2 +- .../Database/pgsql/IdentifierProcessor.php | 21 +++++++ .../src/Driver/Database/pgsql/Schema.php | 12 ++-- .../src/Kernel/pgsql/NonPublicSchemaTest.php | 14 ++--- .../tests/src/Kernel/pgsql/SchemaTest.php | 8 +-- .../src/Driver/Database/sqlite/Connection.php | 2 +- .../Database/sqlite/IdentifierProcessor.php | 24 ++++++++ .../StandardPerformanceTest.php | 8 +-- 20 files changed, 193 insertions(+), 81 deletions(-) create mode 100644 core/lib/Drupal/Core/Database/Exception/IdentifierException.php create mode 100644 core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php create mode 100644 core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php create mode 100644 core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php create mode 100644 core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 3e15808aa920..e5c01ec7a20f 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -315,7 +315,7 @@ public function attachDatabase(string $database): void { */ public function getPrefix(): string { @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); - return $this->identifiers->tablePrefix; + return $this->identifiers->identifierProcessor->tablePrefix; } /** @@ -372,7 +372,7 @@ public function prefixTables($sql) { * This method should only be called by database API code. */ public function quoteIdentifiers($sql) { - return str_replace(['[', ']'], $this->identifiers->identifierQuotes, $sql); + return str_replace(['[', ']'], $this->identifiers->identifierProcessor->identifierQuotes, $sql); } /** @@ -970,7 +970,7 @@ public function condition($conjunction) { */ public function escapeDatabase($database) { $database = preg_replace('/[^A-Za-z0-9_]+/', '', $database); - [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; + [$start_quote, $end_quote] = $this->identifiers->identifierProcessor->identifierQuotes; return $start_quote . $database . $end_quote; } @@ -1012,7 +1012,7 @@ public function escapeTable($table) { public function escapeField($field) { if (!isset($this->escapedFields[$field])) { $escaped = preg_replace('/[^A-Za-z0-9_.]+/', '', $field); - [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; + [$start_quote, $end_quote] = $this->identifiers->identifierProcessor->identifierQuotes; // Sometimes fields have the format table_alias.field. In such cases // both identifiers should be quoted, for example, "table_alias"."field". $this->escapedFields[$field] = $start_quote . str_replace('.', $end_quote . '.' . $start_quote, $escaped) . $end_quote; @@ -1036,7 +1036,7 @@ public function escapeField($field) { */ public function escapeAlias($field) { if (!isset($this->escapedAliases[$field])) { - [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; + [$start_quote, $end_quote] = $this->identifiers->identifierProcessor->identifierQuotes; $this->escapedAliases[$field] = $start_quote . preg_replace('/[^A-Za-z0-9_]+/', '', $field) . $end_quote; } return $this->escapedAliases[$field]; diff --git a/core/lib/Drupal/Core/Database/Exception/EventException.php b/core/lib/Drupal/Core/Database/Exception/EventException.php index 66c97d34763f..429305584884 100644 --- a/core/lib/Drupal/Core/Database/Exception/EventException.php +++ b/core/lib/Drupal/Core/Database/Exception/EventException.php @@ -5,7 +5,7 @@ use Drupal\Core\Database\DatabaseException; /** - * Exception thrown by the database event API. + * Exception thrown by the identifier handling API. */ -class EventException extends \RuntimeException implements DatabaseException { +class IdentifierException extends \RuntimeException implements DatabaseException { } diff --git a/core/lib/Drupal/Core/Database/Exception/IdentifierException.php b/core/lib/Drupal/Core/Database/Exception/IdentifierException.php new file mode 100644 index 000000000000..14f48c1984e6 --- /dev/null +++ b/core/lib/Drupal/Core/Database/Exception/IdentifierException.php @@ -0,0 +1,11 @@ +<?php + +namespace Drupal\Core\Database\Exception; + +use Drupal\Core\Database\DatabaseException; + +/** + * Exception thrown by the database event API. + */ +class IdentifierException extends \RuntimeException implements DatabaseException { +} diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index 641cc498a344..85fbe3475bc7 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -4,8 +4,6 @@ namespace Drupal\Core\Database\Identifier; -use Drupal\Component\Assertion\Inspector; - /** * @todo fill in. */ @@ -16,22 +14,9 @@ class IdentifierHandler { */ protected array $identifiers; - /** - * Constructs an IdentifierHandler object. - * - * @param string $tablePrefix - * The table prefix to be used by the database connection. - * @param array{0:string, 1:string} $identifierQuotes - * The identifier quote characters for the database type. An array - * containing the start and end identifier quote characters for the - * database type. The ANSI SQL standard identifier quote character is a - * double quotation mark. - */ public function __construct( - public readonly string $tablePrefix, - public readonly array $identifierQuotes = ['"', '"'], + public readonly IdentifierProcessorBase $identifierProcessor, ) { - assert(count($this->identifierQuotes) === 2 && Inspector::assertAllStrings($this->identifierQuotes), __CLASS__ . '::$identifierQuotes must contain 2 string values'); } /** @@ -51,27 +36,6 @@ public function table(string|Table $tableIdentifier): Table { return $table; } - /** - * @todo fill in. - */ - public function tableEscapeName(string $table): string { - return preg_replace('/[^A-Za-z0-9_.]+/', '', $table); - } - - /** - * @todo fill in. - */ - public function tableMachineName(Table $table): string { - $tableName = $table->needsPrefix ? $this->tablePrefix . $table->table : $table->table; - if ($table->database) { - return implode('.', [$table->database, $table->schema, $tableName]); - } - elseif ($table->schema) { - return implode('.', [$table->schema, $tableName]); - } - return $tableName; - } - /** * @todo fill in. */ diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php new file mode 100644 index 000000000000..05e69d114e5e --- /dev/null +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php @@ -0,0 +1,58 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Database\Identifier; + +use Drupal\Component\Assertion\Inspector; + +/** + * @todo fill in. + */ +abstract class IdentifierProcessorBase { + + /** + * Constructor. + * + * @param string $tablePrefix + * The table prefix to be used by the database connection. + * @param array{0:string, 1:string} $identifierQuotes + * The identifier quote characters for the database type. An array + * containing the start and end identifier quote characters for the + * database type. The ANSI SQL standard identifier quote character is a + * double quotation mark. + */ + public function __construct( + public readonly string $tablePrefix, + public readonly array $identifierQuotes = ['"', '"'], + ) { + assert(count($this->identifierQuotes) === 2 && Inspector::assertAllStrings($this->identifierQuotes), __CLASS__ . '::$identifierQuotes must contain 2 string values'); + } + + /** + * @todo fill in. + */ + abstract public function getMaxLength(IdentifierType $type): int; + + /** + * @todo fill in. + */ + public function tableEscapeName(string $table): string { + return preg_replace('/[^A-Za-z0-9_.]+/', '', $table); + } + + /** + * @todo fill in. + */ + public function tableMachineName(Table $table): string { + $tableName = $table->needsPrefix ? $this->tablePrefix . $table->table : $table->table; + if ($table->database) { + return implode('.', [$table->database, $table->schema, $tableName]); + } + elseif ($table->schema) { + return implode('.', [$table->schema, $tableName]); + } + return $tableName; + } + +} diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 1733d6329949..1b1d68a9901f 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -4,6 +4,8 @@ namespace Drupal\Core\Database\Identifier; +use Drupal\Core\Database\Exception\IdentifierException; + /** * @todo fill in. */ @@ -44,23 +46,31 @@ public function __construct( 1 => [ NULL, NULL, - $this->identifierHandler->tableEscapeName($parts[0]), + $this->identifierHandler->identifierProcessor->tableEscapeName($parts[0]), ], 2 => [ NULL, - $this->identifierHandler->tableEscapeName($parts[0]), - $this->identifierHandler->tableEscapeName($parts[1]), + $this->identifierHandler->identifierProcessor->tableEscapeName($parts[0]), + $this->identifierHandler->identifierProcessor->tableEscapeName($parts[1]), ], 3 => [ - $this->identifierHandler->tableEscapeName($parts[0]), - $this->identifierHandler->tableEscapeName($parts[1]), - $this->identifierHandler->tableEscapeName($parts[2]), + $this->identifierHandler->identifierProcessor->tableEscapeName($parts[0]), + $this->identifierHandler->identifierProcessor->tableEscapeName($parts[1]), + $this->identifierHandler->identifierProcessor->tableEscapeName($parts[2]), ], }; $this->needsPrefix = match (count($parts)) { 1 => TRUE, default => FALSE, }; + if (strlen($this->needsPrefix ? $this->identifierHandler->identifierProcessor->tablePrefix : '' . $this->table) > $this->identifierHandler->identifierProcessor->getMaxLength(IdentifierType::Table)) { + throw new IdentifierException(sprintf( + 'The machine length of the %s identifier \'%s\' exceeds the maximum allowed (%d)', + IdentifierType::Table->value, + $this->table, + $this->identifierHandler->identifierProcessor->getMaxLength(IdentifierType::Table), + )); + } } /** @@ -68,9 +78,9 @@ public function __construct( */ public function machineName(bool $quoted = TRUE): string { if (!isset($this->machineName)) { - $this->machineName = $this->identifierHandler->tableMachineName($this); + $this->machineName = $this->identifierHandler->identifierProcessor->tableMachineName($this); } - [$start_quote, $end_quote] = $this->identifierHandler->identifierQuotes; + [$start_quote, $end_quote] = $this->identifierHandler->identifierProcessor->identifierQuotes; return $quoted ? $start_quote . str_replace(".", "$end_quote.$start_quote", $this->machineName) . $end_quote : $this->machineName; } diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 6504b70619cd..cbcf24bb3cf0 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -84,7 +84,7 @@ public function nextPlaceholder() { protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { $info = [ 'schema' => $this->defaultSchema, - 'prefix' => isset($this->connection->identifiers) ? $this->connection->identifiers->tablePrefix : '', + 'prefix' => isset($this->connection->identifiers) ? $this->connection->identifiers->identifierProcessor->tablePrefix : '', ]; if ($add_prefix) { $table = $info['prefix'] . $table; @@ -223,7 +223,7 @@ public function findTables($table_expression) { $condition = $this->buildTableNameCondition('%', 'LIKE'); $condition->compile($this->connection, $this); - $prefix = $this->connection->identifiers->tablePrefix; + $prefix = $this->connection->identifiers->identifierProcessor->tablePrefix; $prefix_length = strlen($prefix); $tables = []; // Normally, we would heartily discourage the use of string diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index 53b598bb14db..6a6153b1d844 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -183,7 +183,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition // Default generated table names, limited to 63 characters. $machine_name = str_replace(':', '__', $this->migration->id()); - $prefix_length = isset($this->database->identifiers) ? strlen($this->database->identifiers->tablePrefix) : 0; + $prefix_length = isset($this->database->identifiers) ? strlen($this->database->identifiers->identifierProcessor->tablePrefix) : 0; $this->mapTableName = 'migrate_map_' . mb_strtolower($machine_name); $this->mapTableName = mb_substr($this->mapTableName, 0, 63 - $prefix_length); $this->messageTableName = 'migrate_message_' . mb_strtolower($machine_name); diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php index 83f0995498b5..9e7df4bc02df 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php @@ -87,7 +87,7 @@ public function __construct(\PDO $connection, array $connection_options) { parent::__construct( $connection, $connection_options, - new IdentifierHandler($connection_options['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`']), + new IdentifierHandler(new IdentifierProcessor($connection_options['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`'])), ); } diff --git a/core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php b/core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php new file mode 100644 index 000000000000..e4963837b5cb --- /dev/null +++ b/core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\mysql\Driver\Database\mysql; + +use Drupal\Core\Database\Identifier\IdentifierProcessorBase; +use Drupal\Core\Database\Identifier\IdentifierType; + +/** + * MySQL implementation of the identifier processor. + */ +class IdentifierProcessor extends IdentifierProcessorBase { + + /** + * {@inheritdoc} + */ + public function getMaxLength(IdentifierType $type): int { + // @see https://dev.mysql.com/doc/refman/8.4/en/identifier-length.html + return match ($type) { + IdentifierType::Alias => 256, + default => 64, + }; + } + +} diff --git a/core/modules/mysql/src/Driver/Database/mysql/Schema.php b/core/modules/mysql/src/Driver/Database/mysql/Schema.php index 2cc75a078ab9..a82d2e8b6693 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Schema.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Schema.php @@ -51,7 +51,7 @@ class Schema extends DatabaseSchema { * A keyed array with information about the database, table name and prefix. */ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { - $info = ['prefix' => $this->connection->identifiers->tablePrefix]; + $info = ['prefix' => $this->connection->identifiers->identifierProcessor->tablePrefix]; if ($add_prefix) { $table = $info['prefix'] . $table; } diff --git a/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php b/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php index d6ab61ec886c..d662635096d7 100644 --- a/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php +++ b/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php @@ -35,7 +35,7 @@ protected function setUp(): void { // Create a table with a field type not defined in // \Drupal\Core\Database\Schema::getFieldTypeMap. - $table_name = $connection->identifiers->tablePrefix . 'foo'; + $table_name = $connection->identifiers->identifierProcessor->tablePrefix . 'foo'; $sql = "create table if not exists `$table_name` (`test` datetime NOT NULL);"; $connection->query($sql)->execute(); } diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index e869522a4071..e2087dbcc665 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -330,7 +330,7 @@ public function makeSequenceName($table, $field) { $sequence_name = $this->prefixTables('{' . $table . '}_' . $field . '_seq'); // Remove identifier quotes as we are constructing a new name from a // prefixed and quoted table name. - return str_replace($this->identifiers->identifierQuotes, '', $sequence_name); + return str_replace($this->identifiers->identifierProcessor->identifierQuotes, '', $sequence_name); } /** diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php new file mode 100644 index 000000000000..e7bf885d01df --- /dev/null +++ b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php @@ -0,0 +1,21 @@ +<?php + +namespace Drupal\pgsql\Driver\Database\pgsql; + +use Drupal\Core\Database\Identifier\IdentifierProcessorBase; +use Drupal\Core\Database\Identifier\IdentifierType; + +/** + * MySQL implementation of the identifier processor. + */ +class IdentifierProcessor extends IdentifierProcessorBase { + + /** + * {@inheritdoc} + */ + public function getMaxLength(IdentifierType $type): int { + // @see https://www.postgresql.org/docs/current/limits.html + return 63; + } + +} diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php index cb9ac264c4af..78590773cd9d 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php @@ -128,7 +128,7 @@ protected function ensureIdentifiersLength($table_identifier_part, $column_ident */ public function queryTableInformation($table) { // Generate a key to reference this table's information on. - $prefixed_table = $this->connection->identifiers->tablePrefix . $table; + $prefixed_table = $this->connection->identifiers->identifierProcessor->tablePrefix . $table; $key = $this->connection->prefixTables('{' . $table . '}'); // Take into account that temporary tables are stored in a different schema. @@ -220,7 +220,7 @@ protected function getTempNamespaceName() { * The non-prefixed name of the table. */ protected function resetTableInformation($table) { - $key = $this->defaultSchema . '.' . $this->connection->identifiers->tablePrefix . $table; + $key = $this->defaultSchema . '.' . $this->connection->identifiers->identifierProcessor->tablePrefix . $table; unset($this->tableInformation[$key]); } @@ -521,7 +521,7 @@ public function tableExists($table, $add_prefix = TRUE) { * {@inheritdoc} */ public function findTables($table_expression) { - $prefix = $this->connection->identifiers->tablePrefix; + $prefix = $this->connection->identifiers->identifierProcessor->tablePrefix; $prefix_length = strlen($prefix); $tables = []; @@ -567,7 +567,7 @@ public function renameTable($table, $new_name) { } // Get the schema and tablename for the old table. - $table_name = $this->connection->identifiers->tablePrefix . $table; + $table_name = $this->connection->identifiers->identifierProcessor->tablePrefix . $table; // Index names and constraint names are global in PostgreSQL, so we need to // rename them when renaming the table. $indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', [':schema' => $this->defaultSchema, ':table' => $table_name]); @@ -734,7 +734,7 @@ public function indexExists($table, $name) { $sql_params = [ ':schema' => $this->defaultSchema, - ':table' => $this->connection->identifiers->tablePrefix . $table, + ':table' => $this->connection->identifiers->identifierProcessor->tablePrefix . $table, ':index' => $index_name, ]; return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE schemaname = :schema AND tablename = :table AND indexname = :index", $sql_params)->fetchField(); @@ -1118,7 +1118,7 @@ public function extensionExists($name): bool { protected function getSequenceName(string $table, string $column): ?string { return $this->connection ->query("SELECT pg_get_serial_sequence(:table, :column)", [ - ':table' => $this->defaultSchema . '.' . $this->connection->identifiers->tablePrefix . $table, + ':table' => $this->defaultSchema . '.' . $this->connection->identifiers->identifierProcessor->tablePrefix . $table, ':column' => $column, ]) ->fetchField(); diff --git a/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php b/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php index dfd6b73601bd..075ba2267220 100644 --- a/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php +++ b/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php @@ -118,7 +118,7 @@ public function testExtensionExists(): void { $this->assertTrue($this->testingFakeConnection->schema()->tableExists('faking_table')); // Hardcoded assertion that we created the table in the non-public schema. - $this->assertCount(1, $this->testingFakeConnection->query("SELECT * FROM pg_tables WHERE schemaname = 'testing_fake' AND tablename = :prefixedTable", [':prefixedTable' => $this->testingFakeConnection->identifiers->tablePrefix . "faking_table"])->fetchAll()); + $this->assertCount(1, $this->testingFakeConnection->query("SELECT * FROM pg_tables WHERE schemaname = 'testing_fake' AND tablename = :prefixedTable", [':prefixedTable' => $this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . "faking_table"])->fetchAll()); } /** @@ -273,11 +273,11 @@ public function testIndex(): void { $this->assertTrue($this->testingFakeConnection->schema()->indexExists('faking_table', 'test_field')); - $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->tablePrefix . 'faking_table__test_field__idx'])->fetchAll(); + $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table__test_field__idx'])->fetchAll(); $this->assertCount(1, $results); $this->assertSame('testing_fake', $results[0]->schemaname); - $this->assertSame($this->testingFakeConnection->identifiers->tablePrefix . 'faking_table', $results[0]->tablename); + $this->assertSame($this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table', $results[0]->tablename); $this->assertStringContainsString('USING btree (test_field)', $results[0]->indexdef); $this->testingFakeConnection->schema()->dropIndex('faking_table', 'test_field'); @@ -300,12 +300,12 @@ public function testUniqueKey(): void { // phpcs:ignore // $this->assertTrue($this->testingFakeConnection->schema()->indexExists('faking_table', 'test_field')); - $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->tablePrefix . 'faking_table__test_field__key'])->fetchAll(); + $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table__test_field__key'])->fetchAll(); // Check the unique key columns. $this->assertCount(1, $results); $this->assertSame('testing_fake', $results[0]->schemaname); - $this->assertSame($this->testingFakeConnection->identifiers->tablePrefix . 'faking_table', $results[0]->tablename); + $this->assertSame($this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table', $results[0]->tablename); $this->assertStringContainsString('USING btree (test_field)', $results[0]->indexdef); $this->testingFakeConnection->schema()->dropUniqueKey('faking_table', 'test_field'); @@ -333,7 +333,7 @@ public function testPrimaryKey(): void { $this->assertCount(1, $results); $this->assertSame('testing_fake', $results[0]->schemaname); - $this->assertSame($this->testingFakeConnection->identifiers->tablePrefix . 'faking_table', $results[0]->tablename); + $this->assertSame($this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table', $results[0]->tablename); $this->assertStringContainsString('USING btree (id)', $results[0]->indexdef); $find_primary_keys_columns = new \ReflectionMethod(get_class($this->testingFakeConnection->schema()), 'findPrimaryKeyColumns'); @@ -356,7 +356,7 @@ public function testTable(): void { $result = $this->testingFakeConnection->query("SELECT * FROM information_schema.tables WHERE table_schema = 'testing_fake'")->fetchAll(); $this->assertFalse($this->testingFakeConnection->schema()->tableExists('faking_table')); $this->assertTrue($this->testingFakeConnection->schema()->tableExists('new_faking_table')); - $this->assertEquals($this->testingFakeConnection->identifiers->tablePrefix . 'new_faking_table', $result[0]->table_name); + $this->assertEquals($this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'new_faking_table', $result[0]->table_name); $this->assertEquals('testing_fake', $result[0]->table_schema); sort($tables); $this->assertEquals(['new_faking_table'], $tables); diff --git a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php index c5483be42693..e58ca2b00a9a 100644 --- a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php +++ b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php @@ -281,18 +281,18 @@ public function testPgsqlSequences(): void { // Retrieves a sequence name that is owned by the table and column. $sequence_name = $this->connection ->query("SELECT pg_get_serial_sequence(:table, :column)", [ - ':table' => $this->connection->identifiers->tablePrefix . 'sequence_test', + ':table' => $this->connection->identifiers->identifierProcessor->tablePrefix . 'sequence_test', ':column' => 'uid', ]) ->fetchField(); $schema = $this->connection->getConnectionOptions()['schema'] ?? 'public'; - $this->assertEquals($schema . '.' . $this->connection->identifiers->tablePrefix . 'sequence_test_uid_seq', $sequence_name); + $this->assertEquals($schema . '.' . $this->connection->identifiers->identifierProcessor->tablePrefix . 'sequence_test_uid_seq', $sequence_name); // Checks if the sequence exists. $this->assertTrue((bool) \Drupal::database() ->query("SELECT c.relname FROM pg_class as c WHERE c.relkind = 'S' AND c.relname = :name", [ - ':name' => $this->connection->identifiers->tablePrefix . 'sequence_test_uid_seq', + ':name' => $this->connection->identifiers->identifierProcessor->tablePrefix . 'sequence_test_uid_seq', ]) ->fetchField()); @@ -304,7 +304,7 @@ public function testPgsqlSequences(): void { AND d.refobjsubid > 0 AND d.classid = 'pg_class'::regclass", [':seq_name' => $sequence_name])->fetchObject(); - $this->assertEquals($this->connection->identifiers->tablePrefix . 'sequence_test', $sequence_owner->table_name); + $this->assertEquals($this->connection->identifiers->identifierProcessor->tablePrefix . 'sequence_test', $sequence_owner->table_name); $this->assertEquals('uid', $sequence_owner->field_name, 'New sequence is owned by its table.'); } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 46b787e283bf..634ee3d3c668 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -84,7 +84,7 @@ public function __construct(\PDO $connection, array $connection_options) { // querying an attached database. $prefix .= '.'; } - parent::__construct($connection, $connection_options, new IdentifierHandler($prefix)); + parent::__construct($connection, $connection_options, new IdentifierHandler(new IdentifierProcessor($prefix))); if (isset($attachedDatabaseName)) { $this->attachDatabase($attachedDatabaseName); } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php new file mode 100644 index 000000000000..eb70b4d4ee53 --- /dev/null +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\sqlite\Driver\Database\sqlite; + +use Drupal\Core\Database\Identifier\IdentifierProcessorBase; +use Drupal\Core\Database\Identifier\IdentifierType; + +/** + * SQLite implementation of the identifier processor. + */ +class IdentifierProcessor extends IdentifierProcessorBase { + + /** + * {@inheritdoc} + */ + public function getMaxLength(IdentifierType $type): int { + // There is no hard limit on identifier length in SQLite, so we just use + // common sense. + // @see https://www.sqlite.org/limits.html + // @see https://stackoverflow.com/questions/8135013/table-name-limit-in-sqlite-android + return 128; + } + +} diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php index c43fa6f9f7e2..aa2916f62ced 100644 --- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php +++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php @@ -115,13 +115,13 @@ protected function testAnonymous(): void { 'SELECT "menu_tree"."menu_name" AS "menu_name", "menu_tree"."route_name" AS "route_name", "menu_tree"."route_parameters" AS "route_parameters", "menu_tree"."url" AS "url", "menu_tree"."title" AS "title", "menu_tree"."description" AS "description", "menu_tree"."parent" AS "parent", "menu_tree"."weight" AS "weight", "menu_tree"."options" AS "options", "menu_tree"."expanded" AS "expanded", "menu_tree"."enabled" AS "enabled", "menu_tree"."provider" AS "provider", "menu_tree"."metadata" AS "metadata", "menu_tree"."class" AS "class", "menu_tree"."form_class" AS "form_class", "menu_tree"."id" AS "id" FROM "menu_tree" "menu_tree" WHERE ("route_name" = "view.frontpage.page_1") AND ("route_param_key" = "view_id=frontpage&display_id=page_1") AND ("menu_name" = "main") ORDER BY "depth" ASC, "weight" ASC, "id" ASC', 'SELECT "menu_tree"."menu_name" AS "menu_name", "menu_tree"."route_name" AS "route_name", "menu_tree"."route_parameters" AS "route_parameters", "menu_tree"."url" AS "url", "menu_tree"."title" AS "title", "menu_tree"."description" AS "description", "menu_tree"."parent" AS "parent", "menu_tree"."weight" AS "weight", "menu_tree"."options" AS "options", "menu_tree"."expanded" AS "expanded", "menu_tree"."enabled" AS "enabled", "menu_tree"."provider" AS "provider", "menu_tree"."metadata" AS "metadata", "menu_tree"."class" AS "class", "menu_tree"."form_class" AS "form_class", "menu_tree"."id" AS "id" FROM "menu_tree" "menu_tree" WHERE ("route_name" = "view.frontpage.page_1") AND ("route_param_key" = "view_id=frontpage&display_id=page_1") AND ("menu_name" = "account") ORDER BY "depth" ASC, "weight" ASC, "id" ASC', 'INSERT INTO "semaphore" ("name", "value", "expire") VALUES ("theme_registry:runtime:stark:Drupal\Core\Utility\ThemeRegistry", "LOCK_ID", "EXPIRE")', - 'DELETE FROM "semaphore" WHERE ("name" = "theme_registry:runtime:stark:Drupal\Core\Utility\ThemeRegistry") AND ("value" = "LOCK_ID")', + 'DELETE FROM "semaphore" WHERE ("name" = "theme_registry:runtime:stark:Drupal\Core\Utility\ThemeRegistry") AND ("value" = "LOCK_ID")', 'INSERT INTO "semaphore" ("name", "value", "expire") VALUES ("library_info:stark:Drupal\Core\Cache\CacheCollector", "LOCK_ID", "EXPIRE")', - 'DELETE FROM "semaphore" WHERE ("name" = "library_info:stark:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', + 'DELETE FROM "semaphore" WHERE ("name" = "library_info:stark:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', 'INSERT INTO "semaphore" ("name", "value", "expire") VALUES ("path_alias_prefix_list:Drupal\Core\Cache\CacheCollector", "LOCK_ID", "EXPIRE")', - 'DELETE FROM "semaphore" WHERE ("name" = "path_alias_prefix_list:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', + 'DELETE FROM "semaphore" WHERE ("name" = "path_alias_prefix_list:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', 'INSERT INTO "semaphore" ("name", "value", "expire") VALUES ("active-trail:route:view.frontpage.page_1:route_parameters:a:2:{s:10:"display_id";s:6:"page_1";s:7:"view_id";s:9:"frontpage";}:Drupal\Core\Cache\CacheCollector", "LOCK_ID", "EXPIRE")', - 'DELETE FROM "semaphore" WHERE ("name" = "active-trail:route:view.frontpage.page_1:route_parameters:a:2:{s:10:"display_id";s:6:"page_1";s:7:"view_id";s:9:"frontpage";}:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', + 'DELETE FROM "semaphore" WHERE ("name" = "active-trail:route:view.frontpage.page_1:route_parameters:a:2:{s:10:"display_id";s:6:"page_1";s:7:"view_id";s:9:"frontpage";}:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', ]; $recorded_queries = $performance_data->getQueries(); $this->assertSame($expected_queries, $recorded_queries); -- GitLab From ac9554faa6c15add0c912480c06d1239e452f1e2 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 10:11:14 +0100 Subject: [PATCH 20/66] wip --- core/lib/Drupal/Core/Database/Exception/EventException.php | 4 ++-- .../Drupal/Core/Database/Exception/IdentifierException.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Exception/EventException.php b/core/lib/Drupal/Core/Database/Exception/EventException.php index 429305584884..66c97d34763f 100644 --- a/core/lib/Drupal/Core/Database/Exception/EventException.php +++ b/core/lib/Drupal/Core/Database/Exception/EventException.php @@ -5,7 +5,7 @@ use Drupal\Core\Database\DatabaseException; /** - * Exception thrown by the identifier handling API. + * Exception thrown by the database event API. */ -class IdentifierException extends \RuntimeException implements DatabaseException { +class EventException extends \RuntimeException implements DatabaseException { } diff --git a/core/lib/Drupal/Core/Database/Exception/IdentifierException.php b/core/lib/Drupal/Core/Database/Exception/IdentifierException.php index 14f48c1984e6..429305584884 100644 --- a/core/lib/Drupal/Core/Database/Exception/IdentifierException.php +++ b/core/lib/Drupal/Core/Database/Exception/IdentifierException.php @@ -5,7 +5,7 @@ use Drupal\Core\Database\DatabaseException; /** - * Exception thrown by the database event API. + * Exception thrown by the identifier handling API. */ class IdentifierException extends \RuntimeException implements DatabaseException { } -- GitLab From e8a8eed1238093da1e1426c623a327553428e046 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 10:21:04 +0100 Subject: [PATCH 21/66] wip --- .../Core/Database/Stub/StubConnection.php | 2 +- .../Database/Stub/StubIdentifierProcessor.php | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierProcessor.php diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php index 99bcae961835..607e78fc8748 100644 --- a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php +++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php @@ -45,7 +45,7 @@ public function __construct(\PDO $connection, array $connection_options, $identi parent::__construct( $connection, $connection_options, - new IdentifierHandler($connection_options['prefix'] ?? '', $identifier_quotes), + new IdentifierHandler(new StubIdentifierProcessor($connection_options['prefix'] ?? '', $identifier_quotes)), ); } diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierProcessor.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierProcessor.php new file mode 100644 index 000000000000..c6e5564e1346 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierProcessor.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\Core\Database\Stub; + +use Drupal\Core\Database\Identifier\IdentifierProcessorBase; +use Drupal\Core\Database\Identifier\IdentifierType; + +/** + * A stub of the abstract IdentifierProcessorBase class for testing purposes. + */ +class StubIdentifierProcessor extends IdentifierProcessorBase { + + /** + * {@inheritdoc} + */ + public function getMaxLength(IdentifierType $type): int { + return 4000; + } + +} -- GitLab From 060bc3f3367b2dda4b908de23d935de0221dd437 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 12:59:29 +0100 Subject: [PATCH 22/66] wip --- .../d7/MigrateLanguageContentCommentSettingsTest.php | 2 +- .../tests/src/Functional/System/ErrorHandlerTest.php | 11 ++++++++--- .../views_ui/tests/src/Functional/PreviewTest.php | 7 +------ .../Drupal/Tests/Core/Database/ConnectionTest.php | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php index 8f66d9bda709..16923949a2e1 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php @@ -43,7 +43,7 @@ protected function setUp(): void { */ public function testLanguageCommentSettings(): void { // Confirm there is no message about a missing bundle. - $this->assertEmpty($this->migrateMessages, (string) $this->migrateMessages['error'][0] ?? ''); + $this->assertEmpty($this->migrateMessages, $this->migrateMessages['error'][0] ?? ''); // Article and Blog content type have multilingual settings of 'Enabled, // with Translation'. Assert that comments are translatable and the default diff --git a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php index dc39f89606ec..67cb567bd5ca 100644 --- a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php +++ b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php @@ -133,8 +133,8 @@ public function testExceptionHandler(): void { $error_pdo_exception = [ '%type' => 'DatabaseExceptionWrapper', '@message' => PHP_VERSION_ID >= 80400 ? - $message : - 'SELECT "b"\.\* FROM .*bananas_are_awesome. "b"', + $message : + 'SELECT .*b.*\.\*[.\n]*FROM[.\n]*.*bananas_are_awesome.*', '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerPDOException()', '%line' => 64, '%file' => $this->getModulePath('error_test') . '/error_test.module', @@ -160,7 +160,12 @@ public function testExceptionHandler(): void { $this->assertSession()->pageTextContains($error_pdo_exception['%type']); // Assert statement improved since static queries adds table alias in the // error message. - $this->assertSession()->pageTextMatches($error_pdo_exception['@message']); + if (PHP_VERSION_ID >= 80400) { + $this->assertSession()->pageTextContains($error_pdo_exception['@message']); + } + else { + $this->assertSession()->pageTextMatches($error_pdo_exception['@message']); + } $error_details = new FormattableMarkup('in %function (line ', $error_pdo_exception); $this->assertSession()->responseContains($error_details); $this->drupalGet('error-test/trigger-renderer-exception'); diff --git a/core/modules/views_ui/tests/src/Functional/PreviewTest.php b/core/modules/views_ui/tests/src/Functional/PreviewTest.php index fde2034eadf3..66343c4789e9 100644 --- a/core/modules/views_ui/tests/src/Functional/PreviewTest.php +++ b/core/modules/views_ui/tests/src/Functional/PreviewTest.php @@ -123,12 +123,7 @@ public function testPreviewUI(): void { $this->assertSession()->pageTextContains('Query execute time'); $this->assertSession()->pageTextContains('View render time'); $this->assertSession()->responseContains('<strong>Query</strong>'); - $query_string = <<<SQL -SELECT \"views_test_data\"\.\"name\" AS \"views_test_data_name\" -FROM -.*views_test_data.* \"views_test_data\" -WHERE \(views_test_data\.id \= '100'\) -SQL; + $query_string = 'SELECT \"views_test_data\"\.\"name\" AS \"views_test_data_name\"[.\n]*FROM[.\n]*.*views_test_data.* \"views_test_data\"[.\n]*WHERE \(views_test_data\.id \= \'100\'\)'; $this->assertSession()->pageTextMatches($query_string); // Test that the statistics and query are rendered above the preview. diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index d93bdaedf1a5..b28571048cc6 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -539,7 +539,7 @@ public function testEscapeDatabase($expected, $name, array $identifier_quote = [ */ public function testIdentifierQuotesAssertCount(): void { $this->expectException(\AssertionError::class); - $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierHandler::$identifierQuotes must contain 2 string values'); + $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierProcessorBase::$identifierQuotes must contain 2 string values'); $mock_pdo = $this->createMock(StubPDO::class); new StubConnection($mock_pdo, [], ['"']); } @@ -549,7 +549,7 @@ public function testIdentifierQuotesAssertCount(): void { */ public function testIdentifierQuotesAssertString(): void { $this->expectException(\AssertionError::class); - $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierHandler::$identifierQuotes must contain 2 string values'); + $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierProcessorBase::$identifierQuotes must contain 2 string values'); $mock_pdo = $this->createMock(StubPDO::class); new StubConnection($mock_pdo, [], [0, '1']); } -- GitLab From 9cd45c89a912106d2f521291ee61635325076970 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 13:09:30 +0100 Subject: [PATCH 23/66] wip --- .../system/tests/src/Functional/System/ErrorHandlerTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php index 67cb567bd5ca..220f0f0b1326 100644 --- a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php +++ b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php @@ -132,9 +132,7 @@ public function testExceptionHandler(): void { $message = str_replace(["\r", "\n"], ' ', $message); $error_pdo_exception = [ '%type' => 'DatabaseExceptionWrapper', - '@message' => PHP_VERSION_ID >= 80400 ? - $message : - 'SELECT .*b.*\.\*[.\n]*FROM[.\n]*.*bananas_are_awesome.*', + '@message' => PHP_VERSION_ID >= 80400 ? $message : 'SELECT .*b.*\.\*[.\n]*FROM[.\n]*.*bananas_are_awesome.*', '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerPDOException()', '%line' => 64, '%file' => $this->getModulePath('error_test') . '/error_test.module', -- GitLab From 36b05b1881cecfcf65d911ddacce01fe56ac300b Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 14:29:22 +0100 Subject: [PATCH 24/66] wip --- .../system/tests/src/Functional/System/ErrorHandlerTest.php | 2 +- core/modules/views_ui/tests/src/Functional/PreviewTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php index 220f0f0b1326..44488c32a9c9 100644 --- a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php +++ b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php @@ -132,7 +132,7 @@ public function testExceptionHandler(): void { $message = str_replace(["\r", "\n"], ' ', $message); $error_pdo_exception = [ '%type' => 'DatabaseExceptionWrapper', - '@message' => PHP_VERSION_ID >= 80400 ? $message : 'SELECT .*b.*\.\*[.\n]*FROM[.\n]*.*bananas_are_awesome.*', + '@message' => PHP_VERSION_ID >= 80400 ? $message : '/SELECT "b"\.\* FROM .*bananas_are_awesome. "b"/', '%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerPDOException()', '%line' => 64, '%file' => $this->getModulePath('error_test') . '/error_test.module', diff --git a/core/modules/views_ui/tests/src/Functional/PreviewTest.php b/core/modules/views_ui/tests/src/Functional/PreviewTest.php index 66343c4789e9..c70a90467705 100644 --- a/core/modules/views_ui/tests/src/Functional/PreviewTest.php +++ b/core/modules/views_ui/tests/src/Functional/PreviewTest.php @@ -123,7 +123,7 @@ public function testPreviewUI(): void { $this->assertSession()->pageTextContains('Query execute time'); $this->assertSession()->pageTextContains('View render time'); $this->assertSession()->responseContains('<strong>Query</strong>'); - $query_string = 'SELECT \"views_test_data\"\.\"name\" AS \"views_test_data_name\"[.\n]*FROM[.\n]*.*views_test_data.* \"views_test_data\"[.\n]*WHERE \(views_test_data\.id \= \'100\'\)'; + $query_string = '/SELECT "views_test_data"\."name" AS "views_test_data_name" FROM .*\."views_test_data" "views_test_data" WHERE \(views_test_data\.id = \'100\'\)/'; $this->assertSession()->pageTextMatches($query_string); // Test that the statistics and query are rendered above the preview. -- GitLab From d649991e9881eaa89245da3055c466455bccf044 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 15:14:40 +0100 Subject: [PATCH 25/66] wip --- .../Exception/IdentifierException.php | 2 + .../Database/Identifier/IdentifierBase.php | 7 +++- .../Database/Identifier/IdentifierHandler.php | 2 +- .../Identifier/IdentifierProcessorBase.php | 31 +++++++++++++- .../Drupal/Core/Database/Identifier/Table.php | 41 +++++-------------- .../Database/mysql/IdentifierProcessor.php | 2 + .../Database/pgsql/IdentifierProcessor.php | 2 + .../Database/sqlite/IdentifierProcessor.php | 2 + 8 files changed, 55 insertions(+), 34 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Exception/IdentifierException.php b/core/lib/Drupal/Core/Database/Exception/IdentifierException.php index 429305584884..a069749f918d 100644 --- a/core/lib/Drupal/Core/Database/Exception/IdentifierException.php +++ b/core/lib/Drupal/Core/Database/Exception/IdentifierException.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\Core\Database\Exception; use Drupal\Core\Database\DatabaseException; diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php index ce18df7e2b6a..f07d80e5bf71 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -9,8 +9,13 @@ */ abstract class IdentifierBase implements \Stringable { + /** + * @todo fill in. + */ + public readonly string $canonicalName; + public function __construct( - protected readonly IdentifierHandler $identifierHandler, + protected readonly IdentifierProcessorBase $identifierProcessor, public readonly string $identifier, ) { } diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php index 85fbe3475bc7..80e46062f90b 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php @@ -30,7 +30,7 @@ public function table(string|Table $tableIdentifier): Table { $table = $this->getIdentifier($tableIdentifier, IdentifierType::Table); } else { - $table = new Table($this, $tableIdentifier); + $table = new Table($this->identifierProcessor, $tableIdentifier); $this->setIdentifier($tableIdentifier, IdentifierType::Table, $table); } return $table; diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php index 05e69d114e5e..7141c1105fb1 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php @@ -34,6 +34,35 @@ public function __construct( */ abstract public function getMaxLength(IdentifierType $type): int; + /** + * @todo fill in. + */ + public function parseTableIdentifier(string $identifier): array { + $parts = explode(".", $identifier); + [$database, $schema, $table] = match (count($parts)) { + 1 => [ + NULL, + NULL, + $this->tableEscapeName($parts[0]), + ], + 2 => [ + NULL, + $this->tableEscapeName($parts[0]), + $this->tableEscapeName($parts[1]), + ], + 3 => [ + $this->tableEscapeName($parts[0]), + $this->tableEscapeName($parts[1]), + $this->tableEscapeName($parts[2]), + ], + }; + $needsPrefix = match (count($parts)) { + 1 => TRUE, + default => FALSE, + }; + return [$database, $schema, $table, $needsPrefix]; + } + /** * @todo fill in. */ @@ -45,7 +74,7 @@ public function tableEscapeName(string $table): string { * @todo fill in. */ public function tableMachineName(Table $table): string { - $tableName = $table->needsPrefix ? $this->tablePrefix . $table->table : $table->table; + $tableName = $table->needsPrefix ? $this->tablePrefix . $table->canonicalName : $table->canonicalName; if ($table->database) { return implode('.', [$table->database, $table->schema, $tableName]); } diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 1b1d68a9901f..4811889b9286 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -24,12 +24,12 @@ class Table extends IdentifierBase { /** * @todo fill in. */ - public readonly string $table; + public readonly bool $needsPrefix; /** * @todo fill in. */ - public readonly bool $needsPrefix; + public readonly string $canonicalName; /** * @todo fill in. @@ -37,38 +37,17 @@ class Table extends IdentifierBase { protected string $machineName; public function __construct( - IdentifierHandler $identifierHandler, + IdentifierProcessorBase $identifierProcessor, string $identifier, ) { - parent::__construct($identifierHandler, $identifier); - $parts = explode(".", $identifier); - [$this->database, $this->schema, $this->table] = match (count($parts)) { - 1 => [ - NULL, - NULL, - $this->identifierHandler->identifierProcessor->tableEscapeName($parts[0]), - ], - 2 => [ - NULL, - $this->identifierHandler->identifierProcessor->tableEscapeName($parts[0]), - $this->identifierHandler->identifierProcessor->tableEscapeName($parts[1]), - ], - 3 => [ - $this->identifierHandler->identifierProcessor->tableEscapeName($parts[0]), - $this->identifierHandler->identifierProcessor->tableEscapeName($parts[1]), - $this->identifierHandler->identifierProcessor->tableEscapeName($parts[2]), - ], - }; - $this->needsPrefix = match (count($parts)) { - 1 => TRUE, - default => FALSE, - }; - if (strlen($this->needsPrefix ? $this->identifierHandler->identifierProcessor->tablePrefix : '' . $this->table) > $this->identifierHandler->identifierProcessor->getMaxLength(IdentifierType::Table)) { + parent::__construct($identifierProcessor, $identifier); + [$this->database, $this->schema, $this->canonicalName, $this->needsPrefix] = $this->identifierProcessor->parseTableIdentifier($identifier); + if (strlen($this->needsPrefix ? $this->identifierProcessor->tablePrefix : '' . $this->canonicalName) > $this->identifierProcessor->getMaxLength(IdentifierType::Table)) { throw new IdentifierException(sprintf( 'The machine length of the %s identifier \'%s\' exceeds the maximum allowed (%d)', IdentifierType::Table->value, - $this->table, - $this->identifierHandler->identifierProcessor->getMaxLength(IdentifierType::Table), + $this->canonicalName, + $this->identifierProcessor->getMaxLength(IdentifierType::Table), )); } } @@ -78,9 +57,9 @@ public function __construct( */ public function machineName(bool $quoted = TRUE): string { if (!isset($this->machineName)) { - $this->machineName = $this->identifierHandler->identifierProcessor->tableMachineName($this); + $this->machineName = $this->identifierProcessor->tableMachineName($this); } - [$start_quote, $end_quote] = $this->identifierHandler->identifierProcessor->identifierQuotes; + [$start_quote, $end_quote] = $this->identifierProcessor->identifierQuotes; return $quoted ? $start_quote . str_replace(".", "$end_quote.$start_quote", $this->machineName) . $end_quote : $this->machineName; } diff --git a/core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php b/core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php index e4963837b5cb..1529d58f1b17 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php +++ b/core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\mysql\Driver\Database\mysql; use Drupal\Core\Database\Identifier\IdentifierProcessorBase; diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php index e7bf885d01df..57e90796aeb9 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\pgsql\Driver\Database\pgsql; use Drupal\Core\Database\Identifier\IdentifierProcessorBase; diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php index eb70b4d4ee53..03a53ff1dcbb 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\sqlite\Driver\Database\sqlite; use Drupal\Core\Database\Identifier\IdentifierProcessorBase; -- GitLab From ddca074bbfeb9bb80050ed203e849920b9559121 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 16:14:23 +0100 Subject: [PATCH 26/66] wip --- core/lib/Drupal/Core/Database/Connection.php | 2 +- .../Core/Database/Identifier/Database.php | 35 +++++++++++++++++++ .../Database/Identifier/IdentifierBase.php | 13 ++++--- .../Identifier/IdentifierProcessorBase.php | 33 ++++++++++------- .../Core/Database/Identifier/Schema.php | 35 +++++++++++++++++++ .../Drupal/Core/Database/Identifier/Table.php | 27 ++++++++------ 6 files changed, 116 insertions(+), 29 deletions(-) create mode 100644 core/lib/Drupal/Core/Database/Identifier/Database.php create mode 100644 core/lib/Drupal/Core/Database/Identifier/Schema.php diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index e5c01ec7a20f..a2577336fb8c 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -993,7 +993,7 @@ public function escapeDatabase($database) { */ public function escapeTable($table) { @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); - return $this->identifiers->table($table)->machineName(quoted: FALSE); + return $this->identifiers->table($table)->canonical(); } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/Database.php b/core/lib/Drupal/Core/Database/Identifier/Database.php new file mode 100644 index 000000000000..7d2c53904347 --- /dev/null +++ b/core/lib/Drupal/Core/Database/Identifier/Database.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Database\Identifier; + +use Drupal\Core\Database\Exception\IdentifierException; + +/** + * Handles a database identifier. + * + * In full namespaced tables, the identifier is defined as + * <database>.<schema>.<table>. + */ +class Database extends IdentifierBase { + + public function __construct( + IdentifierProcessorBase $identifierProcessor, + string $identifier, + ) { + parent::__construct( + $identifierProcessor, + $identifier, + $identifierProcessor->canonicalizeIdentifier($identifier, IdentifierType::Database), + ); + } + + /** + * {@inheritdoc} + */ + public function machineName(bool $quoted = TRUE): string { + return $quoted ? $this->identifierProcessor->quote($this->canonicalName) : $this->canonicalName; + } + +} diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php index f07d80e5bf71..b053c3524e87 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -9,17 +9,20 @@ */ abstract class IdentifierBase implements \Stringable { - /** - * @todo fill in. - */ - public readonly string $canonicalName; - public function __construct( protected readonly IdentifierProcessorBase $identifierProcessor, public readonly string $identifier, + public readonly string $canonicalName, ) { } + /** + * @todo fill in. + */ + public function canonical(): string { + return $this->canonicalName; + } + /** * @todo fill in. */ diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php index 7141c1105fb1..8897909cae63 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php @@ -34,6 +34,20 @@ public function __construct( */ abstract public function getMaxLength(IdentifierType $type): int; + /** + * @todo fill in. + */ + public function quote(string $value): string { + return $this->identifierQuotes[0] . $value . $this->identifierQuotes[1]; + } + + /** + * @todo fill in. + */ + public function canonicalizeIdentifier(string $identifier, IdentifierType $type): string { + return preg_replace('/[^A-Za-z0-9_]+/', '', $identifier); + } + /** * @todo fill in. */ @@ -43,17 +57,17 @@ public function parseTableIdentifier(string $identifier): array { 1 => [ NULL, NULL, - $this->tableEscapeName($parts[0]), + $this->canonicalizeIdentifier($parts[0], IdentifierType::Table), ], 2 => [ NULL, - $this->tableEscapeName($parts[0]), - $this->tableEscapeName($parts[1]), + new Schema($this, $parts[0]), + $this->canonicalizeIdentifier($parts[1], IdentifierType::Table), ], 3 => [ - $this->tableEscapeName($parts[0]), - $this->tableEscapeName($parts[1]), - $this->tableEscapeName($parts[2]), + new Database($this, $parts[0]), + new Schema($this, $parts[1]), + $this->canonicalizeIdentifier($parts[2], IdentifierType::Table), ], }; $needsPrefix = match (count($parts)) { @@ -63,13 +77,6 @@ public function parseTableIdentifier(string $identifier): array { return [$database, $schema, $table, $needsPrefix]; } - /** - * @todo fill in. - */ - public function tableEscapeName(string $table): string { - return preg_replace('/[^A-Za-z0-9_.]+/', '', $table); - } - /** * @todo fill in. */ diff --git a/core/lib/Drupal/Core/Database/Identifier/Schema.php b/core/lib/Drupal/Core/Database/Identifier/Schema.php new file mode 100644 index 000000000000..56a06618a5d0 --- /dev/null +++ b/core/lib/Drupal/Core/Database/Identifier/Schema.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Database\Identifier; + +use Drupal\Core\Database\Exception\IdentifierException; + +/** + * Handles a schema identifier. + * + * In full namespaced tables, the identifier is defined as + * <database>.<schema>.<table>. + */ +class Schema extends IdentifierBase { + + public function __construct( + IdentifierProcessorBase $identifierProcessor, + string $identifier, + ) { + parent::__construct( + $identifierProcessor, + $identifier, + $identifierProcessor->canonicalizeIdentifier($identifier, IdentifierType::Schema), + ); + } + + /** + * {@inheritdoc} + */ + public function machineName(bool $quoted = TRUE): string { + return $quoted ? $this->identifierProcessor->quote($this->canonicalName) : $this->canonicalName; + } + +} diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 4811889b9286..7b808d7d626b 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -14,23 +14,18 @@ class Table extends IdentifierBase { /** * @todo fill in. */ - public readonly ?string $database; + public readonly ?Database $database; /** * @todo fill in. */ - public readonly ?string $schema; + public readonly ?Schema $schema; /** * @todo fill in. */ public readonly bool $needsPrefix; - /** - * @todo fill in. - */ - public readonly string $canonicalName; - /** * @todo fill in. */ @@ -40,8 +35,10 @@ public function __construct( IdentifierProcessorBase $identifierProcessor, string $identifier, ) { - parent::__construct($identifierProcessor, $identifier); - [$this->database, $this->schema, $this->canonicalName, $this->needsPrefix] = $this->identifierProcessor->parseTableIdentifier($identifier); + $parts = $identifierProcessor->parseTableIdentifier($identifier); + parent::__construct($identifierProcessor, $identifier, $parts[2]); + + [$this->database, $this->schema, $tmp, $this->needsPrefix] = $parts; if (strlen($this->needsPrefix ? $this->identifierProcessor->tablePrefix : '' . $this->canonicalName) > $this->identifierProcessor->getMaxLength(IdentifierType::Table)) { throw new IdentifierException(sprintf( 'The machine length of the %s identifier \'%s\' exceeds the maximum allowed (%d)', @@ -53,7 +50,17 @@ public function __construct( } /** - * @todo fill in. + * {@inheritdoc} + */ + public function canonical(): string { + $ret = isset($this->database) ? $this->database->canonical() . '.' : ''; + $ret .= isset($this->schema) ? $this->schema->canonical() . '.' : ''; + $ret .= $this->canonicalName; + return $ret; + } + + /** + * {@inheritdoc} */ public function machineName(bool $quoted = TRUE): string { if (!isset($this->machineName)) { -- GitLab From 156e357923562c561ea2356dadad7ca3ae231ad7 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 16:35:52 +0100 Subject: [PATCH 27/66] wip --- core/lib/Drupal/Core/Database/Identifier/Database.php | 2 -- core/lib/Drupal/Core/Database/Identifier/Schema.php | 2 -- core/lib/Drupal/Core/Database/Identifier/Table.php | 5 ++++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/Database.php b/core/lib/Drupal/Core/Database/Identifier/Database.php index 7d2c53904347..2a8ada453549 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Database.php +++ b/core/lib/Drupal/Core/Database/Identifier/Database.php @@ -4,8 +4,6 @@ namespace Drupal\Core\Database\Identifier; -use Drupal\Core\Database\Exception\IdentifierException; - /** * Handles a database identifier. * diff --git a/core/lib/Drupal/Core/Database/Identifier/Schema.php b/core/lib/Drupal/Core/Database/Identifier/Schema.php index 56a06618a5d0..fdc9a77c0b99 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Schema.php +++ b/core/lib/Drupal/Core/Database/Identifier/Schema.php @@ -4,8 +4,6 @@ namespace Drupal\Core\Database\Identifier; -use Drupal\Core\Database\Exception\IdentifierException; - /** * Handles a schema identifier. * diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 7b808d7d626b..c2f5ce7c5156 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -38,7 +38,10 @@ public function __construct( $parts = $identifierProcessor->parseTableIdentifier($identifier); parent::__construct($identifierProcessor, $identifier, $parts[2]); - [$this->database, $this->schema, $tmp, $this->needsPrefix] = $parts; + $this->database = $parts[0]; + $this->schema = $parts[1]; + $this->needsPrefix = $parts[3]; + if (strlen($this->needsPrefix ? $this->identifierProcessor->tablePrefix : '' . $this->canonicalName) > $this->identifierProcessor->getMaxLength(IdentifierType::Table)) { throw new IdentifierException(sprintf( 'The machine length of the %s identifier \'%s\' exceeds the maximum allowed (%d)', -- GitLab From db5da06c88f92c11d7ca0d4a28bc40283e901965 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 17:41:47 +0100 Subject: [PATCH 28/66] wip --- .../Drupal/Core/Database/Identifier/Database.php | 7 ------- .../Core/Database/Identifier/IdentifierBase.php | 4 +++- .../Identifier/IdentifierProcessorBase.php | 14 +++++--------- .../lib/Drupal/Core/Database/Identifier/Schema.php | 7 ------- core/lib/Drupal/Core/Database/Identifier/Table.php | 12 +++--------- .../Driver/Database/sqlite/IdentifierProcessor.php | 11 +++++++++++ 6 files changed, 22 insertions(+), 33 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/Database.php b/core/lib/Drupal/Core/Database/Identifier/Database.php index 2a8ada453549..49f9a2b9dd5c 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Database.php +++ b/core/lib/Drupal/Core/Database/Identifier/Database.php @@ -23,11 +23,4 @@ public function __construct( ); } - /** - * {@inheritdoc} - */ - public function machineName(bool $quoted = TRUE): string { - return $quoted ? $this->identifierProcessor->quote($this->canonicalName) : $this->canonicalName; - } - } diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php index b053c3524e87..e8797397040e 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -26,7 +26,9 @@ public function canonical(): string { /** * @todo fill in. */ - abstract public function machineName(bool $quoted = TRUE): string; + public function machineName(bool $quoted = TRUE): string { + return $quoted ? $this->identifierProcessor->quote($this->canonicalName) : $this->canonicalName; + } /** * @todo fill in. diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php index 8897909cae63..0242d575d51a 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php @@ -80,15 +80,11 @@ public function parseTableIdentifier(string $identifier): array { /** * @todo fill in. */ - public function tableMachineName(Table $table): string { - $tableName = $table->needsPrefix ? $this->tablePrefix . $table->canonicalName : $table->canonicalName; - if ($table->database) { - return implode('.', [$table->database, $table->schema, $tableName]); - } - elseif ($table->schema) { - return implode('.', [$table->schema, $tableName]); - } - return $tableName; + public function getTableMachineName(Table $table): string { + $ret = isset($table->database) ? $this->quote($table->database->canonical()) . '.' : ''; + $ret .= isset($table->schema) ? $this->quote($table->schema->canonical()) . '.' : ''; + $ret .= $this->quote($table->canonicalName); + return $ret; } } diff --git a/core/lib/Drupal/Core/Database/Identifier/Schema.php b/core/lib/Drupal/Core/Database/Identifier/Schema.php index fdc9a77c0b99..fe5a08f4829d 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Schema.php +++ b/core/lib/Drupal/Core/Database/Identifier/Schema.php @@ -23,11 +23,4 @@ public function __construct( ); } - /** - * {@inheritdoc} - */ - public function machineName(bool $quoted = TRUE): string { - return $quoted ? $this->identifierProcessor->quote($this->canonicalName) : $this->canonicalName; - } - } diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index c2f5ce7c5156..55eebff7e168 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -26,11 +26,6 @@ class Table extends IdentifierBase { */ public readonly bool $needsPrefix; - /** - * @todo fill in. - */ - protected string $machineName; - public function __construct( IdentifierProcessorBase $identifierProcessor, string $identifier, @@ -66,11 +61,10 @@ public function canonical(): string { * {@inheritdoc} */ public function machineName(bool $quoted = TRUE): string { - if (!isset($this->machineName)) { - $this->machineName = $this->identifierProcessor->tableMachineName($this); + if (!$quoted) { + return $this->canonical(); } - [$start_quote, $end_quote] = $this->identifierProcessor->identifierQuotes; - return $quoted ? $start_quote . str_replace(".", "$end_quote.$start_quote", $this->machineName) . $end_quote : $this->machineName; + return $this->identifierProcessor->getTableMachineName($this); } } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php index 03a53ff1dcbb..33abf91c95c0 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php @@ -6,6 +6,7 @@ use Drupal\Core\Database\Identifier\IdentifierProcessorBase; use Drupal\Core\Database\Identifier\IdentifierType; +use Drupal\Core\Database\Identifier\Table; /** * SQLite implementation of the identifier processor. @@ -23,4 +24,14 @@ public function getMaxLength(IdentifierType $type): int { return 128; } + /** + * {@inheritdoc} + */ + public function getTableMachineName(Table $table): string { + if (!isset($table->database) && !isset($table->schema) && $table->needsPrefix) { + return $this->quote(rtrim($this->tablePrefix, '.')) . '.' . $this->quote($table->canonicalName); + } + return parent::getTableMachineName($table); + } + } -- GitLab From 3c9ca1bbc7eb6875e5120c46a7380ce512e2fc2c Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 17:59:02 +0100 Subject: [PATCH 29/66] wip --- .../Database/Identifier/IdentifierProcessorBase.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php index 0242d575d51a..7f740811be78 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php @@ -70,10 +70,15 @@ public function parseTableIdentifier(string $identifier): array { $this->canonicalizeIdentifier($parts[2], IdentifierType::Table), ], }; - $needsPrefix = match (count($parts)) { - 1 => TRUE, - default => FALSE, - }; + if ($this->tablePrefix !== '') { + $needsPrefix = match (count($parts)) { + 1 => TRUE, + default => FALSE, + }; + } + else { + $needsPrefix = FALSE; + } return [$database, $schema, $table, $needsPrefix]; } -- GitLab From 135c6d5c2c34144ed10e2eca6d9463a0acb0bf01 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 20:25:07 +0100 Subject: [PATCH 30/66] wip --- .../Drupal/Core/Database/Identifier/IdentifierProcessorBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php index 7f740811be78..0d62a54030ab 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php @@ -88,7 +88,7 @@ public function parseTableIdentifier(string $identifier): array { public function getTableMachineName(Table $table): string { $ret = isset($table->database) ? $this->quote($table->database->canonical()) . '.' : ''; $ret .= isset($table->schema) ? $this->quote($table->schema->canonical()) . '.' : ''; - $ret .= $this->quote($table->canonicalName); + $ret .= $this->quote($this->tablePrefix . $table->canonicalName); return $ret; } -- GitLab From 9d38c17a1d1418c8ae3b6c5a647abc80b7eb51a4 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 9 Mar 2025 20:47:52 +0100 Subject: [PATCH 31/66] wip --- .../sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php index 33abf91c95c0..9c57a9eb13d3 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php @@ -31,7 +31,9 @@ public function getTableMachineName(Table $table): string { if (!isset($table->database) && !isset($table->schema) && $table->needsPrefix) { return $this->quote(rtrim($this->tablePrefix, '.')) . '.' . $this->quote($table->canonicalName); } - return parent::getTableMachineName($table); + $ret = isset($table->schema) ? $this->quote($table->schema->canonical()) . '.' : ''; + $ret .= $this->quote($table->canonicalName); + return $ret; } } -- GitLab From 40358cccd257b59fe4555066752bd3e35f5b0685 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Mon, 10 Mar 2025 21:21:04 +0100 Subject: [PATCH 32/66] simplify --- core/lib/Drupal/Core/Database/Connection.php | 16 ++--- .../Core/Database/Identifier/Database.php | 6 +- .../Database/Identifier/IdentifierBase.php | 4 +- .../Database/Identifier/IdentifierHandler.php | 60 ------------------- ...ssorBase.php => IdentifierHandlerBase.php} | 45 +++++++++++++- .../Core/Database/Identifier/Schema.php | 6 +- .../Drupal/Core/Database/Identifier/Table.php | 12 ++-- core/lib/Drupal/Core/Database/Schema.php | 4 +- .../migrate/src/Plugin/migrate/id_map/Sql.php | 2 +- .../src/Driver/Database/mysql/Connection.php | 3 +- ...ierProcessor.php => IdentifierHandler.php} | 6 +- .../src/Driver/Database/mysql/Schema.php | 2 +- .../mysql/Console/DbDumpCommandTest.php | 2 +- .../src/Driver/Database/pgsql/Connection.php | 3 +- ...ierProcessor.php => IdentifierHandler.php} | 6 +- .../src/Driver/Database/pgsql/Schema.php | 12 ++-- .../src/Kernel/pgsql/NonPublicSchemaTest.php | 14 ++--- .../tests/src/Kernel/pgsql/SchemaTest.php | 8 +-- .../src/Driver/Database/sqlite/Connection.php | 3 +- ...ierProcessor.php => IdentifierHandler.php} | 6 +- .../Tests/Core/Database/ConnectionTest.php | 4 +- .../Core/Database/Stub/StubConnection.php | 3 +- ...rocessor.php => StubIdentifierHandler.php} | 6 +- 23 files changed, 106 insertions(+), 127 deletions(-) delete mode 100644 core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php rename core/lib/Drupal/Core/Database/Identifier/{IdentifierProcessorBase.php => IdentifierHandlerBase.php} (66%) rename core/modules/mysql/src/Driver/Database/mysql/{IdentifierProcessor.php => IdentifierHandler.php} (70%) rename core/modules/pgsql/src/Driver/Database/pgsql/{IdentifierProcessor.php => IdentifierHandler.php} (64%) rename core/modules/sqlite/src/Driver/Database/sqlite/{IdentifierProcessor.php => IdentifierHandler.php} (84%) rename core/tests/Drupal/Tests/Core/Database/Stub/{StubIdentifierProcessor.php => StubIdentifierHandler.php} (55%) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index a2577336fb8c..f7e082ac296f 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -4,7 +4,7 @@ use Drupal\Core\Database\Event\DatabaseEvent; use Drupal\Core\Database\Exception\EventException; -use Drupal\Core\Database\Identifier\IdentifierHandler; +use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\Query\Delete; use Drupal\Core\Database\Query\Insert; @@ -141,13 +141,13 @@ abstract class Connection { * - prefix * - namespace * - Other driver-specific options. - * @param \Drupal\Core\Database\Identifier\IdentifierHandler|null $identifiers + * @param \Drupal\Core\Database\Identifier\IdentifierHandlerBase|null $identifiers * The identifiers handler. */ public function __construct( object $connection, array $connection_options, - public readonly ?IdentifierHandler $identifiers, + public readonly ?IdentifierHandlerBase $identifiers, ) { // Manage the table prefix. $connection_options['prefix'] = $connection_options['prefix'] ?? ''; @@ -315,7 +315,7 @@ public function attachDatabase(string $database): void { */ public function getPrefix(): string { @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); - return $this->identifiers->identifierProcessor->tablePrefix; + return $this->identifiers->tablePrefix; } /** @@ -372,7 +372,7 @@ public function prefixTables($sql) { * This method should only be called by database API code. */ public function quoteIdentifiers($sql) { - return str_replace(['[', ']'], $this->identifiers->identifierProcessor->identifierQuotes, $sql); + return str_replace(['[', ']'], $this->identifiers->identifierQuotes, $sql); } /** @@ -970,7 +970,7 @@ public function condition($conjunction) { */ public function escapeDatabase($database) { $database = preg_replace('/[^A-Za-z0-9_]+/', '', $database); - [$start_quote, $end_quote] = $this->identifiers->identifierProcessor->identifierQuotes; + [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; return $start_quote . $database . $end_quote; } @@ -1012,7 +1012,7 @@ public function escapeTable($table) { public function escapeField($field) { if (!isset($this->escapedFields[$field])) { $escaped = preg_replace('/[^A-Za-z0-9_.]+/', '', $field); - [$start_quote, $end_quote] = $this->identifiers->identifierProcessor->identifierQuotes; + [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; // Sometimes fields have the format table_alias.field. In such cases // both identifiers should be quoted, for example, "table_alias"."field". $this->escapedFields[$field] = $start_quote . str_replace('.', $end_quote . '.' . $start_quote, $escaped) . $end_quote; @@ -1036,7 +1036,7 @@ public function escapeField($field) { */ public function escapeAlias($field) { if (!isset($this->escapedAliases[$field])) { - [$start_quote, $end_quote] = $this->identifiers->identifierProcessor->identifierQuotes; + [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; $this->escapedAliases[$field] = $start_quote . preg_replace('/[^A-Za-z0-9_]+/', '', $field) . $end_quote; } return $this->escapedAliases[$field]; diff --git a/core/lib/Drupal/Core/Database/Identifier/Database.php b/core/lib/Drupal/Core/Database/Identifier/Database.php index 49f9a2b9dd5c..309de67a4cf4 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Database.php +++ b/core/lib/Drupal/Core/Database/Identifier/Database.php @@ -13,13 +13,13 @@ class Database extends IdentifierBase { public function __construct( - IdentifierProcessorBase $identifierProcessor, + IdentifierHandlerBase $identifierHandler, string $identifier, ) { parent::__construct( - $identifierProcessor, + $identifierHandler, $identifier, - $identifierProcessor->canonicalizeIdentifier($identifier, IdentifierType::Database), + $identifierHandler->canonicalizeIdentifier($identifier, IdentifierType::Database), ); } diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php index e8797397040e..3875733d2304 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -10,7 +10,7 @@ abstract class IdentifierBase implements \Stringable { public function __construct( - protected readonly IdentifierProcessorBase $identifierProcessor, + protected readonly IdentifierHandlerBase $identifierHandler, public readonly string $identifier, public readonly string $canonicalName, ) { @@ -27,7 +27,7 @@ public function canonical(): string { * @todo fill in. */ public function machineName(bool $quoted = TRUE): string { - return $quoted ? $this->identifierProcessor->quote($this->canonicalName) : $this->canonicalName; + return $quoted ? $this->identifierHandler->quote($this->canonicalName) : $this->canonicalName; } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php deleted file mode 100644 index 80e46062f90b..000000000000 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandler.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\Core\Database\Identifier; - -/** - * @todo fill in. - */ -class IdentifierHandler { - - /** - * @var array{'identifier':array<string,array<string,string>>,'machine':array<string,array<string,string>>} - */ - protected array $identifiers; - - public function __construct( - public readonly IdentifierProcessorBase $identifierProcessor, - ) { - } - - /** - * @todo fill in. - */ - public function table(string|Table $tableIdentifier): Table { - if ($tableIdentifier instanceof Table) { - $tableIdentifier = $tableIdentifier->identifier; - } - if ($this->hasIdentifier($tableIdentifier, IdentifierType::Table)) { - $table = $this->getIdentifier($tableIdentifier, IdentifierType::Table); - } - else { - $table = new Table($this->identifierProcessor, $tableIdentifier); - $this->setIdentifier($tableIdentifier, IdentifierType::Table, $table); - } - return $table; - } - - /** - * @todo fill in. - */ - protected function setIdentifier(string $id, IdentifierType $type, IdentifierBase $identifier): void { - $this->identifiers['identifier'][$id][$type->value] = $identifier; - } - - /** - * @todo fill in. - */ - protected function hasIdentifier(string $id, IdentifierType $type): bool { - return isset($this->identifiers['identifier'][$id][$type->value]); - } - - /** - * @todo fill in. - */ - protected function getIdentifier(string $id, IdentifierType $type): IdentifierBase { - return $this->identifiers['identifier'][$id][$type->value]; - } - -} diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php similarity index 66% rename from core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php rename to core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index 0d62a54030ab..24bb723450f9 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierProcessorBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -9,7 +9,12 @@ /** * @todo fill in. */ -abstract class IdentifierProcessorBase { +abstract class IdentifierHandlerBase { + + /** + * @var array{'identifier':array<string,array<string,string>>,'machine':array<string,array<string,string>>} + */ + protected array $identifiers; /** * Constructor. @@ -29,6 +34,44 @@ public function __construct( assert(count($this->identifierQuotes) === 2 && Inspector::assertAllStrings($this->identifierQuotes), __CLASS__ . '::$identifierQuotes must contain 2 string values'); } + /** + * @todo fill in. + */ + public function table(string|Table $tableIdentifier): Table { + if ($tableIdentifier instanceof Table) { + $tableIdentifier = $tableIdentifier->identifier; + } + if ($this->hasIdentifier($tableIdentifier, IdentifierType::Table)) { + $table = $this->getIdentifier($tableIdentifier, IdentifierType::Table); + } + else { + $table = new Table($this, $tableIdentifier); + $this->setIdentifier($tableIdentifier, IdentifierType::Table, $table); + } + return $table; + } + + /** + * @todo fill in. + */ + protected function setIdentifier(string $id, IdentifierType $type, IdentifierBase $identifier): void { + $this->identifiers['identifier'][$id][$type->value] = $identifier; + } + + /** + * @todo fill in. + */ + protected function hasIdentifier(string $id, IdentifierType $type): bool { + return isset($this->identifiers['identifier'][$id][$type->value]); + } + + /** + * @todo fill in. + */ + protected function getIdentifier(string $id, IdentifierType $type): IdentifierBase { + return $this->identifiers['identifier'][$id][$type->value]; + } + /** * @todo fill in. */ diff --git a/core/lib/Drupal/Core/Database/Identifier/Schema.php b/core/lib/Drupal/Core/Database/Identifier/Schema.php index fe5a08f4829d..dcd3fef19f76 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Schema.php +++ b/core/lib/Drupal/Core/Database/Identifier/Schema.php @@ -13,13 +13,13 @@ class Schema extends IdentifierBase { public function __construct( - IdentifierProcessorBase $identifierProcessor, + IdentifierHandlerBase $identifierHandler, string $identifier, ) { parent::__construct( - $identifierProcessor, + $identifierHandler, $identifier, - $identifierProcessor->canonicalizeIdentifier($identifier, IdentifierType::Schema), + $identifierHandler->canonicalizeIdentifier($identifier, IdentifierType::Schema), ); } diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 55eebff7e168..da42f6bf5b8d 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -27,22 +27,22 @@ class Table extends IdentifierBase { public readonly bool $needsPrefix; public function __construct( - IdentifierProcessorBase $identifierProcessor, + IdentifierHandlerBase $identifierHandler, string $identifier, ) { - $parts = $identifierProcessor->parseTableIdentifier($identifier); - parent::__construct($identifierProcessor, $identifier, $parts[2]); + $parts = $identifierHandler->parseTableIdentifier($identifier); + parent::__construct($identifierHandler, $identifier, $parts[2]); $this->database = $parts[0]; $this->schema = $parts[1]; $this->needsPrefix = $parts[3]; - if (strlen($this->needsPrefix ? $this->identifierProcessor->tablePrefix : '' . $this->canonicalName) > $this->identifierProcessor->getMaxLength(IdentifierType::Table)) { + if (strlen($this->needsPrefix ? $this->identifierHandler->tablePrefix : '' . $this->canonicalName) > $this->identifierHandler->getMaxLength(IdentifierType::Table)) { throw new IdentifierException(sprintf( 'The machine length of the %s identifier \'%s\' exceeds the maximum allowed (%d)', IdentifierType::Table->value, $this->canonicalName, - $this->identifierProcessor->getMaxLength(IdentifierType::Table), + $this->identifierHandler->getMaxLength(IdentifierType::Table), )); } } @@ -64,7 +64,7 @@ public function machineName(bool $quoted = TRUE): string { if (!$quoted) { return $this->canonical(); } - return $this->identifierProcessor->getTableMachineName($this); + return $this->identifierHandler->getTableMachineName($this); } } diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index cbcf24bb3cf0..6504b70619cd 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -84,7 +84,7 @@ public function nextPlaceholder() { protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { $info = [ 'schema' => $this->defaultSchema, - 'prefix' => isset($this->connection->identifiers) ? $this->connection->identifiers->identifierProcessor->tablePrefix : '', + 'prefix' => isset($this->connection->identifiers) ? $this->connection->identifiers->tablePrefix : '', ]; if ($add_prefix) { $table = $info['prefix'] . $table; @@ -223,7 +223,7 @@ public function findTables($table_expression) { $condition = $this->buildTableNameCondition('%', 'LIKE'); $condition->compile($this->connection, $this); - $prefix = $this->connection->identifiers->identifierProcessor->tablePrefix; + $prefix = $this->connection->identifiers->tablePrefix; $prefix_length = strlen($prefix); $tables = []; // Normally, we would heartily discourage the use of string diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index 6a6153b1d844..53b598bb14db 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -183,7 +183,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition // Default generated table names, limited to 63 characters. $machine_name = str_replace(':', '__', $this->migration->id()); - $prefix_length = isset($this->database->identifiers) ? strlen($this->database->identifiers->identifierProcessor->tablePrefix) : 0; + $prefix_length = isset($this->database->identifiers) ? strlen($this->database->identifiers->tablePrefix) : 0; $this->mapTableName = 'migrate_map_' . mb_strtolower($machine_name); $this->mapTableName = mb_substr($this->mapTableName, 0, 63 - $prefix_length); $this->messageTableName = 'migrate_message_' . mb_strtolower($machine_name); diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php index 9e7df4bc02df..defef3b8568d 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php @@ -7,7 +7,6 @@ use Drupal\Core\Database\DatabaseAccessDeniedException; use Drupal\Core\Database\DatabaseConnectionRefusedException; use Drupal\Core\Database\DatabaseNotFoundException; -use Drupal\Core\Database\Identifier\IdentifierHandler; use Drupal\Core\Database\StatementWrapperIterator; use Drupal\Core\Database\SupportsTemporaryTablesInterface; use Drupal\Core\Database\Transaction\TransactionManagerInterface; @@ -87,7 +86,7 @@ public function __construct(\PDO $connection, array $connection_options) { parent::__construct( $connection, $connection_options, - new IdentifierHandler(new IdentifierProcessor($connection_options['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`'])), + new IdentifierHandler($connection_options['prefix'], $is_ansi_quotes_mode ? ['"', '"'] : ['`', '`']), ); } diff --git a/core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php similarity index 70% rename from core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php rename to core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php index 1529d58f1b17..fb7aa609e7f1 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/IdentifierProcessor.php +++ b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php @@ -4,13 +4,13 @@ namespace Drupal\mysql\Driver\Database\mysql; -use Drupal\Core\Database\Identifier\IdentifierProcessorBase; +use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Identifier\IdentifierType; /** - * MySQL implementation of the identifier processor. + * MySQL implementation of the identifier handler. */ -class IdentifierProcessor extends IdentifierProcessorBase { +class IdentifierHandler extends IdentifierHandlerBase { /** * {@inheritdoc} diff --git a/core/modules/mysql/src/Driver/Database/mysql/Schema.php b/core/modules/mysql/src/Driver/Database/mysql/Schema.php index a82d2e8b6693..2cc75a078ab9 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Schema.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Schema.php @@ -51,7 +51,7 @@ class Schema extends DatabaseSchema { * A keyed array with information about the database, table name and prefix. */ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { - $info = ['prefix' => $this->connection->identifiers->identifierProcessor->tablePrefix]; + $info = ['prefix' => $this->connection->identifiers->tablePrefix]; if ($add_prefix) { $table = $info['prefix'] . $table; } diff --git a/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php b/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php index d662635096d7..d6ab61ec886c 100644 --- a/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php +++ b/core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php @@ -35,7 +35,7 @@ protected function setUp(): void { // Create a table with a field type not defined in // \Drupal\Core\Database\Schema::getFieldTypeMap. - $table_name = $connection->identifiers->identifierProcessor->tablePrefix . 'foo'; + $table_name = $connection->identifiers->tablePrefix . 'foo'; $sql = "create table if not exists `$table_name` (`test` datetime NOT NULL);"; $connection->query($sql)->execute(); } diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index e2087dbcc665..b04ab6393647 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -7,7 +7,6 @@ use Drupal\Core\Database\DatabaseAccessDeniedException; use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\ExceptionHandler; -use Drupal\Core\Database\Identifier\IdentifierHandler; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Database\StatementWrapperIterator; use Drupal\Core\Database\SupportsTemporaryTablesInterface; @@ -330,7 +329,7 @@ public function makeSequenceName($table, $field) { $sequence_name = $this->prefixTables('{' . $table . '}_' . $field . '_seq'); // Remove identifier quotes as we are constructing a new name from a // prefixed and quoted table name. - return str_replace($this->identifiers->identifierProcessor->identifierQuotes, '', $sequence_name); + return str_replace($this->identifiers->identifierQuotes, '', $sequence_name); } /** diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php similarity index 64% rename from core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php rename to core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php index 57e90796aeb9..d27746454f7c 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierProcessor.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php @@ -4,13 +4,13 @@ namespace Drupal\pgsql\Driver\Database\pgsql; -use Drupal\Core\Database\Identifier\IdentifierProcessorBase; +use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Identifier\IdentifierType; /** - * MySQL implementation of the identifier processor. + * MySQL implementation of the identifier handler. */ -class IdentifierProcessor extends IdentifierProcessorBase { +class IdentifierHandler extends IdentifierHandlerBase { /** * {@inheritdoc} diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php index 0cd9068a3a62..3b992977cce0 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php @@ -128,7 +128,7 @@ protected function ensureIdentifiersLength($table_identifier_part, $column_ident */ public function queryTableInformation($table) { // Generate a key to reference this table's information on. - $prefixed_table = $this->connection->identifiers->identifierProcessor->tablePrefix . $table; + $prefixed_table = $this->connection->identifiers->tablePrefix . $table; $key = $this->connection->prefixTables('{' . $table . '}'); // Take into account that temporary tables are stored in a different schema. @@ -220,7 +220,7 @@ protected function getTempNamespaceName() { * The non-prefixed name of the table. */ protected function resetTableInformation($table) { - $key = $this->defaultSchema . '.' . $this->connection->identifiers->identifierProcessor->tablePrefix . $table; + $key = $this->defaultSchema . '.' . $this->connection->identifiers->tablePrefix . $table; unset($this->tableInformation[$key]); } @@ -521,7 +521,7 @@ public function tableExists($table, $add_prefix = TRUE) { * {@inheritdoc} */ public function findTables($table_expression) { - $prefix = $this->connection->identifiers->identifierProcessor->tablePrefix; + $prefix = $this->connection->identifiers->tablePrefix; $prefix_length = strlen($prefix); $tables = []; @@ -567,7 +567,7 @@ public function renameTable($table, $new_name) { } // Get the schema and tablename for the old table. - $table_name = $this->connection->identifiers->identifierProcessor->tablePrefix . $table; + $table_name = $this->connection->identifiers->tablePrefix . $table; // Index names and constraint names are global in PostgreSQL, so we need to // rename them when renaming the table. $indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', [':schema' => $this->defaultSchema, ':table' => $table_name]); @@ -734,7 +734,7 @@ public function indexExists($table, $name) { $sql_params = [ ':schema' => $this->defaultSchema, - ':table' => $this->connection->identifiers->identifierProcessor->tablePrefix . $table, + ':table' => $this->connection->identifiers->tablePrefix . $table, ':index' => $index_name, ]; return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE schemaname = :schema AND tablename = :table AND indexname = :index", $sql_params)->fetchField(); @@ -1118,7 +1118,7 @@ public function extensionExists($name): bool { protected function getSequenceName(string $table, string $column): ?string { return $this->connection ->query("SELECT pg_get_serial_sequence(:table, :column)", [ - ':table' => $this->defaultSchema . '.' . $this->connection->identifiers->identifierProcessor->tablePrefix . $table, + ':table' => $this->defaultSchema . '.' . $this->connection->identifiers->tablePrefix . $table, ':column' => $column, ]) ->fetchField(); diff --git a/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php b/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php index 075ba2267220..dfd6b73601bd 100644 --- a/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php +++ b/core/modules/pgsql/tests/src/Kernel/pgsql/NonPublicSchemaTest.php @@ -118,7 +118,7 @@ public function testExtensionExists(): void { $this->assertTrue($this->testingFakeConnection->schema()->tableExists('faking_table')); // Hardcoded assertion that we created the table in the non-public schema. - $this->assertCount(1, $this->testingFakeConnection->query("SELECT * FROM pg_tables WHERE schemaname = 'testing_fake' AND tablename = :prefixedTable", [':prefixedTable' => $this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . "faking_table"])->fetchAll()); + $this->assertCount(1, $this->testingFakeConnection->query("SELECT * FROM pg_tables WHERE schemaname = 'testing_fake' AND tablename = :prefixedTable", [':prefixedTable' => $this->testingFakeConnection->identifiers->tablePrefix . "faking_table"])->fetchAll()); } /** @@ -273,11 +273,11 @@ public function testIndex(): void { $this->assertTrue($this->testingFakeConnection->schema()->indexExists('faking_table', 'test_field')); - $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table__test_field__idx'])->fetchAll(); + $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->tablePrefix . 'faking_table__test_field__idx'])->fetchAll(); $this->assertCount(1, $results); $this->assertSame('testing_fake', $results[0]->schemaname); - $this->assertSame($this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table', $results[0]->tablename); + $this->assertSame($this->testingFakeConnection->identifiers->tablePrefix . 'faking_table', $results[0]->tablename); $this->assertStringContainsString('USING btree (test_field)', $results[0]->indexdef); $this->testingFakeConnection->schema()->dropIndex('faking_table', 'test_field'); @@ -300,12 +300,12 @@ public function testUniqueKey(): void { // phpcs:ignore // $this->assertTrue($this->testingFakeConnection->schema()->indexExists('faking_table', 'test_field')); - $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table__test_field__key'])->fetchAll(); + $results = $this->testingFakeConnection->query("SELECT * FROM pg_indexes WHERE indexname = :indexname", [':indexname' => $this->testingFakeConnection->identifiers->tablePrefix . 'faking_table__test_field__key'])->fetchAll(); // Check the unique key columns. $this->assertCount(1, $results); $this->assertSame('testing_fake', $results[0]->schemaname); - $this->assertSame($this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table', $results[0]->tablename); + $this->assertSame($this->testingFakeConnection->identifiers->tablePrefix . 'faking_table', $results[0]->tablename); $this->assertStringContainsString('USING btree (test_field)', $results[0]->indexdef); $this->testingFakeConnection->schema()->dropUniqueKey('faking_table', 'test_field'); @@ -333,7 +333,7 @@ public function testPrimaryKey(): void { $this->assertCount(1, $results); $this->assertSame('testing_fake', $results[0]->schemaname); - $this->assertSame($this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'faking_table', $results[0]->tablename); + $this->assertSame($this->testingFakeConnection->identifiers->tablePrefix . 'faking_table', $results[0]->tablename); $this->assertStringContainsString('USING btree (id)', $results[0]->indexdef); $find_primary_keys_columns = new \ReflectionMethod(get_class($this->testingFakeConnection->schema()), 'findPrimaryKeyColumns'); @@ -356,7 +356,7 @@ public function testTable(): void { $result = $this->testingFakeConnection->query("SELECT * FROM information_schema.tables WHERE table_schema = 'testing_fake'")->fetchAll(); $this->assertFalse($this->testingFakeConnection->schema()->tableExists('faking_table')); $this->assertTrue($this->testingFakeConnection->schema()->tableExists('new_faking_table')); - $this->assertEquals($this->testingFakeConnection->identifiers->identifierProcessor->tablePrefix . 'new_faking_table', $result[0]->table_name); + $this->assertEquals($this->testingFakeConnection->identifiers->tablePrefix . 'new_faking_table', $result[0]->table_name); $this->assertEquals('testing_fake', $result[0]->table_schema); sort($tables); $this->assertEquals(['new_faking_table'], $tables); diff --git a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php index c413c09113ae..be23a3679e0b 100644 --- a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php +++ b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php @@ -281,18 +281,18 @@ public function testPgsqlSequences(): void { // Retrieves a sequence name that is owned by the table and column. $sequence_name = $this->connection ->query("SELECT pg_get_serial_sequence(:table, :column)", [ - ':table' => $this->connection->identifiers->identifierProcessor->tablePrefix . 'sequence_test', + ':table' => $this->connection->identifiers->tablePrefix . 'sequence_test', ':column' => 'uid', ]) ->fetchField(); $schema = $this->connection->getConnectionOptions()['schema'] ?? 'public'; - $this->assertEquals($schema . '.' . $this->connection->identifiers->identifierProcessor->tablePrefix . 'sequence_test_uid_seq', $sequence_name); + $this->assertEquals($schema . '.' . $this->connection->identifiers->tablePrefix . 'sequence_test_uid_seq', $sequence_name); // Checks if the sequence exists. $this->assertTrue((bool) \Drupal::database() ->query("SELECT c.relname FROM pg_class as c WHERE c.relkind = 'S' AND c.relname = :name", [ - ':name' => $this->connection->identifiers->identifierProcessor->tablePrefix . 'sequence_test_uid_seq', + ':name' => $this->connection->identifiers->tablePrefix . 'sequence_test_uid_seq', ]) ->fetchField()); @@ -304,7 +304,7 @@ public function testPgsqlSequences(): void { AND d.refobjsubid > 0 AND d.classid = 'pg_class'::regclass", [':seq_name' => $sequence_name])->fetchObject(); - $this->assertEquals($this->connection->identifiers->identifierProcessor->tablePrefix . 'sequence_test', $sequence_owner->table_name); + $this->assertEquals($this->connection->identifiers->tablePrefix . 'sequence_test', $sequence_owner->table_name); $this->assertEquals('uid', $sequence_owner->field_name, 'New sequence is owned by its table.'); } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 634ee3d3c668..123ebe2fd63f 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -6,7 +6,6 @@ use Drupal\Core\Database\Connection as DatabaseConnection; use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\ExceptionHandler; -use Drupal\Core\Database\Identifier\IdentifierHandler; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Database\SupportsTemporaryTablesInterface; use Drupal\Core\Database\Transaction\TransactionManagerInterface; @@ -84,7 +83,7 @@ public function __construct(\PDO $connection, array $connection_options) { // querying an attached database. $prefix .= '.'; } - parent::__construct($connection, $connection_options, new IdentifierHandler(new IdentifierProcessor($prefix))); + parent::__construct($connection, $connection_options, new IdentifierHandler($prefix)); if (isset($attachedDatabaseName)) { $this->attachDatabase($attachedDatabaseName); } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php similarity index 84% rename from core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php rename to core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php index 9c57a9eb13d3..84d3d48db1db 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierProcessor.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php @@ -4,14 +4,14 @@ namespace Drupal\sqlite\Driver\Database\sqlite; -use Drupal\Core\Database\Identifier\IdentifierProcessorBase; +use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Identifier\IdentifierType; use Drupal\Core\Database\Identifier\Table; /** - * SQLite implementation of the identifier processor. + * SQLite implementation of the identifier handler. */ -class IdentifierProcessor extends IdentifierProcessorBase { +class IdentifierHandler extends IdentifierHandlerBase { /** * {@inheritdoc} diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index b28571048cc6..119a0179cc18 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -539,7 +539,7 @@ public function testEscapeDatabase($expected, $name, array $identifier_quote = [ */ public function testIdentifierQuotesAssertCount(): void { $this->expectException(\AssertionError::class); - $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierProcessorBase::$identifierQuotes must contain 2 string values'); + $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierHandlerBase::$identifierQuotes must contain 2 string values'); $mock_pdo = $this->createMock(StubPDO::class); new StubConnection($mock_pdo, [], ['"']); } @@ -549,7 +549,7 @@ public function testIdentifierQuotesAssertCount(): void { */ public function testIdentifierQuotesAssertString(): void { $this->expectException(\AssertionError::class); - $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierProcessorBase::$identifierQuotes must contain 2 string values'); + $this->expectExceptionMessage('Drupal\\Core\\Database\\Identifier\\IdentifierHandlerBase::$identifierQuotes must contain 2 string values'); $mock_pdo = $this->createMock(StubPDO::class); new StubConnection($mock_pdo, [], [0, '1']); } diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php index 607e78fc8748..3b57f4a5f6f8 100644 --- a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php +++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php @@ -6,7 +6,6 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Database\ExceptionHandler; -use Drupal\Core\Database\Identifier\IdentifierHandler; use Drupal\Core\Database\Log; use Drupal\Core\Database\StatementWrapperIterator; use Drupal\Tests\Core\Database\Stub\Driver\Schema; @@ -45,7 +44,7 @@ public function __construct(\PDO $connection, array $connection_options, $identi parent::__construct( $connection, $connection_options, - new IdentifierHandler(new StubIdentifierProcessor($connection_options['prefix'] ?? '', $identifier_quotes)), + new StubIdentifierHandler($connection_options['prefix'] ?? '', $identifier_quotes), ); } diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierProcessor.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierHandler.php similarity index 55% rename from core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierProcessor.php rename to core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierHandler.php index c6e5564e1346..b3d4e0b66fb8 100644 --- a/core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierProcessor.php +++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubIdentifierHandler.php @@ -4,13 +4,13 @@ namespace Drupal\Tests\Core\Database\Stub; -use Drupal\Core\Database\Identifier\IdentifierProcessorBase; +use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Identifier\IdentifierType; /** - * A stub of the abstract IdentifierProcessorBase class for testing purposes. + * A stub of the abstract IdentifierHandlerBase class for testing purposes. */ -class StubIdentifierProcessor extends IdentifierProcessorBase { +class StubIdentifierHandler extends IdentifierHandlerBase { /** * {@inheritdoc} -- GitLab From 8c71efbecb1a5c39ce59be1988496e427d3b9b2b Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Mon, 10 Mar 2025 22:10:41 +0100 Subject: [PATCH 33/66] wip --- .../Identifier/IdentifierHandlerBase.php | 40 +++++++++++++++++-- .../Database/sqlite/IdentifierHandler.php | 34 ++++++++++++++-- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index 24bb723450f9..d7306c2322aa 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -34,6 +34,40 @@ public function __construct( assert(count($this->identifierQuotes) === 2 && Inspector::assertAllStrings($this->identifierQuotes), __CLASS__ . '::$identifierQuotes must contain 2 string values'); } + /** + * @todo fill in. + */ + public function database(string|Database $identifier): Database { + if ($identifier instanceof Database) { + $identifier = $identifier->identifier; + } + if ($this->hasIdentifier($identifier, IdentifierType::Database)) { + $database = $this->getIdentifier($identifier, IdentifierType::Database); + } + else { + $database = new Database($this, $identifier); + $this->setIdentifier($identifier, IdentifierType::Database, $database); + } + return $database; + } + + /** + * @todo fill in. + */ + public function schema(string|Schema $identifier): Schema { + if ($identifier instanceof Schema) { + $identifier = $identifier->identifier; + } + if ($this->hasIdentifier($identifier, IdentifierType::Schema)) { + $schema = $this->getIdentifier($identifier, IdentifierType::Schema); + } + else { + $schema = new Schema($this, $identifier); + $this->setIdentifier($identifier, IdentifierType::Schema, $schema); + } + return $schema; + } + /** * @todo fill in. */ @@ -104,12 +138,12 @@ public function parseTableIdentifier(string $identifier): array { ], 2 => [ NULL, - new Schema($this, $parts[0]), + $this->schema($parts[0]), $this->canonicalizeIdentifier($parts[1], IdentifierType::Table), ], 3 => [ - new Database($this, $parts[0]), - new Schema($this, $parts[1]), + $this->database($parts[0]), + $this->schema($parts[1]), $this->canonicalizeIdentifier($parts[2], IdentifierType::Table), ], }; diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php index 84d3d48db1db..0c15f95af274 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php @@ -27,11 +27,37 @@ public function getMaxLength(IdentifierType $type): int { /** * {@inheritdoc} */ - public function getTableMachineName(Table $table): string { - if (!isset($table->database) && !isset($table->schema) && $table->needsPrefix) { - return $this->quote(rtrim($this->tablePrefix, '.')) . '.' . $this->quote($table->canonicalName); + public function parseTableIdentifier(string $identifier): array { + $parts = explode(".", $identifier); + [$database, $schema, $table] = match (count($parts)) { + 1 => [ + NULL, + NULL, + $this->canonicalizeIdentifier($parts[0], IdentifierType::Table), + ], + 2 => [ + NULL, + $this->schema($parts[0]), + $this->canonicalizeIdentifier($parts[1], IdentifierType::Table), + ], + 3 => [ + $this->database($parts[0]), + $this->schema($parts[1]), + $this->canonicalizeIdentifier($parts[2], IdentifierType::Table), + ], + }; + if ($this->tablePrefix !== '' && count($parts) === 1) { + $database = $this->database(rtrim($this->tablePrefix, '.')); } - $ret = isset($table->schema) ? $this->quote($table->schema->canonical()) . '.' : ''; + $needsPrefix = FALSE; + return [$database, $schema, $table, $needsPrefix]; + } + + /** + * {@inheritdoc} + */ + public function getTableMachineName(Table $table): string { + $ret = isset($table->database) ? $this->quote($table->database->canonical()) . '.' : ''; $ret .= $this->quote($table->canonicalName); return $ret; } -- GitLab From 92d57bfc643589787c3a201b4297b103367c44a3 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Tue, 11 Mar 2025 19:38:23 +0100 Subject: [PATCH 34/66] wip --- core/lib/Drupal/Core/Database/Connection.php | 4 +- .../Core/Database/Identifier/Database.php | 8 +- .../Database/Identifier/IdentifierBase.php | 8 +- .../Identifier/IdentifierHandlerBase.php | 77 ++++++++++++------- .../Core/Database/Identifier/Schema.php | 8 +- .../Drupal/Core/Database/Identifier/Table.php | 30 +++----- .../lib/Drupal/Core/Database/Query/Delete.php | 2 +- .../lib/Drupal/Core/Database/Query/Insert.php | 2 +- .../lib/Drupal/Core/Database/Query/Select.php | 2 +- .../Drupal/Core/Database/Query/Truncate.php | 4 +- .../lib/Drupal/Core/Database/Query/Update.php | 2 +- .../src/Driver/Database/pgsql/Connection.php | 2 +- .../src/Driver/Database/sqlite/Connection.php | 2 +- .../Database/sqlite/IdentifierHandler.php | 38 ++------- .../src/Driver/Database/sqlite/Truncate.php | 2 +- 15 files changed, 87 insertions(+), 104 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index f7e082ac296f..5a75d28628be 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -346,7 +346,7 @@ public function prefixTables($sql) { $replacements = $tables = []; preg_match_all('/(\{(\S*)\})/', $sql, $tables, PREG_SET_ORDER, 0); foreach ($tables as $table) { - $replacements[$table[1]] = $this->identifiers->table($table[2])->machineName(); + $replacements[$table[1]] = $this->identifiers->table($table[2])->forMachine(); } return str_replace(array_keys($replacements), array_values($replacements), $sql); } @@ -385,7 +385,7 @@ public function quoteIdentifiers($sql) { * The fully qualified table name. */ public function getFullQualifiedTableName($table) { - return $this->identifiers->table($this->getConnectionOptions()['database'] . '.' . $table)->machineName(); + return $this->identifiers->table($this->getConnectionOptions()['database'] . '.' . $table)->forMachine(); } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/Database.php b/core/lib/Drupal/Core/Database/Identifier/Database.php index 309de67a4cf4..916b9998bc2b 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Database.php +++ b/core/lib/Drupal/Core/Database/Identifier/Database.php @@ -16,11 +16,9 @@ public function __construct( IdentifierHandlerBase $identifierHandler, string $identifier, ) { - parent::__construct( - $identifierHandler, - $identifier, - $identifierHandler->canonicalizeIdentifier($identifier, IdentifierType::Database), - ); + $canonicalName = $identifierHandler->canonicalize($identifier, IdentifierType::Database); + $machineName = $identifierHandler->resolveForMachine($canonicalName, IdentifierType::Database); + parent::__construct($identifier, $canonicalName, $machineName); } } diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php index 3875733d2304..8920415b3ead 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -10,9 +10,9 @@ abstract class IdentifierBase implements \Stringable { public function __construct( - protected readonly IdentifierHandlerBase $identifierHandler, public readonly string $identifier, public readonly string $canonicalName, + public readonly string $machineName, ) { } @@ -26,15 +26,15 @@ public function canonical(): string { /** * @todo fill in. */ - public function machineName(bool $quoted = TRUE): string { - return $quoted ? $this->identifierHandler->quote($this->canonicalName) : $this->canonicalName; + public function forMachine(): string { + return $this->machineName; } /** * @todo fill in. */ public function __toString(): string { - return $this->machineName(); + return $this->forMachine(); } } diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index d7306c2322aa..6fef9dd09ad3 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -5,6 +5,7 @@ namespace Drupal\Core\Database\Identifier; use Drupal\Component\Assertion\Inspector; +use Drupal\Core\Database\Exception\IdentifierException; /** * @todo fill in. @@ -121,8 +122,45 @@ public function quote(string $value): string { /** * @todo fill in. */ - public function canonicalizeIdentifier(string $identifier, IdentifierType $type): string { - return preg_replace('/[^A-Za-z0-9_]+/', '', $identifier); + public function canonicalize(string $identifier, IdentifierType $type): string { + $canonicalName = preg_replace('/[^A-Za-z0-9_]+/', '', $identifier); + $canonicalNameLength = strlen($canonicalName); + if ($canonicalNameLength > $this->getMaxLength($type) || $canonicalNameLength === 0) { + throw new IdentifierException(sprintf( + 'The length of the %s identifier \'%s\' once canonicalized to \'%s\' is invalid (maximum allowed: %d)', + $type->value, + $identifier, + $canonicalName, + $this->getMaxLength($type), + )); + } + return $canonicalName; + } + + /** + * @todo fill in. + */ + public function resolveForMachine(string $canonicalName, IdentifierType $type): string { + return match ($type) { + IdentifierType::Table => $this->resolveTableForMachine($canonicalName), + default => $this->quote($canonicalName), + }; + } + + /** + * @todo fill in. + */ + protected function resolveTableForMachine(string $canonicalName): string { + if (strlen($this->tablePrefix . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { + throw new IdentifierException(sprintf( + 'The machine length of the %s canonicalized identifier \'%s\' once table prefix \'%s\' is added is invalid (maximum allowed: %d)', + IdentifierType::Table->value, + $canonicalName, + $this->tablePrefix, + $this->getMaxLength($type), + )); + } + return $this->quote($this->tablePrefix ? $this->tablePrefix . $canonicalName : $canonicalName); } /** @@ -131,21 +169,9 @@ public function canonicalizeIdentifier(string $identifier, IdentifierType $type) public function parseTableIdentifier(string $identifier): array { $parts = explode(".", $identifier); [$database, $schema, $table] = match (count($parts)) { - 1 => [ - NULL, - NULL, - $this->canonicalizeIdentifier($parts[0], IdentifierType::Table), - ], - 2 => [ - NULL, - $this->schema($parts[0]), - $this->canonicalizeIdentifier($parts[1], IdentifierType::Table), - ], - 3 => [ - $this->database($parts[0]), - $this->schema($parts[1]), - $this->canonicalizeIdentifier($parts[2], IdentifierType::Table), - ], + 1 => [NULL, NULL, $parts[0]], + 2 => [NULL, $this->schema($parts[0]), $parts[1]], + 3 => [$this->database($parts[0]), $this->schema($parts[1]), $parts[2]], }; if ($this->tablePrefix !== '') { $needsPrefix = match (count($parts)) { @@ -156,17 +182,12 @@ public function parseTableIdentifier(string $identifier): array { else { $needsPrefix = FALSE; } - return [$database, $schema, $table, $needsPrefix]; - } - - /** - * @todo fill in. - */ - public function getTableMachineName(Table $table): string { - $ret = isset($table->database) ? $this->quote($table->database->canonical()) . '.' : ''; - $ret .= isset($table->schema) ? $this->quote($table->schema->canonical()) . '.' : ''; - $ret .= $this->quote($this->tablePrefix . $table->canonicalName); - return $ret; + return [ + 'database' => $database, + 'schema' => $schema, + 'table' => $table, + 'needs_prefix' => $needsPrefix, + ]; } } diff --git a/core/lib/Drupal/Core/Database/Identifier/Schema.php b/core/lib/Drupal/Core/Database/Identifier/Schema.php index dcd3fef19f76..0fcdc8b075d2 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Schema.php +++ b/core/lib/Drupal/Core/Database/Identifier/Schema.php @@ -16,11 +16,9 @@ public function __construct( IdentifierHandlerBase $identifierHandler, string $identifier, ) { - parent::__construct( - $identifierHandler, - $identifier, - $identifierHandler->canonicalizeIdentifier($identifier, IdentifierType::Schema), - ); + $canonicalName = $identifierHandler->canonicalize($identifier, IdentifierType::Schema); + $machineName = $identifierHandler->resolveForMachine($canonicalName, IdentifierType::Schema); + parent::__construct($identifier, $canonicalName, $machineName); } } diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index da42f6bf5b8d..5cdc1ce52b2b 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -4,8 +4,6 @@ namespace Drupal\Core\Database\Identifier; -use Drupal\Core\Database\Exception\IdentifierException; - /** * @todo fill in. */ @@ -31,20 +29,14 @@ public function __construct( string $identifier, ) { $parts = $identifierHandler->parseTableIdentifier($identifier); - parent::__construct($identifierHandler, $identifier, $parts[2]); - $this->database = $parts[0]; - $this->schema = $parts[1]; - $this->needsPrefix = $parts[3]; + $canonicalName = $identifierHandler->canonicalize($parts['table'], IdentifierType::Table); + $machineName = $identifierHandler->resolveForMachine($canonicalName, IdentifierType::Table); + parent::__construct($identifier, $canonicalName, $machineName); - if (strlen($this->needsPrefix ? $this->identifierHandler->tablePrefix : '' . $this->canonicalName) > $this->identifierHandler->getMaxLength(IdentifierType::Table)) { - throw new IdentifierException(sprintf( - 'The machine length of the %s identifier \'%s\' exceeds the maximum allowed (%d)', - IdentifierType::Table->value, - $this->canonicalName, - $this->identifierHandler->getMaxLength(IdentifierType::Table), - )); - } + $this->database = $parts['database']; + $this->schema = $parts['schema']; + $this->needsPrefix = $parts['needs_prefix']; } /** @@ -60,11 +52,11 @@ public function canonical(): string { /** * {@inheritdoc} */ - public function machineName(bool $quoted = TRUE): string { - if (!$quoted) { - return $this->canonical(); - } - return $this->identifierHandler->getTableMachineName($this); + public function forMachine(): string { + $ret = isset($this->database) ? $this->database->forMachine() . '.' : ''; + $ret .= isset($this->schema) ? $this->schema->forMachine() . '.' : ''; + $ret .= $this->machineName; + return $ret; } } diff --git a/core/lib/Drupal/Core/Database/Query/Delete.php b/core/lib/Drupal/Core/Database/Query/Delete.php index a3213c2b45eb..da1e4748adf1 100644 --- a/core/lib/Drupal/Core/Database/Query/Delete.php +++ b/core/lib/Drupal/Core/Database/Query/Delete.php @@ -70,7 +70,7 @@ public function __toString() { // Create a sanitized comment string to prepend to the query. $comments = $this->connection->makeComment($this->comments); - $query = $comments . 'DELETE FROM ' . $this->connection->identifiers->table($this->table)->machineName(); + $query = $comments . 'DELETE FROM ' . $this->connection->identifiers->table($this->table)->forMachine(); if (count($this->condition)) { diff --git a/core/lib/Drupal/Core/Database/Query/Insert.php b/core/lib/Drupal/Core/Database/Query/Insert.php index 33ff994af21f..eedb974fe74a 100644 --- a/core/lib/Drupal/Core/Database/Query/Insert.php +++ b/core/lib/Drupal/Core/Database/Query/Insert.php @@ -116,7 +116,7 @@ public function __toString() { $insert_fields = array_merge($this->defaultFields, $this->insertFields); if (!empty($this->fromQuery)) { - return $comments . 'INSERT INTO ' . $this->connection->identifiers->table($this->table)->machineName() . ' (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; + return $comments . 'INSERT INTO ' . $this->connection->identifiers->table($this->table)->forMachine() . ' (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; } // For simplicity, we will use the $placeholders array to inject diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php index 61f4bcba476f..2e3f0f49d5fd 100644 --- a/core/lib/Drupal/Core/Database/Query/Select.php +++ b/core/lib/Drupal/Core/Database/Query/Select.php @@ -854,7 +854,7 @@ public function __toString() { $table_string = '(' . (string) $subquery . ')'; } else { - $table_string = $this->connection->identifiers->table($table['table'])->machineName(); + $table_string = $this->connection->identifiers->table($table['table'])->forMachine(); } // Don't use the AS keyword for table aliases, as some diff --git a/core/lib/Drupal/Core/Database/Query/Truncate.php b/core/lib/Drupal/Core/Database/Query/Truncate.php index 5f491cb5aaba..044340cc33d8 100644 --- a/core/lib/Drupal/Core/Database/Query/Truncate.php +++ b/core/lib/Drupal/Core/Database/Query/Truncate.php @@ -75,10 +75,10 @@ public function __toString() { // The statement actually built depends on whether a transaction is active. // @see ::execute() if ($this->connection->inTransaction()) { - return $comments . 'DELETE FROM ' . $this->connection->identifiers->table($this->table)->machineName(); + return $comments . 'DELETE FROM ' . $this->connection->identifiers->table($this->table)->forMachine(); } else { - return $comments . 'TRUNCATE ' . $this->connection->identifiers->table($this->table)->machineName(); + return $comments . 'TRUNCATE ' . $this->connection->identifiers->table($this->table)->forMachine(); } } diff --git a/core/lib/Drupal/Core/Database/Query/Update.php b/core/lib/Drupal/Core/Database/Query/Update.php index d2080a0c2d5b..82e33988b702 100644 --- a/core/lib/Drupal/Core/Database/Query/Update.php +++ b/core/lib/Drupal/Core/Database/Query/Update.php @@ -166,7 +166,7 @@ public function __toString() { $update_fields[] = $this->connection->escapeField($field) . '=' . $placeholders[$max_placeholder++]; } - $query = $comments . 'UPDATE ' . $this->connection->identifiers->table($this->table)->machineName() . ' SET ' . implode(', ', $update_fields); + $query = $comments . 'UPDATE ' . $this->connection->identifiers->table($this->table)->forMachine() . ' SET ' . implode(', ', $update_fields); if (count($this->condition)) { $this->condition->compile($this->connection, $this); diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index b04ab6393647..84d2651917db 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -341,7 +341,7 @@ public function getFullQualifiedTableName($table) { // The fully qualified table name in PostgreSQL is in the form of // <database>.<schema>.<table>. - return $options['database'] . '.' . $schema . '.' . $this->identifiers->table($table)->machineName(quoted: FALSE); + return $options['database'] . '.' . $schema . '.' . $this->identifiers->table($table)->forMachine(); } /** diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 123ebe2fd63f..2d293cc7f037 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -431,7 +431,7 @@ public function prepareStatement(string $query, array $options, bool $allow_row_ */ public function getFullQualifiedTableName($table) { // Don't include the SQLite database file name as part of the table name. - return $this->identifiers->table($table)->machineName(); + return $this->identifiers->table($table)->forMachine(); } /** diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php index 0c15f95af274..83bcae15de99 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php @@ -21,45 +21,19 @@ public function getMaxLength(IdentifierType $type): int { // common sense. // @see https://www.sqlite.org/limits.html // @see https://stackoverflow.com/questions/8135013/table-name-limit-in-sqlite-android - return 128; + return 256; } /** * {@inheritdoc} */ public function parseTableIdentifier(string $identifier): array { - $parts = explode(".", $identifier); - [$database, $schema, $table] = match (count($parts)) { - 1 => [ - NULL, - NULL, - $this->canonicalizeIdentifier($parts[0], IdentifierType::Table), - ], - 2 => [ - NULL, - $this->schema($parts[0]), - $this->canonicalizeIdentifier($parts[1], IdentifierType::Table), - ], - 3 => [ - $this->database($parts[0]), - $this->schema($parts[1]), - $this->canonicalizeIdentifier($parts[2], IdentifierType::Table), - ], - }; - if ($this->tablePrefix !== '' && count($parts) === 1) { - $database = $this->database(rtrim($this->tablePrefix, '.')); + $parts = parent::parseTableIdentifier($identifier); + if ($this->tablePrefix !== '' && $parts['database'] === NULL && $parts['schema'] === NULL) { + $parts['schema'] = $this->schema(rtrim($this->tablePrefix, '.')); + $parts['needs_prefix'] = FALSE; } - $needsPrefix = FALSE; - return [$database, $schema, $table, $needsPrefix]; - } - - /** - * {@inheritdoc} - */ - public function getTableMachineName(Table $table): string { - $ret = isset($table->database) ? $this->quote($table->database->canonical()) . '.' : ''; - $ret .= $this->quote($table->canonicalName); - return $ret; + return $parts; } } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Truncate.php b/core/modules/sqlite/src/Driver/Database/sqlite/Truncate.php index e5222023eb23..192b6bcbfdcb 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Truncate.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Truncate.php @@ -19,7 +19,7 @@ public function __toString() { // Create a sanitized comment string to prepend to the query. $comments = $this->connection->makeComment($this->comments); - return $comments . 'DELETE FROM ' . $this->connection->identifiers->table($this->table)->machineName(); + return $comments . 'DELETE FROM ' . $this->connection->identifiers->table($this->table)->forMachine(); } } -- GitLab From a47ef1ca9a7dfd8e11a3269744162421f9987a77 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Tue, 11 Mar 2025 19:57:17 +0100 Subject: [PATCH 35/66] wip --- .../src/Driver/Database/sqlite/IdentifierHandler.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php index 83bcae15de99..43864ea2b514 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php @@ -36,4 +36,11 @@ public function parseTableIdentifier(string $identifier): array { return $parts; } + /** + * {@inheritdoc} + */ + protected function resolveTableForMachine(string $canonicalName): string { + return $this->quote($canonicalName); + } + } -- GitLab From c6bf90285d0ed6e2afbeb93bef049af98ab1bcb5 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Tue, 11 Mar 2025 20:30:04 +0100 Subject: [PATCH 36/66] fixes --- .../Drupal/Core/Database/Identifier/IdentifierHandlerBase.php | 2 +- .../sqlite/src/Driver/Database/sqlite/IdentifierHandler.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index 6fef9dd09ad3..1874e7cf027f 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -157,7 +157,7 @@ protected function resolveTableForMachine(string $canonicalName): string { IdentifierType::Table->value, $canonicalName, $this->tablePrefix, - $this->getMaxLength($type), + $this->getMaxLength($IdentifierType::Table), )); } return $this->quote($this->tablePrefix ? $this->tablePrefix . $canonicalName : $canonicalName); diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php index 43864ea2b514..1c47fb10dc3f 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php @@ -6,7 +6,6 @@ use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Identifier\IdentifierType; -use Drupal\Core\Database\Identifier\Table; /** * SQLite implementation of the identifier handler. -- GitLab From 7b2d969bd9d88e93da2fc55570746bef795356aa Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Tue, 11 Mar 2025 20:57:20 +0100 Subject: [PATCH 37/66] fixes --- .../Identifier/IdentifierHandlerBase.php | 2 +- .../src/Driver/Database/sqlite/Schema.php | 27 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index 1874e7cf027f..c2c6653c649d 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -157,7 +157,7 @@ protected function resolveTableForMachine(string $canonicalName): string { IdentifierType::Table->value, $canonicalName, $this->tablePrefix, - $this->getMaxLength($IdentifierType::Table), + $this->getMaxLength(IdentifierType::Table), )); } return $this->quote($this->tablePrefix ? $this->tablePrefix . $canonicalName : $canonicalName); diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php index 3779e7625195..071e53be2e06 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php @@ -52,7 +52,7 @@ public function createTableSql($name, $table) { } $sql = []; - $sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumnsSql($name, $table) . "\n)\n"; + $sql[] = "CREATE TABLE " . $this->connection->identifiers->table($name)->forMachine() . " (\n" . $this->createColumnsSql($name, $table) . "\n)\n"; return array_merge($sql, $this->createIndexSql($name, $table)); } @@ -60,16 +60,21 @@ public function createTableSql($name, $table) { * Build the SQL expression for indexes. */ protected function createIndexSql($tablename, $schema) { + // In SQLite, the 'CREATE [UNIQUE] INDEX' DDL statements requires that the + // table name be NOT prefixed by the schema name. We cannot use the + // Table::forMachine() method but should rather pick Table->machineName + // directly. + // @see https://www.sqlite.org/syntax/create-index-stmt.html $sql = []; $info = $this->getPrefixInfo($tablename); if (!empty($schema['unique keys'])) { foreach ($schema['unique keys'] as $key => $fields) { - $sql[] = 'CREATE UNIQUE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON [' . $info['table'] . '] (' . $this->createKeySql($fields) . ")\n"; + $sql[] = 'CREATE UNIQUE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON ' . $this->connection->identifiers->table($info['table'])->machineName . ' (' . $this->createKeySql($fields) . ")\n"; } } if (!empty($schema['indexes'])) { foreach ($schema['indexes'] as $key => $fields) { - $sql[] = 'CREATE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON [' . $info['table'] . '] (' . $this->createKeySql($fields) . ")\n"; + $sql[] = 'CREATE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON ' . $this->connection->identifiers->table($info['table'])->machineName . ' (' . $this->createKeySql($fields) . ")\n"; } } return $sql; @@ -266,13 +271,13 @@ public function renameTable($table, $new_name) { $schema = $this->introspectSchema($table); - // SQLite doesn't allow you to rename tables outside of the current - // database. So the syntax '... RENAME TO database.table' would fail. - // So we must determine the full table name here rather than surrounding - // the table with curly braces in case the db_prefix contains a reference - // to a database outside of our existing database. + // SQLite doesn't allow you to rename tables outside of the current schema, + // so the syntax '... RENAME TO schema.table' would fail. We cannot use the + // Table::forMachine() method but should rather pick Table->machineName + // directly. + // @see https://www.sqlite.org/syntax/alter-table-stmt.html $info = $this->getPrefixInfo($new_name); - $this->executeDdlStatement('ALTER TABLE {' . $table . '} RENAME TO [' . $info['table'] . ']'); + $this->executeDdlStatement('ALTER TABLE ' . $this->connection->identifiers->table($table)->forMachine() . ' RENAME TO ' . $this->connection->identifiers->table($info['table'])->machineName); // Drop the indexes, there is no RENAME INDEX command in SQLite. if (!empty($schema['unique keys'])) { @@ -301,7 +306,7 @@ public function dropTable($table) { return FALSE; } $this->connection->tableDropped = TRUE; - $this->executeDdlStatement('DROP TABLE {' . $table . '}'); + $this->executeDdlStatement('DROP TABLE ' . $this->connection->identifiers->table($table)->forMachine()); return TRUE; } @@ -325,7 +330,7 @@ public function addField($table, $field, $specification, $keys_new = []) { if (empty($keys_new) && (empty($specification['not null']) || isset($specification['default']))) { // When we don't have to create new keys and we are not creating a NOT // NULL column without a default value, we can use the quicker version. - $query = 'ALTER TABLE {' . $table . '} ADD ' . $this->createFieldSql($field, $this->processField($specification)); + $query = 'ALTER TABLE ' . $this->connection->identifiers->table($table)->forMachine() . ' ADD ' . $this->createFieldSql($field, $this->processField($specification)); $this->executeDdlStatement($query); // Apply the initial value if set. -- GitLab From 593e2097fa099e308754630caeb74be2b068b4dc Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Tue, 11 Mar 2025 21:24:05 +0100 Subject: [PATCH 38/66] deprecate Connection::escapeDatabase --- core/lib/Drupal/Core/Database/Connection.php | 5 ++--- .../Core/Database/Identifier/IdentifierHandlerBase.php | 4 ++++ core/lib/Drupal/Core/Database/Query/Insert.php | 2 +- core/tests/Drupal/Tests/Core/Database/ConnectionTest.php | 2 ++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 5a75d28628be..72b6658ff799 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -969,9 +969,8 @@ public function condition($conjunction) { * The sanitized database name. */ public function escapeDatabase($database) { - $database = preg_replace('/[^A-Za-z0-9_]+/', '', $database); - [$start_quote, $end_quote] = $this->identifiers->identifierQuotes; - return $start_quote . $database . $end_quote; + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); + return $this->identifiers->database($database)->forMachine(); } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index c2c6653c649d..9f943236dcc7 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -121,6 +121,10 @@ public function quote(string $value): string { /** * @todo fill in. + * + * Standard SQL identifiers designate basic Latin letters, digits 0-9, + * dollar and underscore as valid characters. Drupal is stricter in the + * sense that the dollar character is not allowed. */ public function canonicalize(string $identifier, IdentifierType $type): string { $canonicalName = preg_replace('/[^A-Za-z0-9_]+/', '', $identifier); diff --git a/core/lib/Drupal/Core/Database/Query/Insert.php b/core/lib/Drupal/Core/Database/Query/Insert.php index eedb974fe74a..60bccaea343c 100644 --- a/core/lib/Drupal/Core/Database/Query/Insert.php +++ b/core/lib/Drupal/Core/Database/Query/Insert.php @@ -126,7 +126,7 @@ public function __toString() { $placeholders = array_pad($placeholders, count($this->defaultFields), 'default'); $placeholders = array_pad($placeholders, count($this->insertFields), '?'); - return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES (' . implode(', ', $placeholders) . ')'; + return $comments . 'INSERT INTO ' . $this->connection->identifiers->table($this->table)->forMachine() . ' (' . implode(', ', $insert_fields) . ') VALUES (' . implode(', ', $placeholders) . ')'; } /** diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index 119a0179cc18..1d43896a1c40 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -526,8 +526,10 @@ public static function providerEscapeDatabase() { /** * @covers ::escapeDatabase * @dataProvider providerEscapeDatabase + * @group legacy */ public function testEscapeDatabase($expected, $name, array $identifier_quote = ['"', '"']): void { + $this->expectDeprecation('Drupal\Core\Database\Connection::escapeDatabase() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312'); $mock_pdo = $this->createMock(StubPDO::class); $connection = new StubConnection($mock_pdo, [], $identifier_quote); -- GitLab From a77fd171399bb72f66a2b53a1eda7970a246bac9 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Wed, 12 Mar 2025 13:55:35 +0100 Subject: [PATCH 39/66] fixes --- core/lib/Drupal/Core/Database/Connection.php | 6 +++++- core/lib/Drupal/Core/Database/Identifier/Database.php | 2 +- .../Core/Database/Identifier/IdentifierHandlerBase.php | 8 ++++---- core/lib/Drupal/Core/Database/Identifier/Schema.php | 2 +- core/lib/Drupal/Core/Database/Identifier/Table.php | 2 +- .../src/Driver/Database/sqlite/IdentifierHandler.php | 2 +- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 72b6658ff799..895f1f81f3a6 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -385,7 +385,11 @@ public function quoteIdentifiers($sql) { * The fully qualified table name. */ public function getFullQualifiedTableName($table) { - return $this->identifiers->table($this->getConnectionOptions()['database'] . '.' . $table)->forMachine(); + $tableIdentifier = $this->identifiers->table($table); + if ($tableIdentifier->database || $tableIdentifier->schema) { + return $tableIdentifier->forMachine(); + } + return $this->identifiers->schema($this->getConnectionOptions()['database'])->forMachine() . '.' . $tableIdentifier->machineName; } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/Database.php b/core/lib/Drupal/Core/Database/Identifier/Database.php index 916b9998bc2b..e8b68d1ab61a 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Database.php +++ b/core/lib/Drupal/Core/Database/Identifier/Database.php @@ -17,7 +17,7 @@ public function __construct( string $identifier, ) { $canonicalName = $identifierHandler->canonicalize($identifier, IdentifierType::Database); - $machineName = $identifierHandler->resolveForMachine($canonicalName, IdentifierType::Database); + $machineName = $identifierHandler->resolveForMachine($canonicalName, [], IdentifierType::Database); parent::__construct($identifier, $canonicalName, $machineName); } diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index 9f943236dcc7..e46a99e5dcc8 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -144,9 +144,9 @@ public function canonicalize(string $identifier, IdentifierType $type): string { /** * @todo fill in. */ - public function resolveForMachine(string $canonicalName, IdentifierType $type): string { + public function resolveForMachine(string $canonicalName, array $info, IdentifierType $type): string { return match ($type) { - IdentifierType::Table => $this->resolveTableForMachine($canonicalName), + IdentifierType::Table => $this->resolveTableForMachine($canonicalName, $info), default => $this->quote($canonicalName), }; } @@ -154,7 +154,7 @@ public function resolveForMachine(string $canonicalName, IdentifierType $type): /** * @todo fill in. */ - protected function resolveTableForMachine(string $canonicalName): string { + protected function resolveTableForMachine(string $canonicalName, array $info): string { if (strlen($this->tablePrefix . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { throw new IdentifierException(sprintf( 'The machine length of the %s canonicalized identifier \'%s\' once table prefix \'%s\' is added is invalid (maximum allowed: %d)', @@ -164,7 +164,7 @@ protected function resolveTableForMachine(string $canonicalName): string { $this->getMaxLength(IdentifierType::Table), )); } - return $this->quote($this->tablePrefix ? $this->tablePrefix . $canonicalName : $canonicalName); + return $this->quote($info['needs_prefix'] ? $this->tablePrefix . $canonicalName : $canonicalName); } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/Schema.php b/core/lib/Drupal/Core/Database/Identifier/Schema.php index 0fcdc8b075d2..08c69aa309b3 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Schema.php +++ b/core/lib/Drupal/Core/Database/Identifier/Schema.php @@ -17,7 +17,7 @@ public function __construct( string $identifier, ) { $canonicalName = $identifierHandler->canonicalize($identifier, IdentifierType::Schema); - $machineName = $identifierHandler->resolveForMachine($canonicalName, IdentifierType::Schema); + $machineName = $identifierHandler->resolveForMachine($canonicalName, [], IdentifierType::Schema); parent::__construct($identifier, $canonicalName, $machineName); } diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 5cdc1ce52b2b..7164e6e668b0 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -31,7 +31,7 @@ public function __construct( $parts = $identifierHandler->parseTableIdentifier($identifier); $canonicalName = $identifierHandler->canonicalize($parts['table'], IdentifierType::Table); - $machineName = $identifierHandler->resolveForMachine($canonicalName, IdentifierType::Table); + $machineName = $identifierHandler->resolveForMachine($canonicalName, $parts, IdentifierType::Table); parent::__construct($identifier, $canonicalName, $machineName); $this->database = $parts['database']; diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php index 1c47fb10dc3f..abcdf2008ca5 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php @@ -38,7 +38,7 @@ public function parseTableIdentifier(string $identifier): array { /** * {@inheritdoc} */ - protected function resolveTableForMachine(string $canonicalName): string { + protected function resolveTableForMachine(string $canonicalName, array $info): string { return $this->quote($canonicalName); } -- GitLab From 1bb63ec97738e3b6d9341439bdcf076ce40fdfb8 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Wed, 12 Mar 2025 14:26:28 +0100 Subject: [PATCH 40/66] cs --- core/lib/Drupal/Core/Database/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 895f1f81f3a6..78dd16eba412 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -389,7 +389,7 @@ public function getFullQualifiedTableName($table) { if ($tableIdentifier->database || $tableIdentifier->schema) { return $tableIdentifier->forMachine(); } - return $this->identifiers->schema($this->getConnectionOptions()['database'])->forMachine() . '.' . $tableIdentifier->machineName; + return $this->identifiers->schema($this->getConnectionOptions()['database'])->forMachine() . '.' . $tableIdentifier->machineName; } /** -- GitLab From b31b0590b96bb8d44f8d26bf623f215317262d11 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Wed, 12 Mar 2025 15:10:23 +0100 Subject: [PATCH 41/66] fixes --- .../Drupal/Core/Database/Identifier/IdentifierHandlerBase.php | 2 +- .../Migrate/d7/MigrateLanguageContentCommentSettingsTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index e46a99e5dcc8..2fb4c57e095d 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -155,7 +155,7 @@ public function resolveForMachine(string $canonicalName, array $info, Identifier * @todo fill in. */ protected function resolveTableForMachine(string $canonicalName, array $info): string { - if (strlen($this->tablePrefix . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { + if (strlen($info['needs_prefix'] ? $this->tablePrefix : '' . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { throw new IdentifierException(sprintf( 'The machine length of the %s canonicalized identifier \'%s\' once table prefix \'%s\' is added is invalid (maximum allowed: %d)', IdentifierType::Table->value, diff --git a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php index 16923949a2e1..8f66d9bda709 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php @@ -43,7 +43,7 @@ protected function setUp(): void { */ public function testLanguageCommentSettings(): void { // Confirm there is no message about a missing bundle. - $this->assertEmpty($this->migrateMessages, $this->migrateMessages['error'][0] ?? ''); + $this->assertEmpty($this->migrateMessages, (string) $this->migrateMessages['error'][0] ?? ''); // Article and Blog content type have multilingual settings of 'Enabled, // with Translation'. Assert that comments are translatable and the default -- GitLab From d5a2aa67e923e97524941b7cf6e9917adac4f362 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Wed, 12 Mar 2025 15:55:12 +0100 Subject: [PATCH 42/66] fixes --- core/lib/Drupal/Core/Database/Connection.php | 3 --- .../d7/MigrateLanguageContentCommentSettingsTest.php | 2 +- core/modules/mysql/src/Driver/Database/mysql/Connection.php | 4 ++++ .../sqlite/src/Driver/Database/sqlite/Connection.php | 3 --- .../system/tests/src/Kernel/Scripts/DbCommandBaseTest.php | 4 ++-- core/modules/views_ui/tests/src/Functional/PreviewTest.php | 2 +- .../src/FunctionalJavascript/StandardPerformanceTest.php | 6 +++--- 7 files changed, 11 insertions(+), 13 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 78dd16eba412..d118e170671a 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -152,9 +152,6 @@ public function __construct( // Manage the table prefix. $connection_options['prefix'] = $connection_options['prefix'] ?? ''; assert(is_string($connection_options['prefix']), 'The \'prefix\' connection option to ' . __METHOD__ . '() must be a string.'); - if (is_array($connection_options['prefix'])) { - $connection_options['prefix'] = $connection_options['prefix'][0] ?? ''; - } // Work out the database driver namespace if none is provided. This normally // written to setting.php by installer or set by diff --git a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php index 8f66d9bda709..2a2ab96cd682 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php @@ -43,7 +43,7 @@ protected function setUp(): void { */ public function testLanguageCommentSettings(): void { // Confirm there is no message about a missing bundle. - $this->assertEmpty($this->migrateMessages, (string) $this->migrateMessages['error'][0] ?? ''); + $this->assertEmpty($this->migrateMessages, isset($this->migrateMessages['error'][0]) ? (string) $this->migrateMessages['error'][0] : ''); // Article and Blog content type have multilingual settings of 'Enabled, // with Translation'. Assert that comments are translatable and the default diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php index defef3b8568d..6da0b1db7d35 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php @@ -83,6 +83,10 @@ public function __construct(\PDO $connection, array $connection_options) { } } + // Manage the table prefix. + $connection_options['prefix'] = $connection_options['prefix'] ?? ''; + assert(is_string($connection_options['prefix']), 'The \'prefix\' connection option to ' . __METHOD__ . '() must be a string.'); + parent::__construct( $connection, $connection_options, diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 2d293cc7f037..44114a77e0f1 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -74,9 +74,6 @@ public function __construct(\PDO $connection, array $connection_options) { // Empty prefix means query the main database -- no need to attach anything. $prefix = $connection_options['prefix'] ?? ''; assert(is_string($prefix), 'The \'prefix\' connection option to ' . __METHOD__ . '() must be a string.'); - if (is_array($prefix)) { - $prefix = $prefix[0] ?? ''; - } if ($prefix !== '') { $attachedDatabaseName = $prefix; // Add a ., so queries become prefix.table, which is proper syntax for diff --git a/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php b/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php index 8856055b8ce5..07a841f9f4d1 100644 --- a/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php +++ b/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php @@ -84,13 +84,13 @@ public function testPrefix(): void { '--database' => 'magic_db', '--prefix' => 'extra', ]); - $this->assertEquals('extra', $command->getDatabaseConnection($command_tester->getInput())->getPrefix()); + $this->assertEquals('extra', $command->getDatabaseConnection($command_tester->getInput())->identifiers->tablePrefix); $command_tester->execute([ '-db-url' => Database::getConnectionInfoAsUrl(), '--prefix' => 'extra2', ]); - $this->assertEquals('extra2', $command->getDatabaseConnection($command_tester->getInput())->getPrefix()); + $this->assertEquals('extra2', $command->getDatabaseConnection($command_tester->getInput())->identifiers->tablePrefix); // This breaks test cleanup. // @code diff --git a/core/modules/views_ui/tests/src/Functional/PreviewTest.php b/core/modules/views_ui/tests/src/Functional/PreviewTest.php index c70a90467705..ae2e8cdbe14d 100644 --- a/core/modules/views_ui/tests/src/Functional/PreviewTest.php +++ b/core/modules/views_ui/tests/src/Functional/PreviewTest.php @@ -123,7 +123,7 @@ public function testPreviewUI(): void { $this->assertSession()->pageTextContains('Query execute time'); $this->assertSession()->pageTextContains('View render time'); $this->assertSession()->responseContains('<strong>Query</strong>'); - $query_string = '/SELECT "views_test_data"\."name" AS "views_test_data_name" FROM .*\."views_test_data" "views_test_data" WHERE \(views_test_data\.id = \'100\'\)/'; + $query_string = '/SELECT "views_test_data"\."name" AS "views_test_data_name" FROM .*views_test_data" "views_test_data" WHERE \(views_test_data\.id = \'100\'\)/'; $this->assertSession()->pageTextMatches($query_string); // Test that the statistics and query are rendered above the preview. diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php index b9efe2ff5b10..f2dfa66bb073 100644 --- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php +++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php @@ -194,9 +194,9 @@ protected function testAnonymous(): void { 'SELECT "menu_tree"."menu_name" AS "menu_name", "menu_tree"."route_name" AS "route_name", "menu_tree"."route_parameters" AS "route_parameters", "menu_tree"."url" AS "url", "menu_tree"."title" AS "title", "menu_tree"."description" AS "description", "menu_tree"."parent" AS "parent", "menu_tree"."weight" AS "weight", "menu_tree"."options" AS "options", "menu_tree"."expanded" AS "expanded", "menu_tree"."enabled" AS "enabled", "menu_tree"."provider" AS "provider", "menu_tree"."metadata" AS "metadata", "menu_tree"."class" AS "class", "menu_tree"."form_class" AS "form_class", "menu_tree"."id" AS "id" FROM "menu_tree" "menu_tree" WHERE ("route_name" = "entity.node.canonical") AND ("route_param_key" = "node=1") AND ("menu_name" = "main") ORDER BY "depth" ASC, "weight" ASC, "id" ASC', 'SELECT "menu_tree"."menu_name" AS "menu_name", "menu_tree"."route_name" AS "route_name", "menu_tree"."route_parameters" AS "route_parameters", "menu_tree"."url" AS "url", "menu_tree"."title" AS "title", "menu_tree"."description" AS "description", "menu_tree"."parent" AS "parent", "menu_tree"."weight" AS "weight", "menu_tree"."options" AS "options", "menu_tree"."expanded" AS "expanded", "menu_tree"."enabled" AS "enabled", "menu_tree"."provider" AS "provider", "menu_tree"."metadata" AS "metadata", "menu_tree"."class" AS "class", "menu_tree"."form_class" AS "form_class", "menu_tree"."id" AS "id" FROM "menu_tree" "menu_tree" WHERE ("route_name" = "entity.node.canonical") AND ("route_param_key" = "node=1") AND ("menu_name" = "account") ORDER BY "depth" ASC, "weight" ASC, "id" ASC', 'INSERT INTO "semaphore" ("name", "value", "expire") VALUES ("theme_registry:runtime:stark:Drupal\Core\Utility\ThemeRegistry", "LOCK_ID", "EXPIRE")', - 'DELETE FROM "semaphore" WHERE ("name" = "theme_registry:runtime:stark:Drupal\Core\Utility\ThemeRegistry") AND ("value" = "LOCK_ID")', + 'DELETE FROM "semaphore" WHERE ("name" = "theme_registry:runtime:stark:Drupal\Core\Utility\ThemeRegistry") AND ("value" = "LOCK_ID")', 'INSERT INTO "semaphore" ("name", "value", "expire") VALUES ("active-trail:route:entity.node.canonical:route_parameters:a:1:{s:4:"node";s:1:"1";}:Drupal\Core\Cache\CacheCollector", "LOCK_ID", "EXPIRE")', - 'DELETE FROM "semaphore" WHERE ("name" = "active-trail:route:entity.node.canonical:route_parameters:a:1:{s:4:"node";s:1:"1";}:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', + 'DELETE FROM "semaphore" WHERE ("name" = "active-trail:route:entity.node.canonical:route_parameters:a:1:{s:4:"node";s:1:"1";}:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', ]; $recorded_queries = $performance_data->getQueries(); $this->assertSame($expected_queries, $recorded_queries); @@ -253,7 +253,7 @@ protected function testAnonymous(): void { 'SELECT "ud".* FROM "users_data" "ud" WHERE ("module" = "contact") AND ("uid" = "2") AND ("name" = "enabled")', 'SELECT "name", "data" FROM "config" WHERE "collection" = "" AND "name" IN ( "contact.settings" )', 'INSERT INTO "semaphore" ("name", "value", "expire") VALUES ("active-trail:route:entity.user.canonical:route_parameters:a:1:{s:4:"user";s:1:"2";}:Drupal\Core\Cache\CacheCollector", "LOCK_ID", "EXPIRE")', - 'DELETE FROM "semaphore" WHERE ("name" = "active-trail:route:entity.user.canonical:route_parameters:a:1:{s:4:"user";s:1:"2";}:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', + 'DELETE FROM "semaphore" WHERE ("name" = "active-trail:route:entity.user.canonical:route_parameters:a:1:{s:4:"user";s:1:"2";}:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', ]; $recorded_queries = $performance_data->getQueries(); $this->assertSame($expected_queries, $recorded_queries); -- GitLab From 8e660d37d6c27320e6e84f982039d8532ab2fc41 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Wed, 12 Mar 2025 21:36:21 +0100 Subject: [PATCH 43/66] wip --- .../Core/Database/Identifier/Database.php | 4 +- .../Database/Identifier/IdentifierBase.php | 17 +++-- .../Identifier/IdentifierHandlerBase.php | 54 ++++++++++++-- .../Database/Identifier/IdentifierType.php | 5 +- .../Core/Database/Identifier/Schema.php | 4 +- .../Drupal/Core/Database/Identifier/Table.php | 11 +-- .../Database/mysql/IdentifierHandler.php | 25 +++++++ .../src/Kernel/mysql/MysqlDriverTest.php | 2 +- .../mysql/tests/src/Unit/IdentifierTest.php | 72 +++++++++++++++++++ .../Database/sqlite/IdentifierHandler.php | 5 +- 10 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 core/modules/mysql/tests/src/Unit/IdentifierTest.php diff --git a/core/lib/Drupal/Core/Database/Identifier/Database.php b/core/lib/Drupal/Core/Database/Identifier/Database.php index e8b68d1ab61a..f1ba4fbd5675 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Database.php +++ b/core/lib/Drupal/Core/Database/Identifier/Database.php @@ -7,8 +7,8 @@ /** * Handles a database identifier. * - * In full namespaced tables, the identifier is defined as - * <database>.<schema>.<table>. + * When using full notation, a table can be identified as + * [database.][schema.]table. */ class Database extends IdentifierBase { diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php index 8920415b3ead..29d79565860d 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -5,7 +5,7 @@ namespace Drupal\Core\Database\Identifier; /** - * @todo fill in. + * The base class for database identifier value objects. */ abstract class IdentifierBase implements \Stringable { @@ -17,21 +17,30 @@ public function __construct( } /** - * @todo fill in. + * Returns the canonical name of the identifier. + * + * @return string + * The canonical name of the identifier. */ public function canonical(): string { return $this->canonicalName; } /** - * @todo fill in. + * Returns the identifier in a format suitable for including in SQL queries. + * + * @return string + * The identifier in a format suitable for including in SQL queries. */ public function forMachine(): string { return $this->machineName; } /** - * @todo fill in. + * Returns the identifier in a format suitable for including in SQL queries. + * + * @return string + * The identifier in a format suitable for including in SQL queries. */ public function __toString(): string { return $this->forMachine(); diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index 2fb4c57e095d..44f3e0ce899e 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -8,11 +8,16 @@ use Drupal\Core\Database\Exception\IdentifierException; /** - * @todo fill in. + * Base class to handle identifier value objects. + * + * Database drivers should extend this class to implement db-specific + * limitations and behaviors. */ abstract class IdentifierHandlerBase { /** + * A cache of all identifiers handled. + * * @var array{'identifier':array<string,array<string,string>>,'machine':array<string,array<string,string>>} */ protected array $identifiers; @@ -108,7 +113,10 @@ protected function getIdentifier(string $id, IdentifierType $type): IdentifierBa } /** - * @todo fill in. + * Returns the maximum length, in bytes, of an identifier type. + * + * @return positive-int + * The maximum length, in bytes, of an identifier type. */ abstract public function getMaxLength(IdentifierType $type): int; @@ -120,14 +128,48 @@ public function quote(string $value): string { } /** - * @todo fill in. + * Returns a canonicalized and validated identifier string. * * Standard SQL identifiers designate basic Latin letters, digits 0-9, * dollar and underscore as valid characters. Drupal is stricter in the * sense that the dollar character is not allowed. + * + * @param string $identifier + * A raw identifier string. Can include quote characters and any character + * in general. + * @param \Drupal\Core\Database\Identifier\IdentifierType $type + * The type of identifier. + * + * @return string + * A canonicalized and validated identifier string. + * + * @throws \Drupal\Core\Database\Exception\IdentifierException + * If the identifier is invalid. */ public function canonicalize(string $identifier, IdentifierType $type): string { $canonicalName = preg_replace('/[^A-Za-z0-9_]+/', '', $identifier); + $this->validateCanonicalName($identifier, $canonicalName, $type); + return $canonicalName; + } + + /** + * Validates a canonicalized identifier string. + * + * @param string $identifier + * A raw identifier string. Can include quote characters and any character + * in general. + * @param string $canonicalName + * A canonical identifier string. + * @param \Drupal\Core\Database\Identifier\IdentifierType $type + * The type of identifier. + * + * @return true + * Upon successful validation. + * + * @throws \Drupal\Core\Database\Exception\IdentifierException + * If the identifier is invalid. + */ + protected function validateCanonicalName(string $identifier, string $canonicalName, IdentifierType $type): TRUE { $canonicalNameLength = strlen($canonicalName); if ($canonicalNameLength > $this->getMaxLength($type) || $canonicalNameLength === 0) { throw new IdentifierException(sprintf( @@ -138,7 +180,7 @@ public function canonicalize(string $identifier, IdentifierType $type): string { $this->getMaxLength($type), )); } - return $canonicalName; + return TRUE; } /** @@ -176,6 +218,10 @@ public function parseTableIdentifier(string $identifier): array { 1 => [NULL, NULL, $parts[0]], 2 => [NULL, $this->schema($parts[0]), $parts[1]], 3 => [$this->database($parts[0]), $this->schema($parts[1]), $parts[2]], + default => throw new IdentifierException(sprintf( + 'The table identifier \'%s\' does not comply with the syntax [database.][schema.]table', + $identifier, + )), }; if ($this->tablePrefix !== '') { $needsPrefix = match (count($parts)) { diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php index d6605218ce3c..52dafe0a4f48 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php @@ -5,10 +5,9 @@ namespace Drupal\Core\Database\Identifier; /** - * Enum for database identifier types. + * Enumeration of database identifier types. */ enum IdentifierType: string { - case Generic = 'unknown'; case Database = 'database'; case Schema = 'schema'; case Sequence = 'sequence'; @@ -17,4 +16,6 @@ enum IdentifierType: string { case Index = 'index'; case Alias = 'alias'; + + case Unknown = 'unknown'; } diff --git a/core/lib/Drupal/Core/Database/Identifier/Schema.php b/core/lib/Drupal/Core/Database/Identifier/Schema.php index 08c69aa309b3..b2c5af9147dd 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Schema.php +++ b/core/lib/Drupal/Core/Database/Identifier/Schema.php @@ -7,8 +7,8 @@ /** * Handles a schema identifier. * - * In full namespaced tables, the identifier is defined as - * <database>.<schema>.<table>. + * When using full notation, a table can be identified as + * [database.][schema.]table. */ class Schema extends IdentifierBase { diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 7164e6e668b0..8f6cbef361b2 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -5,22 +5,25 @@ namespace Drupal\Core\Database\Identifier; /** - * @todo fill in. + * Handles a table identifier. + * + * When using full notation, a table can be identified as + * [database.][schema.]table. */ class Table extends IdentifierBase { /** - * @todo fill in. + * The database identifier, if specified. */ public readonly ?Database $database; /** - * @todo fill in. + * The schema identifier, if specified. */ public readonly ?Schema $schema; /** - * @todo fill in. + * Whether the table requires to be prefixed. */ public readonly bool $needsPrefix; diff --git a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php index fb7aa609e7f1..d10485deb1b4 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php +++ b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php @@ -4,6 +4,7 @@ namespace Drupal\mysql\Driver\Database\mysql; +use Drupal\Core\Database\Exception\IdentifierException; use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Identifier\IdentifierType; @@ -23,4 +24,28 @@ public function getMaxLength(IdentifierType $type): int { }; } + /** + * {@inheritdoc} + */ + protected function validateCanonicalName(string $identifier, string $canonicalName, IdentifierType $type): true { + return match ($type) { + IdentifierType::Table => true, + default => parent::validateCanonicalName($identifier, $canonicalName, $type), + }; + } + + /** + * @todo fill in. + */ + public function parseTableIdentifier(string $identifier): array { + $parts = parent::parseTableIdentifier($identifier); + if ($parts['database']) { + throw new IdentifierException(sprintf( + 'MySql does not support the syntax [database.][schema.]table for the table identifier \'%s\'. Avoid specifying the \'database\' part', + $identifier, + )); + } + return $parts; + } + } diff --git a/core/modules/mysql/tests/src/Kernel/mysql/MysqlDriverTest.php b/core/modules/mysql/tests/src/Kernel/mysql/MysqlDriverTest.php index 4fcb75b60aa0..48905dc0e6d6 100644 --- a/core/modules/mysql/tests/src/Kernel/mysql/MysqlDriverTest.php +++ b/core/modules/mysql/tests/src/Kernel/mysql/MysqlDriverTest.php @@ -19,7 +19,7 @@ class MysqlDriverTest extends DriverSpecificKernelTestBase { * @covers \Drupal\mysql\Driver\Database\mysql\Connection */ public function testConnection(): void { - $connection = new Connection($this->createMock(StubPDO::class), []); + $connection = new Connection($this->createMock(StubPDO::class), ['prefix' => '']); $this->assertInstanceOf(Connection::class, $connection); } diff --git a/core/modules/mysql/tests/src/Unit/IdentifierTest.php b/core/modules/mysql/tests/src/Unit/IdentifierTest.php new file mode 100644 index 000000000000..00c254ad3f92 --- /dev/null +++ b/core/modules/mysql/tests/src/Unit/IdentifierTest.php @@ -0,0 +1,72 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\mysql\Unit; + +use Drupal\mysql\Driver\Database\mysql\Connection; +use Drupal\Tests\UnitTestCase; +use Prophecy\Argument; + +/** + * Tests MySQL database identifiers. + * + * @coversDefaultClass \Drupal\mysql\Driver\Database\mysql\IdentifierHandler + * @group Database + */ +class IdentifierTest extends UnitTestCase { + + /** + * A MySql connection. + */ + private Connection $connection; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->connection = new Connection($this->createMock(\PDO::class), [ + 'prefix' => 'blahblah', + 'init_commands' => [ + 'sql_mode' => 'ANSI', + ], + ]); + } + + /** + * Data provider for testTable. + * + * @return array + * An indexed array of where each value is an array of arguments to pass to + * testEscapeField. The first value is the expected value, and the second + * value is the value to test. + */ + public static function providerTable(): array { + return [ + ['nocase', 'nocase'], + ['camelCase', 'camelCase'], + ['backtick', '`backtick`', ['`', '`']], + ['brackets', '[brackets]', ['[', ']']], + ['camelCase', '"camelCase"'], + ['camelCase', 'camel/Case'], + ['camelCase', 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar'], + // Sometimes, table names are following the pattern database.schema.table. + ['', 'Chowra.Teressa.Bompuka.Katchal'], + ['', 'Chowra.Teressa.Bompuka'], + ['"Nancowry"."Tillangchong"', '"Nancowry"."Tillangchong"'], + ['"Nancowry"."Tillangchong"', '!Nancowry?.$Tillangchong%%%'], + ['', 'Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar'], + ['', 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Trinket'], + ]; + } + + /** + * @dataProvider providerTable + */ + public function testTable($expected, $name, array $identifier_quote = ['"', '"']): void { + $this->assertEquals($expected, $this->connection->identifiers->table($name)->forMachine()); + } + +} diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php index abcdf2008ca5..b7510e5c200e 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php @@ -17,10 +17,11 @@ class IdentifierHandler extends IdentifierHandlerBase { */ public function getMaxLength(IdentifierType $type): int { // There is no hard limit on identifier length in SQLite, so we just use - // common sense. + // common sense: identifiers longer than 128 characters are hardly + // readable. // @see https://www.sqlite.org/limits.html // @see https://stackoverflow.com/questions/8135013/table-name-limit-in-sqlite-android - return 256; + return 128; } /** -- GitLab From 55b72cff842c210f7863aa9595ecd453749b00d4 Mon Sep 17 00:00:00 2001 From: mondrake <28163-mondrake@users.noreply.drupalcode.org> Date: Thu, 13 Mar 2025 09:41:17 +0000 Subject: [PATCH 44/66] Update file IdentifierTest.php --- .../mysql/tests/src/Unit/IdentifierTest.php | 100 ++++++++++++------ 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/core/modules/mysql/tests/src/Unit/IdentifierTest.php b/core/modules/mysql/tests/src/Unit/IdentifierTest.php index 00c254ad3f92..1c43dffcc9b8 100644 --- a/core/modules/mysql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/mysql/tests/src/Unit/IdentifierTest.php @@ -16,25 +16,6 @@ */ class IdentifierTest extends UnitTestCase { - /** - * A MySql connection. - */ - private Connection $connection; - - /** - * {@inheritdoc} - */ - protected function setUp(): void { - parent::setUp(); - - $this->connection = new Connection($this->createMock(\PDO::class), [ - 'prefix' => 'blahblah', - 'init_commands' => [ - 'sql_mode' => 'ANSI', - ], - ]); - } - /** * Data provider for testTable. * @@ -45,28 +26,79 @@ protected function setUp(): void { */ public static function providerTable(): array { return [ - ['nocase', 'nocase'], - ['camelCase', 'camelCase'], - ['backtick', '`backtick`', ['`', '`']], - ['brackets', '[brackets]', ['[', ']']], - ['camelCase', '"camelCase"'], - ['camelCase', 'camel/Case'], - ['camelCase', 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar'], + [ + 'identifier' => 'nocase', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"nocase"', + ], + [ + 'identifier' => 'camelCase', + 'expectedCanonical' => 'camelCase', + 'expectedMachine' => '"camelCase"', + ], + [ + 'identifier' => '`backtick`', + 'expectedCanonical' => 'backtick', + 'expectedMachine' => '"backtick"', + ], + [ + 'identifier' => '[brackets]', + 'expectedCanonical' => 'brackets', + 'expectedMachine' => '"brackets"', + ], + [ + 'identifier' => 'no/case', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"nocase"', + ], + [ + 'identifier' => 'no"case', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"nocase"', + ], + [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', + ], // Sometimes, table names are following the pattern database.schema.table. - ['', 'Chowra.Teressa.Bompuka.Katchal'], - ['', 'Chowra.Teressa.Bompuka'], - ['"Nancowry"."Tillangchong"', '"Nancowry"."Tillangchong"'], - ['"Nancowry"."Tillangchong"', '!Nancowry?.$Tillangchong%%%'], - ['', 'Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar'], - ['', 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Trinket'], + [ + 'identifier' => 'Chowra.Teressa.Bompuka.Katchal', + ], + [ + 'identifier' => 'Chowra.Teressa.Bompuka', + ], + [ + 'identifier' => '"Nancowry"."Tillangchong"', + 'expectedCanonical' => 'Nancowry.Tillangchong', + 'expectedMachine' => '"Nancowry"."Tillangchong"', + ], + [ + 'identifier' => '!Nancowry?.$Tillangchong%%%', + 'expectedCanonical' => 'Nancowry.Tillangchong', + 'expectedMachine' => '"Nancowry"."Tillangchong"', + ], + [ + 'identifier' => 'Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + ], + [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Trinket', + ], ]; } /** * @dataProvider providerTable */ - public function testTable($expected, $name, array $identifier_quote = ['"', '"']): void { - $this->assertEquals($expected, $this->connection->identifiers->table($name)->forMachine()); + public function testTable(string $identifier, ?string $expectedCanonical = '', ?string $expectedMachine = '', ?string $expectedException = NULL): void { + $connection = new Connection($this->createMock(\PDO::class), [ + 'prefix' => 'blahblah', + 'init_commands' => [ + 'sql_mode' => 'ANSI', + ], + ]); + $this->assertSame($expectedCanonical, $connection->identifiers->table($identifier)->canonical()); + $this->assertSame($expectedMachine, $connection->identifiers->table($identifier)->forMachine()); } } -- GitLab From c1654d5e7d2e13450954936b8c33dd26e5e01bd4 Mon Sep 17 00:00:00 2001 From: mondrake <28163-mondrake@users.noreply.drupalcode.org> Date: Thu, 13 Mar 2025 13:56:48 +0000 Subject: [PATCH 45/66] Update 2 files - /core/modules/mysql/tests/src/Unit/IdentifierTest.php - /core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php --- .../Database/mysql/IdentifierHandler.php | 14 ++++- .../mysql/tests/src/Unit/IdentifierTest.php | 55 ++++++++++++++++++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php index d10485deb1b4..c33a8543884e 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php +++ b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php @@ -35,7 +35,7 @@ protected function validateCanonicalName(string $identifier, string $canonicalNa } /** - * @todo fill in. + * {@inheritdoc} */ public function parseTableIdentifier(string $identifier): array { $parts = parent::parseTableIdentifier($identifier); @@ -48,4 +48,16 @@ public function parseTableIdentifier(string $identifier): array { return $parts; } + /** + * {@inheritdoc} + */ + protected function resolveTableForMachine(string $canonicalName, array $info): string { + if (strlen($info['needs_prefix'] ? $this->tablePrefix : '' . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { + $hash = substr(hash('sha256', $canonicalName), 0, 10); + $shortened = substr($canonicalName, 0, $this->getMaxLength(IdentifierType::Table) - strlen($this->tablePrefix) - 10); + return $this->quote($info['needs_prefix'] ? $this->tablePrefix . $shortened . $hash : $shortened . $hash); + } + return $this->quote($info['needs_prefix'] ? $this->tablePrefix . $canonicalName : $canonicalName); + } + } diff --git a/core/modules/mysql/tests/src/Unit/IdentifierTest.php b/core/modules/mysql/tests/src/Unit/IdentifierTest.php index 1c43dffcc9b8..3fac728c0965 100644 --- a/core/modules/mysql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/mysql/tests/src/Unit/IdentifierTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\mysql\Unit; +use Drupal\Core\Database\Exception\IdentifierException; use Drupal\mysql\Driver\Database\mysql\Connection; use Drupal\Tests\UnitTestCase; use Prophecy\Argument; @@ -31,11 +32,29 @@ public static function providerTable(): array { 'expectedCanonical' => 'nocase', 'expectedMachine' => '"nocase"', ], + [ + 'prefix' => 'foobar', + 'identifier' => 'nocase', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"foobarnocase"', + ], + [ + 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', + 'identifier' => 'nocase', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"foobarnocase"', + ], [ 'identifier' => 'camelCase', 'expectedCanonical' => 'camelCase', 'expectedMachine' => '"camelCase"', ], + [ + 'prefix' => 'foobar', + 'identifier' => 'camelCase', + 'expectedCanonical' => 'camelCase', + 'expectedMachine' => '"foobarcamelCase"', + ], [ 'identifier' => '`backtick`', 'expectedCanonical' => 'backtick', @@ -61,18 +80,44 @@ public static function providerTable(): array { 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', ], + [ + 'prefix' => 'foobar', + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', + ], + [ + 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', + ], // Sometimes, table names are following the pattern database.schema.table. [ 'identifier' => 'Chowra.Teressa.Bompuka.Katchal', + 'expectedException' => 'The table identifier \'Chowra.Teressa.Bompuka.Katchal\' does not comply with the syntax [database.][schema.]table', ], [ 'identifier' => 'Chowra.Teressa.Bompuka', + 'expectedException' => 'MySql does not support the syntax [database.][schema.]table for the table identifier \'Chowra.Teressa.Bompuka\'. Avoid specifying the \'database\' part', ], [ 'identifier' => '"Nancowry"."Tillangchong"', 'expectedCanonical' => 'Nancowry.Tillangchong', 'expectedMachine' => '"Nancowry"."Tillangchong"', ], + [ + 'prefix' => 'foobar', + 'identifier' => '"Nancowry"."Tillangchong"', + 'expectedCanonical' => 'Nancowry.Tillangchong', + 'expectedMachine' => '"Nancowry"."Tillangchong"', + ], + [ + 'prefix' => 'foobar', + 'identifier' => '"Nancowry"."foobarTillangchong"', + 'expectedCanonical' => 'Nancowry.Tillangchong', + 'expectedMachine' => '"Nancowry"."foobarTillangchong"', + ], [ 'identifier' => '!Nancowry?.$Tillangchong%%%', 'expectedCanonical' => 'Nancowry.Tillangchong', @@ -80,9 +125,11 @@ public static function providerTable(): array { ], [ 'identifier' => 'Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedException' => '....', ], [ 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Trinket', + 'expectedException' => 'The length of the schema identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' once canonicalized to \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' is invalid (maximum allowed: 64)', ], ]; } @@ -90,13 +137,17 @@ public static function providerTable(): array { /** * @dataProvider providerTable */ - public function testTable(string $identifier, ?string $expectedCanonical = '', ?string $expectedMachine = '', ?string $expectedException = NULL): void { + public function testTable(string $identifier, string $prefix = '', ?string $expectedCanonical = '', ?string $expectedMachine = '', ?string $expectedException = NULL): void { $connection = new Connection($this->createMock(\PDO::class), [ - 'prefix' => 'blahblah', + 'prefix' => $prefix, 'init_commands' => [ 'sql_mode' => 'ANSI', ], ]); + if ($expectedException) { + $this->expectException(IdentifierException::class); + $this->expectExceptionMessage($expectedException); + } $this->assertSame($expectedCanonical, $connection->identifiers->table($identifier)->canonical()); $this->assertSame($expectedMachine, $connection->identifiers->table($identifier)->forMachine()); } -- GitLab From ca88040cf6e0e28144dee16cbf2dbfb7783c578f Mon Sep 17 00:00:00 2001 From: mondrake <28163-mondrake@users.noreply.drupalcode.org> Date: Thu, 13 Mar 2025 14:23:39 +0000 Subject: [PATCH 46/66] Update file IdentifierTest.php --- .../mysql/tests/src/Unit/IdentifierTest.php | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/core/modules/mysql/tests/src/Unit/IdentifierTest.php b/core/modules/mysql/tests/src/Unit/IdentifierTest.php index 3fac728c0965..a4d3402d25f2 100644 --- a/core/modules/mysql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/mysql/tests/src/Unit/IdentifierTest.php @@ -29,29 +29,31 @@ public static function providerTable(): array { return [ [ 'identifier' => 'nocase', + 'prefix' => '', 'expectedCanonical' => 'nocase', 'expectedMachine' => '"nocase"', ], [ - 'prefix' => 'foobar', 'identifier' => 'nocase', + 'prefix' => 'foobar', 'expectedCanonical' => 'nocase', 'expectedMachine' => '"foobarnocase"', ], [ - 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', 'identifier' => 'nocase', + 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', 'expectedCanonical' => 'nocase', 'expectedMachine' => '"foobarnocase"', ], [ 'identifier' => 'camelCase', + 'prefix' => '', 'expectedCanonical' => 'camelCase', 'expectedMachine' => '"camelCase"', ], [ - 'prefix' => 'foobar', 'identifier' => 'camelCase', + 'prefix' => 'foobar', 'expectedCanonical' => 'camelCase', 'expectedMachine' => '"foobarcamelCase"', ], @@ -62,73 +64,91 @@ public static function providerTable(): array { ], [ 'identifier' => '[brackets]', + 'prefix' => '', 'expectedCanonical' => 'brackets', 'expectedMachine' => '"brackets"', ], [ 'identifier' => 'no/case', + 'prefix' => '', 'expectedCanonical' => 'nocase', 'expectedMachine' => '"nocase"', ], [ 'identifier' => 'no"case', + 'prefix' => '', 'expectedCanonical' => 'nocase', 'expectedMachine' => '"nocase"', ], [ 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => '', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', ], [ - 'prefix' => 'foobar', 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'foobar', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', ], [ - 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', ], // Sometimes, table names are following the pattern database.schema.table. [ 'identifier' => 'Chowra.Teressa.Bompuka.Katchal', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', 'expectedException' => 'The table identifier \'Chowra.Teressa.Bompuka.Katchal\' does not comply with the syntax [database.][schema.]table', ], [ 'identifier' => 'Chowra.Teressa.Bompuka', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', 'expectedException' => 'MySql does not support the syntax [database.][schema.]table for the table identifier \'Chowra.Teressa.Bompuka\'. Avoid specifying the \'database\' part', ], [ 'identifier' => '"Nancowry"."Tillangchong"', + 'prefix' => '', 'expectedCanonical' => 'Nancowry.Tillangchong', 'expectedMachine' => '"Nancowry"."Tillangchong"', ], [ - 'prefix' => 'foobar', 'identifier' => '"Nancowry"."Tillangchong"', + 'prefix' => 'foobar', 'expectedCanonical' => 'Nancowry.Tillangchong', 'expectedMachine' => '"Nancowry"."Tillangchong"', ], [ - 'prefix' => 'foobar', 'identifier' => '"Nancowry"."foobarTillangchong"', + 'prefix' => 'foobar', 'expectedCanonical' => 'Nancowry.Tillangchong', 'expectedMachine' => '"Nancowry"."foobarTillangchong"', ], [ 'identifier' => '!Nancowry?.$Tillangchong%%%', + 'prefix' => '', 'expectedCanonical' => 'Nancowry.Tillangchong', 'expectedMachine' => '"Nancowry"."Tillangchong"', ], [ 'identifier' => 'Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', 'expectedException' => '....', ], [ 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Trinket', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', 'expectedException' => 'The length of the schema identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' once canonicalized to \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' is invalid (maximum allowed: 64)', ], ]; -- GitLab From 29ff2080c28103a1e8d710aac246481085bfd172 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Thu, 13 Mar 2025 22:22:54 +0100 Subject: [PATCH 47/66] unit tests --- .../Database/mysql/IdentifierHandler.php | 31 ++- .../mysql/tests/src/Unit/IdentifierTest.php | 182 ++++++++++-------- .../Database/sqlite/IdentifierHandler.php | 9 +- .../sqlite/tests/src/Unit/IdentifierTest.php | 162 ++++++++++++++++ 4 files changed, 297 insertions(+), 87 deletions(-) create mode 100644 core/modules/sqlite/tests/src/Unit/IdentifierTest.php diff --git a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php index c33a8543884e..7870dc7ceabc 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php +++ b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php @@ -52,12 +52,35 @@ public function parseTableIdentifier(string $identifier): array { * {@inheritdoc} */ protected function resolveTableForMachine(string $canonicalName, array $info): string { - if (strlen($info['needs_prefix'] ? $this->tablePrefix : '' . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { + if ($info['schema']) { + // We are processing a fully qualified table identifier, canonical name + // should not be processed, just checked it does not exceed max length. + if (strlen($canonicalName) > $this->getMaxLength(IdentifierType::Table)) { + throw new IdentifierException(sprintf( + 'Table identifier \'%s\' exceeds maximum allowed length (%d)', + $canonicalName, + $this->getMaxLength(IdentifierType::Table), + )); + } + return $this->quote($canonicalName); + } + + $prefix = $info['needs_prefix'] ? $this->tablePrefix : ''; + if (strlen($prefix . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { $hash = substr(hash('sha256', $canonicalName), 0, 10); - $shortened = substr($canonicalName, 0, $this->getMaxLength(IdentifierType::Table) - strlen($this->tablePrefix) - 10); - return $this->quote($info['needs_prefix'] ? $this->tablePrefix . $shortened . $hash : $shortened . $hash); + $allowedLength = $this->getMaxLength(IdentifierType::Table) - strlen($this->tablePrefix) - 10; + if ($allowedLength < 4) { + throw new IdentifierException(sprintf( + 'Table canonical identifier \'%s\' cannot be converted into a machine identifier%s', + $canonicalName, + $info['needs_prefix'] ? "; table prefix '{$this->tablePrefix}'" : '', + )); + } + $lSize = (int) ($allowedLength / 2); + $rSize = $allowedLength - $lSize; + return $this->quote($prefix . substr($canonicalName, 0, $lSize) . $hash . substr($canonicalName, -$rSize)); } - return $this->quote($info['needs_prefix'] ? $this->tablePrefix . $canonicalName : $canonicalName); + return $this->quote($prefix . $canonicalName); } } diff --git a/core/modules/mysql/tests/src/Unit/IdentifierTest.php b/core/modules/mysql/tests/src/Unit/IdentifierTest.php index a4d3402d25f2..054e974d5aa8 100644 --- a/core/modules/mysql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/mysql/tests/src/Unit/IdentifierTest.php @@ -5,9 +5,9 @@ namespace Drupal\Tests\mysql\Unit; use Drupal\Core\Database\Exception\IdentifierException; -use Drupal\mysql\Driver\Database\mysql\Connection; +use Drupal\Core\Database\Identifier\IdentifierType; +use Drupal\mysql\Driver\Database\mysql\IdentifierHandler; use Drupal\Tests\UnitTestCase; -use Prophecy\Argument; /** * Tests MySQL database identifiers. @@ -21,135 +21,155 @@ class IdentifierTest extends UnitTestCase { * Data provider for testTable. * * @return array - * An indexed array of where each value is an array of arguments to pass to - * testEscapeField. The first value is the expected value, and the second - * value is the value to test. + * An associative array of test case data. */ public static function providerTable(): array { return [ - [ - 'identifier' => 'nocase', - 'prefix' => '', + 'No prefix' => [ + 'identifier' => 'nocase', + 'prefix' => '', 'expectedCanonical' => 'nocase', 'expectedMachine' => '"nocase"', ], - [ - 'identifier' => 'nocase', - 'prefix' => 'foobar', + 'Prefix' => [ + 'identifier' => 'nocase', + 'prefix' => 'foobar', 'expectedCanonical' => 'nocase', 'expectedMachine' => '"foobarnocase"', ], - [ - 'identifier' => 'nocase', - 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', - 'expectedCanonical' => 'nocase', - 'expectedMachine' => '"foobarnocase"', - ], - [ - 'identifier' => 'camelCase', - 'prefix' => '', + 'No prefix, camelCase' => [ + 'identifier' => 'camelCase', + 'prefix' => '', 'expectedCanonical' => 'camelCase', 'expectedMachine' => '"camelCase"', ], - [ - 'identifier' => 'camelCase', - 'prefix' => 'foobar', + 'Prefix, camelCase' => [ + 'identifier' => 'camelCase', + 'prefix' => 'foobar', 'expectedCanonical' => 'camelCase', 'expectedMachine' => '"foobarcamelCase"', ], - [ - 'identifier' => '`backtick`', + 'No prefix, backtick' => [ + 'identifier' => '`backtick`', + 'prefix' => '', 'expectedCanonical' => 'backtick', 'expectedMachine' => '"backtick"', ], - [ - 'identifier' => '[brackets]', - 'prefix' => '', + 'No prefix, brackets' => [ + 'identifier' => '[brackets]', + 'prefix' => '', 'expectedCanonical' => 'brackets', 'expectedMachine' => '"brackets"', ], - [ - 'identifier' => 'no/case', - 'prefix' => '', + 'No prefix, remove slash' => [ + 'identifier' => 'no/case', + 'prefix' => '', 'expectedCanonical' => 'nocase', 'expectedMachine' => '"nocase"', ], - [ - 'identifier' => 'no"case', - 'prefix' => '', + 'No prefix, remove quote' => [ + 'identifier' => 'no"case', + 'prefix' => '', 'expectedCanonical' => 'nocase', 'expectedMachine' => '"nocase"', ], - [ - 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', - 'prefix' => '', + 'No prefix, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => '', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '"VeryVeryVeryVeryHungryHungr25fa0c3753ngryHungryHungryCaterpillar"', + ], + 'Prefix, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'foobar', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', - 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', + 'expectedMachine' => '"foobarVeryVeryVeryVeryHungryHu25fa0c3753yHungryHungryCaterpillar"', ], - [ - 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', - 'prefix' => 'foobar', + 'Prefix one less than overflow, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'prefix____123456789012345678901234567890123456789', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', - 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', + 'expectedMachine' => '"prefix____123456789012345678901234567890123456789Ve25fa0c3753lar"', ], - [ - 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', - 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', + 'Prefix just right, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'prefix____1234567890123456789012345678901234567890', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', - 'expectedMachine' => '"VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar"', + 'expectedMachine' => '"prefix____1234567890123456789012345678901234567890Ve25fa0c3753ar"', + ], + 'Prefix one more than overflow, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'prefix____12345678901234567890123456789012345678901', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '', + 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; table prefix \'prefix____12345678901234567890123456789012345678901\'', + ], + 'Prefix too long to fit, table does not require shortening' => [ + 'identifier' => 'nocase', + 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '', + 'expectedException' => 'Table canonical identifier \'nocase\' cannot be converted into a machine identifier; table prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', + ], + 'Prefix too long to fit, table requires shortening' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '', + 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; table prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', ], // Sometimes, table names are following the pattern database.schema.table. - [ - 'identifier' => 'Chowra.Teressa.Bompuka.Katchal', - 'prefix' => '', + 'Fully qualified - too many parts: 4' => [ + 'identifier' => 'Chowra.Teressa.Bompuka.Katchal', + 'prefix' => '', 'expectedCanonical' => '', 'expectedMachine' => '', 'expectedException' => 'The table identifier \'Chowra.Teressa.Bompuka.Katchal\' does not comply with the syntax [database.][schema.]table', ], - [ - 'identifier' => 'Chowra.Teressa.Bompuka', - 'prefix' => '', + 'Fully qualified - too many parts: MySql not supporting \'database\'' => [ + 'identifier' => 'Chowra.Teressa.Bompuka', + 'prefix' => '', 'expectedCanonical' => '', 'expectedMachine' => '', 'expectedException' => 'MySql does not support the syntax [database.][schema.]table for the table identifier \'Chowra.Teressa.Bompuka\'. Avoid specifying the \'database\' part', ], - [ - 'identifier' => '"Nancowry"."Tillangchong"', - 'prefix' => '', + 'Fully qualified - no prefix' => [ + 'identifier' => '"Nancowry"."Tillangchong"', + 'prefix' => '', 'expectedCanonical' => 'Nancowry.Tillangchong', 'expectedMachine' => '"Nancowry"."Tillangchong"', ], - [ - 'identifier' => '"Nancowry"."Tillangchong"', - 'prefix' => 'foobar', + 'Fully qualified - prefix' => [ + 'identifier' => '"Nancowry"."Tillangchong"', + 'prefix' => 'foobar', 'expectedCanonical' => 'Nancowry.Tillangchong', 'expectedMachine' => '"Nancowry"."Tillangchong"', ], - [ - 'identifier' => '"Nancowry"."foobarTillangchong"', - 'prefix' => 'foobar', - 'expectedCanonical' => 'Nancowry.Tillangchong', + 'Fully qualified - prefix not duplicated' => [ + 'identifier' => 'Nancowry.foobarTillangchong', + 'prefix' => 'foobar', + 'expectedCanonical' => 'Nancowry.foobarTillangchong', 'expectedMachine' => '"Nancowry"."foobarTillangchong"', ], - [ - 'identifier' => '!Nancowry?.$Tillangchong%%%', - 'prefix' => '', + 'Fully qualified - remove not canonical characters' => [ + 'identifier' => '!Nancowry?.$Tillangchong%%%', + 'prefix' => '', 'expectedCanonical' => 'Nancowry.Tillangchong', 'expectedMachine' => '"Nancowry"."Tillangchong"', ], - [ - 'identifier' => 'Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', - 'prefix' => '', + 'Fully qualified - invalid schema name length' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Trinket', + 'prefix' => '', 'expectedCanonical' => '', 'expectedMachine' => '', - 'expectedException' => '....', + 'expectedException' => 'The length of the schema identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' once canonicalized to \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' is invalid (maximum allowed: 64)', ], - [ - 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Trinket', - 'prefix' => '', - 'expectedCanonical' => '', + 'Fully qualified - invalid table name length' => [ + 'identifier' => 'Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => '', + 'expectedCanonical' => 'Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'expectedMachine' => '', - 'expectedException' => 'The length of the schema identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' once canonicalized to \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' is invalid (maximum allowed: 64)', + 'expectedException' => 'Table identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' exceeds maximum allowed length (64)', ], ]; } @@ -158,18 +178,16 @@ public static function providerTable(): array { * @dataProvider providerTable */ public function testTable(string $identifier, string $prefix = '', ?string $expectedCanonical = '', ?string $expectedMachine = '', ?string $expectedException = NULL): void { - $connection = new Connection($this->createMock(\PDO::class), [ - 'prefix' => $prefix, - 'init_commands' => [ - 'sql_mode' => 'ANSI', - ], - ]); + $handler = new IdentifierHandler($prefix); if ($expectedException) { $this->expectException(IdentifierException::class); $this->expectExceptionMessage($expectedException); } - $this->assertSame($expectedCanonical, $connection->identifiers->table($identifier)->canonical()); - $this->assertSame($expectedMachine, $connection->identifiers->table($identifier)->forMachine()); + $this->assertSame($expectedCanonical, $handler->table($identifier)->canonical()); + $this->assertSame($expectedMachine, $handler->table($identifier)->forMachine()); + // The machine name includes the quote characters so we need to subtract + // those from the length. + $this->assertLessThanOrEqual($handler->getMaxLength(IdentifierType::Table), strlen($handler->table($identifier)->forMachine()) - 2, 'Invalid machine table length.'); } } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php index b7510e5c200e..7c0b65c21ca6 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php @@ -4,6 +4,7 @@ namespace Drupal\sqlite\Driver\Database\sqlite; +use Drupal\Core\Database\Exception\IdentifierException; use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Identifier\IdentifierType; @@ -29,7 +30,13 @@ public function getMaxLength(IdentifierType $type): int { */ public function parseTableIdentifier(string $identifier): array { $parts = parent::parseTableIdentifier($identifier); - if ($this->tablePrefix !== '' && $parts['database'] === NULL && $parts['schema'] === NULL) { + if ($parts['database']) { + throw new IdentifierException(sprintf( + 'SQLite does not support the syntax [database.][schema.]table for the table identifier \'%s\'. Avoid specifying the \'database\' part', + $identifier, + )); + } + if ($this->tablePrefix !== '' && $parts['schema'] === NULL) { $parts['schema'] = $this->schema(rtrim($this->tablePrefix, '.')); $parts['needs_prefix'] = FALSE; } diff --git a/core/modules/sqlite/tests/src/Unit/IdentifierTest.php b/core/modules/sqlite/tests/src/Unit/IdentifierTest.php new file mode 100644 index 000000000000..fdceda59b234 --- /dev/null +++ b/core/modules/sqlite/tests/src/Unit/IdentifierTest.php @@ -0,0 +1,162 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\sqlite\Unit; + +use Drupal\Core\Database\Exception\IdentifierException; +use Drupal\Core\Database\Identifier\IdentifierType; +use Drupal\sqlite\Driver\Database\sqlite\IdentifierHandler; +use Drupal\Tests\UnitTestCase; + +/** + * Tests SQLite database identifiers. + * + * @coversDefaultClass \Drupal\sqlite\Driver\Database\sqlite\IdentifierHandler + * @group Database + */ +class IdentifierTest extends UnitTestCase { + + /** + * Data provider for testTable. + * + * @return array + * An associative array of test case data. + */ + public static function providerTable(): array { + return [ + 'No prefix' => [ + 'identifier' => 'nocase', + 'prefix' => '', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"nocase"', + ], + 'Prefix' => [ + 'identifier' => 'nocase', + 'prefix' => 'foobar', + 'expectedCanonical' => 'foobar.nocase', + 'expectedMachine' => '"foobar"."nocase"', + ], + 'No prefix, camelCase' => [ + 'identifier' => 'camelCase', + 'prefix' => '', + 'expectedCanonical' => 'camelCase', + 'expectedMachine' => '"camelCase"', + ], + 'Prefix, camelCase' => [ + 'identifier' => 'camelCase', + 'prefix' => 'foobar', + 'expectedCanonical' => 'foobar.camelCase', + 'expectedMachine' => '"foobar"."camelCase"', + ], + 'No prefix, backtick' => [ + 'identifier' => '`backtick`', + 'prefix' => '', + 'expectedCanonical' => 'backtick', + 'expectedMachine' => '"backtick"', + ], + 'No prefix, brackets' => [ + 'identifier' => '[brackets]', + 'prefix' => '', + 'expectedCanonical' => 'brackets', + 'expectedMachine' => '"brackets"', + ], + 'No prefix, remove slash' => [ + 'identifier' => 'no/case', + 'prefix' => '', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"nocase"', + ], + 'No prefix, remove quote' => [ + 'identifier' => 'no"case', + 'prefix' => '', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"nocase"', + ], + 'No prefix, too long table name' => [ + 'identifier' => 'VeryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', + 'expectedException' => 'The length of the table identifier \'VeryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' once canonicalized to \'VeryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' is invalid (maximum allowed: 128)', + ], + 'Prefix too long to fit' => [ + 'identifier' => 'nocase', + 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongVeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', + 'expectedCanonical' => '', + 'expectedMachine' => '', + 'expectedException' => 'The length of the schema identifier \'VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongVeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\' once canonicalized to \'VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongVeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\' is invalid (maximum allowed: 128)', + ], + // Sometimes, table names are following the pattern database.schema.table. + 'Fully qualified - too many parts: 4' => [ + 'identifier' => 'Chowra.Teressa.Bompuka.Katchal', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', + 'expectedException' => 'The table identifier \'Chowra.Teressa.Bompuka.Katchal\' does not comply with the syntax [database.][schema.]table', + ], + 'Fully qualified - too many parts: SQLite not supporting \'database\'' => [ + 'identifier' => 'Chowra.Teressa.Bompuka', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', + 'expectedException' => 'SQLite does not support the syntax [database.][schema.]table for the table identifier \'Chowra.Teressa.Bompuka\'. Avoid specifying the \'database\' part', + ], + 'Fully qualified - no prefix' => [ + 'identifier' => '"Nancowry"."Tillangchong"', + 'prefix' => '', + 'expectedCanonical' => 'Nancowry.Tillangchong', + 'expectedMachine' => '"Nancowry"."Tillangchong"', + ], + 'Fully qualified - prefix' => [ + 'identifier' => '"Nancowry"."Tillangchong"', + 'prefix' => 'foobar', + 'expectedCanonical' => 'Nancowry.Tillangchong', + 'expectedMachine' => '"Nancowry"."Tillangchong"', + ], + 'Fully qualified - prefix not duplicated' => [ + 'identifier' => 'Nancowry.foobarTillangchong', + 'prefix' => 'foobar', + 'expectedCanonical' => 'Nancowry.foobarTillangchong', + 'expectedMachine' => '"Nancowry"."foobarTillangchong"', + ], + 'Fully qualified - remove not canonical characters' => [ + 'identifier' => '!Nancowry?.$Tillangchong%%%', + 'prefix' => '', + 'expectedCanonical' => 'Nancowry.Tillangchong', + 'expectedMachine' => '"Nancowry"."Tillangchong"', + ], + 'Fully qualified - invalid schema name length' => [ + 'identifier' => 'VeryVeryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Trinket', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', + 'expectedException' => 'The length of the schema identifier \'VeryVeryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' once canonicalized to \'VeryVeryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' is invalid (maximum allowed: 128)', + ], + 'Fully qualified - invalid table name length' => [ + 'identifier' => 'Camorta.VeryVeryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', + 'expectedException' => 'The length of the table identifier \'VeryVeryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' once canonicalized to \'VeryVeryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryVeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' is invalid (maximum allowed: 128)', + ], + ]; + } + + /** + * @dataProvider providerTable + */ + public function testTable(string $identifier, string $prefix = '', ?string $expectedCanonical = '', ?string $expectedMachine = '', ?string $expectedException = NULL): void { + $handler = new IdentifierHandler($prefix); + if ($expectedException) { + $this->expectException(IdentifierException::class); + $this->expectExceptionMessage($expectedException); + } + $this->assertSame($expectedCanonical, $handler->table($identifier)->canonical()); + $this->assertSame($expectedMachine, $handler->table($identifier)->forMachine()); + // The machine name includes the quote characters so we need to subtract + // those from the length. + $this->assertLessThanOrEqual($handler->getMaxLength(IdentifierType::Table), strlen($handler->table($identifier)->forMachine()) - 2, 'Invalid machine table length.'); + } + +} -- GitLab From e1d94f246d16437ab2cc1155948a1cb5117ccf85 Mon Sep 17 00:00:00 2001 From: mondrake <28163-mondrake@users.noreply.drupalcode.org> Date: Fri, 14 Mar 2025 09:54:04 +0000 Subject: [PATCH 48/66] Update 3 files - /core/modules/mysql/tests/src/Unit/IdentifierTest.php - /core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php - /core/modules/sqlite/tests/src/Unit/IdentifierTest.php --- .../mysql/src/Driver/Database/mysql/IdentifierHandler.php | 2 +- core/modules/mysql/tests/src/Unit/IdentifierTest.php | 4 ++++ core/modules/sqlite/tests/src/Unit/IdentifierTest.php | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php index 7870dc7ceabc..d191f2ecf9f0 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php +++ b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php @@ -29,7 +29,7 @@ public function getMaxLength(IdentifierType $type): int { */ protected function validateCanonicalName(string $identifier, string $canonicalName, IdentifierType $type): true { return match ($type) { - IdentifierType::Table => true, + IdentifierType::Table => TRUE, default => parent::validateCanonicalName($identifier, $canonicalName, $type), }; } diff --git a/core/modules/mysql/tests/src/Unit/IdentifierTest.php b/core/modules/mysql/tests/src/Unit/IdentifierTest.php index 054e974d5aa8..0527f4b9ef49 100644 --- a/core/modules/mysql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/mysql/tests/src/Unit/IdentifierTest.php @@ -23,6 +23,7 @@ class IdentifierTest extends UnitTestCase { * @return array * An associative array of test case data. */ + // cSpell:disable public static function providerTable(): array { return [ 'No prefix' => [ @@ -173,8 +174,11 @@ public static function providerTable(): array { ], ]; } + // cSpell:enable /** + * Tests table identifiers. + * * @dataProvider providerTable */ public function testTable(string $identifier, string $prefix = '', ?string $expectedCanonical = '', ?string $expectedMachine = '', ?string $expectedException = NULL): void { diff --git a/core/modules/sqlite/tests/src/Unit/IdentifierTest.php b/core/modules/sqlite/tests/src/Unit/IdentifierTest.php index fdceda59b234..871062f1cbd6 100644 --- a/core/modules/sqlite/tests/src/Unit/IdentifierTest.php +++ b/core/modules/sqlite/tests/src/Unit/IdentifierTest.php @@ -23,6 +23,7 @@ class IdentifierTest extends UnitTestCase { * @return array * An associative array of test case data. */ + // cSpell:disable public static function providerTable(): array { return [ 'No prefix' => [ @@ -142,8 +143,11 @@ public static function providerTable(): array { ], ]; } + // cSpell:enable /** + * Tests table identifiers. + * * @dataProvider providerTable */ public function testTable(string $identifier, string $prefix = '', ?string $expectedCanonical = '', ?string $expectedMachine = '', ?string $expectedException = NULL): void { -- GitLab From f7ff8f1647612a09a0a02366e688d73688998d78 Mon Sep 17 00:00:00 2001 From: mondrake <28163-mondrake@users.noreply.drupalcode.org> Date: Fri, 14 Mar 2025 09:59:54 +0000 Subject: [PATCH 49/66] Update 2 files - /core/modules/sqlite/tests/src/Unit/IdentifierTest.php - /core/modules/mysql/tests/src/Unit/IdentifierTest.php --- core/modules/mysql/tests/src/Unit/IdentifierTest.php | 4 +++- core/modules/sqlite/tests/src/Unit/IdentifierTest.php | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/modules/mysql/tests/src/Unit/IdentifierTest.php b/core/modules/mysql/tests/src/Unit/IdentifierTest.php index 0527f4b9ef49..75ddb9339867 100644 --- a/core/modules/mysql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/mysql/tests/src/Unit/IdentifierTest.php @@ -17,13 +17,14 @@ */ class IdentifierTest extends UnitTestCase { + // cSpell:disable + /** * Data provider for testTable. * * @return array * An associative array of test case data. */ - // cSpell:disable public static function providerTable(): array { return [ 'No prefix' => [ @@ -174,6 +175,7 @@ public static function providerTable(): array { ], ]; } + // cSpell:enable /** diff --git a/core/modules/sqlite/tests/src/Unit/IdentifierTest.php b/core/modules/sqlite/tests/src/Unit/IdentifierTest.php index 871062f1cbd6..a144ae6c1379 100644 --- a/core/modules/sqlite/tests/src/Unit/IdentifierTest.php +++ b/core/modules/sqlite/tests/src/Unit/IdentifierTest.php @@ -17,13 +17,14 @@ */ class IdentifierTest extends UnitTestCase { + // cSpell:disable + /** * Data provider for testTable. * * @return array * An associative array of test case data. */ - // cSpell:disable public static function providerTable(): array { return [ 'No prefix' => [ @@ -143,6 +144,7 @@ public static function providerTable(): array { ], ]; } + // cSpell:enable /** -- GitLab From 791f98a99d212af1e614543d01ca181e2f71bce1 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Fri, 14 Mar 2025 20:35:31 +0100 Subject: [PATCH 50/66] pgsql --- core/lib/Drupal/Core/Database/Connection.php | 6 +- .../src/Driver/Database/mysql/Connection.php | 9 +- .../src/Driver/Database/pgsql/Connection.php | 23 ++- .../Database/pgsql/IdentifierHandler.php | 46 +++++ .../pgsql/tests/src/Unit/IdentifierTest.php | 193 ++++++++++++++++++ .../src/Driver/Database/sqlite/Connection.php | 7 +- 6 files changed, 259 insertions(+), 25 deletions(-) create mode 100644 core/modules/pgsql/tests/src/Unit/IdentifierTest.php diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index d118e170671a..f485604b69c7 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -383,9 +383,11 @@ public function quoteIdentifiers($sql) { */ public function getFullQualifiedTableName($table) { $tableIdentifier = $this->identifiers->table($table); + // If already fully qualified, just pass it on. if ($tableIdentifier->database || $tableIdentifier->schema) { return $tableIdentifier->forMachine(); } + // Return as <schema>.<table>. return $this->identifiers->schema($this->getConnectionOptions()['database'])->forMachine() . '.' . $tableIdentifier->machineName; } @@ -971,7 +973,7 @@ public function condition($conjunction) { */ public function escapeDatabase($database) { @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); - return $this->identifiers->database($database)->forMachine(); + return $this->identifiers->schema($database)->forMachine(); } /** @@ -1224,6 +1226,8 @@ abstract public function databaseType(); * * @param string $database * The name of the database to create. + * + * @throws \Drupal\Core\Database\DatabaseNotFoundException */ abstract public function createDatabase($database); diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php index 6da0b1db7d35..8748db1203ef 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php @@ -295,16 +295,11 @@ public function databaseType() { } /** - * Overrides \Drupal\Core\Database\Connection::createDatabase(). - * - * @param string $database - * The name of the database to create. - * - * @throws \Drupal\Core\Database\DatabaseNotFoundException + * {@inheritdoc} */ public function createDatabase($database) { // Escape the database name. - $database = Database::getConnection()->escapeDatabase($database); + $database = Database::getConnection()->identifiers->schema($database)->forMachine(); try { // Create the database and set it as active. diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index 84d2651917db..df7f25cf93a9 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -262,16 +262,11 @@ public function databaseType() { } /** - * Overrides \Drupal\Core\Database\Connection::createDatabase(). - * - * @param string $database - * The name of the database to create. - * - * @throws \Drupal\Core\Database\DatabaseNotFoundException + * {@inheritdoc} */ public function createDatabase($database) { // Escape the database name. - $database = Database::getConnection()->escapeDatabase($database); + $database = Database::getConnection()->identifiers->database($database)->forMachine(); $db_created = FALSE; // Try to determine the proper locales for character classification and @@ -336,12 +331,18 @@ public function makeSequenceName($table, $field) { * {@inheritdoc} */ public function getFullQualifiedTableName($table) { - $options = $this->getConnectionOptions(); - $schema = $options['schema'] ?? 'public'; - + $tableIdentifier = $this->identifiers->table($table); + // If already fully qualified, just pass it on. + if ($tableIdentifier->database || $tableIdentifier->schema) { + return $tableIdentifier->forMachine(); + } // The fully qualified table name in PostgreSQL is in the form of // <database>.<schema>.<table>. - return $options['database'] . '.' . $schema . '.' . $this->identifiers->table($table)->forMachine(); + $options = $this->getConnectionOptions(); + $schema = $options['schema'] ?? 'public'; + return $this->identifiers->database($options['database'])->forMachine() . '.' . + $this->identifiers->schema($schema)->forMachine() . '.' . + $tableIdentifier->machineName; } /** diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php index d27746454f7c..5ce639b3f2c1 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php @@ -4,6 +4,7 @@ namespace Drupal\pgsql\Driver\Database\pgsql; +use Drupal\Core\Database\Exception\IdentifierException; use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Identifier\IdentifierType; @@ -20,4 +21,49 @@ public function getMaxLength(IdentifierType $type): int { return 63; } + /** + * {@inheritdoc} + */ + protected function validateCanonicalName(string $identifier, string $canonicalName, IdentifierType $type): true { + return match ($type) { + IdentifierType::Table => true, + default => parent::validateCanonicalName($identifier, $canonicalName, $type), + }; + } + + /** + * {@inheritdoc} + */ + protected function resolveTableForMachine(string $canonicalName, array $info): string { + if ($info['schema']) { + // We are processing a fully qualified table identifier, canonical name + // should not be processed, just checked it does not exceed max length. + if (strlen($canonicalName) > $this->getMaxLength(IdentifierType::Table)) { + throw new IdentifierException(sprintf( + 'Table identifier \'%s\' exceeds maximum allowed length (%d)', + $canonicalName, + $this->getMaxLength(IdentifierType::Table), + )); + } + return $this->quote($canonicalName); + } + + $prefix = $info['needs_prefix'] ? $this->tablePrefix : ''; + if (strlen($prefix . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { + $hash = substr(hash('sha256', $canonicalName), 0, 10); + $allowedLength = $this->getMaxLength(IdentifierType::Table) - strlen($this->tablePrefix) - 10; + if ($allowedLength < 4) { + throw new IdentifierException(sprintf( + 'Table canonical identifier \'%s\' cannot be converted into a machine identifier%s', + $canonicalName, + $info['needs_prefix'] ? "; table prefix '{$this->tablePrefix}'" : '', + )); + } + $lSize = (int) ($allowedLength / 2); + $rSize = $allowedLength - $lSize; + return $this->quote($prefix . substr($canonicalName, 0, $lSize) . $hash . substr($canonicalName, -$rSize)); + } + return $this->quote($prefix . $canonicalName); + } + } diff --git a/core/modules/pgsql/tests/src/Unit/IdentifierTest.php b/core/modules/pgsql/tests/src/Unit/IdentifierTest.php new file mode 100644 index 000000000000..c950022e7615 --- /dev/null +++ b/core/modules/pgsql/tests/src/Unit/IdentifierTest.php @@ -0,0 +1,193 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\pgsql\Unit; + +use Drupal\Core\Database\Exception\IdentifierException; +use Drupal\Core\Database\Identifier\IdentifierType; +use Drupal\pgsql\Driver\Database\pgsql\IdentifierHandler; +use Drupal\Tests\UnitTestCase; + +/** + * Tests PostgreSql database identifiers. + * + * @coversDefaultClass \Drupal\pgsql\Driver\Database\pgsql\IdentifierHandler + * @group Database + */ +class IdentifierTest extends UnitTestCase { + + /** + * Data provider for testTable. + * + * @return array + * An associative array of test case data. + */ + public static function providerTable(): array { + return [ + 'No prefix' => [ + 'identifier' => 'nocase', + 'prefix' => '', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"nocase"', + ], + 'Prefix' => [ + 'identifier' => 'nocase', + 'prefix' => 'foobar', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"foobarnocase"', + ], + 'No prefix, camelCase' => [ + 'identifier' => 'camelCase', + 'prefix' => '', + 'expectedCanonical' => 'camelCase', + 'expectedMachine' => '"camelCase"', + ], + 'Prefix, camelCase' => [ + 'identifier' => 'camelCase', + 'prefix' => 'foobar', + 'expectedCanonical' => 'camelCase', + 'expectedMachine' => '"foobarcamelCase"', + ], + 'No prefix, backtick' => [ + 'identifier' => '`backtick`', + 'prefix' => '', + 'expectedCanonical' => 'backtick', + 'expectedMachine' => '"backtick"', + ], + 'No prefix, brackets' => [ + 'identifier' => '[brackets]', + 'prefix' => '', + 'expectedCanonical' => 'brackets', + 'expectedMachine' => '"brackets"', + ], + 'No prefix, remove slash' => [ + 'identifier' => 'no/case', + 'prefix' => '', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"nocase"', + ], + 'No prefix, remove quote' => [ + 'identifier' => 'no"case', + 'prefix' => '', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '"nocase"', + ], + 'No prefix, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => '', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '"VeryVeryVeryVeryHungryHung25fa0c3753ngryHungryHungryCaterpillar"', + ], + 'Prefix, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'foobar', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '"foobarVeryVeryVeryVeryHungryH25fa0c3753yHungryHungryCaterpillar"', + ], + 'Prefix one less than overflow, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'prefix____12345678901234567890123456789012345678', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '"prefix____12345678901234567890123456789012345678Ve25fa0c3753lar"', + ], + 'Prefix just right, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'prefix____123456789012345678901234567890123456789', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '"prefix____123456789012345678901234567890123456789Ve25fa0c3753ar"', + ], + 'Prefix one more than overflow, shortened machine name' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'prefix____1234567890123456789012345678901234567890', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '', + 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; table prefix \'prefix____1234567890123456789012345678901234567890\'', + ], + 'Prefix too long to fit, table does not require shortening' => [ + 'identifier' => 'nocase', + 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', + 'expectedCanonical' => 'nocase', + 'expectedMachine' => '', + 'expectedException' => 'Table canonical identifier \'nocase\' cannot be converted into a machine identifier; table prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', + ], + 'Prefix too long to fit, table requires shortening' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', + 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '', + 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; table prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', + ], + // Sometimes, table names are following the pattern database.schema.table. + 'Fully qualified - too many parts: 4' => [ + 'identifier' => 'Chowra.Teressa.Bompuka.Katchal', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', + 'expectedException' => 'The table identifier \'Chowra.Teressa.Bompuka.Katchal\' does not comply with the syntax [database.][schema.]table', + ], + 'Fully qualified - no prefix' => [ + 'identifier' => '"Laouk"."Nancowry"."Tillangchong"', + 'prefix' => '', + 'expectedCanonical' => 'Laouk.Nancowry.Tillangchong', + 'expectedMachine' => '"Laouk"."Nancowry"."Tillangchong"', + ], + 'Fully qualified - prefix' => [ + 'identifier' => '"Laouk"."Nancowry"."Tillangchong"', + 'prefix' => 'foobar', + 'expectedCanonical' => 'Laouk.Nancowry.Tillangchong', + 'expectedMachine' => '"Laouk"."Nancowry"."Tillangchong"', + ], + 'Fully qualified - prefix not duplicated' => [ + 'identifier' => 'Laouk.Nancowry.foobarTillangchong', + 'prefix' => 'foobar', + 'expectedCanonical' => 'Laouk.Nancowry.foobarTillangchong', + 'expectedMachine' => '"Laouk"."Nancowry"."foobarTillangchong"', + ], + 'Fully qualified - remove not canonical characters' => [ + 'identifier' => '&Laouk£.!Nancowry?.$Tillangchong%%%', + 'prefix' => '', + 'expectedCanonical' => 'Laouk.Nancowry.Tillangchong', + 'expectedMachine' => '"Laouk"."Nancowry"."Tillangchong"', + ], + 'Fully qualified - invalid database name length' => [ + 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Laouk.Trinket', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', + 'expectedException' => 'The length of the database identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' once canonicalized to \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' is invalid (maximum allowed: 63)', + ], + 'Fully qualified - invalid schema name length' => [ + 'identifier' => 'Laouk.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar.Trinket', + 'prefix' => '', + 'expectedCanonical' => '', + 'expectedMachine' => '', + 'expectedException' => 'The length of the schema identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' once canonicalized to \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' is invalid (maximum allowed: 63)', + ], + 'Fully qualified - invalid table name length' => [ + 'identifier' => 'Laouk.Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'prefix' => '', + 'expectedCanonical' => 'Laouk.Camorta.VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', + 'expectedMachine' => '', + 'expectedException' => 'Table identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' exceeds maximum allowed length (63)', + ], + ]; + } + + /** + * @dataProvider providerTable + */ + public function testTable(string $identifier, string $prefix = '', ?string $expectedCanonical = '', ?string $expectedMachine = '', ?string $expectedException = NULL): void { + $handler = new IdentifierHandler($prefix); + if ($expectedException) { + $this->expectException(IdentifierException::class); + $this->expectExceptionMessage($expectedException); + } + $this->assertSame($expectedCanonical, $handler->table($identifier)->canonical()); + $this->assertSame($expectedMachine, $handler->table($identifier)->forMachine()); + // The machine name includes the quote characters so we need to subtract + // those from the length. + $this->assertLessThanOrEqual($handler->getMaxLength(IdentifierType::Table), strlen($handler->table($identifier)->forMachine()) - 2, 'Invalid machine table length.'); + } + +} diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 44114a77e0f1..85c7473c0463 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -382,12 +382,7 @@ public function databaseType() { } /** - * Overrides \Drupal\Core\Database\Connection::createDatabase(). - * - * @param string $database - * The name of the database to create. - * - * @throws \Drupal\Core\Database\DatabaseNotFoundException + * {@inheritdoc} */ public function createDatabase($database) { // Verify the database is writable. -- GitLab From a86fc316c463a03a3b94faf5976e54fe9fc62370 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Fri, 14 Mar 2025 21:41:42 +0100 Subject: [PATCH 51/66] cs fix --- .../pgsql/src/Driver/Database/pgsql/IdentifierHandler.php | 2 +- core/modules/pgsql/tests/src/Unit/IdentifierTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php index 5ce639b3f2c1..4163851976f4 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php @@ -26,7 +26,7 @@ public function getMaxLength(IdentifierType $type): int { */ protected function validateCanonicalName(string $identifier, string $canonicalName, IdentifierType $type): true { return match ($type) { - IdentifierType::Table => true, + IdentifierType::Table => TRUE, default => parent::validateCanonicalName($identifier, $canonicalName, $type), }; } diff --git a/core/modules/pgsql/tests/src/Unit/IdentifierTest.php b/core/modules/pgsql/tests/src/Unit/IdentifierTest.php index c950022e7615..08df2022a0fa 100644 --- a/core/modules/pgsql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/pgsql/tests/src/Unit/IdentifierTest.php @@ -17,6 +17,8 @@ */ class IdentifierTest extends UnitTestCase { + // cSpell:disable + /** * Data provider for testTable. * @@ -174,6 +176,8 @@ public static function providerTable(): array { ]; } + // cSpell:enable + /** * @dataProvider providerTable */ -- GitLab From 49ae688992aa97595269a327ba8692661f560d03 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sat, 15 Mar 2025 19:22:18 +0100 Subject: [PATCH 52/66] pgsql schema fixes --- .../src/Driver/Database/pgsql/Connection.php | 16 +++++----- .../Database/pgsql/IdentifierHandler.php | 30 ++++++++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index df7f25cf93a9..d59dd98bc470 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -87,17 +87,17 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn * Constructs a connection object. */ public function __construct(\PDO $connection, array $connection_options) { - // Sanitize the schema name here, so we do not have to do it in other - // functions. + // Get the IdentifierHandler early to process the schema if needed. + $identifierHandler = new IdentifierHandler($connection_options['prefix'] ?? ''); + + // Sanitize the schema name here. if (isset($connection_options['schema']) && ($connection_options['schema'] !== 'public')) { - $connection_options['schema'] = preg_replace('/[^A-Za-z0-9_]+/', '', $connection_options['schema']); + $schema = $identifierHandler->schema($connection_options['schema']); + $identifierHandler->setConnectionSchema($schema); + $connection_options['schema'] = $schema->canonical(); } - // We need to set the connectionOptions before the parent, because setPrefix - // needs this. - $this->connectionOptions = $connection_options; - - parent::__construct($connection, $connection_options, new IdentifierHandler($connection_options['prefix'])); + parent::__construct($connection, $connection_options, $identifierHandler); // Force PostgreSQL to use the UTF-8 character set by default. $this->connection->exec("SET NAMES 'UTF8'"); diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php index 4163851976f4..505b23e2fb9e 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php @@ -7,12 +7,28 @@ use Drupal\Core\Database\Exception\IdentifierException; use Drupal\Core\Database\Identifier\IdentifierHandlerBase; use Drupal\Core\Database\Identifier\IdentifierType; +use Drupal\Core\Database\Identifier\Schema; /** * MySQL implementation of the identifier handler. */ class IdentifierHandler extends IdentifierHandlerBase { + /** + * The default schema identifier, if specified. + */ + protected ?Schema $schema; + + /** + * @todo fill in. + */ + public function setConnectionSchema(Schema $identifier): void { + if (isset($this->schema)) { + throw new \LogicException("A default schema identifier {$this->schema} is already set for this connection"); + } + $this->schema = $identifier; + } + /** * {@inheritdoc} */ @@ -35,7 +51,7 @@ protected function validateCanonicalName(string $identifier, string $canonicalNa * {@inheritdoc} */ protected function resolveTableForMachine(string $canonicalName, array $info): string { - if ($info['schema']) { + if ($info['schema'] && ($info['schema_default_added'] ?? FALSE) === FALSE) { // We are processing a fully qualified table identifier, canonical name // should not be processed, just checked it does not exceed max length. if (strlen($canonicalName) > $this->getMaxLength(IdentifierType::Table)) { @@ -66,4 +82,16 @@ protected function resolveTableForMachine(string $canonicalName, array $info): s return $this->quote($prefix . $canonicalName); } + /** + * {@inheritdoc} + */ + public function parseTableIdentifier(string $identifier): array { + $parts = parent::parseTableIdentifier($identifier); + if (isset($this->schema) && $parts['schema'] === NULL) { + $parts['schema'] = $this->schema; + $parts['schema_default_added'] = TRUE; + } + return $parts; + } + } -- GitLab From bca2707fa8a9f565af254eb3ecb00fcbeb96093b Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 16 Mar 2025 11:34:01 +0100 Subject: [PATCH 53/66] fix --- .../src/Driver/Database/pgsql/Connection.php | 17 +++++----- .../Database/pgsql/IdentifierHandler.php | 31 ++++++++++++++----- .../OpenTelemetryNodePagePerformanceTest.php | 4 +-- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index d59dd98bc470..fa88b9d0a922 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -87,17 +87,14 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn * Constructs a connection object. */ public function __construct(\PDO $connection, array $connection_options) { - // Get the IdentifierHandler early to process the schema if needed. - $identifierHandler = new IdentifierHandler($connection_options['prefix'] ?? ''); - - // Sanitize the schema name here. - if (isset($connection_options['schema']) && ($connection_options['schema'] !== 'public')) { - $schema = $identifierHandler->schema($connection_options['schema']); - $identifierHandler->setConnectionSchema($schema); - $connection_options['schema'] = $schema->canonical(); - } + // Manage the table prefix. + $prefix = $connection_options['prefix'] ?? ''; + assert(is_string($prefix), 'The \'prefix\' connection option to ' . __METHOD__ . '() must be a string.'); + + // Manage the schema name. + $defaultSchema = $connection_options['schema'] ?? ''; - parent::__construct($connection, $connection_options, $identifierHandler); + parent::__construct($connection, $connection_options, new IdentifierHandler($prefix, $defaultSchema)); // Force PostgreSQL to use the UTF-8 character set by default. $this->connection->exec("SET NAMES 'UTF8'"); diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php index 505b23e2fb9e..af45236f3dd4 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php @@ -17,16 +17,31 @@ class IdentifierHandler extends IdentifierHandlerBase { /** * The default schema identifier, if specified. */ - protected ?Schema $schema; + public readonly ?Schema $defaultSchema; /** - * @todo fill in. + * Constructor. + * + * @param string $tablePrefix + * The table prefix to be used by the database connection. + * @param string $defaultSchema + * The default schema to use for database operations. If not specified, + * it's assumed to use the 'public' schema. + * @param array{0:string, 1:string} $identifierQuotes + * The identifier quote characters for the database type. An array + * containing the start and end identifier quote characters for the + * database type. The ANSI SQL standard identifier quote character is a + * double quotation mark. */ - public function setConnectionSchema(Schema $identifier): void { - if (isset($this->schema)) { - throw new \LogicException("A default schema identifier {$this->schema} is already set for this connection"); + public function __construct( + string $tablePrefix, + string $defaultSchema = '', + array $identifierQuotes = ['"', '"'], + ) { + parent::__construct($tablePrefix, $identifierQuotes); + if ($defaultSchema !== '' && $defaultSchema !== 'public') { + $this->defaultSchema = $this->schema($defaultSchema); } - $this->schema = $identifier; } /** @@ -87,8 +102,8 @@ protected function resolveTableForMachine(string $canonicalName, array $info): s */ public function parseTableIdentifier(string $identifier): array { $parts = parent::parseTableIdentifier($identifier); - if (isset($this->schema) && $parts['schema'] === NULL) { - $parts['schema'] = $this->schema; + if (isset($this->defaultSchema) && $parts['schema'] === NULL) { + $parts['schema'] = $this->defaultSchema; $parts['schema_default_added'] = TRUE; } return $parts; diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php index 7204388bbb34..be3031b0e286 100644 --- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php +++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php @@ -279,9 +279,9 @@ protected function testNodePageWarmCache(): void { 'SELECT "base_table"."id" AS "id", "base_table"."path" AS "path", "base_table"."alias" AS "alias", "base_table"."langcode" AS "langcode" FROM "path_alias" "base_table" WHERE ("base_table"."status" = 1) AND ("base_table"."alias" LIKE "/node" ESCAPE \'\\\\\') AND ("base_table"."langcode" IN ("en", "und")) ORDER BY "base_table"."langcode" ASC, "base_table"."id" DESC', 'SELECT "menu_tree"."menu_name" AS "menu_name", "menu_tree"."route_name" AS "route_name", "menu_tree"."route_parameters" AS "route_parameters", "menu_tree"."url" AS "url", "menu_tree"."title" AS "title", "menu_tree"."description" AS "description", "menu_tree"."parent" AS "parent", "menu_tree"."weight" AS "weight", "menu_tree"."options" AS "options", "menu_tree"."expanded" AS "expanded", "menu_tree"."enabled" AS "enabled", "menu_tree"."provider" AS "provider", "menu_tree"."metadata" AS "metadata", "menu_tree"."class" AS "class", "menu_tree"."form_class" AS "form_class", "menu_tree"."id" AS "id" FROM "menu_tree" "menu_tree" WHERE ("route_name" = "entity.node.canonical") AND ("route_param_key" = "node=1") AND ("menu_name" = "footer") ORDER BY "depth" ASC, "weight" ASC, "id" ASC', 'INSERT INTO "semaphore" ("name", "value", "expire") VALUES ("theme_registry:runtime:umami:Drupal\Core\Utility\ThemeRegistry", "LOCK_ID", "EXPIRE")', - 'DELETE FROM "semaphore" WHERE ("name" = "theme_registry:runtime:umami:Drupal\Core\Utility\ThemeRegistry") AND ("value" = "LOCK_ID")', + 'DELETE FROM "semaphore" WHERE ("name" = "theme_registry:runtime:umami:Drupal\Core\Utility\ThemeRegistry") AND ("value" = "LOCK_ID")', 'INSERT INTO "semaphore" ("name", "value", "expire") VALUES ("active-trail:route:entity.node.canonical:route_parameters:a:1:{s:4:"node";s:1:"1";}:Drupal\Core\Cache\CacheCollector", "LOCK_ID", "EXPIRE")', - 'DELETE FROM "semaphore" WHERE ("name" = "active-trail:route:entity.node.canonical:route_parameters:a:1:{s:4:"node";s:1:"1";}:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', + 'DELETE FROM "semaphore" WHERE ("name" = "active-trail:route:entity.node.canonical:route_parameters:a:1:{s:4:"node";s:1:"1";}:Drupal\Core\Cache\CacheCollector") AND ("value" = "LOCK_ID")', ]; $recorded_queries = $performance_data->getQueries(); $this->assertSame($expected_queries, $recorded_queries); -- GitLab From 2b13db1db97a1d2767bcc0095bcf5628944375e5 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 16 Mar 2025 12:05:15 +0100 Subject: [PATCH 54/66] fix --- .../Identifier/IdentifierHandlerBase.php | 19 ++++++++++++++ .../Database/mysql/IdentifierHandler.php | 14 ++-------- .../mysql/tests/src/Unit/IdentifierTest.php | 6 ++--- .../Database/pgsql/IdentifierHandler.php | 26 +++++++------------ .../pgsql/tests/src/Unit/IdentifierTest.php | 6 ++--- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index 44f3e0ce899e..9c9052b25410 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -193,6 +193,25 @@ public function resolveForMachine(string $canonicalName, array $info, Identifier }; } + /** + * @todo fill in. + */ + protected function cropByHashing(string $canonicalName, array $info, IdentifierType $type, string $prefix, int $length, int $hashLength): string { + $allowedLength = $length - strlen($prefix) - $hashLength; + if ($allowedLength < 4) { + throw new IdentifierException(sprintf( + '%s canonical identifier \'%s\' cannot be converted into a machine identifier%s', + ucfirst($type->value), + $canonicalName, + $prefix !== '' ? "; prefix '{$prefix}'" : '', + )); + } + $hash = substr(hash('sha256', $canonicalName), 0, $hashLength); + $lSize = (int) ($allowedLength / 2); + $rSize = $allowedLength - $lSize; + return $prefix . substr($canonicalName, 0, $lSize) . $hash . substr($canonicalName, -$rSize); + } + /** * @todo fill in. */ diff --git a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php index d191f2ecf9f0..dddb18bacce5 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php +++ b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php @@ -67,18 +67,8 @@ protected function resolveTableForMachine(string $canonicalName, array $info): s $prefix = $info['needs_prefix'] ? $this->tablePrefix : ''; if (strlen($prefix . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { - $hash = substr(hash('sha256', $canonicalName), 0, 10); - $allowedLength = $this->getMaxLength(IdentifierType::Table) - strlen($this->tablePrefix) - 10; - if ($allowedLength < 4) { - throw new IdentifierException(sprintf( - 'Table canonical identifier \'%s\' cannot be converted into a machine identifier%s', - $canonicalName, - $info['needs_prefix'] ? "; table prefix '{$this->tablePrefix}'" : '', - )); - } - $lSize = (int) ($allowedLength / 2); - $rSize = $allowedLength - $lSize; - return $this->quote($prefix . substr($canonicalName, 0, $lSize) . $hash . substr($canonicalName, -$rSize)); + // We shorten too long table names. + return $this->quote($this->cropByHashing($canonicalName, $info, IdentifierType::Table, $prefix, $this->getMaxLength(IdentifierType::Table), 10)); } return $this->quote($prefix . $canonicalName); } diff --git a/core/modules/mysql/tests/src/Unit/IdentifierTest.php b/core/modules/mysql/tests/src/Unit/IdentifierTest.php index 75ddb9339867..a2349b898bc2 100644 --- a/core/modules/mysql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/mysql/tests/src/Unit/IdentifierTest.php @@ -104,21 +104,21 @@ public static function providerTable(): array { 'prefix' => 'prefix____12345678901234567890123456789012345678901', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'expectedMachine' => '', - 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; table prefix \'prefix____12345678901234567890123456789012345678901\'', + 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; prefix \'prefix____12345678901234567890123456789012345678901\'', ], 'Prefix too long to fit, table does not require shortening' => [ 'identifier' => 'nocase', 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', 'expectedCanonical' => 'nocase', 'expectedMachine' => '', - 'expectedException' => 'Table canonical identifier \'nocase\' cannot be converted into a machine identifier; table prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', + 'expectedException' => 'Table canonical identifier \'nocase\' cannot be converted into a machine identifier; prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', ], 'Prefix too long to fit, table requires shortening' => [ 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'expectedMachine' => '', - 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; table prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', + 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', ], // Sometimes, table names are following the pattern database.schema.table. 'Fully qualified - too many parts: 4' => [ diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php index af45236f3dd4..9ba008007246 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php @@ -10,7 +10,7 @@ use Drupal\Core\Database\Identifier\Schema; /** - * MySQL implementation of the identifier handler. + * PostgreSql implementation of the identifier handler. */ class IdentifierHandler extends IdentifierHandlerBase { @@ -28,10 +28,7 @@ class IdentifierHandler extends IdentifierHandlerBase { * The default schema to use for database operations. If not specified, * it's assumed to use the 'public' schema. * @param array{0:string, 1:string} $identifierQuotes - * The identifier quote characters for the database type. An array - * containing the start and end identifier quote characters for the - * database type. The ANSI SQL standard identifier quote character is a - * double quotation mark. + * The identifier quote characters. */ public function __construct( string $tablePrefix, @@ -42,6 +39,9 @@ public function __construct( if ($defaultSchema !== '' && $defaultSchema !== 'public') { $this->defaultSchema = $this->schema($defaultSchema); } + else { + $this->defaultSchema = NULL; + } } /** @@ -81,18 +81,8 @@ protected function resolveTableForMachine(string $canonicalName, array $info): s $prefix = $info['needs_prefix'] ? $this->tablePrefix : ''; if (strlen($prefix . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { - $hash = substr(hash('sha256', $canonicalName), 0, 10); - $allowedLength = $this->getMaxLength(IdentifierType::Table) - strlen($this->tablePrefix) - 10; - if ($allowedLength < 4) { - throw new IdentifierException(sprintf( - 'Table canonical identifier \'%s\' cannot be converted into a machine identifier%s', - $canonicalName, - $info['needs_prefix'] ? "; table prefix '{$this->tablePrefix}'" : '', - )); - } - $lSize = (int) ($allowedLength / 2); - $rSize = $allowedLength - $lSize; - return $this->quote($prefix . substr($canonicalName, 0, $lSize) . $hash . substr($canonicalName, -$rSize)); + // We shorten too long table names. + return $this->quote($this->cropByHashing($canonicalName, $info, IdentifierType::Table, $prefix, $this->getMaxLength(IdentifierType::Table), 10)); } return $this->quote($prefix . $canonicalName); } @@ -103,6 +93,8 @@ protected function resolveTableForMachine(string $canonicalName, array $info): s public function parseTableIdentifier(string $identifier): array { $parts = parent::parseTableIdentifier($identifier); if (isset($this->defaultSchema) && $parts['schema'] === NULL) { + // When a non-public schema is defined for the connection, machine names + // for table identifiers should be in the "schema"."table" format. $parts['schema'] = $this->defaultSchema; $parts['schema_default_added'] = TRUE; } diff --git a/core/modules/pgsql/tests/src/Unit/IdentifierTest.php b/core/modules/pgsql/tests/src/Unit/IdentifierTest.php index 08df2022a0fa..052e0fde3a9f 100644 --- a/core/modules/pgsql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/pgsql/tests/src/Unit/IdentifierTest.php @@ -104,21 +104,21 @@ public static function providerTable(): array { 'prefix' => 'prefix____1234567890123456789012345678901234567890', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'expectedMachine' => '', - 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; table prefix \'prefix____1234567890123456789012345678901234567890\'', + 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; prefix \'prefix____1234567890123456789012345678901234567890\'', ], 'Prefix too long to fit, table does not require shortening' => [ 'identifier' => 'nocase', 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', 'expectedCanonical' => 'nocase', 'expectedMachine' => '', - 'expectedException' => 'Table canonical identifier \'nocase\' cannot be converted into a machine identifier; table prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', + 'expectedException' => 'Table canonical identifier \'nocase\' cannot be converted into a machine identifier; prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', ], 'Prefix too long to fit, table requires shortening' => [ 'identifier' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'prefix' => 'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix', 'expectedCanonical' => 'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar', 'expectedMachine' => '', - 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; table prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', + 'expectedException' => 'Table canonical identifier \'VeryVeryVeryVeryHungryHungryHungryHungryHungryHungryHungryCaterpillar\' cannot be converted into a machine identifier; prefix \'VeryVeryVeryVeryVeryVeryVeryVeryLongLongLongLongLongLongLongPrefix\'', ], // Sometimes, table names are following the pattern database.schema.table. 'Fully qualified - too many parts: 4' => [ -- GitLab From fda8ad184987717a8330e41e8357377852768f5b Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 16 Mar 2025 12:57:47 +0100 Subject: [PATCH 55/66] fix for deprecated method --- core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php index be23a3679e0b..e26e298bd740 100644 --- a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php +++ b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php @@ -323,7 +323,7 @@ public function testTableExists(): void { ], ]; $this->schema->createTable($table_name, $table_specification); - $prefixed_table_name = $this->connection->getPrefix($table_name) . $table_name; + $prefixed_table_name = $this->connection->identifiers->tablePrefix . $table_name; // Three different calls to the method Schema::tableExists() with an // unprefixed table name. -- GitLab From a9bffa2ffe51017af450706f59aa010b48227da0 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 16 Mar 2025 15:23:50 +0100 Subject: [PATCH 56/66] add docs for the Identier classes --- .../Core/Database/Identifier/Database.php | 2 +- .../Identifier/IdentifierHandlerBase.php | 229 +++++++++++++----- .../Database/Identifier/IdentifierType.php | 18 ++ .../Core/Database/Identifier/Schema.php | 2 +- .../Drupal/Core/Database/Identifier/Table.php | 2 +- 5 files changed, 188 insertions(+), 65 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/Database.php b/core/lib/Drupal/Core/Database/Identifier/Database.php index f1ba4fbd5675..82c0743329ec 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Database.php +++ b/core/lib/Drupal/Core/Database/Identifier/Database.php @@ -10,7 +10,7 @@ * When using full notation, a table can be identified as * [database.][schema.]table. */ -class Database extends IdentifierBase { +final class Database extends IdentifierBase { public function __construct( IdentifierHandlerBase $identifierHandler, diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index 9c9052b25410..c1f3484391c7 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -17,8 +17,6 @@ abstract class IdentifierHandlerBase { /** * A cache of all identifiers handled. - * - * @var array{'identifier':array<string,array<string,string>>,'machine':array<string,array<string,string>>} */ protected array $identifiers; @@ -41,75 +39,113 @@ public function __construct( } /** - * @todo fill in. + * Returns a database identifier value object. + * + * @param string|\Drupal\Core\Database\Identifier\Database $identifier + * A database name as a string, or a Database value object. + * + * @return \Drupal\Core\Database\Identifier\Database + * A database identifier value object. */ public function database(string|Database $identifier): Database { - if ($identifier instanceof Database) { - $identifier = $identifier->identifier; - } - if ($this->hasIdentifier($identifier, IdentifierType::Database)) { - $database = $this->getIdentifier($identifier, IdentifierType::Database); - } - else { - $database = new Database($this, $identifier); - $this->setIdentifier($identifier, IdentifierType::Database, $database); - } - return $database; + return $this->getIdentifierValueObject(IdentifierType::Database, $identifier); } /** - * @todo fill in. + * Returns a schema identifier value object. + * + * @param string|\Drupal\Core\Database\Identifier\Schema $identifier + * A schema name as a string, or a Schema value object. + * + * @return \Drupal\Core\Database\Identifier\Schema + * A schema identifier value object. */ public function schema(string|Schema $identifier): Schema { - if ($identifier instanceof Schema) { - $identifier = $identifier->identifier; - } - if ($this->hasIdentifier($identifier, IdentifierType::Schema)) { - $schema = $this->getIdentifier($identifier, IdentifierType::Schema); - } - else { - $schema = new Schema($this, $identifier); - $this->setIdentifier($identifier, IdentifierType::Schema, $schema); - } - return $schema; + return $this->getIdentifierValueObject(IdentifierType::Schema, $identifier); } /** - * @todo fill in. + * Returns a table identifier value object. + * + * @param string|\Drupal\Core\Database\Identifier\Table $identifier + * A table name as a string, or a Table value object. + * + * @return \Drupal\Core\Database\Identifier\Table + * A table identifier value object. */ - public function table(string|Table $tableIdentifier): Table { - if ($tableIdentifier instanceof Table) { - $tableIdentifier = $tableIdentifier->identifier; + public function table(string|Table $identifier): Table { + return $this->getIdentifierValueObject(IdentifierType::Table, $identifier); + } + + /** + * Returns the value object of an identifier. + * + * @param \Drupal\Core\Database\Identifier\IdentifierType $type + * The type of identifier. + * @param string|\Drupal\Core\Database\Identifier\IdentifierBase $identifier + * An identifier as a string, or an identifier value object. + * + * @return \Drupal\Core\Database\Identifier\IdentifierBase + * An identifier value object. + */ + protected function getIdentifierValueObject(IdentifierType $type, string|IdentifierBase $identifier): IdentifierBase { + $valueObjectClass = IdentifierType::valueObjectClass($type); + if ($identifier instanceof $valueObjectClass) { + $identifier = $identifier->identifier; } - if ($this->hasIdentifier($tableIdentifier, IdentifierType::Table)) { - $table = $this->getIdentifier($tableIdentifier, IdentifierType::Table); + if ($this->isCached($identifier, $type)) { + $valueObject = $this->fromCache($identifier, $type); } else { - $table = new Table($this, $tableIdentifier); - $this->setIdentifier($tableIdentifier, IdentifierType::Table, $table); + $valueObject = new $valueObjectClass($this, $identifier); + $this->toCache($identifier, $type, $valueObject); } - return $table; + return $valueObject; } /** - * @todo fill in. + * Adds an identifier value object to the local cache. + * + * @param string $identifier + * The raw identifier string. + * @param \Drupal\Core\Database\Identifier\IdentifierType $type + * The type of identifier. + * @param \Drupal\Core\Database\Identifier\IdentifierBase $identifierValueObject + * The identifier value object. */ - protected function setIdentifier(string $id, IdentifierType $type, IdentifierBase $identifier): void { - $this->identifiers['identifier'][$id][$type->value] = $identifier; + protected function toCache(string $identifier, IdentifierType $type, IdentifierBase $identifierValueObject): void { + $this->identifiers['identifier'][$identifier][$type->value] = $identifierValueObject; } /** - * @todo fill in. + * Checks if an identifier value object is present in the local cache. + * + * @param string $identifier + * The raw identifier string. + * @param \Drupal\Core\Database\Identifier\IdentifierType $type + * The type of identifier. + * + * @return bool + * TRUE if the identifier value object is available in tha local cache, + * FALSE otherwise. */ - protected function hasIdentifier(string $id, IdentifierType $type): bool { - return isset($this->identifiers['identifier'][$id][$type->value]); + protected function isCached(string $identifier, IdentifierType $type): bool { + return isset($this->identifiers['identifier'][$identifier][$type->value]); } /** - * @todo fill in. + * Gets an identifier value object from the local cache. + * + * @param string $identifier + * The raw identifier string. + * @param \Drupal\Core\Database\Identifier\IdentifierType $type + * The type of identifier. + * + * @return \Drupal\Core\Database\Identifier\IdentifierBase + * The identifier value object. */ - protected function getIdentifier(string $id, IdentifierType $type): IdentifierBase { - return $this->identifiers['identifier'][$id][$type->value]; + protected function fromCache(string $identifier, IdentifierType $type): IdentifierBase { + return $this->identifiers['identifier'][$identifier][$type->value]; } /** @@ -121,7 +157,13 @@ protected function getIdentifier(string $id, IdentifierType $type): IdentifierBa abstract public function getMaxLength(IdentifierType $type): int; /** - * @todo fill in. + * Returns a string with initial and final quote characters. + * + * @param string $value + * The input string + * + * @return string + * The quoted string. */ public function quote(string $value): string { return $this->identifierQuotes[0] . $value . $this->identifierQuotes[1]; @@ -184,17 +226,30 @@ protected function validateCanonicalName(string $identifier, string $canonicalNa } /** - * @todo fill in. - */ - public function resolveForMachine(string $canonicalName, array $info, IdentifierType $type): string { - return match ($type) { - IdentifierType::Table => $this->resolveTableForMachine($canonicalName, $info), - default => $this->quote($canonicalName), - }; - } - - /** - * @todo fill in. + * Shortens an identifier's canonical name by adding an hash. + * + * This method calculates an hash of the canonical name and then returns a + * string suitable for machine use. The hash is insterted in the middle of + * the remaining part of the canonical name once a prefix has been added. + * + * @param string $canonicalName + * A canonical identifier string. + * @param array<string,mixed> $info + * An associative array of context information. + * @param \Drupal\Core\Database\Identifier\IdentifierType $type + * The type of identifier. + * @param string $prefix + * A prefix that cannot be part of the shortening. + * @param positive-int $length + * The maximum length of the returned string. + * @param positive-int $hashLength + * The length of the hashed part in the returned string. + * + * @return string + * The shortened string. + * + * @throws \Drupal\Core\Database\Exception\IdentifierException + * If a shortened string could not be calculated. */ protected function cropByHashing(string $canonicalName, array $info, IdentifierType $type, string $prefix, int $length, int $hashLength): string { $allowedLength = $length - strlen($prefix) - $hashLength; @@ -213,7 +268,46 @@ protected function cropByHashing(string $canonicalName, array $info, IdentifierT } /** - * @todo fill in. + * Returns the machine accepted string for an identifer. + * + * This method converts a canonical identifier in the machine readable + * version. It could shorten the canonical name or perform other + * transformation as necessary. The returned value is stored in the + * identifier's $machineName property. + * + * @param string $canonicalName + * A canonical identifier string. + * @param array<string,mixed> $info + * An associative array of context information. + * @param \Drupal\Core\Database\Identifier\IdentifierType $type + * The type of identifier. + * + * @return string + * The machine accepted string for an identifer. + * + * @throws \Drupal\Core\Database\Exception\IdentifierException + * If a machine string could not be determined. + */ + public function resolveForMachine(string $canonicalName, array $info, IdentifierType $type): string { + return match ($type) { + IdentifierType::Table => $this->resolveTableForMachine($canonicalName, $info), + default => $this->quote($canonicalName), + }; + } + + /** + * Returns the machine accepted string for a table. + * + * @param string $canonicalName + * A canonical table name string. + * @param array<string,mixed> $info + * An associative array of context information. + * + * @return string + * The machine accepted string for a table. + * + * @throws \Drupal\Core\Database\Exception\IdentifierException + * If a machine string could not be determined. */ protected function resolveTableForMachine(string $canonicalName, array $info): string { if (strlen($info['needs_prefix'] ? $this->tablePrefix : '' . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { @@ -229,7 +323,21 @@ protected function resolveTableForMachine(string $canonicalName, array $info): s } /** - * @todo fill in. + * Parses a raw table identifier string into its components. + * + * A raw table identifier may include database and/or schema information in + * the format [database.][schema.]table and may include quote characters. + * This method returns the parts that can be used to get a Table identifier + * value object. + * + * @param string $identifier + * A raw table identifier string. + * + * @return array{database: \Drupal\Core\Database\Identifier\Database|null,schema: \Drupal\Core\Database\Identifier\Schema|null, table: string, needs_prefix: bool} + * The parts that can be used to get a Table identifier value object. + * + * @throws \Drupal\Core\Database\Exception\IdentifierException + * If an error occurred. */ public function parseTableIdentifier(string $identifier): array { $parts = explode(".", $identifier); @@ -243,10 +351,7 @@ public function parseTableIdentifier(string $identifier): array { )), }; if ($this->tablePrefix !== '') { - $needsPrefix = match (count($parts)) { - 1 => TRUE, - default => FALSE, - }; + $needsPrefix = count($parts) === 1; } else { $needsPrefix = FALSE; diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php index 52dafe0a4f48..67515d8e179d 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierType.php @@ -18,4 +18,22 @@ enum IdentifierType: string { case Alias = 'alias'; case Unknown = 'unknown'; + + /** + * Returns the value object class for an identifier type. + * + * @param Drupal\Core\Database\Identifier\IdentifierType $case + * The identifier type. + * + * @return class-string<\Drupal\Core\Database\Identifier\IdentifierBase> + * The class of the identifier type value object. + */ + public static function valueObjectClass(self $case): string { + return match ($case) { + IdentifierType::Database => Database::class, + IdentifierType::Schema => Schema::class, + IdentifierType::Table => Table::class, + }; + } + } diff --git a/core/lib/Drupal/Core/Database/Identifier/Schema.php b/core/lib/Drupal/Core/Database/Identifier/Schema.php index b2c5af9147dd..0ba66888f69d 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Schema.php +++ b/core/lib/Drupal/Core/Database/Identifier/Schema.php @@ -10,7 +10,7 @@ * When using full notation, a table can be identified as * [database.][schema.]table. */ -class Schema extends IdentifierBase { +final class Schema extends IdentifierBase { public function __construct( IdentifierHandlerBase $identifierHandler, diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 8f6cbef361b2..1e123670a00c 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -10,7 +10,7 @@ * When using full notation, a table can be identified as * [database.][schema.]table. */ -class Table extends IdentifierBase { +final class Table extends IdentifierBase { /** * The database identifier, if specified. -- GitLab From 3112c727700f8010ffda65e6588e53440df81602 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 16 Mar 2025 15:39:54 +0100 Subject: [PATCH 57/66] cs fixes --- .../Core/Database/Identifier/IdentifierHandlerBase.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index c1f3484391c7..626f2260aeee 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -160,7 +160,7 @@ abstract public function getMaxLength(IdentifierType $type): int; * Returns a string with initial and final quote characters. * * @param string $value - * The input string + * The input string. * * @return string * The quoted string. @@ -229,7 +229,7 @@ protected function validateCanonicalName(string $identifier, string $canonicalNa * Shortens an identifier's canonical name by adding an hash. * * This method calculates an hash of the canonical name and then returns a - * string suitable for machine use. The hash is insterted in the middle of + * string suitable for machine use. The hash is inserted in the middle of * the remaining part of the canonical name once a prefix has been added. * * @param string $canonicalName @@ -268,7 +268,7 @@ protected function cropByHashing(string $canonicalName, array $info, IdentifierT } /** - * Returns the machine accepted string for an identifer. + * Returns the machine accepted string for an identifier. * * This method converts a canonical identifier in the machine readable * version. It could shorten the canonical name or perform other @@ -283,7 +283,7 @@ protected function cropByHashing(string $canonicalName, array $info, IdentifierT * The type of identifier. * * @return string - * The machine accepted string for an identifer. + * The machine accepted string for an identifier. * * @throws \Drupal\Core\Database\Exception\IdentifierException * If a machine string could not be determined. -- GitLab From 6b9144abc8da6ad46ec315c04e26f464333a0884 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 16 Mar 2025 22:02:56 +0100 Subject: [PATCH 58/66] proper deprecations in Connection --- core/lib/Drupal/Core/Database/Connection.php | 29 ++++++++-- .../Tests/Core/Database/ConnectionTest.php | 58 +++++++++++++++++-- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index f485604b69c7..f92ae0e5b941 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -171,7 +171,7 @@ public function __construct( */ public function __get($name): mixed { if (in_array($name, ['prefix', 'escapedTables', 'identifierQuotes', 'tablePlaceholderReplacements'])) { - @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.9.0 and the property is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/1234567", E_USER_DEPRECATED); + @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.9.0 and the property is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); return []; } return NULL; @@ -309,9 +309,14 @@ public function attachDatabase(string $database): void { * * @return string * The table prefix. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * IdentifierHandler methods instead. + * + * @see https://www.drupal.org/node/3513282 */ public function getPrefix(): string { - @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); return $this->identifiers->tablePrefix; } @@ -320,9 +325,14 @@ public function getPrefix(): string { * * @param string $prefix * A single prefix. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass the + * table prefix to the IdentifierHandler constructor instead. + * + * @see https://www.drupal.org/node/3513282 */ protected function setPrefix($prefix) { - @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass the table prefix to the IdentifierHandler constructor instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); } /** @@ -970,9 +980,14 @@ public function condition($conjunction) { * * @return string * The sanitized database name. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * IdentifierHandler methods instead. + * + * @see https://www.drupal.org/node/3513282 */ public function escapeDatabase($database) { - @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); return $this->identifiers->schema($database)->forMachine(); } @@ -990,11 +1005,15 @@ public function escapeDatabase($database) { * @return string * The sanitized table name. * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * IdentifierHandler methods instead. + * + * @see https://www.drupal.org/node/3513282 * @see \Drupal\Core\Database\Connection::prefixTables() * @see \Drupal\Core\Database\Connection::setPrefix() */ public function escapeTable($table) { - @trigger_error(__METHOD__ . "() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312", E_USER_DEPRECATED); + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); return $this->identifiers->table($table)->canonical(); } diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index 1d43896a1c40..b287fbe6af0d 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -22,6 +22,52 @@ */ class ConnectionTest extends UnitTestCase { + /** + * Data provider for testPrefixRoundTrip(). + * + * @return array + * Array of arrays with the following elements: + * - Arguments to pass to Connection::setPrefix(). + * - Expected result from Connection::getPrefix(). + */ + public static function providerPrefixRoundTrip() { + return [ + [ + [ + '' => 'test_', + ], + 'test_', + ], + [ + [ + 'fooTable' => 'foo_', + 'barTable' => 'foo_', + ], + 'foo_', + ], + ]; + } + + /** + * Exercise setPrefix() and getPrefix(). + */ + #[DataProvider('providerEscapeTables')] + public function testPrefixRoundTrip($expected, $prefix_info): void { + $mock_pdo = $this->createMock(StubPDO::class); + $connection = new StubConnection($mock_pdo, []); + + // setPrefix() is protected, so we make it accessible with reflection. + $reflection = new \ReflectionClass(StubConnection::class); + $set_prefix = $reflection->getMethod('setPrefix'); + + // Set the prefix data. + $set_prefix->invokeArgs($connection, [$prefix_info]); + // Check the round-trip. + foreach ($expected as $prefix) { + $this->assertEquals($prefix, $connection->getPrefix()); + } + } + /** * Data provider for testPrefixTables(). * @@ -428,10 +474,10 @@ public static function providerEscapeTables() { } /** - * @covers ::escapeTable - * @dataProvider providerEscapeTables - * @group legacy + * Tests legacy ::escapeTable */ + #[IgnoreDeprecations] + #[DataProvider('providerEscapeTables')] public function testEscapeTable($expected, $name, array $identifier_quote = ['"', '"']): void { $this->expectDeprecation('Drupal\Core\Database\Connection::escapeTable() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312'); $mock_pdo = $this->createMock(StubPDO::class); @@ -524,10 +570,10 @@ public static function providerEscapeDatabase() { } /** - * @covers ::escapeDatabase - * @dataProvider providerEscapeDatabase - * @group legacy + * Tests legacy ::escapeDatabase */ + #[IgnoreDeprecations] + #[DataProvider('providerEscapeDatabase')] public function testEscapeDatabase($expected, $name, array $identifier_quote = ['"', '"']): void { $this->expectDeprecation('Drupal\Core\Database\Connection::escapeDatabase() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312'); $mock_pdo = $this->createMock(StubPDO::class); -- GitLab From f71282eb10cf65b5cc3803cd68770f8a40ca628d Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 16 Mar 2025 22:30:32 +0100 Subject: [PATCH 59/66] fixes --- .../Tests/Core/Database/ConnectionTest.php | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index b287fbe6af0d..9eeb586d2701 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -30,7 +30,7 @@ class ConnectionTest extends UnitTestCase { * - Arguments to pass to Connection::setPrefix(). * - Expected result from Connection::getPrefix(). */ - public static function providerPrefixRoundTrip() { + public static function providerPrefixRoundTrip(): array { return [ [ [ @@ -49,12 +49,16 @@ public static function providerPrefixRoundTrip() { } /** - * Exercise setPrefix() and getPrefix(). + * Exercise legacy setPrefix() and getPrefix(). */ - #[DataProvider('providerEscapeTables')] + #[IgnoreDeprecations] + #[DataProvider('providerPrefixRoundTrip')] public function testPrefixRoundTrip($expected, $prefix_info): void { + $this->expectDeprecation('Drupal\\Core\\Database\\Connection::setPrefix() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass the table prefix to the IdentifierHandler constructor instead. See https://www.drupal.org/node/3513282'); + $this->expectDeprecation('Drupal\\Core\\Database\\Connection::getPrefix() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); + $mock_pdo = $this->createMock(StubPDO::class); - $connection = new StubConnection($mock_pdo, []); + $connection = new StubConnection($mock_pdo, ['prefix' => $prefix_info]); // setPrefix() is protected, so we make it accessible with reflection. $reflection = new \ReflectionClass(StubConnection::class); @@ -457,7 +461,7 @@ public function testFilterComments($expected, $comment): void { * testEscapeField. The first value is the expected value, and the second * value is the value to test. */ - public static function providerEscapeTables() { + public static function providerEscapeTables(): array { return [ ['nocase', 'nocase'], ['camelCase', 'camelCase'], @@ -474,12 +478,12 @@ public static function providerEscapeTables() { } /** - * Tests legacy ::escapeTable + * Tests legacy ::escapeTable. */ #[IgnoreDeprecations] #[DataProvider('providerEscapeTables')] public function testEscapeTable($expected, $name, array $identifier_quote = ['"', '"']): void { - $this->expectDeprecation('Drupal\Core\Database\Connection::escapeTable() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312'); + $this->expectDeprecation('Drupal\\Core\\Database\\Connection::escapeTable() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); $mock_pdo = $this->createMock(StubPDO::class); $connection = new StubConnection($mock_pdo, [], $identifier_quote); @@ -559,7 +563,7 @@ public function testEscapeField($expected, $name, array $identifier_quote = ['"' * testEscapeField. The first value is the expected value, and the second * value is the value to test. */ - public static function providerEscapeDatabase() { + public static function providerEscapeDatabase(): array { return [ ['/name/', 'name', ['/', '/']], ['`backtick`', 'backtick', ['`', '`']], @@ -570,12 +574,12 @@ public static function providerEscapeDatabase() { } /** - * Tests legacy ::escapeDatabase + * Tests legacy ::escapeDatabase. */ #[IgnoreDeprecations] #[DataProvider('providerEscapeDatabase')] public function testEscapeDatabase($expected, $name, array $identifier_quote = ['"', '"']): void { - $this->expectDeprecation('Drupal\Core\Database\Connection::escapeDatabase() is deprecated in drupal:11.9.0 and is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/7654312'); + $this->expectDeprecation('Drupal\\Core\\Database\\Connection::escapeDatabase() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); $mock_pdo = $this->createMock(StubPDO::class); $connection = new StubConnection($mock_pdo, [], $identifier_quote); -- GitLab From 486a51a95850c5130899a787f0ebe2afc70548e9 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Sun, 16 Mar 2025 22:37:51 +0100 Subject: [PATCH 60/66] cs fix --- core/tests/Drupal/Tests/Core/Database/ConnectionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index 9eeb586d2701..0b307223bf34 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -58,7 +58,7 @@ public function testPrefixRoundTrip($expected, $prefix_info): void { $this->expectDeprecation('Drupal\\Core\\Database\\Connection::getPrefix() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); $mock_pdo = $this->createMock(StubPDO::class); - $connection = new StubConnection($mock_pdo, ['prefix' => $prefix_info]); + $connection = new StubConnection($mock_pdo, ['prefix' => $prefix_info]); // setPrefix() is protected, so we make it accessible with reflection. $reflection = new \ReflectionClass(StubConnection::class); -- GitLab From e11adcf75fd209cbf4799c5b53aa3ea6bff52680 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Mon, 17 Mar 2025 16:36:57 +0100 Subject: [PATCH 61/66] BC layer for Connection --- core/lib/Drupal/Core/Database/Connection.php | 110 +++++++++++++-- .../Core/Database/ConnectionLegacyTest.php | 71 ++++++++++ .../Database/Stub/StubLegacyConnection.php | 129 ++++++++++++++++++ 3 files changed, 301 insertions(+), 9 deletions(-) create mode 100644 core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php create mode 100644 core/tests/Drupal/Tests/Core/Database/Stub/StubLegacyConnection.php diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index f92ae0e5b941..42128a2ab165 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -5,6 +5,7 @@ use Drupal\Core\Database\Event\DatabaseEvent; use Drupal\Core\Database\Exception\EventException; use Drupal\Core\Database\Identifier\IdentifierHandlerBase; +use Drupal\Core\Database\Identifier\IdentifierType; use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\Query\Delete; use Drupal\Core\Database\Query\Insert; @@ -131,6 +132,11 @@ abstract class Connection { */ protected TransactionManagerInterface $transactionManager; + /** + * The identifiers handler. + */ + public readonly IdentifierHandlerBase $identifiers; + /** * Constructs a Connection object. * @@ -141,13 +147,13 @@ abstract class Connection { * - prefix * - namespace * - Other driver-specific options. - * @param \Drupal\Core\Database\Identifier\IdentifierHandlerBase|null $identifiers + * @param \Drupal\Core\Database\Identifier\IdentifierHandlerBase|null $identifierHandler * The identifiers handler. */ public function __construct( object $connection, array $connection_options, - public readonly ?IdentifierHandlerBase $identifiers, + ?IdentifierHandlerBase $identifierHandler = NULL, ) { // Manage the table prefix. $connection_options['prefix'] = $connection_options['prefix'] ?? ''; @@ -160,21 +166,107 @@ public function __construct( $connection_options['namespace'] = (new \ReflectionObject($this))->getNamespaceName(); } + if ($identifierHandler) { + $this->identifiers = $identifierHandler; + } + else { + @trigger_error("Not passing an IdentiferHandler object to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); + $this->identifiers = new class($connection_options['prefix'] ?? '') extends IdentifierHandlerBase { + + /** + * {@inheritdoc} + */ + public function getMaxLength(IdentifierType $type): int { + return 128; + } + + }; + } + $this->connection = $connection; $this->connectionOptions = $connection_options; } /** * Implements the magic __get() method. - * - * @todo Remove the method in Drupal 1x. */ - public function __get($name): mixed { - if (in_array($name, ['prefix', 'escapedTables', 'identifierQuotes', 'tablePlaceholderReplacements'])) { - @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.9.0 and the property is removed from drupal:12.0.0. This is no longer used. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); - return []; + public function __get(string $name): mixed { + switch ($name) { + case 'prefix': + @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); + return $this->identifiers->tablePrefix ?? ''; + + case 'escapedTables': + @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); + return []; + + case 'identifierQuotes': + @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); + return $this->identifiers->identifierQuotes ?? ''; + + case 'tablePlaceholderReplacements': + @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); + $identifierQuotes = $this->identifiers->identifierQuotes ?? ['"', '"']; + return [ + $identifierQuotes[0] . str_replace('.', $identifierQuotes[1] . '.' . $identifierQuotes[0], $this->identifiers->tablePrefix ?? ''), + $identifierQuotes[1], + ]; + + default: + throw new \LogicException("The \${$name} property is undefined in " . __CLASS__); + + } + } + + /** + * Implements the magic __set() method. + */ + public function __set(string $name, mixed $value): void { + switch ($name) { + case 'prefix': + case 'escapedTables': + case 'identifierQuotes': + case 'tablePlaceholderReplacements': + @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); + return; + + default: + throw new \LogicException("The \${$name} property is undefined in " . __CLASS__); + } + } + + /** + * Implements the magic __isset() method. + */ + public function __isset(string $name): bool { + switch ($name) { + case 'prefix': + case 'escapedTables': + case 'identifierQuotes': + case 'tablePlaceholderReplacements': + @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); + return TRUE; + + default: + throw new \LogicException("The \${$name} property is undefined in " . __CLASS__); + } + } + + /** + * Implements the magic __unset() method. + */ + public function __unset(string $name): void { + switch ($name) { + case 'prefix': + case 'escapedTables': + case 'identifierQuotes': + case 'tablePlaceholderReplacements': + @trigger_error("Accessing Connection::\${$name} is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); + return; + + default: + throw new \LogicException("The \${$name} property is undefined in " . __CLASS__); } - return NULL; } /** diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php new file mode 100644 index 000000000000..f94c7bb34638 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php @@ -0,0 +1,71 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\Core\Database; + +use Drupal\Tests\Core\Database\Stub\StubLegacyConnection; +use Drupal\Tests\Core\Database\Stub\StubPDO; +use Drupal\Tests\UnitTestCase; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; + +/** + * Tests deprecations of the Connection class. + * + * @group Database + */ +class ConnectionLegacyTest extends UnitTestCase { + + /** + * Deprecation of Connection::$prefix. + */ + #[IgnoreDeprecations] + public function testPrefix(): void { + $this->expectDeprecation('Accessing Connection::$prefix is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); + $connection = new StubLegacyConnection($this->createMock(StubPDO::class), ['prefix' => 'foo']); + $this->assertSame('foo', $connection->prefix); + $this->expectDeprecation('Accessing Connection::$prefix is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); + $connection->prefix = 'bar'; + $this->assertSame('foo', $connection->prefix); + } + + /** + * Deprecation of Connection::$escapedTables. + */ + #[IgnoreDeprecations] + public function testEscapedTables(): void { + $this->expectDeprecation('Accessing Connection::$escapedTables is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); + $connection = new StubLegacyConnection($this->createMock(StubPDO::class), ['prefix' => 'foo']); + $this->assertSame([], $connection->escapedTables); + $this->expectDeprecation('Accessing Connection::$escapedTables is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); + $connection->escapedTables = 'bar'; + $this->assertSame([], $connection->escapedTables); + } + + /** + * Deprecation of Connection::$identifierQuotes. + */ + #[IgnoreDeprecations] + public function testIdentifierQuotes(): void { + $this->expectDeprecation('Accessing Connection::$identifierQuotes is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); + $connection = new StubLegacyConnection($this->createMock(StubPDO::class), ['prefix' => 'foo']); + $this->assertSame(['"', '"'], $connection->identifierQuotes); + $this->expectDeprecation('Accessing Connection::$identifierQuotes is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); + $connection->identifierQuotes = 'bar'; + $this->assertSame(['"', '"'], $connection->identifierQuotes); + } + + /** + * Deprecation of Connection::$tablePlaceholderReplacements. + */ + #[IgnoreDeprecations] + public function testTablePlaceholderReplacements(): void { + $this->expectDeprecation('Accessing Connection::$tablePlaceholderReplacements is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); + $connection = new StubLegacyConnection($this->createMock(StubPDO::class), ['prefix' => 'foo']); + $this->assertSame(['"foo', '"'], $connection->tablePlaceholderReplacements); + $this->expectDeprecation('Accessing Connection::$tablePlaceholderReplacements is deprecated in drupal:11.2.0 and the property is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/3513282'); + $connection->tablePlaceholderReplacements = 'bar'; + $this->assertSame(['"foo', '"'], $connection->tablePlaceholderReplacements); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubLegacyConnection.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubLegacyConnection.php new file mode 100644 index 000000000000..cef6b9befebd --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubLegacyConnection.php @@ -0,0 +1,129 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\Core\Database\Stub; + +use Drupal\Core\Database\Connection; +use Drupal\Core\Database\ExceptionHandler; +use Drupal\Core\Database\Log; +use Drupal\Core\Database\StatementWrapperIterator; +use Drupal\Tests\Core\Database\Stub\Driver\Schema; + +/** + * A stub of the abstract Connection class for testing purposes. + * + * Includes minimal implementations of Connection's abstract methods. + */ +class StubLegacyConnection extends Connection { + + /** + * {@inheritdoc} + */ + protected $statementWrapperClass = StatementWrapperIterator::class; + + /** + * Public property so we can test driver loading mechanism. + * + * @var string + * @see driver(). + */ + public $driver = 'stub'; + + /** + * Constructs a Connection object. + * + * @param \PDO $connection + * An object of the PDO class representing a database connection. + * @param array $connection_options + * An array of options for the connection. + */ + public function __construct(\PDO $connection, array $connection_options) { + parent::__construct( + $connection, + $connection_options, + ); + } + + /** + * {@inheritdoc} + */ + public static function open(array &$connection_options = []) { + return new \stdClass(); + } + + /** + * {@inheritdoc} + */ + public function queryRange($query, $from, $count, array $args = [], array $options = []) { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function driver() { + return $this->driver; + } + + /** + * {@inheritdoc} + */ + public function databaseType() { + return 'stub'; + } + + /** + * {@inheritdoc} + */ + public function createDatabase($database) {} + + /** + * {@inheritdoc} + */ + public function mapConditionOperator($operator) { + return NULL; + } + + /** + * Helper method to test database classes are not included in backtraces. + * + * @return array + * The caller stack entry. + */ + public function testLogCaller() { + return (new Log())->findCaller(); + } + + /** + * {@inheritdoc} + */ + public function exceptionHandler() { + return new ExceptionHandler(); + } + + /** + * {@inheritdoc} + */ + public function upsert($table, array $options = []) { + return new StubUpsert($this, $table, $options); + } + + /** + * {@inheritdoc} + */ + public function schema() { + if (empty($this->schema)) { + $this->schema = new Schema(); + } + return $this->schema; + } + + /** + * {@inheritdoc} + */ + public function condition($conjunction) { + return new StubCondition($conjunction); + } + +} -- GitLab From 1ac3ae15d127aee531276764086bdcef9ca0c25a Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Mon, 17 Mar 2025 17:11:13 +0100 Subject: [PATCH 62/66] fixes --- core/.phpstan-baseline.php | 6 ++++++ core/lib/Drupal/Core/Database/Connection.php | 2 +- .../Drupal/Tests/Core/Database/ConnectionLegacyTest.php | 9 +++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php index 340665f31ede..cc39d334b5b7 100644 --- a/core/.phpstan-baseline.php +++ b/core/.phpstan-baseline.php @@ -55670,6 +55670,12 @@ 'count' => 1, 'path' => __DIR__ . '/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\Core\\\\Database\\\\Stub\\\\StubLegacyConnection\\:\\:createDatabase\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/tests/Drupal/Tests/Core/Database/Stub/StubLegacyConnection.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\Core\\\\Database\\\\Stub\\\\StubSchema\\:\\:addField\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 42128a2ab165..13cbbe69ee53 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -170,7 +170,7 @@ public function __construct( $this->identifiers = $identifierHandler; } else { - @trigger_error("Not passing an IdentiferHandler object to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); + @trigger_error("Not passing an IdentifierHandler object to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. See https://www.drupal.org/node/3513282", E_USER_DEPRECATED); $this->identifiers = new class($connection_options['prefix'] ?? '') extends IdentifierHandlerBase { /** diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php index f94c7bb34638..7dfa843be250 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php @@ -16,6 +16,15 @@ */ class ConnectionLegacyTest extends UnitTestCase { + /** + * Tests that missing IdentifierHandler throws a deprecation. + */ + #[IgnoreDeprecations] + public function testMissingIdentifierHandler(): void { + $this->expectDeprecation('Not passing an IdentifierHandler object to Drupal\\Core\\Database\\Connection::__construct() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. See https://www.drupal.org/node/3513282'); + $connection = new StubLegacyConnection($this->createMock(StubPDO::class), ['prefix' => 'foo']); + } + /** * Deprecation of Connection::$prefix. */ -- GitLab From 2f7d4cca17506b525b4a9cc6c52ff69105b16657 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Mon, 17 Mar 2025 17:56:46 +0100 Subject: [PATCH 63/66] fix --- core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php index 7dfa843be250..f298be160dc6 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionLegacyTest.php @@ -23,6 +23,7 @@ class ConnectionLegacyTest extends UnitTestCase { public function testMissingIdentifierHandler(): void { $this->expectDeprecation('Not passing an IdentifierHandler object to Drupal\\Core\\Database\\Connection::__construct() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. See https://www.drupal.org/node/3513282'); $connection = new StubLegacyConnection($this->createMock(StubPDO::class), ['prefix' => 'foo']); + $this->assertInstanceOf(StubLegacyConnection::class, $connection); } /** -- GitLab From 83a9b10880e5acfae5dd9df0a617410caa1cfc16 Mon Sep 17 00:00:00 2001 From: mondrake <28163-mondrake@users.noreply.drupalcode.org> Date: Tue, 18 Mar 2025 09:33:36 +0000 Subject: [PATCH 64/66] Update 8 files - /core/lib/Drupal/Core/Database/Identifier/Database.php - /core/lib/Drupal/Core/Database/Identifier/Schema.php - /core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php - /core/lib/Drupal/Core/Database/Identifier/Table.php - /core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php - /core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php - /core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php - /core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php --- .../lib/Drupal/Core/Database/Identifier/Database.php | 2 +- .../Core/Database/Identifier/IdentifierBase.php | 3 ++- .../Database/Identifier/IdentifierHandlerBase.php | 4 ++-- core/lib/Drupal/Core/Database/Identifier/Schema.php | 2 +- core/lib/Drupal/Core/Database/Identifier/Table.php | 12 ++++++------ .../src/Driver/Database/mysql/IdentifierHandler.php | 6 +++--- .../src/Driver/Database/pgsql/IdentifierHandler.php | 6 +++--- .../src/Driver/Database/sqlite/IdentifierHandler.php | 2 +- 8 files changed, 19 insertions(+), 18 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Identifier/Database.php b/core/lib/Drupal/Core/Database/Identifier/Database.php index 82c0743329ec..6f5173303e5f 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Database.php +++ b/core/lib/Drupal/Core/Database/Identifier/Database.php @@ -18,7 +18,7 @@ public function __construct( ) { $canonicalName = $identifierHandler->canonicalize($identifier, IdentifierType::Database); $machineName = $identifierHandler->resolveForMachine($canonicalName, [], IdentifierType::Database); - parent::__construct($identifier, $canonicalName, $machineName); + parent::__construct($identifier, $canonicalName, $machineName, $identifierHandler->quote($machineName)); } } diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php index 29d79565860d..68d9266a183b 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierBase.php @@ -13,6 +13,7 @@ public function __construct( public readonly string $identifier, public readonly string $canonicalName, public readonly string $machineName, + public readonly string $quotedMachineName, ) { } @@ -33,7 +34,7 @@ public function canonical(): string { * The identifier in a format suitable for including in SQL queries. */ public function forMachine(): string { - return $this->machineName; + return $this->quotedMachineName; } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php index 626f2260aeee..f67092dcabdf 100644 --- a/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php +++ b/core/lib/Drupal/Core/Database/Identifier/IdentifierHandlerBase.php @@ -291,7 +291,7 @@ protected function cropByHashing(string $canonicalName, array $info, IdentifierT public function resolveForMachine(string $canonicalName, array $info, IdentifierType $type): string { return match ($type) { IdentifierType::Table => $this->resolveTableForMachine($canonicalName, $info), - default => $this->quote($canonicalName), + default => $canonicalName, }; } @@ -319,7 +319,7 @@ protected function resolveTableForMachine(string $canonicalName, array $info): s $this->getMaxLength(IdentifierType::Table), )); } - return $this->quote($info['needs_prefix'] ? $this->tablePrefix . $canonicalName : $canonicalName); + return $info['needs_prefix'] ? $this->tablePrefix . $canonicalName : $canonicalName; } /** diff --git a/core/lib/Drupal/Core/Database/Identifier/Schema.php b/core/lib/Drupal/Core/Database/Identifier/Schema.php index 0ba66888f69d..e9df7f0d56e4 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Schema.php +++ b/core/lib/Drupal/Core/Database/Identifier/Schema.php @@ -18,7 +18,7 @@ public function __construct( ) { $canonicalName = $identifierHandler->canonicalize($identifier, IdentifierType::Schema); $machineName = $identifierHandler->resolveForMachine($canonicalName, [], IdentifierType::Schema); - parent::__construct($identifier, $canonicalName, $machineName); + parent::__construct($identifier, $canonicalName, $machineName, $identifierHandler->quote($machineName)); } } diff --git a/core/lib/Drupal/Core/Database/Identifier/Table.php b/core/lib/Drupal/Core/Database/Identifier/Table.php index 1e123670a00c..45a7386291e3 100644 --- a/core/lib/Drupal/Core/Database/Identifier/Table.php +++ b/core/lib/Drupal/Core/Database/Identifier/Table.php @@ -35,7 +35,7 @@ public function __construct( $canonicalName = $identifierHandler->canonicalize($parts['table'], IdentifierType::Table); $machineName = $identifierHandler->resolveForMachine($canonicalName, $parts, IdentifierType::Table); - parent::__construct($identifier, $canonicalName, $machineName); + parent::__construct($identifier, $canonicalName, $machineName, $identifierHandler->quote($machineName)); $this->database = $parts['database']; $this->schema = $parts['schema']; @@ -46,8 +46,8 @@ public function __construct( * {@inheritdoc} */ public function canonical(): string { - $ret = isset($this->database) ? $this->database->canonical() . '.' : ''; - $ret .= isset($this->schema) ? $this->schema->canonical() . '.' : ''; + $ret = isset($this->database) ? $this->database->canonicalName . '.' : ''; + $ret .= isset($this->schema) ? $this->schema->canonicalName . '.' : ''; $ret .= $this->canonicalName; return $ret; } @@ -56,9 +56,9 @@ public function canonical(): string { * {@inheritdoc} */ public function forMachine(): string { - $ret = isset($this->database) ? $this->database->forMachine() . '.' : ''; - $ret .= isset($this->schema) ? $this->schema->forMachine() . '.' : ''; - $ret .= $this->machineName; + $ret = isset($this->database) ? $this->database->quotedMachineName . '.' : ''; + $ret .= isset($this->schema) ? $this->schema->quotedMachineName . '.' : ''; + $ret .= $this->quotedMachineName; return $ret; } diff --git a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php index dddb18bacce5..6bd320862c11 100644 --- a/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php +++ b/core/modules/mysql/src/Driver/Database/mysql/IdentifierHandler.php @@ -62,15 +62,15 @@ protected function resolveTableForMachine(string $canonicalName, array $info): s $this->getMaxLength(IdentifierType::Table), )); } - return $this->quote($canonicalName); + return $canonicalName; } $prefix = $info['needs_prefix'] ? $this->tablePrefix : ''; if (strlen($prefix . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { // We shorten too long table names. - return $this->quote($this->cropByHashing($canonicalName, $info, IdentifierType::Table, $prefix, $this->getMaxLength(IdentifierType::Table), 10)); + return $this->cropByHashing($canonicalName, $info, IdentifierType::Table, $prefix, $this->getMaxLength(IdentifierType::Table), 10); } - return $this->quote($prefix . $canonicalName); + return $prefix . $canonicalName; } } diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php index 9ba008007246..441087406833 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/IdentifierHandler.php @@ -76,15 +76,15 @@ protected function resolveTableForMachine(string $canonicalName, array $info): s $this->getMaxLength(IdentifierType::Table), )); } - return $this->quote($canonicalName); + return $canonicalName; } $prefix = $info['needs_prefix'] ? $this->tablePrefix : ''; if (strlen($prefix . $canonicalName) > $this->getMaxLength(IdentifierType::Table)) { // We shorten too long table names. - return $this->quote($this->cropByHashing($canonicalName, $info, IdentifierType::Table, $prefix, $this->getMaxLength(IdentifierType::Table), 10)); + return $this->cropByHashing($canonicalName, $info, IdentifierType::Table, $prefix, $this->getMaxLength(IdentifierType::Table), 10); } - return $this->quote($prefix . $canonicalName); + return $prefix . $canonicalName; } /** diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php index 7c0b65c21ca6..dca9b770b91d 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/IdentifierHandler.php @@ -47,7 +47,7 @@ public function parseTableIdentifier(string $identifier): array { * {@inheritdoc} */ protected function resolveTableForMachine(string $canonicalName, array $info): string { - return $this->quote($canonicalName); + return $canonicalName; } } -- GitLab From 897213146ada400de0c3e1baff3ffd68380a4dd5 Mon Sep 17 00:00:00 2001 From: mondrake <28163-mondrake@users.noreply.drupalcode.org> Date: Tue, 18 Mar 2025 11:10:21 +0000 Subject: [PATCH 65/66] Update 6 files - /core/modules/mysql/tests/src/Unit/IdentifierTest.php - /core/modules/pgsql/tests/src/Unit/IdentifierTest.php - /core/modules/pgsql/src/Driver/Database/pgsql/Connection.php - /core/modules/sqlite/tests/src/Unit/IdentifierTest.php - /core/modules/sqlite/src/Driver/Database/sqlite/Schema.php - /core/lib/Drupal/Core/Database/Connection.php --- core/lib/Drupal/Core/Database/Connection.php | 4 ++-- .../mysql/tests/src/Unit/IdentifierTest.php | 2 +- .../pgsql/src/Driver/Database/pgsql/Connection.php | 8 ++++---- .../pgsql/tests/src/Unit/IdentifierTest.php | 2 +- .../sqlite/src/Driver/Database/sqlite/Schema.php | 14 +++++++------- .../sqlite/tests/src/Unit/IdentifierTest.php | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 13cbbe69ee53..7380f733d552 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -489,8 +489,8 @@ public function getFullQualifiedTableName($table) { if ($tableIdentifier->database || $tableIdentifier->schema) { return $tableIdentifier->forMachine(); } - // Return as <schema>.<table>. - return $this->identifiers->schema($this->getConnectionOptions()['database'])->forMachine() . '.' . $tableIdentifier->machineName; + // Return as "<schema>"."<table>". + return $this->identifiers->schema($this->getConnectionOptions()['database'])->quotedMachineName . '.' . $tableIdentifier->quotedMachineName; } /** diff --git a/core/modules/mysql/tests/src/Unit/IdentifierTest.php b/core/modules/mysql/tests/src/Unit/IdentifierTest.php index a2349b898bc2..7efff8516024 100644 --- a/core/modules/mysql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/mysql/tests/src/Unit/IdentifierTest.php @@ -193,7 +193,7 @@ public function testTable(string $identifier, string $prefix = '', ?string $expe $this->assertSame($expectedMachine, $handler->table($identifier)->forMachine()); // The machine name includes the quote characters so we need to subtract // those from the length. - $this->assertLessThanOrEqual($handler->getMaxLength(IdentifierType::Table), strlen($handler->table($identifier)->forMachine()) - 2, 'Invalid machine table length.'); + $this->assertLessThanOrEqual($handler->getMaxLength(IdentifierType::Table), strlen($handler->table($identifier)->machineName), 'Invalid machine table length.'); } } diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php index fa88b9d0a922..a9d5d4e1e155 100644 --- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php +++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php @@ -334,12 +334,12 @@ public function getFullQualifiedTableName($table) { return $tableIdentifier->forMachine(); } // The fully qualified table name in PostgreSQL is in the form of - // <database>.<schema>.<table>. + // "<database>"."<schema>"."<table>". $options = $this->getConnectionOptions(); $schema = $options['schema'] ?? 'public'; - return $this->identifiers->database($options['database'])->forMachine() . '.' . - $this->identifiers->schema($schema)->forMachine() . '.' . - $tableIdentifier->machineName; + return $this->identifiers->database($options['database'])->quotedMachineName . '.' . + $this->identifiers->schema($schema)->quotedMachineName . '.' . + $tableIdentifier->quotedMachineName; } /** diff --git a/core/modules/pgsql/tests/src/Unit/IdentifierTest.php b/core/modules/pgsql/tests/src/Unit/IdentifierTest.php index 052e0fde3a9f..9f6e48aeb907 100644 --- a/core/modules/pgsql/tests/src/Unit/IdentifierTest.php +++ b/core/modules/pgsql/tests/src/Unit/IdentifierTest.php @@ -191,7 +191,7 @@ public function testTable(string $identifier, string $prefix = '', ?string $expe $this->assertSame($expectedMachine, $handler->table($identifier)->forMachine()); // The machine name includes the quote characters so we need to subtract // those from the length. - $this->assertLessThanOrEqual($handler->getMaxLength(IdentifierType::Table), strlen($handler->table($identifier)->forMachine()) - 2, 'Invalid machine table length.'); + $this->assertLessThanOrEqual($handler->getMaxLength(IdentifierType::Table), strlen($handler->table($identifier)->machineName), 'Invalid machine table length.'); } } diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php index 071e53be2e06..b17637f662b1 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php @@ -62,19 +62,19 @@ public function createTableSql($name, $table) { protected function createIndexSql($tablename, $schema) { // In SQLite, the 'CREATE [UNIQUE] INDEX' DDL statements requires that the // table name be NOT prefixed by the schema name. We cannot use the - // Table::forMachine() method but should rather pick Table->machineName - // directly. + // Table::forMachine() method but should rather pick + // Table->quotedMachineName directly. // @see https://www.sqlite.org/syntax/create-index-stmt.html $sql = []; $info = $this->getPrefixInfo($tablename); if (!empty($schema['unique keys'])) { foreach ($schema['unique keys'] as $key => $fields) { - $sql[] = 'CREATE UNIQUE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON ' . $this->connection->identifiers->table($info['table'])->machineName . ' (' . $this->createKeySql($fields) . ")\n"; + $sql[] = 'CREATE UNIQUE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON ' . $this->connection->identifiers->table($info['table'])->quotedMachineName . ' (' . $this->createKeySql($fields) . ")\n"; } } if (!empty($schema['indexes'])) { foreach ($schema['indexes'] as $key => $fields) { - $sql[] = 'CREATE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON ' . $this->connection->identifiers->table($info['table'])->machineName . ' (' . $this->createKeySql($fields) . ")\n"; + $sql[] = 'CREATE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON ' . $this->connection->identifiers->table($info['table'])->quotedMachineName . ' (' . $this->createKeySql($fields) . ")\n"; } } return $sql; @@ -273,11 +273,11 @@ public function renameTable($table, $new_name) { // SQLite doesn't allow you to rename tables outside of the current schema, // so the syntax '... RENAME TO schema.table' would fail. We cannot use the - // Table::forMachine() method but should rather pick Table->machineName - // directly. + // Table::forMachine() method but should rather pick + // Table->quotedMachineName directly. // @see https://www.sqlite.org/syntax/alter-table-stmt.html $info = $this->getPrefixInfo($new_name); - $this->executeDdlStatement('ALTER TABLE ' . $this->connection->identifiers->table($table)->forMachine() . ' RENAME TO ' . $this->connection->identifiers->table($info['table'])->machineName); + $this->executeDdlStatement('ALTER TABLE ' . $this->connection->identifiers->table($table)->forMachine() . ' RENAME TO ' . $this->connection->identifiers->table($info['table'])->quotedMachineName); // Drop the indexes, there is no RENAME INDEX command in SQLite. if (!empty($schema['unique keys'])) { diff --git a/core/modules/sqlite/tests/src/Unit/IdentifierTest.php b/core/modules/sqlite/tests/src/Unit/IdentifierTest.php index a144ae6c1379..f27928b8253b 100644 --- a/core/modules/sqlite/tests/src/Unit/IdentifierTest.php +++ b/core/modules/sqlite/tests/src/Unit/IdentifierTest.php @@ -162,7 +162,7 @@ public function testTable(string $identifier, string $prefix = '', ?string $expe $this->assertSame($expectedMachine, $handler->table($identifier)->forMachine()); // The machine name includes the quote characters so we need to subtract // those from the length. - $this->assertLessThanOrEqual($handler->getMaxLength(IdentifierType::Table), strlen($handler->table($identifier)->forMachine()) - 2, 'Invalid machine table length.'); + $this->assertLessThanOrEqual($handler->getMaxLength(IdentifierType::Table), strlen($handler->table($identifier)->machineName), 'Invalid machine table length.'); } } -- GitLab From 30ba2f3de02d2af531963559458fc848f342f0e4 Mon Sep 17 00:00:00 2001 From: mondrake <mondrake.org@gmail.com> Date: Wed, 19 Mar 2025 19:13:12 +0100 Subject: [PATCH 66/66] sqlite Schema --- core/lib/Drupal/Core/Database/Schema.php | 2 + .../src/Driver/Database/sqlite/Schema.php | 194 +++++++++++++++--- .../tests/src/Kernel/sqlite/SchemaTest.php | 2 +- .../Database/DriverSpecificSchemaTestBase.php | 44 ++-- .../Database/SchemaIntrospectionTestTrait.php | 33 ++- 5 files changed, 218 insertions(+), 57 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 6504b70619cd..64f4bb18594b 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -82,6 +82,7 @@ public function nextPlaceholder() { * A keyed array with information about the schema, table name and prefix. */ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); $info = [ 'schema' => $this->defaultSchema, 'prefix' => isset($this->connection->identifiers) ? $this->connection->identifiers->tablePrefix : '', @@ -111,6 +112,7 @@ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { * This prevents using {} around non-table names like indexes and keys. */ public function prefixNonTable($table) { + @trigger_error(__METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use IdentifierHandler methods instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); $args = func_get_args(); $info = $this->getPrefixInfo($table); $args[0] = $info['table']; diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php index b17637f662b1..2f091e316a8c 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php @@ -2,6 +2,7 @@ namespace Drupal\sqlite\Driver\Database\sqlite; +use Drupal\Core\Database\Identifier\Table as TableIdentifier; use Drupal\Core\Database\SchemaObjectExistsException; use Drupal\Core\Database\SchemaObjectDoesNotExistException; use Drupal\Core\Database\Schema as DatabaseSchema; @@ -29,16 +30,30 @@ class Schema extends DatabaseSchema { * {@inheritdoc} */ public function tableExists($table, $add_prefix = TRUE) { - $info = $this->getPrefixInfo($table, $add_prefix); + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + + $schema = $table->schema ?? $this->defaultSchema; - // 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(); + $sql = sprintf( + 'SELECT 1 FROM %s WHERE type = :type AND name = :name', + $this->connection->identifiers->table($schema . '.sqlite_master'), + ); + + return (bool) $this->connection->query($sql, [':type' => 'table', ':name' => $table->machineName])->fetchField(); } /** * {@inheritdoc} */ public function fieldExists($table, $column) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + $schema = $this->introspectSchema($table); return !empty($schema['fields'][$column]); } @@ -47,12 +62,17 @@ public function fieldExists($table, $column) { * {@inheritdoc} */ public function createTableSql($name, $table) { + if (!$name instanceof TableIdentifier) { + $name = $this->connection->identifiers->table($name); + } + assert($name instanceof TableIdentifier); + if (!empty($table['primary key']) && is_array($table['primary key'])) { $this->ensureNotNullPrimaryKey($table['primary key'], $table['fields']); } $sql = []; - $sql[] = "CREATE TABLE " . $this->connection->identifiers->table($name)->forMachine() . " (\n" . $this->createColumnsSql($name, $table) . "\n)\n"; + $sql[] = "CREATE TABLE " . $name->forMachine() . " (\n" . $this->createColumnsSql($name, $table) . "\n)\n"; return array_merge($sql, $this->createIndexSql($name, $table)); } @@ -60,21 +80,41 @@ public function createTableSql($name, $table) { * Build the SQL expression for indexes. */ protected function createIndexSql($tablename, $schema) { + if (!$tablename instanceof TableIdentifier) { + @trigger_error("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); + $table = $this->connection->identifiers->table($tablename); + } + else { + $table = $tablename; + } + assert($table instanceof TableIdentifier); + // In SQLite, the 'CREATE [UNIQUE] INDEX' DDL statements requires that the // table name be NOT prefixed by the schema name. We cannot use the // Table::forMachine() method but should rather pick // Table->quotedMachineName directly. // @see https://www.sqlite.org/syntax/create-index-stmt.html $sql = []; - $info = $this->getPrefixInfo($tablename); if (!empty($schema['unique keys'])) { foreach ($schema['unique keys'] as $key => $fields) { - $sql[] = 'CREATE UNIQUE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON ' . $this->connection->identifiers->table($info['table'])->quotedMachineName . ' (' . $this->createKeySql($fields) . ")\n"; + $sql[] = sprintf( + "CREATE UNIQUE INDEX %s.%s ON %s (%s)\n", + $table->schema, + '[' . $table->machineName . '_' . $key . ']', + $table->quotedMachineName, + $this->createKeySql($fields), + ); } } if (!empty($schema['indexes'])) { foreach ($schema['indexes'] as $key => $fields) { - $sql[] = 'CREATE INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $key . '] ON ' . $this->connection->identifiers->table($info['table'])->quotedMachineName . ' (' . $this->createKeySql($fields) . ")\n"; + $sql[] = sprintf( + "CREATE INDEX %s.%s ON %s (%s)\n", + $table->schema, + '[' . $table->machineName . '_' . $key . ']', + $table->quotedMachineName, + $this->createKeySql($fields), + ); } } return $sql; @@ -84,6 +124,15 @@ protected function createIndexSql($tablename, $schema) { * Build the SQL expression for creating columns. */ protected function createColumnsSql($tablename, $schema) { + if (!$tablename instanceof TableIdentifier) { + @trigger_error("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); + $table = $this->connection->identifiers->table($tablename); + } + else { + $table = $tablename; + } + assert($table instanceof TableIdentifier); + $sql_array = []; // Add the SQL statement for each field. @@ -262,6 +311,15 @@ public function getFieldTypeMap() { * {@inheritdoc} */ public function renameTable($table, $new_name) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$new_name instanceof TableIdentifier) { + $new_name = $this->connection->identifiers->table($new_name); + } + assert($new_name instanceof TableIdentifier); + if (!$this->tableExists($table)) { throw new SchemaObjectDoesNotExistException("Cannot rename '$table' to '$new_name': table '$table' doesn't exist."); } @@ -276,8 +334,7 @@ public function renameTable($table, $new_name) { // Table::forMachine() method but should rather pick // Table->quotedMachineName directly. // @see https://www.sqlite.org/syntax/alter-table-stmt.html - $info = $this->getPrefixInfo($new_name); - $this->executeDdlStatement('ALTER TABLE ' . $this->connection->identifiers->table($table)->forMachine() . ' RENAME TO ' . $this->connection->identifiers->table($info['table'])->quotedMachineName); + $this->executeDdlStatement(sprintf('ALTER TABLE %s RENAME TO %s', $table->forMachine(), $new_name->quotedMachineName)); // Drop the indexes, there is no RENAME INDEX command in SQLite. if (!empty($schema['unique keys'])) { @@ -302,11 +359,16 @@ public function renameTable($table, $new_name) { * {@inheritdoc} */ public function dropTable($table) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->tableExists($table)) { return FALSE; } $this->connection->tableDropped = TRUE; - $this->executeDdlStatement('DROP TABLE ' . $this->connection->identifiers->table($table)->forMachine()); + $this->executeDdlStatement('DROP TABLE ' . $table->forMachine()); return TRUE; } @@ -314,6 +376,11 @@ public function dropTable($table) { * {@inheritdoc} */ public function addField($table, $field, $specification, $keys_new = []) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->tableExists($table)) { throw new SchemaObjectDoesNotExistException("Cannot add field '$table.$field': table doesn't exist."); } @@ -330,7 +397,7 @@ public function addField($table, $field, $specification, $keys_new = []) { if (empty($keys_new) && (empty($specification['not null']) || isset($specification['default']))) { // When we don't have to create new keys and we are not creating a NOT // NULL column without a default value, we can use the quicker version. - $query = 'ALTER TABLE ' . $this->connection->identifiers->table($table)->forMachine() . ' ADD ' . $this->createFieldSql($field, $this->processField($specification)); + $query = 'ALTER TABLE ' . $table->forMachine() . ' ADD ' . $this->createFieldSql($field, $this->processField($specification)); $this->executeDdlStatement($query); // Apply the initial value if set. @@ -343,12 +410,12 @@ public function addField($table, $field, $specification, $keys_new = []) { $expression = $specification['initial_from_field']; $arguments = []; } - $this->connection->update($table) + $this->connection->update($table->identifier) ->expression($field, $expression, $arguments) ->execute(); } elseif (isset($specification['initial'])) { - $this->connection->update($table) + $this->connection->update($table->identifier) ->fields([$field => $specification['initial']]) ->execute(); } @@ -420,15 +487,21 @@ public function addField($table, $field, $specification, $keys_new = []) { * that will be used as an expression field. */ protected function alterTable($table, $old_schema, $new_schema, array $mapping = []) { + if (!$table instanceof TableIdentifier) { + @trigger_error("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + $i = 0; do { - $new_table = $table . '_' . $i++; + $new_table = $this->connection->identifiers->table($table->canonicalName . '_' . $i++); } while ($this->tableExists($new_table)); $this->createTable($new_table, $new_schema); // Build a SQL query to migrate the data from the old table to the new. - $select = $this->connection->select($table); + $select = $this->connection->select($table->identifier); // Complete the mapping. $possible_keys = array_keys($new_schema['fields']); @@ -450,12 +523,12 @@ protected function alterTable($table, $old_schema, $new_schema, array $mapping = } // Execute the data migration query. - $this->connection->insert($new_table) + $this->connection->insert($new_table->identifier) ->from($select) ->execute(); - $old_count = $this->connection->query('SELECT COUNT(*) FROM {' . $table . '}')->fetchField(); - $new_count = $this->connection->query('SELECT COUNT(*) FROM {' . $new_table . '}')->fetchField(); + $old_count = $this->connection->query('SELECT COUNT(*) FROM ' . $table->forMachine())->fetchField(); + $new_count = $this->connection->query('SELECT COUNT(*) FROM ' . $new_table->forMachine())->fetchField(); if ($old_count == $new_count) { $this->dropTable($table); $this->renameTable($new_table, $table); @@ -479,6 +552,12 @@ protected function alterTable($table, $old_schema, $new_schema, array $mapping = * If a column of the table could not be parsed. */ protected function introspectSchema($table) { + if (!$table instanceof TableIdentifier) { + @trigger_error("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + $mapped_fields = array_flip($this->getFieldTypeMap()); $schema = [ 'fields' => [], @@ -487,8 +566,7 @@ protected function introspectSchema($table) { 'indexes' => [], ]; - $info = $this->getPrefixInfo($table); - $result = $this->connection->query('PRAGMA [' . $info['schema'] . '].table_info([' . $info['table'] . '])'); + $result = $this->connection->query('PRAGMA ' . $table->schema . '.table_info(' . $table->quotedMachineName . ')'); foreach ($result as $row) { if (preg_match('/^([^(]+)\((.*)\)$/', $row->type, $matches)) { $type = $matches[1]; @@ -544,7 +622,7 @@ protected function introspectSchema($table) { $schema['primary key'] = array_values($schema['primary key']); $indexes = []; - $result = $this->connection->query('PRAGMA [' . $info['schema'] . '].index_list([' . $info['table'] . '])'); + $result = $this->connection->query('PRAGMA ' . $table->schema . '.index_list(' . $table->quotedMachineName . ')'); foreach ($result as $row) { if (!str_starts_with($row->name, 'sqlite_autoindex_')) { $indexes[] = [ @@ -556,8 +634,8 @@ protected function introspectSchema($table) { foreach ($indexes as $index) { $name = $index['name']; // Get index name without prefix. - $index_name = substr($name, strlen($info['table']) + 1); - $result = $this->connection->query('PRAGMA [' . $info['schema'] . '].index_info([' . $name . '])'); + $index_name = substr($name, strlen($table->machineName) + 1); + $result = $this->connection->query('PRAGMA ' . $table->schema . '.index_info([' . $name . '])'); foreach ($result as $row) { $schema[$index['schema_key']][$index_name][] = $row->name; } @@ -569,6 +647,11 @@ protected function introspectSchema($table) { * {@inheritdoc} */ public function dropField($table, $field) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->fieldExists($table, $field)) { return FALSE; } @@ -605,6 +688,11 @@ public function dropField($table, $field) { * {@inheritdoc} */ public function changeField($table, $field, $field_new, $spec, $keys_new = []) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->fieldExists($table, $field)) { throw new SchemaObjectDoesNotExistException("Cannot change the definition of field '$table.$field': field doesn't exist."); } @@ -678,6 +766,11 @@ protected function mapKeyDefinition(array $key_definition, array $mapping) { * {@inheritdoc} */ public function addIndex($table, $name, $fields, array $spec) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->tableExists($table)) { throw new SchemaObjectDoesNotExistException("Cannot add index '$name' to table '$table': table doesn't exist."); } @@ -696,29 +789,40 @@ public function addIndex($table, $name, $fields, array $spec) { * {@inheritdoc} */ public function indexExists($table, $name) { - $info = $this->getPrefixInfo($table); + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); - return $this->connection->query('PRAGMA [' . $info['schema'] . '].index_info([' . $info['table'] . '_' . $name . '])')->fetchField() != ''; + return $this->connection->query('PRAGMA ' . $table->schema . '.index_info([' . $table->machineName . '_' . $name . '])')->fetchField() != ''; } /** * {@inheritdoc} */ public function dropIndex($table, $name) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->indexExists($table, $name)) { return FALSE; } - $info = $this->getPrefixInfo($table); - - $this->executeDdlStatement('DROP INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $name . ']'); - return TRUE; + $this->executeDdlStatement('DROP INDEX ' . $table->schema . '.[' . $table->machineName . '_' . $name . ']'); + return TRUE; } /** * {@inheritdoc} */ public function addUniqueKey($table, $name, $fields) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->tableExists($table)) { throw new SchemaObjectDoesNotExistException("Cannot add unique key '$name' to table '$table': table doesn't exist."); } @@ -737,13 +841,16 @@ public function addUniqueKey($table, $name, $fields) { * {@inheritdoc} */ public function dropUniqueKey($table, $name) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->indexExists($table, $name)) { return FALSE; } - $info = $this->getPrefixInfo($table); - - $this->executeDdlStatement('DROP INDEX [' . $info['schema'] . '].[' . $info['table'] . '_' . $name . ']'); + $this->executeDdlStatement('DROP INDEX ' . $table->schema . '.[' . $table->machineName . '_' . $name . ']'); return TRUE; } @@ -751,6 +858,11 @@ public function dropUniqueKey($table, $name) { * {@inheritdoc} */ public function addPrimaryKey($table, $fields) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->tableExists($table)) { throw new SchemaObjectDoesNotExistException("Cannot add primary key to table '$table': table doesn't exist."); } @@ -771,6 +883,11 @@ public function addPrimaryKey($table, $fields) { * {@inheritdoc} */ public function dropPrimaryKey($table) { + if (!$table instanceof TableIdentifier) { + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + $old_schema = $this->introspectSchema($table); $new_schema = $old_schema; @@ -787,6 +904,12 @@ public function dropPrimaryKey($table) { * {@inheritdoc} */ protected function findPrimaryKeyColumns($table) { + if (!$table instanceof TableIdentifier) { + @trigger_error("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->tableExists($table)) { return FALSE; } @@ -798,6 +921,13 @@ protected function findPrimaryKeyColumns($table) { * {@inheritdoc} */ protected function introspectIndexSchema($table) { + if (!$table instanceof TableIdentifier) { + @trigger_error("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); +# throw new \Exception("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132"); + $table = $this->connection->identifiers->table($table); + } + assert($table instanceof TableIdentifier); + if (!$this->tableExists($table)) { throw new SchemaObjectDoesNotExistException("The table $table doesn't exist."); } diff --git a/core/modules/sqlite/tests/src/Kernel/sqlite/SchemaTest.php b/core/modules/sqlite/tests/src/Kernel/sqlite/SchemaTest.php index 8848c8586604..9a9ea16c537f 100644 --- a/core/modules/sqlite/tests/src/Kernel/sqlite/SchemaTest.php +++ b/core/modules/sqlite/tests/src/Kernel/sqlite/SchemaTest.php @@ -96,7 +96,7 @@ public function testIntrospectIndexSchema(): void { unset($table_specification['fields']); $introspect_index_schema = new \ReflectionMethod(get_class($this->schema), 'introspectIndexSchema'); - $index_schema = $introspect_index_schema->invoke($this->schema, $table_name); + $index_schema = $introspect_index_schema->invoke($this->schema, $this->connection->identifiers->table($table_name)); $this->assertEquals($table_specification, $index_schema); } diff --git a/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificSchemaTestBase.php b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificSchemaTestBase.php index ca5fb32936b5..f2efcd9cecf9 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificSchemaTestBase.php +++ b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificSchemaTestBase.php @@ -255,7 +255,7 @@ public function testSchema(): void { // Test the primary key columns. $method = new \ReflectionMethod(get_class($this->schema), 'findPrimaryKeyColumns'); - $this->assertSame(['test_serial'], $method->invoke($this->schema, 'test_table')); + $this->assertSame(['test_serial'], $method->invoke($this->schema, $this->connection->identifiers->table('test_table'))); $this->assertTrue($this->tryInsert(), 'Insert with a serial succeeded.'); $max1 = $this->connection->query('SELECT MAX([test_serial]) FROM {test_table}')->fetchField(); @@ -270,7 +270,7 @@ public function testSchema(): void { $this->schema->addField('test_table', 'test_composite_primary_key', ['type' => 'int', 'not null' => TRUE, 'default' => 0], ['primary key' => ['test_serial', 'test_composite_primary_key']]); // Test the primary key columns. - $this->assertSame(['test_serial', 'test_composite_primary_key'], $method->invoke($this->schema, 'test_table')); + $this->assertSame(['test_serial', 'test_composite_primary_key'], $method->invoke($this->schema, $this->connection->identifiers->table('test_table'))); // Test renaming of keys and constraints. $this->schema->dropTable('test_table'); @@ -295,21 +295,21 @@ public function testSchema(): void { // SQLite does not have any limit. Use the lowest common value and create a // table name as long as possible in order to cover edge cases around // identifier names for the table's primary or unique key constraints. - $table_name = strtolower($this->getRandomGenerator()->name(63 - strlen($this->getDatabasePrefix()))); - $this->schema->createTable($table_name, $table_specification); + $table = $this->connection->identifiers->table(strtolower($this->getRandomGenerator()->name(63 - strlen($this->getDatabasePrefix())))); + $this->schema->createTable($table, $table_specification); - $this->assertIndexOnColumns($table_name, ['id'], 'primary'); - $this->assertIndexOnColumns($table_name, ['test_field'], 'unique'); + $this->assertIndexOnColumns($table, ['id'], 'primary'); + $this->assertIndexOnColumns($table, ['test_field'], 'unique'); - $new_table_name = strtolower($this->getRandomGenerator()->name(63 - strlen($this->getDatabasePrefix()))); - $this->assertNull($this->schema->renameTable($table_name, $new_table_name)); + $new_table = $this->connection->identifiers->table(strtolower($this->getRandomGenerator()->name(63 - strlen($this->getDatabasePrefix())))); + $this->assertNull($this->schema->renameTable($table, $new_table)); // Test for renamed primary and unique keys. - $this->assertIndexOnColumns($new_table_name, ['id'], 'primary'); - $this->assertIndexOnColumns($new_table_name, ['test_field'], 'unique'); + $this->assertIndexOnColumns($new_table, ['id'], 'primary'); + $this->assertIndexOnColumns($new_table, ['test_field'], 'unique'); // Check that the ID sequence gets renamed when the table is renamed. - $this->checkSequenceRenaming($new_table_name); + $this->checkSequenceRenaming($new_table->identifier); } /** @@ -582,7 +582,7 @@ public function testSchemaChangePrimaryKey(array $initial_primary_key, array $re $find_primary_key_columns = new \ReflectionMethod(get_class($this->schema), 'findPrimaryKeyColumns'); // Test making the field the primary key of the table upon creation. - $table_name = 'test_table'; + $table_name = $this->connection->identifiers->table('test_table'); $table_spec = [ 'fields' => [ 'test_field' => ['type' => 'int', 'not null' => TRUE], @@ -908,7 +908,7 @@ public function testFindPrimaryKeyColumns(): void { ], 'primary key' => ['id'], ]); - $this->assertSame(['id'], $method->invoke($this->schema, 'table_with_pk_0')); + $this->assertSame(['id'], $method->invoke($this->schema, $this->connection->identifiers->table('table_with_pk_0'))); // Test with multiple column primary key. $this->schema->createTable('table_with_pk_1', [ @@ -929,7 +929,7 @@ public function testFindPrimaryKeyColumns(): void { ], 'primary key' => ['id0', 'id1'], ]); - $this->assertSame(['id0', 'id1'], $method->invoke($this->schema, 'table_with_pk_1')); + $this->assertSame(['id0', 'id1'], $method->invoke($this->schema, $this->connection->identifiers->table('table_with_pk_1'))); // Test with multiple column primary key and not being the first column of // the table definition. @@ -955,7 +955,7 @@ public function testFindPrimaryKeyColumns(): void { ], 'primary key' => ['id4', 'id3'], ]); - $this->assertSame(['id4', 'id3'], $method->invoke($this->schema, 'table_with_pk_2')); + $this->assertSame(['id4', 'id3'], $method->invoke($this->schema, $this->connection->identifiers->table('table_with_pk_2'))); // Test with multiple column primary key in a different order. For the // PostgreSQL and the SQLite drivers is sorting used to get the primary key @@ -982,7 +982,7 @@ public function testFindPrimaryKeyColumns(): void { ], 'primary key' => ['id3', 'test_field_2', 'id4'], ]); - $this->assertSame(['id3', 'test_field_2', 'id4'], $method->invoke($this->schema, 'table_with_pk_3')); + $this->assertSame(['id3', 'test_field_2', 'id4'], $method->invoke($this->schema, $this->connection->identifiers->table('table_with_pk_3'))); // Test with table without a primary key. $this->schema->createTable('table_without_pk_1', [ @@ -998,7 +998,7 @@ public function testFindPrimaryKeyColumns(): void { ], ], ]); - $this->assertSame([], $method->invoke($this->schema, 'table_without_pk_1')); + $this->assertSame([], $method->invoke($this->schema, $this->connection->identifiers->table('table_without_pk_1'))); // Test with table with an empty primary key. $this->schema->createTable('table_without_pk_2', [ @@ -1015,10 +1015,10 @@ public function testFindPrimaryKeyColumns(): void { ], 'primary key' => [], ]); - $this->assertSame([], $method->invoke($this->schema, 'table_without_pk_2')); + $this->assertSame([], $method->invoke($this->schema, $this->connection->identifiers->table('table_without_pk_2'))); // Test with non existing table. - $this->assertFalse($method->invoke($this->schema, 'non_existing_table')); + $this->assertFalse($method->invoke($this->schema, $this->connection->identifiers->table('non_existing_table'))); } /** @@ -1271,7 +1271,7 @@ public function testReservedKeywordsForNaming(): void { // Check the primary key columns. $find_primary_key_columns = new \ReflectionMethod(get_class($this->schema), 'findPrimaryKeyColumns'); - $this->assertEquals([$field_name], $find_primary_key_columns->invoke($this->schema, $table_name_new)); + $this->assertEquals([$field_name], $find_primary_key_columns->invoke($this->schema, $this->connection->identifiers->table($table_name_new))); // Dropping a primary key. $this->schema->dropPrimaryKey($table_name_new); @@ -1288,7 +1288,7 @@ public function testReservedKeywordsForNaming(): void { // Check the unique key columns. $introspect_index_schema = new \ReflectionMethod(get_class($this->schema), 'introspectIndexSchema'); - $this->assertEquals([$field_name_new], $introspect_index_schema->invoke($this->schema, $table_name_new)['unique keys'][$unique_key_introspect_name]); + $this->assertEquals([$field_name_new], $introspect_index_schema->invoke($this->schema, $this->connection->identifiers->table($table_name_new))['unique keys'][$unique_key_introspect_name]); // Dropping an unique key $this->schema->dropUniqueKey($table_name_new, $unique_key_name); @@ -1303,7 +1303,7 @@ public function testReservedKeywordsForNaming(): void { $this->assertTrue($this->schema->indexExists($table_name_new, $index_name)); // Check the index columns. - $this->assertEquals(['update'], $introspect_index_schema->invoke($this->schema, $table_name_new)['indexes'][$index_introspect_name]); + $this->assertEquals(['update'], $introspect_index_schema->invoke($this->schema, $this->connection->identifiers->table($table_name_new))['indexes'][$index_introspect_name]); // Dropping an index. $this->schema->dropIndex($table_name_new, $index_name); diff --git a/core/tests/Drupal/Tests/Core/Database/SchemaIntrospectionTestTrait.php b/core/tests/Drupal/Tests/Core/Database/SchemaIntrospectionTestTrait.php index 2168fdb2a112..9cb02730939c 100644 --- a/core/tests/Drupal/Tests/Core/Database/SchemaIntrospectionTestTrait.php +++ b/core/tests/Drupal/Tests/Core/Database/SchemaIntrospectionTestTrait.php @@ -4,6 +4,8 @@ namespace Drupal\Tests\Core\Database; +use Drupal\Core\Database\Identifier\Table as TableIdentifier; + /** * Provides methods for testing database schema characteristics. */ @@ -21,6 +23,15 @@ trait SchemaIntrospectionTestTrait { * 'primary'. Defaults to 'index'. */ protected function assertIndexOnColumns($table_name, array $column_names, $index_type = 'index') { + if (!$table_name instanceof TableIdentifier) { + @trigger_error("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); + $table = $this->connection->identifiers->table($table_name); + } + else { + $table = $table_name; + } + assert($table instanceof TableIdentifier); + foreach ($this->getIndexColumnNames($table_name, $index_type) as $index_columns) { if ($column_names == $index_columns) { $this->assertTrue(TRUE); @@ -42,7 +53,16 @@ protected function assertIndexOnColumns($table_name, array $column_names, $index * 'primary'. Defaults to 'index'. */ protected function assertNoIndexOnColumns($table_name, array $column_names, $index_type = 'index') { - foreach ($this->getIndexColumnNames($table_name, $index_type) as $index_columns) { + if (!$table_name instanceof TableIdentifier) { + @trigger_error("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); + $table = $this->connection->identifiers->table($table_name); + } + else { + $table = $table_name; + } + assert($table instanceof TableIdentifier); + + foreach ($this->getIndexColumnNames($table, $index_type) as $index_columns) { if ($column_names == $index_columns) { $this->assertTrue(FALSE); } @@ -63,11 +83,20 @@ protected function assertNoIndexOnColumns($table_name, array $column_names, $ind * the given type. */ protected function getIndexColumnNames($table_name, $index_type) { + if (!$table_name instanceof TableIdentifier) { + @trigger_error("Passing a table identifier as a string to " . __METHOD__ . "() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a Table identifier value object instead. See https://www.drupal.org/node/7654132", E_USER_DEPRECATED); + $table = $this->connection->identifiers->table($table_name); + } + else { + $table = $table_name; + } + assert($table instanceof TableIdentifier); + assert(in_array($index_type, ['index', 'unique', 'primary'], TRUE)); $schema = \Drupal::database()->schema(); $introspect_index_schema = new \ReflectionMethod(get_class($schema), 'introspectIndexSchema'); - $index_schema = $introspect_index_schema->invoke($schema, $table_name); + $index_schema = $introspect_index_schema->invoke($schema, $table); // Filter the indexes by type. if ($index_type === 'primary') { -- GitLab