diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc index 9a778c76c47b351cc760b587aa26a482a98a9019..c5e3ed919085fae98bbbf778fcd7a77ef6a60893 100644 --- a/includes/database/sqlite/database.inc +++ b/includes/database/sqlite/database.inc @@ -24,7 +24,7 @@ class DatabaseConnection_sqlite extends DatabaseConnection { * Version of sqlite lower then 3.6.8 can't use savepoints. * See http://www.sqlite.org/releaselog/3_6_8.html * - * @var bool + * @var boolean */ protected $savepointSupport = FALSE; @@ -35,6 +35,26 @@ class DatabaseConnection_sqlite extends DatabaseConnection { */ protected $willRollback; + /** + * All databases attached to the current database. This is used to allow + * prefixes to be safely handled without locking the table + * + * @var array + */ + protected $attachedDatabases = array(); + + /** + * 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 + * being empty. + * + * This variable is set to public because DatabaseSchema_sqlite needs to + * access it. However, it should not be manually set. + * + * @var boolean + */ + var $tableDropped = FALSE; + public function __construct(array $connection_options = array()) { // We don't need a specific PDOStatement class here, we simulate it below. $this->statementClass = NULL; @@ -51,6 +71,27 @@ public function __construct(array $connection_options = array()) { PDO::ATTR_STRINGIFY_FETCHES => TRUE, )); + // Attach one database for each registered prefix. + $prefixes = &$this->prefixes; + if (!empty($this->defaultPrefix)) { + // Add in the default prefix, which is also attached. + $prefixes[] = &$this->defaultPrefix; + } + foreach ($this->prefixes as $table => &$prefix) { + // 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; + $this->query('ATTACH DATABASE :database AS :prefix', array(':database' => $connection_options['database'] . '-' . $prefix, ':prefix' => $prefix)); + } + + // Add a ., so queries become prefix.table, which is proper syntax for + // querying an attached database. + $prefix .= '.'; + } + } + $this->exec('PRAGMA encoding="UTF-8"'); // Detect support for SAVEPOINT. @@ -69,6 +110,36 @@ public function __construct(array $connection_options = array()) { $this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand')); } + /** + * Destructor for the SQLite connection. + * + * 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', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField(); + + // We can prune the database file if it doens't have any tables. + if ($count == 0) { + // Detach the database. + $this->query('DETACH DATABASE :schema', array(':schema' => $prefix)); + // Destroy 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. + } + } + } + } + /** * SQLite compatibility implementation for the IF() SQL function. */ diff --git a/includes/database/sqlite/schema.inc b/includes/database/sqlite/schema.inc index 6830e6b19757b16caa57a994e0a3585b19b627f0..a7871a577dd13fe9b164dd0f4b68fbb50e7a9d12 100644 --- a/includes/database/sqlite/schema.inc +++ b/includes/database/sqlite/schema.inc @@ -271,7 +271,7 @@ public function dropTable($table) { if (!$this->tableExists($table)) { return FALSE; } - + $this->connection->tableDropped = TRUE; $this->connection->query('DROP TABLE {' . $table . '}'); return TRUE; } @@ -622,9 +622,16 @@ public function fieldSetNoDefault($table, $field) { } public function findTables($table_expression) { - // Don't use {} around sqlite_master table. - $result = db_query("SELECT name FROM sqlite_master WHERE name LIKE :table_name", array( - ':table_name' => $table_expression, + // Don't use getPrefixInfo -- $table_expression includes the prefix. + list($prefix, $table) = explode('.', $table_expression); + if (empty($table)) { + $table = $prefix; + $prefix = NULL; + } + // Can't use query placeholders because the query would have to be + // :prefixsqlite_master, which does not work. + $result = db_query("SELECT name FROM " . ($prefix ? $prefix . '.' : '') . "sqlite_master WHERE name LIKE :table_name", array( + ':table_name' => $table, )); return $result->fetchAllKeyed(0, 0); } diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index e9d5ad43dd0c81b1eab808647836e3d3959dc4a8..841451746ea6989428dc98d7524bf9cbc9a0aca5 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -1313,9 +1313,32 @@ protected function setUp() { * set up a clean environment for the current test run. */ protected function preloadRegistry() { + // Use two separate queries, each with their own connections: copy the + // {registry} and {registry_file} tables over from the parent installation + // to the child installation. $original_connection = Database::getConnection('default', 'simpletest_original_default'); - db_query('INSERT INTO {registry} SELECT * FROM ' . $original_connection->prefixTables('{registry}')); - db_query('INSERT INTO {registry_file} SELECT * FROM ' . $original_connection->prefixTables('{registry_file}')); + $test_connection = Database::getConnection(); + + foreach (array('registry', 'registry_file') as $table) { + // Find the records from the parent database. + $source_query = $original_connection + ->select($table, array(), array('fetch' => PDO::FETCH_ASSOC)) + ->fields($table); + + $dest_query = $test_connection->insert($table); + + $first = TRUE; + foreach ($source_query->execute() as $row) { + if ($first) { + $dest_query->fields(array_keys($row)); + $first = FALSE; + } + // Insert the records into the child database. + $dest_query->values($row); + } + + $dest_query->execute(); + } } /**