Bug 458811 - Allow for multiple statements to be executed at in a transaction asynchronously
authorShawn Wilsher <sdwilsh@shawnwilsher.com>
Mon, 13 Oct 2008 18:45:40 -0400
changeset 20442 60e6f428bd13c8cf36d0fff50de4cadb7078b2cb
parent 20441 1ee3957c661721a0b2e71e64dd75e4fc17458135
child 20443 9ed217d6381bd27c0182a3d9428768b04f9eefab
push idunknown
push userunknown
push dateunknown
bugs458811
milestone1.9.1b2pre
Bug 458811 - Allow for multiple statements to be executed at in a transaction asynchronously This changeset allows consumers to execute a series of statements, in order, asynchronously in a transaction. r=dcamp sr=sicking
storage/public/mozIStorageConnection.idl
storage/public/mozStorageHelper.h
storage/src/mozStorageConnection.cpp
storage/src/mozStorageEvents.cpp
storage/src/mozStorageEvents.h
storage/src/mozStorageStatement.cpp
storage/test/unit/test_connection_executeAsync.js
--- a/storage/public/mozIStorageConnection.idl
+++ b/storage/public/mozIStorageConnection.idl
@@ -40,33 +40,35 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface mozIStorageAggregateFunction;
 interface mozIStorageFunction;
 interface mozIStorageProgressHandler;
 interface mozIStorageStatement;
+interface mozIStorageStatementCallback;
+interface mozIStoragePendingStatement;
 interface nsIFile;
 
 /**
  * mozIStorageConnection represents a database connection attached to
  * a specific file or to the in-memory data storage.  It is the
  * primary interface for interacting with a database, including
  * creating prepared statements, executing SQL, and examining database
  * errors.
  *
  * @threadsafe
  */
