Bug 658135 - Use sqlite3_stmt_readonly to check if multiple async statements need a transaction.
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 24 May 2011 00:15:01 +0200
changeset 69635 36bf5b4f5e0aee5e1da5586466429782f85700cf
parent 69634 3c49458e56f35f041382a64d2d93ec36aed03476
child 70042 7b7c80a035148eafc516068d762e48e781f5fb1e
push id185
push usermak77@bonardo.net
push dateMon, 23 May 2011 22:15:39 +0000
bugs658135
milestone6.0a1
Bug 658135 - Use sqlite3_stmt_readonly to check if multiple async statements need a transaction. r=sdwilsh
db/sqlite3/src/sqlite.def
storage/src/mozStorageAsyncStatementExecution.cpp
storage/src/mozStorageAsyncStatementExecution.h
storage/src/mozStorageConnection.h
storage/src/mozStorageStatementData.h
storage/test/Makefile.in
storage/test/storage_test_harness.h
storage/test/test_asyncStatementExecution_transaction.cpp
storage/test/test_async_callbacks_with_spun_event_loops.cpp
storage/test/test_true_async.cpp
storage/test/unit/test_connection_executeAsync.js
--- a/db/sqlite3/src/sqlite.def
+++ b/db/sqlite3/src/sqlite.def
@@ -150,16 +150,17 @@ EXPORTS
         sqlite3_set_authorizer
         sqlite3_set_auxdata
         sqlite3_shutdown
         sqlite3_sleep
         sqlite3_snprintf
         sqlite3_sql
         sqlite3_status
         sqlite3_step
+        sqlite3_stmt_readonly
         sqlite3_stmt_status
         sqlite3_thread_cleanup
         sqlite3_total_changes
         sqlite3_trace
         sqlite3_transfer_bindings
         sqlite3_unlock_notify
         sqlite3_update_hook
         sqlite3_user_data
--- a/storage/src/mozStorageAsyncStatementExecution.cpp
+++ b/storage/src/mozStorageAsyncStatementExecution.cpp
@@ -529,16 +529,31 @@ AsyncExecuteStatements::notifyResults()
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS2(
   AsyncExecuteStatements,
   nsIRunnable,
   mozIStoragePendingStatement
 )
 
