Bug 1283083 - Cloning a connection should re-attach databases to the clone. r=asuth
authorMarco Bonardo <mbonardo@mozilla.com>
Wed, 29 Jun 2016 17:33:15 +0200
changeset 333604 9b5be9da64bafc31baba3bc81c5d42785081e3d9
parent 333602 21585b3e48141dc0ecb55fece8f424ba5855b08f
child 333607 20d8f274cc880211d59708b569358cc70134aff5
push idunknown
push userunknown
push dateunknown
reviewersasuth
bugs1283083
milestone50.0a1
Bug 1283083 - Cloning a connection should re-attach databases to the clone. r=asuth MozReview-Commit-ID: CSZqvJNWthB
storage/mozIStorageAsyncConnection.idl
storage/mozIStorageConnection.idl
storage/mozStorageConnection.cpp
storage/test/unit/test_storage_connection.js
--- 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();
-}