-[scriptable, uuid(623f9ddb-434b-4d39-bc2d-1c617da241d0)]
+[scriptable, uuid(ac3c486c-69a1-4cbe-8f25-2ad20880eab3)]
 interface mozIStorageConnection : nsISupports {
   /*
    * Initialization and status
    */
-  
+
   /**
    * Closes a database connection.  C++ callers should simply set the database
    * variable to NULL.
    */
    void close();
 
   /**
    * Indicates if the connection is open and ready to use.  This will be false
@@ -122,16 +124,41 @@ interface mozIStorageConnection : nsISup
   /**
    * Execute a SQL expression, expecting no arguments.
    *
    * @param aSQLStatement  The SQL statement to execute
    */
   void executeSimpleSQL(in AUTF8String aSQLStatement);
 
   /**
+   * Execute an array of queries created with this connection asynchronously
+   * using any currently bound parameters.  The statements are ran wrapped in a
+   * transaction.  These statements can be reused immediately, and reset does
+   * not need to be called.
+   *
+   * Note:  If you have any custom defined functions, they must be re-entrant
+   *        since they can be called on multiple threads.
+   *
+   * @param aStatements
+   *        The array of statements to execute asynchronously, in the order they
+   *        are given in the array.
+   * @param aNumStatements
+   *        The number of statements in aStatements.
+   * @param aCallback [optional]
+   *        The callback object that will be notified of progress, errors, and
+   *        completion.
+   * @returns an object that can be used to cancel the statements execution.
+   */
+  mozIStoragePendingStatement executeAsync(
+    [array, size_is(aNumStatements)] in mozIStorageStatement aStatements,
+    in unsigned long aNumStatements,
+    [optional] in mozIStorageStatementCallback aCallback
+  );
+
+  /**
    * Check if the given table exists.
    *
    * @param aTableName   The table to check
    * @returns TRUE if table exists, FALSE otherwise.
    */
   boolean tableExists(in AUTF8String aTableName);
 
   /**
--- a/storage/public/mozStorageHelper.h
+++ b/storage/public/mozStorageHelper.h
@@ -35,16 +35,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _MOZSTORAGEHELPER_H_
 #define _MOZSTORAGEHELPER_H_
 
 #include "mozIStorageConnection.h"
+#include "mozIStorageStatement.h"
 
 
 /**
  * This class wraps a transaction inside a given C++ scope, guaranteeing that
  * the transaction will be completed even if you have an exception or
  * return early.
  *
  * aCommitOnComplete controls whether the transaction is committed or rolled
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -49,16 +49,17 @@
 #include "nsIFile.h"
 #include "nsIVariant.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 
 #include "mozIStorageAggregateFunction.h"
 #include "mozIStorageFunction.h"
 
+#include "mozStorageEvents.h"
 #include "mozStorageUnicodeFunctions.h"
 #include "mozStorageConnection.h"
 #include "mozStorageService.h"
 #include "mozStorageStatement.h"
 #include "mozStorageValueArray.h"
 #include "mozStorage.h"
 
 #include "prlog.h"
@@ -353,16 +354,70 @@ mozStorageConnection::ExecuteSimpleSQL(c
     if (srv != SQLITE_OK) {
         HandleSqliteError(nsPromiseFlatCString(aSQLStatement).get());
         return ConvertResultCode(srv);
     }
 
     return NS_OK;
 }
 
+nsresult
+mozStorageConnection::ExecuteAsync(mozIStorageStatement ** aStatements,
+                                   PRUint32 aNumStatements,
+                                   mozIStorageStatementCallback *aCallback,
+                                   mozIStoragePendingStatement **_stmt)
+{
+    int rc = SQLITE_OK;
+    nsTArray<sqlite3_stmt *> stmts(aNumStatements);
+    for (PRUint32 i = 0; i < aNumStatements && rc == SQLITE_OK; i++) {
+        sqlite3_stmt *old_stmt = aStatements[i]->GetNativeStatementPointer();
+        NS_ASSERTION(sqlite3_db_handle(old_stmt) == mDBConn,
+                     "Statement must be from this database connection!");
+
+        // Clone this statement.  We only need a sqlite3_stmt object, so we can
+        // avoid all the extra work that making a new mozStorageStatement would
+        // normally involve and use the SQLite API.
+        sqlite3_stmt *new_stmt;
+        rc = sqlite3_prepare_v2(mDBConn, sqlite3_sql(old_stmt), -1, &new_stmt,
+                                NULL);
+        if (rc != SQLITE_OK)
+            break;
+
+        // Transfer the bindings
+        rc = sqlite3_transfer_bindings(old_stmt, new_stmt);
+        if (rc != SQLITE_OK)
+            break;
+
+        if (!stmts.AppendElement(new_stmt)) {
+            rc = SQLITE_NOMEM;
+            break;
+        }
+    }
+
+    // Dispatch to the background
+    nsresult rv = NS_OK;
+    if (rc == SQLITE_OK)
+        rv = NS_executeAsync(stmts, this, aCallback, _stmt);
+
+    // We had a failure, so we need to clean up...
+    if (rc != SQLITE_OK || NS_FAILED(rv)) {
+        for (PRUint32 i = 0; i < stmts.Length(); i++)
+            (void)sqlite3_finalize(stmts[i]);
+
+        if (rc != SQLITE_OK)
+            rv = ConvertResultCode(rc);
+    }
+
+    // Always reset all the statements
+    for (PRUint32 i = 0; i < aNumStatements; i++)
+        (void)aStatements[i]->Reset();
+
+    return rv;
+}
+
 NS_IMETHODIMP
 mozStorageConnection::TableExists(const nsACString& aSQLStatement, PRBool *_retval)
 {
     if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
     nsCString query("SELECT name FROM sqlite_master WHERE type = 'table' AND name ='");
     query.Append(aSQLStatement);
     query.AppendLiteral("'");
--- a/storage/src/mozStorageEvents.cpp
+++ b/storage/src/mozStorageEvents.cpp
@@ -41,16 +41,17 @@
 #include "nsAutoPtr.h"
 #include "nsAutoLock.h"
 #include "nsCOMArray.h"
 
 #include "sqlite3.h"
 
 #include "mozIStorageStatementCallback.h"
 #include "mozIStoragePendingStatement.h"
+#include "mozStorageHelper.h"
 #include "mozStorageResultSet.h"
 #include "mozStorageRow.h"
 #include "mozStorageBackground.h"
 #include "mozStorageError.h"
 #include "mozStorageEvents.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Asynchronous Statement Execution
@@ -267,25 +268,29 @@ class AsyncExecute : public nsIRunnable
                    , public iCompletionNotifier
 {
 public:
   NS_DECL_ISUPPORTS
 
   /**
    * This takes ownership of both the statement and the callback.
    */
-  AsyncExecute(sqlite3_stmt *aStatement,
+  AsyncExecute(nsTArray<sqlite3_stmt *> &aStatements,
+               mozIStorageConnection *aConnection,
                mozIStorageStatementCallback *aCallback) :
-      mStatement(aStatement)
+      mConnection(aConnection)
+    , mTransactionManager(nsnull)
     , mCallback(aCallback)
     , mCallingThread(do_GetCurrentThread())
     , mState(PENDING)
     , mStateMutex(nsAutoLock::NewLock("AsyncExecute::mStateMutex"))
     , mPendingEventsMutex(nsAutoLock::NewLock("AsyncExecute::mPendingEventsMutex"))
   {
+    (void)mStatements.SwapElements(aStatements);
+    NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
   }
 
   nsresult initialize()
   {
     NS_ENSURE_TRUE(mStateMutex, NS_ERROR_OUT_OF_MEMORY);
     NS_ENSURE_TRUE(mPendingEventsMutex, NS_ERROR_OUT_OF_MEMORY);
     NS_IF_ADDREF(mCallback);
     return NS_OK;
@@ -295,104 +300,116 @@ public:
   {
     // do not run if we have been canceled
     {
       nsAutoLock mutex(mStateMutex);
       if (mState == CANCELED)
         return Complete();
     }
 
-    // Execute the statement, giving the callback results
-    // XXX better chunking of results?
+    // If there is more than one statement, run it in a transaction.  We assume
+    // that we have been given write statements since getting a batch of read
+    // statements doesn't make a whole lot of sense.
+    if (mStatements.Length() > 1) {
+      // We don't error if this failed because it's not terrible if it does.
+      mTransactionManager = new mozStorageTransaction(mConnection, PR_FALSE,
+                                                      mozIStorageConnection::TRANSACTION_IMMEDIATE);
+    }
+
+    // Execute each statement, giving the callback results if it returns any.
     nsresult rv = NS_OK;
-    while (PR_TRUE) {
-      int rc = sqlite3_step(mStatement);
-      // Break out if we have no more results
-      if (rc == SQLITE_DONE)
-        break;
+    for (PRUint32 i = 0; i < mStatements.Length(); i++) {
+      while (PR_TRUE) {
+        int rc = sqlite3_step(mStatements[i]);
+        // Break out if we have no more results
+        if (rc == SQLITE_DONE)
+          break;
+
+        // Some errors are not fatal, and we can handle them and continue.
+        if (rc != SQLITE_OK && rc != SQLITE_ROW) {
+          if (rc == SQLITE_BUSY) {
+            // Yield, and try again
+            PR_Sleep(PR_INTERVAL_NO_WAIT);
+            continue;
+          }
 
-      // Some errors are not fatal, and we can handle them and continue.
-      if (rc != SQLITE_OK && rc != SQLITE_ROW) {
-        if (rc == SQLITE_BUSY) {
-          // Yield, and try again
-          PR_Sleep(PR_INTERVAL_NO_WAIT);
-          continue;
+          // Set error state
+          {
+            nsAutoLock mutex(mStateMutex);
+            mState = ERROR;
+          }
+
+          // Notify
+          sqlite3 *db = sqlite3_db_handle(mStatements[i]);
+          iCancelable *cancelable = ErrorNotifier::Dispatch(
+            mCallingThread, mCallback, this, rc, sqlite3_errmsg(db)
+          );
+          if (cancelable) {
+            nsAutoLock mutex(mPendingEventsMutex);
+            (void)mPendingEvents.AppendObject(cancelable);
+          }
+
+          // And complete
+          return Complete();
         }
 
-        // Set error state
+        // Check to see if we have been canceled
         {
           nsAutoLock mutex(mStateMutex);
-          mState = ERROR;
+          if (mState == CANCELED)
+            return Complete();
+        }
+
+        // If we do not have a callback, there's no point in executing this
+        // statement anymore.
+        if (!mCallback)
+          break;
+
+        // Build result object
+        // XXX bug 454740 chunk these results better
+        nsRefPtr<mozStorageResultSet> results(new mozStorageResultSet());
+        if (!results) {
+          rv = NS_ERROR_OUT_OF_MEMORY;
+          break;
+        }
+
+        nsRefPtr<mozStorageRow> row(new mozStorageRow());
+        if (!row) {
+          rv = NS_ERROR_OUT_OF_MEMORY;
+          break;
         }
 
-        // Notify
-        sqlite3 *db = sqlite3_db_handle(mStatement);
-        iCancelable *cancelable = ErrorNotifier::Dispatch(
-          mCallingThread, mCallback, this, rc, sqlite3_errmsg(db)
-        );
-        if (cancelable) {
-          nsAutoLock mutex(mPendingEventsMutex);
-          (void)mPendingEvents.AppendObject(cancelable);
+        rv = row->initialize(mStatements[i]);
+        if (NS_FAILED(rv)) {
+          rv = NS_ERROR_OUT_OF_MEMORY;
+          break;
         }
 
-        // And complete
-        return Complete();
-      }
+        rv = results->add(row);
+        if (NS_FAILED(rv))
+          break;
 
-      // Check to see if we have been canceled
-      {
-        nsAutoLock mutex(mStateMutex);
-        if (mState == CANCELED)
-          return Complete();
+        // Notify caller
+        nsRefPtr<CallbackResultNotifier> notifier =
+          new CallbackResultNotifier(mCallback, results, this);
+        if (!notifier) {
+          rv = NS_ERROR_OUT_OF_MEMORY;
+          break;
+        }
+
+        nsresult status = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
+        if (NS_SUCCEEDED(status)) {
+          nsAutoLock mutex(mPendingEventsMutex);
+          (void)mPendingEvents.AppendObject(notifier);
+        }
       }
 
-      // If we do not have a callback, but are getting results, we should stop
-      // now since all this work isn't going to accomplish anything
-      if (!mCallback) {
-        nsAutoLock mutex(mStateMutex);
-        mState = COMPLETED;
-        return Complete();
-      }
-
-      // Build result object
-      nsRefPtr<mozStorageResultSet> results(new mozStorageResultSet());
-      if (!results) {
-        rv = NS_ERROR_OUT_OF_MEMORY;
-        break;
-      }
-
-      nsRefPtr<mozStorageRow> row(new mozStorageRow());
-      if (!row) {
-        rv = NS_ERROR_OUT_OF_MEMORY;
-        break;
-      }
-
-      rv = row->initialize(mStatement);
-      if (NS_FAILED(rv)) {
-        rv = NS_ERROR_OUT_OF_MEMORY;
-        break;
-      }
-
-      rv = results->add(row);
+      // If we have an error, we need to break out now.
       if (NS_FAILED(rv))
         break;
-
-      // Notify caller
-      nsRefPtr<CallbackResultNotifier> notifier =
-        new CallbackResultNotifier(mCallback, results, this);
-      if (!notifier) {
-        rv = NS_ERROR_OUT_OF_MEMORY;
-        break;
-      }
-
-      nsresult status = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
-      if (NS_SUCCEEDED(status)) {
-        nsAutoLock mutex(mPendingEventsMutex);
-        (void)mPendingEvents.AppendObject(notifier);
-      }
     }
 
     // We have broken out of the loop because of an error or because we are
     // completed.  Handle accordingly.
     if (NS_FAILED(rv)) {
       // This is a fatal error :(
 
       // Update state
@@ -469,19 +486,43 @@ private:
    * Notifies callback about completion, and does any necessary cleanup.
    * @note: When calling this function, mStateMutex must be held.
    */
   nsresult Complete()
   {
     NS_ASSERTION(mState != PENDING,
                  "Still in a pending state when calling Complete!");
 
-    // Reset the statement
-    (void)sqlite3_finalize(mStatement);
-    mStatement = NULL;
+    // Handle our transaction, if we have one
+    if (mTransactionManager) {
+      if (mState == COMPLETED) {
+        nsresult rv = mTransactionManager->Commit();
+        if (NS_FAILED(rv)) {
+          iCancelable *cancelable = ErrorNotifier::Dispatch(
+            mCallingThread, mCallback, this, mozIStorageError::ERROR,
+            "Transaction failed to commit"
+          );
+          if (cancelable) {
+            nsAutoLock mutex(mPendingEventsMutex);
+            (void)mPendingEvents.AppendObject(cancelable);
+          }
+        }
+      }
+      else {
+        (void)mTransactionManager->Rollback();
+      }
+      delete mTransactionManager;
+      mTransactionManager = nsnull;
+    }
+
+    // Finalize our statements
+    for (PRUint32 i = 0; i < mStatements.Length(); i++) {
+      (void)sqlite3_finalize(mStatements[i]);
+      mStatements[i] = NULL;
+    }
 
     // Notify about completion iff we have a callback.
     if (mCallback) {
       nsRefPtr<CompletionNotifier> completionEvent =
         new CompletionNotifier(mCallback, mState, this);
       nsresult rv = mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
       if (NS_SUCCEEDED(rv)) {
         nsAutoLock mutex(mPendingEventsMutex);
@@ -490,17 +531,19 @@ private:
 
       // We no longer own mCallback (the CompletionNotifier takes ownership).
       mCallback = nsnull;
     }
 
     return NS_OK;
   }
 
-  sqlite3_stmt *mStatement;
+  nsTArray<sqlite3_stmt *> mStatements;
+  mozIStorageConnection *mConnection;
+  mozStorageTransaction *mTransactionManager;
   mozIStorageStatementCallback *mCallback;
   nsCOMPtr<nsIThread> mCallingThread;
 
   /**
    * Indicates the state the object is currently in.
    */
   ExecutionState mState;
 
@@ -522,22 +565,23 @@ private:
 };
 NS_IMPL_THREADSAFE_ISUPPORTS2(
   AsyncExecute,
   nsIRunnable,
   mozIStoragePendingStatement
 )
 
 nsresult
-NS_executeAsync(sqlite3_stmt *aStatement,
+NS_executeAsync(nsTArray<sqlite3_stmt *> &aStatements,
+                mozIStorageConnection *aConnection,
                 mozIStorageStatementCallback *aCallback,
                 mozIStoragePendingStatement **_stmt)
 {
   // Create our event to run in the background
-  nsRefPtr<AsyncExecute> event(new AsyncExecute(aStatement, aCallback));
+  nsRefPtr<AsyncExecute> event(new AsyncExecute(aStatements, aConnection, aCallback));
   NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
 
   nsresult rv = event->initialize();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Dispatch it to the background
   nsIEventTarget *target = mozStorageBackground::getService()->target();
   rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
--- a/storage/src/mozStorageEvents.h
+++ b/storage/src/mozStorageEvents.h
@@ -36,31 +36,35 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _mozStorageEvents_h_
 #define _mozStorageEvents_h_
 
 #include "nscore.h"
+#include "nsTArray.h"
 #include "mozStorageBackground.h"
 struct sqlite3_stmt;
 class mozIStorageStatementCallback;
 class mozIStoragePendingStatement;
 
 /**
  * Executes a statement in the background, and passes results back to the
  * caller.
  *
- * @param aStatement
- *        The SQLite statement to execute in the background.
+ * @param aStatements
+ *        The SQLite statements to execute in the background.
+ * @param aConnection
+ *        The connection that created the statements to execute.
  * @param aCallback
  *        The callback that is notified of results, completion, and errors.
  * @param _stmt
- *        The handle to control the execution of the statement.
+ *        The handle to control the execution of the statements.
  */
 nsresult NS_executeAsync(
-  sqlite3_stmt *aStatement,
+  nsTArray<sqlite3_stmt *> &aStatements,
+  mozIStorageConnection *aConnection,
   mozIStorageStatementCallback *aCallback,
   mozIStoragePendingStatement **_stmt
 );
 
 #endif // _mozStorageEvents_h_
--- a/storage/src/mozStorageStatement.cpp
+++ b/storage/src/mozStorageStatement.cpp
@@ -573,39 +573,18 @@ mozStorageStatement::ExecuteStep(PRBool 
     return ConvertResultCode(srv);
 }
 
 /* nsICancelable executeAsync([optional] in storageIStatementCallback aCallback); */
 nsresult
 mozStorageStatement::ExecuteAsync(mozIStorageStatementCallback *aCallback,
                                   mozIStoragePendingStatement **_stmt)
 {
-    // Clone this statement.  We only need a sqlite3_stmt object, so we can
-    // avoid all the extra work that making a new mozStorageStatement would
-    // normally involve and use the SQLite API.
-    sqlite3 *db = mDBConnection->GetNativeConnection();
-    sqlite3_stmt *stmt;
-    int rc = sqlite3_prepare_v2(db, sqlite3_sql(mDBStatement), -1, &stmt, NULL);
-    if (rc != SQLITE_OK)
-        return ConvertResultCode(rc);
-
-    // Transfer the bindings
-    rc = sqlite3_transfer_bindings(mDBStatement, stmt);
-    if (rc != SQLITE_OK)
-        return ConvertResultCode(rc);
-
-    // Dispatch to the background.
-    nsresult rv = NS_executeAsync(stmt, aCallback, _stmt);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Reset this statement.
-    rv = Reset();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return NS_OK;
+    mozIStorageStatement * stmts[1] = {this};
+    return mDBConnection->ExecuteAsync(stmts, 1, aCallback, _stmt);
 }
 
 /* [noscript,notxpcom] sqlite3stmtptr getNativeStatementPointer(); */
 sqlite3_stmt*
 mozStorageStatement::GetNativeStatementPointer()
 {
     return mDBStatement;
 }
new file mode 100644
--- /dev/null
+++ b/storage/test/unit/test_connection_executeAsync.js
@@ -0,0 +1,180 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Storage Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This file tests the functionality of mozIStorageConnection::executeAsync
+
+const INTEGER = 1;
+const TEXT = "this is test text";
+const REAL = 3.23;
+const BLOB = [1, 2];
+
+function test_create_and_add()
+{
+  dump("test_create_and_add()\n");
+
+  getOpenedDatabase().executeSimpleSQL(
+    "CREATE TABLE test (" +
+      "id INTEGER PRIMARY KEY, " +
+      "string TEXT, " +
+      "number REAL, " +
+      "nuller NULL, " +
+      "blober BLOB" +
+    ")"
+  );
+
+  let stmts = [];
+  stmts[0] = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id, string, number, nuller, blober) VALUES (?, ?, ?, ?, ?)"
+  );
+  stmts[0].bindInt32Parameter(0, INTEGER);
+  stmts[0].bindStringParameter(1, TEXT);
+  stmts[0].bindDoubleParameter(2, REAL);
+  stmts[0].bindNullParameter(3);
+  stmts[0].bindBlobParameter(4, BLOB, BLOB.length);
+  stmts[1] = getOpenedDatabase().createStatement(
+    "INSERT INTO test (string, number, nuller, blober) VALUES (?, ?, ?, ?)"
+  );
+  stmts[1].bindStringParameter(0, TEXT);
+  stmts[1].bindDoubleParameter(1, REAL);
+  stmts[1].bindNullParameter(2);
+  stmts[1].bindBlobParameter(3, BLOB, BLOB.length);
+
+  do_test_pending();
+  getOpenedDatabase().executeAsync(stmts, stmts.length, {
+    handleResult: function(aResultSet)
+    {
+      dump("handleResult("+aResultSet+")\n");
+      do_throw("unexpected results obtained!");
+    },
+    handleError: function(aError)
+    {
+      dump("handleError("+aError.result+")\n");
+      do_throw("unexpected error!");
+    },
+    handleCompletion: function(aReason)
+    {
+      dump("handleCompletion("+aReason+")\n");
+      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
+
+      // Check that the result is in the table
+      let stmt = getOpenedDatabase().createStatement(
+        "SELECT string, number, nuller, blober FROM test WHERE id = ?"
+      );
+      stmt.bindInt32Parameter(0, INTEGER);
+      try {
+        do_check_true(stmt.executeStep());
+        do_check_eq(TEXT, stmt.getString(0));
+        do_check_eq(REAL, stmt.getDouble(1));
+        do_check_true(stmt.getIsNull(2));
+        let count = { value: 0 };
+        let blob = { value: null };
+        stmt.getBlob(3, count, blob);
+        do_check_eq(BLOB.length, count.value);
+        for (let i = 0; i < BLOB.length; i++)
+          do_check_eq(BLOB[i], blob.value[i]);
+      }
+      finally {
+        stmt.finalize();
+      }
+
+      // Make sure we have two rows in the table
+      stmt = getOpenedDatabase().createStatement(
+        "SELECT COUNT(1) FROM test"
+      );
+      try {
+        do_check_true(stmt.executeStep());
+        do_check_eq(2, stmt.getInt32(0));
+      }
+      finally {
+        stmt.finalize();
+      }
+
+      do_test_finished();
+    }
+  });
+  stmts[0].finalize();
+  stmts[1].finalize();
+}
+
+function test_transaction_created()
+{
+  dump("test_transaction_created()\n");
+
+  let stmts = [];
+  stmts[0] = getOpenedDatabase().createStatement(
+    "BEGIN"
+  );
+  stmts[1] = getOpenedDatabase().createStatement(
+    "SELECT * FROM test"
+  );
+
+  do_test_pending()
+  getOpenedDatabase().executeAsync(stmts, stmts.length, {
+    handleResult: function(aResultSet)
+    {
+      dump("handleResults("+aResultSet+")\n");
+      do_throw("unexpected results obtained!");
+    },
+    handleError: function(aError)
+    {
+      dump("handleError("+aError.result+")\n");
+    },
+    handleCompletion: function(aReason)
+    {
+      dump("handleCompletion("+aReason+")\n");
+      do_check_eq(Ci.mozIStorageStatementCallback.REASON_ERROR, aReason);
+      do_test_finished();
+    }
+  });
+  stmts[0].finalize();
+  stmts[1].finalize();
+}
+
+
+let tests =
+[
+  test_create_and_add,
+  test_transaction_created,
+];
+
+function run_test()
+{
+  cleanup();
+
+  for (let i = 0; i < tests.length; i++)
+    tests[i]();
+}