summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAngie Byron <webchick@24967.no-reply.drupal.org>2010-12-22 21:34:13 +0000
committerAngie Byron <webchick@24967.no-reply.drupal.org>2010-12-22 21:34:13 +0000
commita87da0d78a67c307a54ce735aa00b0c6cb11ad69 (patch)
tree9248f88397968f23ff5123ad10ad1511f14a933f
parent1eb122344a6b383741265228cd2a286dc94ee8de (diff)
downloadbrdo-a87da0d78a67c307a54ce735aa00b0c6cb11ad69.tar.gz
brdo-a87da0d78a67c307a54ce735aa00b0c6cb11ad69.tar.bz2
#850852 by Damien Tournoud, dmitrig01, chx: Fixed transaction failure and allow concurrent testing on SQLite
-rw-r--r--includes/database/sqlite/database.inc73
-rw-r--r--includes/database/sqlite/schema.inc15
-rw-r--r--modules/simpletest/drupal_web_test_case.php27
3 files changed, 108 insertions, 7 deletions
diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc
index 9a778c76c..c5e3ed919 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 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
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.
@@ -70,6 +111,36 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
}
/**
+ * 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.
*/
public function sqlFunctionIf($condition, $expr1, $expr2 = NULL) {
diff --git a/includes/database/sqlite/schema.inc b/includes/database/sqlite/schema.inc
index 6830e6b19..a7871a577 100644
--- a/includes/database/sqlite/schema.inc
+++ b/includes/database/sqlite/schema.inc
@@ -271,7 +271,7 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
if (!$this->tableExists($table)) {
return FALSE;
}
-
+ $this->connection->tableDropped = TRUE;
$this->connection->query('DROP TABLE {' . $table . '}');
return TRUE;
}
@@ -622,9 +622,16 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
}
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 e9d5ad43d..841451746 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -1313,9 +1313,32 @@ class DrupalWebTestCase extends DrupalTestCase {
* 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();
+ }
}
/**