+bool
+AsyncExecuteStatements::statementsNeedTransaction()
+{
+  // If there is more than one write statement, run in a transaction.
+  // Additionally, if we have only one statement but it needs a transaction, due
+  // to multiple BindingParams, we will wrap it in one.
+  for (PRUint32 i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
+    transactionsCount += mStatements[i].needsTransaction();
+    if (transactionsCount > 1) {
+      return true;
+    }
+  }
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 //// mozIStoragePendingStatement
 
 NS_IMETHODIMP
 AsyncExecuteStatements::Cancel()
 {
 #ifdef DEBUG
   PRBool onCallingThread = PR_FALSE;
@@ -570,23 +585,17 @@ AsyncExecuteStatements::Run()
   {
     MutexAutoLock lockedScope(mMutex);
     if (mCancelRequested)
       mState = CANCELED;
   }
   if (mState == CANCELED)
     return notifyComplete();
 
-  // 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.
-  // Additionally, if we have only one statement and it needs a transaction, we
-  // will wrap it in one.
-  if (mStatements.Length() > 1 || mStatements[0].needsTransaction()) {
-    // We don't error if this failed because it's not terrible if it does.
+  if (statementsNeedTransaction()) {
     mTransactionManager = new mozStorageTransaction(mConnection, PR_FALSE,
                                                     mozIStorageConnection::TRANSACTION_IMMEDIATE);
   }
 
   // Execute each statement, giving the callback results if it returns any.
   for (PRUint32 i = 0; i < mStatements.Length(); i++) {
     bool finished = (i == (mStatements.Length() - 1));
 
--- a/storage/src/mozStorageAsyncStatementExecution.h
+++ b/storage/src/mozStorageAsyncStatementExecution.h
@@ -196,16 +196,24 @@ private:
 
   /**
    * Notifies the callback about a result set.
    *
    * @pre mMutex is not held
    */
   nsresult notifyResults();
 
+  /**
+   * Tests whether the current statements should be wrapped in an explicit
+   * transaction.
+   *
+   * @return true if an explicit transaction is needed, false otherwise.
+   */
+  bool statementsNeedTransaction();
+
   StatementDataArray mStatements;
   nsRefPtr<Connection> mConnection;
   mozStorageTransaction *mTransactionManager;
   mozIStorageStatementCallback *mCallback;
   nsCOMPtr<nsIThread> mCallingThread;
   nsRefPtr<ResultSet> mResultSet;
 
   /**
--- a/storage/src/mozStorageConnection.h
+++ b/storage/src/mozStorageConnection.h
@@ -41,17 +41,16 @@
 #ifndef mozilla_storage_Connection_h
 #define mozilla_storage_Connection_h
 
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "mozilla/Mutex.h"
 #include "nsIInterfaceRequestor.h"
 
-#include "nsString.h"
 #include "nsDataHashtable.h"
 #include "mozIStorageProgressHandler.h"
 #include "SQLiteMutex.h"
 #include "mozIStorageConnection.h"
 #include "mozStorageService.h"
 
 #include "nsIMutableArray.h"
 
--- a/storage/src/mozStorageStatementData.h
+++ b/storage/src/mozStorageStatementData.h
@@ -40,16 +40,18 @@
 #ifndef mozStorageStatementData_h
 #define mozStorageStatementData_h
 
 #include "sqlite3.h"
 
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "nsIEventTarget.h"
+#include "mozilla/Util.h"
+#include "nsThreadUtils.h"
 
 #include "mozStorageBindingParamsArray.h"
 #include "mozIStorageBaseStatement.h"
 #include "mozStorageConnection.h"
 #include "StorageBaseStatementInternal.h"
 
 struct sqlite3_stmt;
 
@@ -138,23 +140,37 @@ public:
    * Indicates if this statement has parameters to be bound before it is
    * executed.
    *
    * @return true if the statement has parameters to bind against, false
    *         otherwise.
    */
   inline bool hasParametersToBeBound() const { return !!mParamsArray; }
   /**
-   * Indicates if this statement needs a transaction for execution.
+   * Indicates the number of implicit statements generated by this statement
+   * requiring a transaction for execution.  For example a single statement
+   * with N BindingParams will execute N implicit staments.
    *
-   * @return true if the statement needs a transaction, false otherwise.
+   * @return number of statements requiring a transaction for execution.
+   *
+   * @note In the case of AsyncStatements this may actually create the
+   *       statement.
    */
-  inline bool needsTransaction() const
+  inline PRUint32 needsTransaction()
   {
-    return mParamsArray != nsnull && mParamsArray->length() > 1;
+    MOZ_ASSERT(!NS_IsMainThread());
+    // Be sure to use the getSqliteStatement helper, since sqlite3_stmt_readonly
+    // can only analyze prepared statements and AsyncStatements are prepared
+    // lazily.
+    sqlite3_stmt *stmt;
+    int rc = getSqliteStatement(&stmt);
+    if (SQLITE_OK != rc || ::sqlite3_stmt_readonly(stmt)) {
+      return 0;
+    }
+    return mParamsArray ? mParamsArray->length() : 1;
   }
 
 private:
   sqlite3_stmt *mStatement;
   nsRefPtr<BindingParamsArray> mParamsArray;
 
   /**
    * We hold onto a reference of the statement's owner so it doesn't get
--- a/storage/test/Makefile.in
+++ b/storage/test/Makefile.in
@@ -56,16 +56,17 @@ CPP_UNIT_TESTS = \
   test_binding_params.cpp \
   test_true_async.cpp \
   test_unlock_notify.cpp \
   test_service_init_background_thread.cpp \
   test_AsXXX_helpers.cpp \
   test_StatementCache.cpp \
   test_async_callbacks_with_spun_event_loops.cpp \
   test_file_perms.cpp \
+  test_asyncStatementExecution_transaction.cpp \
   $(NULL)
 
 ifdef MOZ_DEBUG
 # FIXME bug 523392: test_deadlock_detector doesn't like Windows
 # FIXME bug 523378: also fails on OS X
 ifneq (,$(filter-out WINNT Darwin,$(OS_ARCH)))
 CPP_UNIT_TESTS += \
   test_deadlock_detector.cpp \
--- a/storage/test/storage_test_harness.h
+++ b/storage/test/storage_test_harness.h
@@ -45,16 +45,17 @@
 #include "mozIStorageConnection.h"
 #include "mozIStorageStatementCallback.h"
 #include "mozIStorageCompletionCallback.h"
 #include "mozIStorageBindingParamsArray.h"
 #include "mozIStorageBindingParams.h"
 #include "mozIStorageAsyncStatement.h"
 #include "mozIStorageStatement.h"
 #include "mozIStoragePendingStatement.h"
+#include "mozIStorageError.h"
 #include "nsThreadUtils.h"
 
 static int gTotalTests = 0;
 static int gPassedTests = 0;
 
 #define do_check_true(aCondition) \
   PR_BEGIN_MACRO \
     gTotalTests++; \
@@ -171,16 +172,30 @@ NS_IMETHODIMP
 AsyncStatementSpinner::HandleResult(mozIStorageResultSet *aResultSet)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AsyncStatementSpinner::HandleError(mozIStorageError *aError)
 {
+  PRInt32 result;
+  nsresult rv = aError->GetResult(&result);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCAutoString message;
+  rv = aError->GetMessage(message);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCAutoString warnMsg;
+  warnMsg.Append("An error occurred while executing an async statement: ");
+  warnMsg.AppendInt(result);
+  warnMsg.Append(" ");
+  warnMsg.Append(message);
+  NS_WARNING(warnMsg.get());
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AsyncStatementSpinner::HandleCompletion(PRUint16 aReason)
 {
   completionReason = aReason;
   mCompleted = true;
@@ -201,8 +216,38 @@ void AsyncStatementSpinner::SpinUntilCom
   PRBool processed = PR_TRUE;
   while (!mCompleted && NS_SUCCEEDED(rv)) {
     rv = thread->ProcessNextEvent(true, &processed);
   }
 }
 
 #define NS_DECL_ASYNCSTATEMENTSPINNER \
   NS_IMETHOD HandleResult(mozIStorageResultSet *aResultSet);
+
+////////////////////////////////////////////////////////////////////////////////
+//// Async Helpers
+
+/**
+ * Execute an async statement, blocking the main thread until we get the
+ * callback completion notification.
+ */
+void
+blocking_async_execute(mozIStorageBaseStatement *stmt)
+{
+  nsRefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
+
+  nsCOMPtr<mozIStoragePendingStatement> pendy;
+  (void)stmt->ExecuteAsync(spinner, getter_AddRefs(pendy));
+  spinner->SpinUntilCompleted();
+}
+
+/**
+ * Invoke AsyncClose on the given connection, blocking the main thread until we
+ * get the completion notification.
+ */
+void
+blocking_async_close(mozIStorageConnection *db)
+{
+  nsRefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
+
+  db->AsyncClose(spinner);
+  spinner->SpinUntilCompleted();
+}
new file mode 100644
--- /dev/null
+++ b/storage/test/test_asyncStatementExecution_transaction.cpp
@@ -0,0 +1,508 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+#include "storage_test_harness.h"
+
+#include "nsIEventTarget.h"
+#include "mozStorageConnection.h"
+
+#include "sqlite3.h"
+
+using namespace mozilla::storage;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers
+
+/**
+ * Commit hook to detect transactions.
+ *
+ * @param aArg
+ *        An integer pointer that will be incremented for each commit.
+ */
+int commit_hook(void *aArg)
+{
+  int *arg = static_cast<int *>(aArg);
+  (*arg)++;
+  return 0;
+}
+
+/**
+ * Executes the passed-in statements and checks if a transaction is created.
+ * When done statements are finalized and database connection is closed.
+ *
+ * @param aDB
+ *        The database connection.
+ * @param aStmts
+ *        Vector of statements.
+ * @param aStmtsLen
+ *        Number of statements.
+ * @param aTransactionExpected
+ *        Whether a transaction is expected or not.
+ */
+void
+check_transaction(mozIStorageConnection *aDB,
+                  mozIStorageBaseStatement **aStmts,
+                  PRUint32 aStmtsLen,
+                  bool aTransactionExpected)
+{
+  // -- install a transaction commit hook.
+  int commit = 0;
+  ::sqlite3_commit_hook(*static_cast<Connection *>(aDB), commit_hook, &commit);
+
+  nsRefPtr<AsyncStatementSpinner> asyncSpin(new AsyncStatementSpinner());
+  nsCOMPtr<mozIStoragePendingStatement> asyncPend;
+  do_check_success(aDB->ExecuteAsync(aStmts, aStmtsLen, asyncSpin,
+                                     getter_AddRefs(asyncPend)));
+  do_check_true(asyncPend);
+
+  // -- complete the execution
+  asyncSpin->SpinUntilCompleted();
+
+  // -- uninstall the transaction commit hook.
+  ::sqlite3_commit_hook(*static_cast<Connection *>(aDB), NULL, NULL);
+
+  // -- check transaction
+  do_check_eq(aTransactionExpected, !!commit);
+
+  // -- check that only one transaction was created.
+  if (aTransactionExpected) {
+    do_check_eq(1, commit);
+  }
+
+  // -- cleanup
+  for (PRUint32 i = 0; i < aStmtsLen; ++i) {
+    aStmts[i]->Finalize();
+  }
+  blocking_async_close(aDB);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+/**
+ * Test that executing multiple readonly AsyncStatements doesn't create a
+ * transaction.
+ */
+void
+test_MultipleAsyncReadStatements()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageAsyncStatement> stmt1;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM sqlite_master"
+  ), getter_AddRefs(stmt1));
+
+  nsCOMPtr<mozIStorageAsyncStatement> stmt2;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM sqlite_master"
+  ), getter_AddRefs(stmt2));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt1,
+    stmt2,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), false);
+}
+
+/**
+ * Test that executing multiple readonly Statements doesn't create a
+ * transaction.
+ */
+void
+test_MultipleReadStatements()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageStatement> stmt1;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM sqlite_master"
+  ), getter_AddRefs(stmt1));
+
+  nsCOMPtr<mozIStorageStatement> stmt2;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM sqlite_master"
+  ), getter_AddRefs(stmt2));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt1,
+    stmt2,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), false);
+}
+
+/**
+ * Test that executing multiple AsyncStatements causing writes creates a
+ * transaction.
+ */
+void
+test_MultipleAsyncReadWriteStatements()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageAsyncStatement> stmt1;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM sqlite_master"
+  ), getter_AddRefs(stmt1));
+
+  nsCOMPtr<mozIStorageAsyncStatement> stmt2;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(stmt2));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt1,
+    stmt2,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), true);
+}
+
+/**
+ * Test that executing multiple Statements causing writes creates a transaction.
+ */
+void
+test_MultipleReadWriteStatements()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageStatement> stmt1;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM sqlite_master"
+  ), getter_AddRefs(stmt1));
+
+  nsCOMPtr<mozIStorageStatement> stmt2;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(stmt2));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt1,
+    stmt2,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), true);
+}
+
+/**
+ * Test that executing multiple AsyncStatements causing writes creates a
+ * single transaction.
+ */
+void
+test_MultipleAsyncWriteStatements()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageAsyncStatement> stmt1;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test1 (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(stmt1));
+
+  nsCOMPtr<mozIStorageAsyncStatement> stmt2;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test2 (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(stmt2));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt1,
+    stmt2,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), true);
+}
+
+/**
+ * Test that executing multiple Statements causing writes creates a
+ * single transaction.
+ */
+void
+test_MultipleWriteStatements()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageStatement> stmt1;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test1 (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(stmt1));
+
+  nsCOMPtr<mozIStorageStatement> stmt2;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test2 (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(stmt2));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt1,
+    stmt2,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), true);
+}
+
+/**
+ * Test that executing a single read-only AsyncStatement doesn't create a
+ * transaction.
+ */
+void
+test_SingleAsyncReadStatement()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageAsyncStatement> stmt;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM sqlite_master"
+  ), getter_AddRefs(stmt));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), false);
+}
+
+/**
+ * Test that executing a single read-only Statement doesn't create a
+ * transaction.
+ */
+void
+test_SingleReadStatement()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageStatement> stmt;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM sqlite_master"
+  ), getter_AddRefs(stmt));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), false);
+}
+
+/**
+ * Test that executing a single AsyncStatement causing writes creates a
+ * transaction.
+ */
+void
+test_SingleAsyncWriteStatement()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageAsyncStatement> stmt;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(stmt));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), true);
+}
+
+/**
+ * Test that executing a single Statement causing writes creates a transaction.
+ */
+void
+test_SingleWriteStatement()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageStatement> stmt;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(stmt));
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), true);
+}
+
+/**
+ * Test that executing a single read-only AsyncStatement with multiple params
+ * doesn't create a transaction.
+ */
+void
+test_MultipleParamsAsyncReadStatement()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageAsyncStatement> stmt;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "SELECT :param FROM sqlite_master"
+  ), getter_AddRefs(stmt));
+
+  // -- bind multiple BindingParams
+  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+  stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+  for (PRInt32 i = 0; i < 2; i++) {
+    nsCOMPtr<mozIStorageBindingParams> params;
+    paramsArray->NewBindingParams(getter_AddRefs(params));
+    params->BindInt32ByName(NS_LITERAL_CSTRING("param"), 1);
+    paramsArray->AddParams(params);
+  }
+  stmt->BindParameters(paramsArray);
+  paramsArray = nsnull;
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), false);
+}
+
+/**
+ * Test that executing a single read-only Statement with multiple params
+ * doesn't create a transaction.
+ */
+void
+test_MultipleParamsReadStatement()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageStatement> stmt;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT :param FROM sqlite_master"
+  ), getter_AddRefs(stmt));
+
+  // -- bind multiple BindingParams
+  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+  stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+  for (PRInt32 i = 0; i < 2; i++) {
+    nsCOMPtr<mozIStorageBindingParams> params;
+    paramsArray->NewBindingParams(getter_AddRefs(params));
+    params->BindInt32ByName(NS_LITERAL_CSTRING("param"), 1);
+    paramsArray->AddParams(params);
+  }
+  stmt->BindParameters(paramsArray);
+  paramsArray = nsnull;
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), false);
+}
+
+/**
+ * Test that executing a single write AsyncStatement with multiple params
+ * creates a transaction.
+ */
+void
+test_MultipleParamsAsyncWriteStatement()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create a table for writes
+  nsCOMPtr<mozIStorageStatement> tableStmt;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(tableStmt));
+  tableStmt->Execute();
+  tableStmt->Finalize();
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageAsyncStatement> stmt;
+  db->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "DELETE FROM test WHERE id = :param"
+  ), getter_AddRefs(stmt));
+
+  // -- bind multiple BindingParams
+  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+  stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+  for (PRInt32 i = 0; i < 2; i++) {
+    nsCOMPtr<mozIStorageBindingParams> params;
+    paramsArray->NewBindingParams(getter_AddRefs(params));
+    params->BindInt32ByName(NS_LITERAL_CSTRING("param"), 1);
+    paramsArray->AddParams(params);
+  }
+  stmt->BindParameters(paramsArray);
+  paramsArray = nsnull;
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), true);
+}
+
+/**
+ * Test that executing a single write Statement with multiple params
+ * creates a transaction.
+ */
+void
+test_MultipleParamsWriteStatement()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- create a table for writes
+  nsCOMPtr<mozIStorageStatement> tableStmt;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "CREATE TABLE test (id INTEGER PRIMARY KEY)"
+  ), getter_AddRefs(tableStmt));
+  tableStmt->Execute();
+  tableStmt->Finalize();
+
+  // -- create statements and execute them
+  nsCOMPtr<mozIStorageStatement> stmt;
+  db->CreateStatement(NS_LITERAL_CSTRING(
+    "DELETE FROM test WHERE id = :param"
+  ), getter_AddRefs(stmt));
+
+  // -- bind multiple BindingParams
+  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+  stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+  for (PRInt32 i = 0; i < 2; i++) {
+    nsCOMPtr<mozIStorageBindingParams> params;
+    paramsArray->NewBindingParams(getter_AddRefs(params));
+    params->BindInt32ByName(NS_LITERAL_CSTRING("param"), 1);
+    paramsArray->AddParams(params);
+  }
+  stmt->BindParameters(paramsArray);
+  paramsArray = nsnull;
+
+  mozIStorageBaseStatement *stmts[] = {
+    stmt,
+  };
+
+  check_transaction(db, stmts, NS_ARRAY_LENGTH(stmts), true);
+}
+
+void (*gTests[])(void) = {
+  test_MultipleAsyncReadStatements,
+  test_MultipleReadStatements,
+  test_MultipleAsyncReadWriteStatements,
+  test_MultipleReadWriteStatements,
+  test_MultipleAsyncWriteStatements,
+  test_MultipleWriteStatements,
+  test_SingleAsyncReadStatement,
+  test_SingleReadStatement,
+  test_SingleAsyncWriteStatement,
+  test_SingleWriteStatement,
+  test_MultipleParamsAsyncReadStatement,
+  test_MultipleParamsReadStatement,
+  test_MultipleParamsAsyncWriteStatement,
+  test_MultipleParamsWriteStatement,
+};
+
+const char *file = __FILE__;
+#define TEST_NAME "async statement execution transaction"
+#define TEST_FILE file
+#include "storage_test_harness_tail.h"
--- a/storage/test/test_async_callbacks_with_spun_event_loops.cpp
+++ b/storage/test/test_async_callbacks_with_spun_event_loops.cpp
@@ -7,29 +7,16 @@
 #include "nsIInterfaceRequestorUtils.h"
 
 #include "sqlite3.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Async Helpers
 
 /**
- * Invoke AsyncClose on the given connection, blocking the main thread until we
- * get the completion notification.
- */
-void
-blocking_async_close(mozIStorageConnection *db)
-{
-  nsRefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
-
-  db->AsyncClose(spinner);
-  spinner->SpinUntilCompleted();
-}
-
-/**
  * Spins the events loop for current thread until aCondition is true.
  */
 void
 spin_events_loop_until_true(const bool* const aCondition)
 {
   nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
   nsresult rv = NS_OK;
   PRBool processed = PR_TRUE;
--- a/storage/test/test_true_async.cpp
+++ b/storage/test/test_true_async.cpp
@@ -167,43 +167,16 @@ private:
   ReentrantMonitor mReentrantMonitor;
   bool unwedged;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Async Helpers
 
 /**
- * Execute an async statement, blocking the main thread until we get the
- * callback completion notification.
- */
-void
-blocking_async_execute(mozIStorageBaseStatement *stmt)
-{
-  nsRefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
-
-  nsCOMPtr<mozIStoragePendingStatement> pendy;
-  (void)stmt->ExecuteAsync(spinner, getter_AddRefs(pendy));
-  spinner->SpinUntilCompleted();
-}
-
-/**
- * Invoke AsyncClose on the given connection, blocking the main thread until we
- * get the completion notification.
- */
-void
-blocking_async_close(mozIStorageConnection *db)
-{
-  nsRefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
-
-  db->AsyncClose(spinner);
-  spinner->SpinUntilCompleted();
-}
-
-/**
  * A horrible hack to figure out what the connection's async thread is.  By
  * creating a statement and async dispatching we can tell from the mutex who
  * is the async thread, PRThread style.  Then we map that to an nsIThread.
  */
 already_AddRefed<nsIThread>
 get_conn_async_thread(mozIStorageConnection *db)
 {
   // Make sure we are tracking the current thread as the watched thread
--- a/storage/test/unit/test_connection_executeAsync.js
+++ b/storage/test/unit/test_connection_executeAsync.js
@@ -126,49 +126,16 @@ function test_create_and_add()
       // Run the next test.
       run_next_test();
     }
   });
   stmts[0].finalize();
   stmts[1].finalize();
 }
 
-function test_transaction_created()
-{
-  let stmts = [];
-  stmts[0] = getOpenedDatabase().createAsyncStatement(
-    "BEGIN"
-  );
-  stmts[1] = getOpenedDatabase().createStatement(
-    "SELECT * FROM test"
-  );
-
-  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);
-
-      // Run the next test.
-      run_next_test();
-    }
-  });
-  stmts[0].finalize();
-  stmts[1].finalize();
-}
-
 function test_multiple_bindings_on_statements()
 {
   // This tests to make sure that we pass all the statements multiply bound
   // parameters when we call executeAsync.
   const AMOUNT_TO_ADD = 5;
   const ITERATIONS = 5;
 
   let stmts = [];
@@ -304,17 +271,16 @@ function test_double_asyncClose_throws()
   run_next_test();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Test Runner
 
 [
   test_create_and_add,
-  test_transaction_created,
   test_multiple_bindings_on_statements,
   test_asyncClose_does_not_complete_before_statements,
   test_asyncClose_does_not_throw_no_callback,
   test_double_asyncClose_throws,
 ].forEach(add_test);
 
 function run_test()
 {