diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 2432771..b088f66 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -139,6 +139,13 @@ protected $prefixReplace = array(); /** + * List of un-prefixed table names, keyed by fully qualified table names. + * + * @var array + */ + protected $prefixedTablesMap = []; + + /** * Constructs a Connection object. * * @param \PDO $connection @@ -291,6 +298,13 @@ protected function setPrefix($prefix) { $this->prefixReplace[] = $this->prefixes['default']; $this->prefixSearch[] = '}'; $this->prefixReplace[] = ''; + + // Set up a map of prefixed => un-prefixed tables. + foreach ($this->prefixes as $table_name => $prefix) { + if ($table_name !== 'default') { + $this->prefixedTablesMap[$prefix . $table_name] = $table_name; + } + } } /** @@ -330,6 +344,17 @@ public function tablePrefix($table = 'default') { } /** + * 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). + */ + public function getPrefixedTablesMap() { + return $this->prefixedTablesMap; + } + + /** * Get a fully qualified table name. * * @param string $table diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 1445f22..cf8206f 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -178,7 +178,7 @@ public function tableExists($table) { } /** - * Find all tables that are like the specified base table name. + * Finds all tables that are like the specified base table name. * * @param string $table_expression * An SQL expression, for example "cache_%" (without the quotes). @@ -187,11 +187,13 @@ public function tableExists($table) { * Both the keys and the values are the matching tables. */ public function findTables($table_expression) { - $condition = $this->buildTableNameCondition($table_expression, 'LIKE'); - + // 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. + $condition = $this->buildTableNameCondition('%', 'LIKE'); $condition->compile($this->connection, $this); - $prefix_length = strlen($this->connection->tablePrefix()); + $individually_prefixed_tables = $this->connection->getPrefixedTablesMap(); + $default_prefix_length = strlen($this->connection->tablePrefix()); $tables = []; // Normally, we would heartily discourage the use of string // concatenation for conditionals like this however, we @@ -199,8 +201,17 @@ public function findTables($table_expression) { // information_schema.tables and the query would fail. // Don't use {} around information_schema.tables table. foreach ($this->connection->query("SELECT table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments()) 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])); + } + else { + $prefix_length = $default_prefix_length; + } + // Remove the prefix from the returned tables. $unprefixed_table_name = substr($table->table_name, $prefix_length); + // The pattern can match a table which is the same as the prefix. That // will become an empty string when we remove the prefix, which will // probably surprise the caller, besides not being a prefixed table. So @@ -209,6 +220,12 @@ public function findTables($table_expression) { $tables[$unprefixed_table_name] = $unprefixed_table_name; } } + + // Convert the table expression from its SQL LIKE syntax to a regular + // expression and escape the delimiter that will be used for matching. + $table_expression = str_replace(array('%', '_'), array('.*?', '.'), preg_quote($table_expression, '/')); + $tables = preg_grep('/^' . $table_expression . '$/i', $tables); + return $tables; } diff --git a/core/modules/system/src/Tests/Database/SchemaTest.php b/core/modules/system/src/Tests/Database/SchemaTest.php index 8ec268d..4511320 100644 --- a/core/modules/system/src/Tests/Database/SchemaTest.php +++ b/core/modules/system/src/Tests/Database/SchemaTest.php @@ -698,4 +698,64 @@ protected function assertFieldChange($old_spec, $new_spec) { // Clean-up. db_drop_table($table_name); } + + /** + * Tests the findTables() method. + */ + public function testFindTables() { + // We will be testing with three tables, two of them using the default + // prefix and the third one with an individually specified prefix. + + // Set up a new connection with different connection info. + $connection_info = Database::getConnectionInfo(); + + // Add per-table prefix to the second table. + $new_connection_info = $connection_info['default']; + $new_connection_info['prefix']['test_2_table'] = $new_connection_info['prefix']['default'] . '_shared_'; + Database::addConnectionInfo('test', 'default', $new_connection_info); + + Database::setActiveConnection('test'); + + // Create the tables. + $table_specification = [ + 'description' => 'Test table.', + 'fields' => [ + 'id' => [ + 'type' => 'int', + 'default' => NULL, + ], + ], + ]; + Database::getConnection()->schema()->createTable('test_1_table', $table_specification); + Database::getConnection()->schema()->createTable('test_2_table', $table_specification); + Database::getConnection()->schema()->createTable('the_third_table', $table_specification); + + // Check the "all tables" syntax. + $tables = Database::getConnection()->schema()->findTables('%'); + sort($tables); + debug($tables); + $expected = [ + // The 'config' table is added by + // \Drupal\simpletest\KernelTestBase::containerBuild(). + 'config', + 'test_1_table', + // This table uses a per-table prefix, yet it is returned as un-prefixed. + 'test_2_table', + 'the_third_table', + ]; + $this->assertEqual($tables, $expected, 'All tables were found.'); + + // Check the restrictive syntax. + $tables = Database::getConnection()->schema()->findTables('test_%'); + sort($tables); + $expected = [ + 'test_1_table', + 'test_2_table', + ]; + $this->assertEqual($tables, $expected, 'Two tables were found.'); + + // Go back to the initial connection. + Database::setActiveConnection('default'); + } + }