Commit e1c6ba37 authored by catch's avatar catch
Browse files

Issue #3124382 by mondrake, daffie, alexpott, catch: Remove per-table prefixing

parent d77a8607
Loading
Loading
Loading
Loading
+56 −80
Original line number Diff line number Diff line
@@ -108,10 +108,32 @@ abstract class Connection {
   */
  protected $schema = NULL;

  /**
   * The prefix used by this database connection.
   *
   * @var string
   */
  private string $prefix;

  /**
   * 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[]
   */
  private array $tablePlaceholderReplacements;

  /**
   * The prefixes used by this database connection.
   *
   * @var array
   *
   * @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
   */
  protected $prefixes = [];

@@ -119,6 +141,11 @@ abstract class Connection {
   * List of search values for use in prefixTables().
   *
   * @var array
   *
   * @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
   */
  protected $prefixSearch = [];

@@ -126,6 +153,11 @@ abstract class Connection {
   * List of replacement values for use in prefixTables().
   *
   * @var array
   *
   * @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
   */
  protected $prefixReplace = [];

@@ -133,6 +165,11 @@ abstract class Connection {
   * List of un-prefixed table names, keyed by prefixed table names.
   *
   * @var array
   *
   * @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
   */
  protected $unprefixedTablesMap = [];

@@ -188,45 +225,13 @@ abstract class Connection {
   *   - prefix
   *   - namespace
   *   - Other driver-specific options.
   *   An 'extra_prefix' option may be present to allow BC for attaching
   *   per-table prefixes, but it is meant for internal use only.
   */
  public function __construct(\PDO $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.
    if (isset($connection_options['prefix']) && is_array($connection_options['prefix'])) {
      if (count($connection_options['prefix']) > 1) {
        // If there are keys left besides the 'default' one, we are in a
        // multi-prefix scenario (for per-table prefixing, or migrations).
        // In that case, we put the non-default keys in a 'extra_prefix' key
        // to avoid mixing up with the normal 'prefix', which is a string since
        // Drupal 9.1.0.
        $prefix = $connection_options['prefix']['default'] ?? '';
        unset($connection_options['prefix']['default']);
        if (isset($connection_options['extra_prefix'])) {
          $connection_options['extra_prefix'] = array_merge($connection_options['extra_prefix'], $connection_options['prefix']);
        }
        else {
          $connection_options['extra_prefix'] = $connection_options['prefix'];
        }
      }
      else {
        $prefix = $connection_options['prefix']['default'] ?? '';
      }
      $connection_options['prefix'] = $prefix;
    }

    // Initialize and prepare the connection prefix.
    if (!isset($connection_options['extra_prefix'])) {
      $prefix = $connection_options['prefix'] ?? '';
    }
    else {
      $default_prefix = $connection_options['prefix'] ?? '';
      $prefix = $connection_options['extra_prefix'];
      $prefix['default'] = $default_prefix;
    }
    $this->setPrefix($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
@@ -358,48 +363,18 @@ public function attachDatabase(string $database): void {
  }

  /**
   * Set the list of prefixes used by this database connection.
   * Set the prefix used by this database connection.
   *
   * @param array|string $prefix
   *   Either a single prefix, or an array of prefixes.
   * @param string $prefix
   *   A single prefix.
   */
  protected function setPrefix($prefix) {
    if (is_array($prefix)) {
      $this->prefixes = $prefix + ['default' => ''];
    }
    else {
      $this->prefixes = ['default' => $prefix];
    }

    [$start_quote, $end_quote] = $this->identifierQuotes;
    // Set up variables for use in prefixTables(). Replace table-specific
    // prefixes first.
    $this->prefixSearch = [];
    $this->prefixReplace = [];
    foreach ($this->prefixes as $key => $val) {
      if ($key != 'default') {
        $this->prefixSearch[] = '{' . $key . '}';
        // $val can point to another database like 'database.users'. In this
        // instance we need to quote the identifiers correctly.
        $val = str_replace('.', $end_quote . '.' . $start_quote, $val);
        $this->prefixReplace[] = $start_quote . $val . $key . $end_quote;
      }
    }
    // Then replace remaining tables with the default prefix.
    $this->prefixSearch[] = '{';
    // $this->prefixes['default'] can point to another database like
    // 'other_db.'. In this instance we need to quote the identifiers correctly.
    // For example, "other_db"."PREFIX_table_name".
    $this->prefixReplace[] = $start_quote . str_replace('.', $end_quote . '.' . $start_quote, $this->prefixes['default']);
    $this->prefixSearch[] = '}';
    $this->prefixReplace[] = $end_quote;

    // Set up a map of prefixed => un-prefixed tables.
    foreach ($this->prefixes as $table_name => $prefix) {
      if ($table_name !== 'default') {
        $this->unprefixedTablesMap[$prefix . $table_name] = $table_name;
      }
    }
    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],
    ];
  }

  /**
@@ -417,7 +392,7 @@ protected function setPrefix($prefix) {
   *   The properly-prefixed string.
   */
  public function prefixTables($sql) {
    return str_replace($this->prefixSearch, $this->prefixReplace, $sql);
    return str_replace(['{', '}'], $this->tablePlaceholderReplacements, $sql);
  }

  /**
@@ -454,12 +429,7 @@ public function quoteIdentifiers($sql) {
   *   (optional) The table to find the prefix for.
   */
  public function tablePrefix($table = 'default') {
    if (isset($this->prefixes[$table])) {
      return $this->prefixes[$table];
    }
    else {
      return $this->prefixes['default'];
    }
    return $this->prefix;
  }

  /**
@@ -468,8 +438,14 @@ public function tablePrefix($table = 'default') {
   * @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;
  }

+2 −20
Original line number Diff line number Diff line
@@ -235,26 +235,8 @@ final public static function parseConnectionInfo(array $info) {
      $info = $info[mt_rand(0, count($info) - 1)];
    }

    // Parse the prefix information.
    // @todo in Drupal 10, fail hard if $info['prefix'] is an array.
    // @see https://www.drupal.org/project/drupal/issues/3124382
    if (!isset($info['prefix'])) {
      // Default to an empty prefix.
      $info['prefix'] = '';
    }
    elseif (is_array($info['prefix'])) {
      $prefix = $info['prefix']['default'] ?? '';
      unset($info['prefix']['default']);
      // If there are keys left besides the 'default' one, we are in a
      // multi-prefix scenario (for per-table prefixing, or migrations).
      // In that case, we put the non-default keys in a 'extra_prefix' key
      // to avoid mixing up with the normal 'prefix', which is a string since
      // Drupal 9.1.0.
      if (count($info['prefix'])) {
        $info['extra_prefix'] = $info['prefix'];
      }
      $info['prefix'] = $prefix;
    }
    // Prefix information, default to an empty prefix.
    $info['prefix'] = $info['prefix'] ?? '';

    // Fallback for Drupal 7 settings.php if namespace is not provided.
    if (empty($info['namespace'])) {
+5 −13
Original line number Diff line number Diff line
@@ -191,9 +191,8 @@ public function findTables($table_expression) {
    $condition = $this->buildTableNameCondition('%', 'LIKE');
    $condition->compile($this->connection, $this);

    $individually_prefixed_tables = $this->connection->getUnprefixedTablesMap();
    $default_prefix = $this->connection->tablePrefix();
    $default_prefix_length = strlen($default_prefix);
    $prefix = $this->connection->tablePrefix();
    $prefix_length = strlen($prefix);
    $tables = [];
    // Normally, we would heartily discourage the use of string
    // concatenation for conditionals like this however, we
@@ -202,18 +201,11 @@ public function findTables($table_expression) {
    // Don't use {} around information_schema.tables table.
    $results = $this->connection->query("SELECT table_name AS table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments());
    foreach ($results as $table) {
      // Take into account tables that have an individual prefix.
      if (isset($individually_prefixed_tables[$table->table_name])) {
        $prefix_length = strlen($this->connection->tablePrefix($individually_prefixed_tables[$table->table_name]));
      }
      elseif ($default_prefix && substr($table->table_name, 0, $default_prefix_length) !== $default_prefix) {
        // This table name does not start the default prefix, which means that
        // it is not managed by Drupal so it should be excluded from the result.
      if ($prefix && substr($table->table_name, 0, $prefix_length) !== $prefix) {
        // This table name does not start the prefix, which means that it is
        // not managed by Drupal so it should be excluded from the result.
        continue;
      }
      else {
        $prefix_length = $default_prefix_length;
      }

      // Remove the prefix from the returned tables.
      $unprefixed_table_name = substr($table->table_name, $prefix_length);
+1 −1
Original line number Diff line number Diff line
@@ -642,7 +642,7 @@ public function changeField($table, $field, $field_new, $spec, $keys_new = []) {
  public function prepareComment($comment, $length = NULL) {
    // Truncate comment to maximum comment length.
    if (isset($length)) {
      // Add table prefixes before truncating.
      // Add table prefix before truncating.
      $comment = Unicode::truncate($this->connection->prefixTables($comment), $length, TRUE, TRUE);
    }
    // Remove semicolons to avoid triggering multi-statement check.
+3 −11
Original line number Diff line number Diff line
@@ -511,27 +511,19 @@ public function tableExists($table) {
   * {@inheritdoc}
   */
  public function findTables($table_expression) {
    $individually_prefixed_tables = $this->connection->getUnprefixedTablesMap();
    $default_prefix = $this->connection->tablePrefix();
    $default_prefix_length = strlen($default_prefix);
    $prefix = $this->connection->tablePrefix();
    $prefix_length = strlen($prefix);
    $tables = [];

    // Load all the tables up front in order to take into account per-table
    // prefixes. The actual matching is done at the bottom of the method.
    $results = $this->connection->query("SELECT tablename FROM pg_tables WHERE schemaname = :schema", [':schema' => $this->defaultSchema]);
    foreach ($results as $table) {
      // Take into account tables that have an individual prefix.
      if (isset($individually_prefixed_tables[$table->tablename])) {
        $prefix_length = strlen($this->connection->tablePrefix($individually_prefixed_tables[$table->tablename]));
      }
      elseif ($default_prefix && substr($table->tablename, 0, $default_prefix_length) !== $default_prefix) {
      if ($prefix && substr($table->tablename, 0, $prefix_length) !== $prefix) {
        // This table name does not start the default prefix, which means that
        // it is not managed by Drupal so it should be excluded from the result.
        continue;
      }
      else {
        $prefix_length = $default_prefix_length;
      }

      // Remove the prefix from the returned tables.
      $unprefixed_table_name = substr($table->tablename, $prefix_length);
Loading