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 70042 36bf5b4f5e0aee5e1da5586466429782f85700cf
parent 70041 3c49458e56f35f041382a64d2d93ec36aed03476
child 70043 7b7c80a035148eafc516068d762e48e781f5fb1e
push idunknown
push userunknown
push dateunknown
bugs658135
milestone6.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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()
 {