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