diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php index 0f9a4d25..7bb6f492 100644 --- a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php @@ -35,6 +35,16 @@ class Connection extends DatabaseConnection { 'NOT LIKE BINARY' => ['postfix' => " ESCAPE '\\'", 'operator' => 'NOT GLOB'], ]; + /** + * Names of database attached to the main database. + * + * If a table prefix is specified, a database with the prefix name is + * attached. + * + * @var array + */ + protected $attachedDatabases = []; + /** * Whether or not a table has been dropped this request: the destructor will * only try to get rid of unnecessary databases if there is potential of them @@ -43,11 +53,6 @@ class Connection extends DatabaseConnection { * This variable is set to public because Schema needs to * access it. However, it should not be manually set. * - * @deprecated in Drupal 9.0.0 and will be removed before Drupal 10.0.0. - * This variable has become irrelevant because Drupal no longer supports - * attaching to other SQLite databases. Instead create another database - * connection for the other SQLite database. - * * @var bool */ public $tableDropped = FALSE; @@ -66,6 +71,29 @@ public function __construct(\PDO $connection, array $connection_options) { $this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE; $this->connectionOptions = $connection_options; + + // Attach a database if a prefix is defined. + $prefix = $this->getPrefix(); + // Empty prefix means query the main database -- no need to attach anything. + if (!empty($prefix)) { + // Only attach the database once. + if (!isset($this->attachedDatabases[$prefix])) { + $this->attachedDatabases[$prefix] = $prefix; + if ($connection_options['database'] === ':memory:') { + // In memory database use ':memory:' as database name. According to + // http://www.sqlite.org/inmemorydb.html it will open a unique + // database so attaching it twice is not a problem. + $this->query('ATTACH DATABASE :database AS :prefix', [':database' => $connection_options['database'], ':prefix' => $prefix]); + } + else { + $this->query('ATTACH DATABASE :database AS :prefix', [':database' => $connection_options['database'] . '-' . $prefix, ':prefix' => $prefix]); + } + } + + // Add a ., so queries become prefix.table, which is proper syntax for + // querying an attached database. + $this->setPrefix($prefix . '.'); + } } /** @@ -136,12 +164,37 @@ public static function open(array &$connection_options = []) { } /** - * Gets all the attached databases. + * Destructor for the SQLite connection. * - * @deprecated in Drupal 9.0.0 and will be removed before Drupal 10.0.0. - * This method has become irrelevant because Drupal no longer supports - * attaching to other SQLite databases. Instead create another database - * connection for the other SQLite database. + * We prune empty databases on destruct, but only if tables have been + * dropped. This is especially needed when running the test suite, which + * creates and destroy databases several times in a row. + */ + public function __destruct() { + if ($this->tableDropped && !empty($this->attachedDatabases)) { + foreach ($this->attachedDatabases as $prefix) { + // Check if the database is now empty, ignore the internal SQLite tables. + try { + $count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', [':type' => 'table', ':pattern' => 'sqlite_%'])->fetchField(); + + // We can prune the database file if it doesn't have any tables. + if ($count == 0) { + // Detaching the database fails at this point, but no other queries + // are executed after the connection is destructed so we can simply + // remove the database file. + unlink($this->connectionOptions['database'] . '-' . $prefix); + } + } + catch (\Exception $e) { + // Ignore the exception and continue. There is nothing we can do here + // to report the error or fail safe. + } + } + } + } + + /** + * Gets all the attached databases. * * @return array * An array of attached database names. @@ -149,7 +202,7 @@ public static function open(array &$connection_options = []) { * @see \Drupal\Core\Database\Driver\sqlite\Connection::__construct() */ public function getAttachedDatabases() { - return []; + return $this->attachedDatabases; } /**