Bug 1283083 - Cloning a connection should re-attach databases to the clone. r=asuth
MozReview-Commit-ID: CSZqvJNWthB
--- a/storage/mozIStorageAsyncConnection.idl
+++ b/storage/mozIStorageAsyncConnection.idl
@@ -42,16 +42,17 @@ interface mozIStorageAsyncConnection : n
* If called on a connection that has already been closed or was
* never properly opened. The callback will still be dispatched
* to the main thread despite the returned error.
*/
void asyncClose([optional] in mozIStorageCompletionCallback aCallback);
/**
* Clone a database and make the clone read only if needed.
+ * SQL Functions and attached on-disk databases are applied to the new clone.
*
* @param aReadOnly
* If true, the returned database should be put into read-only mode.
*
* @param aCallback
* A callback that will be notified when the operation is complete,
* with the following arguments:
* - status: the status of the operation
--- a/storage/mozIStorageConnection.idl
+++ b/storage/mozIStorageConnection.idl
@@ -53,16 +53,17 @@ interface mozIStorageConnection : mozISt
* If any statement has been executed asynchronously on this object.
* @throws NS_ERROR_UNEXPECTED
* If is called on a thread other than the one that opened it.
*/
void close();
/**
* Clones a database connection and makes the clone read only if needed.
+ * SQL Functions and attached on-disk databases are applied to the new clone.
*
* @param aReadOnly
* If true, the returned database should be put into read-only mode.
* Defaults to false.
* @return the cloned database connection.
*
* @throws NS_ERROR_UNEXPECTED
* If this connection is a memory database.
--- a/storage/mozStorageConnection.cpp
+++ b/storage/mozStorageConnection.cpp
@@ -1329,16 +1329,39 @@ nsresult
Connection::initializeClone(Connection* aClone, bool aReadOnly)
{
nsresult rv = mFileURL ? aClone->initialize(mFileURL)
: aClone->initialize(mDatabaseFile);
if (NS_FAILED(rv)) {
return rv;
}
+ // Re-attach on-disk databases that were attached to the original connection.
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = CreateStatement(NS_LITERAL_CSTRING("PRAGMA database_list"),
+ getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ bool hasResult = false;
+ while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ nsAutoCString name;
+ rv = stmt->GetUTF8String(1, name);
+ if (NS_SUCCEEDED(rv) && !name.Equals(NS_LITERAL_CSTRING("main")) &&
+ !name.Equals(NS_LITERAL_CSTRING("temp"))) {
+ nsCString path;
+ rv = stmt->GetUTF8String(2, path);
+ if (NS_SUCCEEDED(rv) && !path.IsEmpty()) {
+ rv = aClone->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ATTACH DATABASE '") +
+ path + NS_LITERAL_CSTRING("' AS ") + name);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "couldn't re-attach database to cloned connection");
+ }
+ }
+ }
+ }
+
// Copy over pragmas from the original connection.
static const char * pragmas[] = {
"cache_size",
"temp_store",
"foreign_keys",
"journal_size_limit",
"synchronous",
"wal_autocheckpoint",
--- a/storage/test/unit/test_storage_connection.js
+++ b/storage/test/unit/test_storage_connection.js
@@ -691,24 +691,58 @@ add_task(function* test_readonly_clone_c
validate(pragma.value, stmt.getInt32(0));
stmt.finalize();
});
db1.close();
db2.close();
});
+add_task(function* test_clone_attach_database() {
+ let db1 = getService().openUnsharedDatabase(getTestDB());
+
+ let c = 0;
+ function attachDB(conn, name) {
+ let file = dirSvc.get("ProfD", Ci.nsIFile);
+ file.append("test_storage_" + (++c) + ".sqlite");
+ let db = getService().openUnsharedDatabase(file);
+ conn.executeSimpleSQL(`ATTACH DATABASE '${db.databaseFile.path}' AS ${name}`);
+ db.close();
+ }
+ attachDB(db1, "attached_1");
+ attachDB(db1, "attached_2");
+
+ // These should not throw.
+ db1.createStatement("SELECT * FROM attached_1.sqlite_master");
+ db1.createStatement("SELECT * FROM attached_2.sqlite_master");
+
+ // R/W clone.
+ let db2 = db1.clone();
+ do_check_true(db2.connectionReady);
+
+ // These should not throw.
+ db2.createStatement("SELECT * FROM attached_1.sqlite_master");
+ db2.createStatement("SELECT * FROM attached_2.sqlite_master");
+
+ // R/O clone.
+ let db3 = db1.clone(true);
+ do_check_true(db3.connectionReady);
+
+ // These should not throw.
+ db3.createStatement("SELECT * FROM attached_1.sqlite_master");
+ db3.createStatement("SELECT * FROM attached_2.sqlite_master");
+
+ db1.close();
+ db2.close();
+ db3.close();
+});
+
add_task(function* test_getInterface() {
let db = getOpenedDatabase();
let target = db.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIEventTarget);
// Just check that target is non-null. Other tests will ensure that it has
// the correct value.
do_check_true(target != null);
yield asyncClose(db);
gDBConn = null;
});
-
-
-function run_test() {
- run_next_test();
-}