Bug 490085 - Add ability to bind multiple sets of parameters and execute asynchronously
☠☠ backed out by 83ba7296195e ☠ ☠
authorShawn Wilsher <sdwilsh@shawnwilsher.com>
Wed, 17 Jun 2009 12:12:51 -0700
changeset 29300 d546bd2c46a4271215bbae1cbe87fdc6975afe21
parent 29299 0997bcc75daf217886ccf010cdb0b37dc3e30b26
child 29301 097f79c9cd62df407ffb19a604990abee70a45df
child 29305 83ba7296195e738749ce695f89d242ea98d4bcc6
push id7555
push usersdwilsh@shawnwilsher.com
push dateWed, 17 Jun 2009 19:16:31 +0000
treeherderautoland@097f79c9cd62 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs490085
milestone1.9.2a1pre
Bug 490085 - Add ability to bind multiple sets of parameters and execute asynchronously Improves the asynchronous API by allowing multiple parameters to be bound before executing it. The statement is then executed with each set of parameters asynchronously. r=asuth sr=vlad
storage/public/Makefile.in
storage/public/mozIStorageBindingParams.idl
storage/public/mozIStorageBindingParamsArray.idl
storage/public/mozIStorageStatement.idl
storage/src/Makefile.in
storage/src/mozStorageAsyncStatementExecution.cpp
storage/src/mozStorageAsyncStatementExecution.h
storage/src/mozStorageBindingParams.cpp
storage/src/mozStorageBindingParams.h
storage/src/mozStorageBindingParamsArray.cpp
storage/src/mozStorageBindingParamsArray.h
storage/src/mozStorageConnection.cpp
storage/src/mozStorageStatement.cpp
storage/src/mozStorageStatement.h
storage/src/mozStorageStatementData.h
storage/test/unit/test_connection_executeAsync.js
storage/test/unit/test_statement_executeAsync.js
storage/test/unit/test_storage_statement_executeAsync.js
--- a/storage/public/Makefile.in
+++ b/storage/public/Makefile.in
@@ -57,16 +57,18 @@ XPIDLSRCS = \
 	mozIStorageStatement.idl \
 	mozIStorageStatementWrapper.idl \
 	mozIStorageValueArray.idl \
 	mozIStorageResultSet.idl \
 	mozIStorageRow.idl \
   mozIStorageError.idl \
   mozIStorageStatementCallback.idl \
   mozIStoragePendingStatement.idl \
+  mozIStorageBindingParamsArray.idl \
+  mozIStorageBindingParams.idl \
 	$(NULL)
 
 EXPORTS_NAMESPACES = mozilla
 
 EXPORTS = \
 	mozStorageHelper.h \
 	mozStorage.h \
 	$(NULL)
new file mode 100644
--- /dev/null
+++ b/storage/public/mozIStorageBindingParams.idl
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * 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 ***** */
+
+#include "nsISupports.idl"
+
+interface nsIVariant;
+
+[scriptable, uuid(a8d4827c-641c-45e3-a9ea-493570b4106b)]
+interface mozIStorageBindingParams : nsISupports {
+  /**
+   * Binds aValue to the parameter with the name aName.
+   *
+   * @param aName
+   *        The name of the parameter to bind aValue to.
+   * @param aValue
+   *        The value to bind.
+   */
+   void bindByName(in AUTF8String aName,
+                   in nsIVariant aValue);
+   [noscript] void bindUTF8StringByName(in AUTF8String aName,
+                                        in AUTF8String aValue);
+   [noscript] void bindStringByName(in AUTF8String aName,
+                                    in AString aValue);
+   [noscript] void bindDoubleByName(in AUTF8String aName,
+                                    in double aValue);
+   [noscript] void bindInt32ByName(in AUTF8String aName,
+                                   in long aValue);
+   [noscript] void bindInt64ByName(in AUTF8String aName,
+                                   in long long aValue);
+   [noscript] void bindNullByName(in AUTF8String aName);
+   void bindBlobByName(in AUTF8String aName,
+                       [array, const, size_is(aValueSize)] in octet aValue,
+                       in unsigned long aValueSize);
+
+   /**
+    * Binds aValue to the parameter with the index aIndex.
+    *
+    * @param aIndex
+    *        The zero-based index of the parameter to bind aValue to.
+    * @param aValue
+    *        The value to bind.
+    */
+   void bindByIndex(in unsigned long aIndex,
+                    in nsIVariant aValue);
+   [noscript] void bindUTF8StringByIndex(in unsigned long aIndex,
+                                         in AUTF8String aValue);
+   [noscript] void bindStringByIndex(in unsigned long aIndex,
+                                     in AString aValue);
+   [noscript] void bindDoubleByIndex(in unsigned long aIndex,
+                                     in double aValue);
+   [noscript] void bindInt32ByIndex(in unsigned long aIndex,
+                                    in long aValue);
+   [noscript] void bindInt64ByIndex(in unsigned long aIndex,
+                                    in long long aValue);
+   [noscript] void bindNullByIndex(in unsigned long aIndex);
+   void bindBlobByIndex(in unsigned long aIndex,
+                        [array, const, size_is(aValueSize)] in octet aValue,
+                        in unsigned long aValueSize);
+};
new file mode 100644
--- /dev/null
+++ b/storage/public/mozIStorageBindingParamsArray.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * 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 ***** */
+
+#include "nsISupports.idl"
+
+interface mozIStorageBindingParams;
+
+[scriptable, uuid(e676e1a3-1dc6-4802-ac03-291fa9de7f93)]
+interface mozIStorageBindingParamsArray : nsISupports {
+  /**
+   * Creates a new mozIStorageBindingParams object that can be added to this
+   * array.
+   *
+   * @returns a mozIStorageBindingParams object that can be used to specify
+   *          parameters that need to be bound.
+   */
+  mozIStorageBindingParams newBindingParams();
+
+  /**
+   * Adds the parameters to the end of this array.
+   *
+   * @param aParameters
+   *        The parameters to add to this array.
+   */
+  void addParams(in mozIStorageBindingParams aParameters);
+};
--- a/storage/public/mozIStorageStatement.idl
+++ b/storage/public/mozIStorageStatement.idl
@@ -38,18 +38,19 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 #include "mozIStorageValueArray.idl"
 
 interface mozIStorageConnection;
 interface mozIStorageStatementCallback;
 interface mozIStoragePendingStatement;
+interface mozIStorageBindingParamsArray;
 
-[scriptable, uuid(52d740cd-1c25-471f-a848-98d1a00a2963)]
+[scriptable, uuid(20c45bdd-51d4-4f07-b70e-5feaa6302197)]
 interface mozIStorageStatement : mozIStorageValueArray {
   /**
    * Finalizes a statement so you can successfully close a database connection.
    * This method does not need to be used from native callers since you can just
    * set the statement to null, but is extremely useful to JS callers.
    */
   void finalize();
 
@@ -125,16 +126,34 @@ interface mozIStorageStatement : mozISto
   void bindInt32Parameter(in unsigned long aParamIndex, in long aValue);
   void bindInt64Parameter(in unsigned long aParamIndex, in long long aValue);
   void bindNullParameter(in unsigned long aParamIndex);
   void bindBlobParameter(in unsigned long aParamIndex,
                          [array,const,size_is(aValueSize)] in octet aValue,
                          in unsigned long aValueSize);
 
   /**
+   * Binds the array of parameters to the statement.  When executeAsync is
+   * called, all the parameters in aParameters are bound and then executed.
+   *
+   * @param aParameters
+   *        The array of parameters to bind to the statement upon execution.
+   */
+  void bindParameters(in mozIStorageBindingParamsArray aParameters);
+
+  /**
+   * Creates a new mozIStorageBindingParamsArray that can be used to bind
+   * multiple sets of data to a statement.
+   *
+   * @returns a mozIStorageBindingParamsArray that multiple sets of parameters
+   *          can be bound to.
+   */
+  mozIStorageBindingParamsArray newBindingParamsArray();
+
+  /**
    * Execute the query, ignoring any results.  This is accomplished by
    * calling step() once, and then calling reset().
    *
    * Error and last insert info, etc. are available from
    * the mozStorageConnection.
    */
   void execute();
 
--- a/storage/src/Makefile.in
+++ b/storage/src/Makefile.in
@@ -78,16 +78,18 @@ CPPSRCS = \
   mozStorageArgValueArray.cpp \
   mozStorageSQLFunctions.cpp \
   mozStorageRow.cpp \
   mozStorageResultSet.cpp \
   mozStorageError.cpp \
   mozStorageAsyncStatementExecution.cpp \
   mozStorageStatementJSHelper.cpp \
   mozStoragePrivateHelpers.cpp \
+  mozStorageBindingParamsArray.cpp \
+  mozStorageBindingParams.cpp \
   $(NULL)
 
 LOCAL_INCLUDES = \
 	$(SQLITE_CFLAGS)
 
 # This is the default value.  If we ever change it when compiling sqlite, we
 # will need to change it here as well.
 DEFINES += -DSQLITE_MAX_LIKE_PATTERN_LENGTH=50000
--- a/storage/src/mozStorageAsyncStatementExecution.cpp
+++ b/storage/src/mozStorageAsyncStatementExecution.cpp
@@ -38,22 +38,24 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsAutoPtr.h"
 #include "prtime.h"
 
 #include "sqlite3.h"
 
 #include "mozIStorageStatementCallback.h"
+#include "mozStorageBindingParams.h"
 #include "mozStorageHelper.h"
 #include "mozStorageResultSet.h"
 #include "mozStorageRow.h"
 #include "mozStorageConnection.h"
 #include "mozStorageError.h"
 #include "mozStoragePrivateHelpers.h"
+#include "mozStorageStatementData.h"
 #include "mozStorageAsyncStatementExecution.h"
 
 namespace mozilla {
 namespace storage {
 
 /**
  * The following constants help batch rows into result sets.
  * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
@@ -167,17 +169,17 @@ private:
 
 } // anonymous namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// AsyncExecuteStatements
 
 /* static */
 nsresult
-AsyncExecuteStatements::execute(sqlite3_stmt_array &aStatements,
+AsyncExecuteStatements::execute(StatementDataArray &aStatements,
                                 Connection *aConnection,
                                 mozIStorageStatementCallback *aCallback,
                                 mozIStoragePendingStatement **_stmt)
 {
   // Create our event to run in the background
   nsRefPtr<AsyncExecuteStatements> event =
     new AsyncExecuteStatements(aStatements, aConnection, aCallback);
   NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
@@ -188,17 +190,17 @@ AsyncExecuteStatements::execute(sqlite3_
   nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Return it as the pending statement object
   NS_ADDREF(*_stmt = event);
   return NS_OK;
 }
 
-AsyncExecuteStatements::AsyncExecuteStatements(sqlite3_stmt_array &aStatements,
+AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements,
                                                Connection *aConnection,
                                                mozIStorageStatementCallback *aCallback)
 : mConnection(aConnection)
 , mTransactionManager(nsnull)
 , mCallback(aCallback)
 , mCallingThread(::do_GetCurrentThread())
 , mMaxIntervalWait(::PR_MicrosecondsToInterval(MAX_MILLISECONDS_BETWEEN_RESULTS))
 , mIntervalStart(::PR_IntervalNow())
@@ -222,16 +224,57 @@ AsyncExecuteStatements::shouldNotify()
 
   // We do not need to acquire mMutex here because it can only ever be written
   // to on the calling thread, and the only thread that can call us is the
   // calling thread, so we know that our access is serialized.
   return !mCancelRequested;
 }
 
 bool
+AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
+                                                       bool aLastStatement)
+{
+  mMutex.AssertNotCurrentThreadOwns();
+
+  sqlite3_stmt *stmt(aData);
+  BindingParamsArray *paramsArray(aData);
+
+  // Iterate through all of our parameters, bind them, and execute.
+  bool continueProcessing = true;
+  BindingParamsArray::iterator itr = paramsArray->begin();
+  BindingParamsArray::iterator end = paramsArray->end();
+  while (itr != end && continueProcessing) {
+    // Bind the data to our statement.
+    nsCOMPtr<mozIStorageError> error;
+    error = (*itr)->bind(stmt);
+    if (error) {
+      // Set our error state.
+      {
+        MutexAutoLock mutex(mMutex);
+        mState = ERROR;
+      }
+
+      // And notify.
+      (void)notifyError(error);
+      return false;
+    }
+
+    // Advance our iterator, execute, and then process the statement.
+    itr++;
+    bool lastStatement = aLastStatement && itr == end;
+    continueProcessing = executeAndProcessStatement(stmt, lastStatement);
+
+    // Always reset our statement.
+    (void)::sqlite3_reset(stmt);
+  }
+
+  return continueProcessing;
+}
+
+bool
 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
                                                    bool aLastStatement)
 {
   mMutex.AssertNotCurrentThreadOwns();
 
   // We need to hold the mutex for statement execution so we can properly
   // reflect state in case we are canceled.  We release the mutex in a few areas
   // in order to allow for cancelation to occur.
@@ -372,20 +415,18 @@ AsyncExecuteStatements::notifyComplete()
 {
   mMutex.AssertNotCurrentThreadOwns();
   NS_ASSERTION(mState != PENDING,
                "Still in a pending state when calling Complete!");
 
   // Finalize our statements before we try to commit or rollback.  If we are
   // canceling and have statements that think they have pending work, the
   // rollback will fail.
-  for (PRUint32 i = 0; i < mStatements.Length(); i++) {
-    (void)::sqlite3_finalize(mStatements[i]);
-    mStatements[i] = NULL;
-  }
+  for (PRUint32 i = 0; i < mStatements.Length(); i++)
+    mStatements[i].finalize();
 
   // Handle our transaction, if we have one
   if (mTransactionManager) {
     if (mState == COMPLETED) {
       nsresult rv = mTransactionManager->Commit();
       if (NS_FAILED(rv)) {
         mState = ERROR;
         (void)notifyError(mozIStorageError::ERROR,
@@ -421,18 +462,29 @@ AsyncExecuteStatements::notifyError(PRIn
   mMutex.AssertNotCurrentThreadOwns();
 
   if (!mCallback)
     return NS_OK;
 
   nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
   NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
 
+  return notifyError(errorObj);
+}
+
+nsresult
+AsyncExecuteStatements::notifyError(mozIStorageError *aError)
+{
+  mMutex.AssertNotCurrentThreadOwns();
+
+  if (!mCallback)
+    return NS_OK;
+
   nsRefPtr<ErrorNotifier> notifier =
-    new ErrorNotifier(mCallback, errorObj, this);
+    new ErrorNotifier(mCallback, aError, this);
   NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
 
   return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
 }
 
 nsresult
 AsyncExecuteStatements::notifyResults()
 {
@@ -505,27 +557,37 @@ AsyncExecuteStatements::Run()
       mState = CANCELED;
   }
   if (cancelRequested)
     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.
-  if (mStatements.Length() > 1) {
+  // Additionally, if we have only one statement and it has parameters to be
+  // bound, we assume that the consumer would want a transaction as well.
+  if (mStatements.Length() > 1 || mStatements[0].hasParametersToBeBound()) {
     // 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.
   for (PRUint32 i = 0; i < mStatements.Length(); i++) {
-    PRBool finished = (i == (mStatements.Length() - 1));
-    if (!executeAndProcessStatement(mStatements[i], finished))
+    bool finished = (i == (mStatements.Length() - 1));
+
+    // If we have parameters to bind, bind them, execute, and process.
+    if (mStatements[i].hasParametersToBeBound()) {
+      if (!bindExecuteAndProcessStatement(mStatements[i], finished))
+        break;
+    }
+    // Otherwise, just execute and process the statement.
+    else if (!executeAndProcessStatement(mStatements[i], finished)) {
       break;
+    }
   }
 
   // If we still have results that we haven't notified about, take care of
   // them now.
   if (mResultSet)
     (void)notifyResults();
 
   // Notify about completion
--- a/storage/src/mozStorageAsyncStatementExecution.h
+++ b/storage/src/mozStorageAsyncStatementExecution.h
@@ -52,16 +52,17 @@
 struct sqlite3_stmt;
 class mozStorageTransaction;
 
 namespace mozilla {
 namespace storage {
 
 class Connection;
 class ResultSet;
+class StatementData;
 
 class AsyncExecuteStatements : public nsIRunnable
                              , public mozIStoragePendingStatement
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
   NS_DECL_MOZISTORAGEPENDINGSTATEMENT
@@ -71,52 +72,69 @@ public:
    */
   enum ExecutionState {
     PENDING = -1,
     COMPLETED = mozIStorageStatementCallback::REASON_FINISHED,
     CANCELED = mozIStorageStatementCallback::REASON_CANCELED,
     ERROR = mozIStorageStatementCallback::REASON_ERROR
   };
 
-  typedef nsTArray<sqlite3_stmt *> sqlite3_stmt_array;
+  typedef nsTArray<StatementData> StatementDataArray;
 
   /**
    * Executes a statement in the background, and passes results back to the
    * caller.
    *
    * @param aStatements
-   *        The SQLite statements to execute in the background.  Ownership is
-   *        transfered from the caller.
+   *        The statements to execute and possibly bind in the background.
+   *        Ownership is transfered from the caller.
    * @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 statements.
    */
-  static nsresult execute(sqlite3_stmt_array &aStatements,
+  static nsresult execute(StatementDataArray &aStatements,
                           Connection *aConnection,
                           mozIStorageStatementCallback *aCallback,
                           mozIStoragePendingStatement **_stmt);
 
   /**
    * Indicates when events on the calling thread should run or not.  Certain
    * events posted back to the calling thread should call this see if they
    * should run or not.
    *
    * @returns true if the event should notify still, false otherwise.
    */
   bool shouldNotify();
 
 private:
-  AsyncExecuteStatements(sqlite3_stmt_array &aStatements,
+  AsyncExecuteStatements(StatementDataArray &aStatements,
                          Connection *aConnection,
                          mozIStorageStatementCallback *aCallback);
 
   /**
+   * Binds and then executes a given statement until completion, an error
+   * occurs, or we are canceled.  If aLastStatement is true, we should set
+   * mState accordingly.
+   *
+   * @pre mMutex is not held
+   *
+   * @param aData
+   *        The StatementData to bind, execute, and then process.
+   * @param aLastStatement
+   *        Indicates if this is the last statement or not.  If it is, we have
+   *        to set the proper state.
+   * @returns true if we should continue to process statements, false otherwise.
+   */
+  bool bindExecuteAndProcessStatement(StatementData &aData,
+                                      bool aLastStatement);
+
+  /**
    * Executes a given statement until completion, an error occurs, or we are
    * canceled.  If aLastStatement is true, we should set mState accordingly.
    *
    * @pre mMutex is not held
    *
    * @param aStatement
    *        The statement to execute and then process.
    * @param aLastStatement
@@ -160,27 +178,30 @@ private:
    * Notifies callback about an error.
    *
    * @pre mMutex is not held
    *
    * @param aErrorCode
    *        The error code defined in mozIStorageError for the error.
    * @param aMessage
    *        The error string, if any.
+   * @param aError
+   *        The error object to notify the caller with.
    */
   nsresult notifyError(PRInt32 aErrorCode, const char *aMessage);
+  nsresult notifyError(mozIStorageError *aError);
 
   /**
    * Notifies the callback about a result set.
    *
    * @pre mMutex is not held
    */
   nsresult notifyResults();
 
-  sqlite3_stmt_array mStatements;
+  StatementDataArray mStatements;
   nsRefPtr<Connection> mConnection;
   mozStorageTransaction *mTransactionManager;
   mozIStorageStatementCallback *mCallback;
   nsCOMPtr<nsIThread> mCallingThread;
   nsRefPtr<ResultSet> mResultSet;
 
   /**
    * The maximum amount of time we want to wait between results.  Defined by
new file mode 100644
--- /dev/null
+++ b/storage/src/mozStorageBindingParams.cpp
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * 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 ***** */
+
+#include <limits.h>
+
+#include "nsString.h"
+
+#include "mozStorageError.h"
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageBindingParams.h"
+#include "mozStorageBindingParamsArray.h"
+#include "Variant.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Local Helper Objects
+
+namespace {
+
+struct BindingColumnData
+{
+  BindingColumnData(sqlite3_stmt *aStmt,
+                    int aColumn)
+  : stmt(aStmt)
+  , column(aColumn)
+  {
+  }
+  sqlite3_stmt *stmt;
+  int column;
+};
+
+} // anonymous namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// sqlite3_stmt Specialization Functions (varaintToSQLite3T)
+
+template < >
+int
+sqlite3_T_int(BindingColumnData aData,
+              int aValue)
+{
+  return ::sqlite3_bind_int(aData.stmt, aData.column + 1, aValue);
+}
+
+template < >
+int
+sqlite3_T_int64(BindingColumnData aData,
+                sqlite3_int64 aValue)
+{
+  return ::sqlite3_bind_int64(aData.stmt, aData.column + 1, aValue);
+}
+
+template < >
+int
+sqlite3_T_double(BindingColumnData aData,
+                 double aValue)
+{
+  return ::sqlite3_bind_double(aData.stmt, aData.column + 1, aValue);
+}
+
+template < >
+int
+sqlite3_T_text16(BindingColumnData aData,
+                 nsString aValue)
+{
+  return ::sqlite3_bind_text16(aData.stmt,
+                               aData.column + 1,
+                               PromiseFlatString(aValue).get(),
+                               aValue.Length() * 2, // Length in bytes!
+                               SQLITE_TRANSIENT);
+}
+
+template < >
+int
+sqlite3_T_null(BindingColumnData aData)
+{
+  return ::sqlite3_bind_null(aData.stmt, aData.column + 1);
+}
+
+template < >
+int
+sqlite3_T_blob(BindingColumnData aData,
+               const void *aBlob,
+               int aSize)
+{
+  return ::sqlite3_bind_blob(aData.stmt, aData.column + 1, aBlob, aSize,
+                             NS_Free);
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BindingParams
+
+BindingParams::BindingParams(BindingParamsArray *aOwningArray,
+                             Statement *aOwningStatement)
+: mOwningArray(aOwningArray)
+, mOwningStatement(aOwningStatement)
+, mLocked(false)
+{
+  (void)mOwningStatement->GetParameterCount(&mParamCount);
+}
+
+void
+BindingParams::lock()
+{
+  NS_ASSERTION(mLocked == false, "Parameters have already been locked!");
+  mLocked = true;
+
+  // We no longer need to hold a reference to our statement or our owning array.
+  // The array owns us at this point, and it will own a reference to the
+  // statement.
+  mOwningStatement = nsnull;
+  mOwningArray = nsnull;
+}
+
+const BindingParamsArray *
+BindingParams::getOwner() const
+{
+  return mOwningArray;
+}
+
+already_AddRefed<mozIStorageError>
+BindingParams::bind(sqlite3_stmt *aStatement)
+{
+  // Iterate through all of our stored data, and bind it.
+  for (PRInt32 i = 0; i < mParameters.Count(); i++) {
+    int rc = variantToSQLiteT(BindingColumnData(aStatement, i), mParameters[i]);
+    if (rc != SQLITE_OK) {
+      // We had an error while trying to bind.  Now we need to create an error
+      // object with the right message.  Note that we special case
+      // SQLITE_MISMATCH, but otherwise get the message from SQLite.
+      const char *msg = "Could not covert nsIVariant to SQLite type.";
+      if (rc != SQLITE_MISMATCH)
+        msg = ::sqlite3_errmsg(::sqlite3_db_handle(aStatement));
+
+      nsCOMPtr<mozIStorageError> err(new Error(rc, msg));
+      return err.forget();
+    }
+  }
+
+  // No error occurred, so return null!
+  return nsnull;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(
+  BindingParams,
+  mozIStorageBindingParams
+)
+
+///////////////////////////////////////////////////////////////////////////////
+//// mozIStorageBindingParams
+
+NS_IMETHODIMP
+BindingParams::BindByName(const nsACString &aName,
+                          nsIVariant *aValue)
+{
+  NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+
+  // Get the column index that we need to store this at.
+  PRUint32 index;
+  nsresult rv = mOwningStatement->GetParameterIndex(aName, &index);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return BindByIndex(index, aValue);
+}
+
+NS_IMETHODIMP
+BindingParams::BindUTF8StringByName(const nsACString &aName,
+                                    const nsACString &aValue)
+{
+  nsCOMPtr<nsIVariant> value(new UTF8TextVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindStringByName(const nsACString &aName,
+                                const nsAString &aValue)
+{
+  nsCOMPtr<nsIVariant> value(new TextVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindDoubleByName(const nsACString &aName,
+                                double aValue)
+{
+  nsCOMPtr<nsIVariant> value(new FloatVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindInt32ByName(const nsACString &aName,
+                               PRInt32 aValue)
+{
+  nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindInt64ByName(const nsACString &aName,
+                               PRInt64 aValue)
+{
+  nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindNullByName(const nsACString &aName)
+{
+  nsCOMPtr<nsIVariant> value(new NullVariant());
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindBlobByName(const nsACString &aName,
+                              const PRUint8 *aValue,
+                              PRUint32 aValueSize)
+{
+  NS_ENSURE_ARG_MAX(aValueSize, INT_MAX);
+  std::pair<const void *, int> data(
+    static_cast<const void *>(aValue),
+    int(aValueSize)
+  );
+  nsCOMPtr<nsIVariant> value(new BlobVariant(data));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindByIndex(PRUint32 aIndex,
+                           nsIVariant *aValue)
+{
+  NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+  ENSURE_INDEX_VALUE(aIndex, mParamCount);
+
+  // Store the variant for later use.
+  NS_ENSURE_TRUE(mParameters.InsertObjectAt(aValue, aIndex),
+                 NS_ERROR_OUT_OF_MEMORY);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BindingParams::BindUTF8StringByIndex(PRUint32 aIndex,
+                                     const nsACString &aValue)
+{
+  nsCOMPtr<nsIVariant> value(new UTF8TextVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindStringByIndex(PRUint32 aIndex,
+                                 const nsAString &aValue)
+{
+  nsCOMPtr<nsIVariant> value(new TextVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindDoubleByIndex(PRUint32 aIndex,
+                                 double aValue)
+{
+  nsCOMPtr<nsIVariant> value(new FloatVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindInt32ByIndex(PRUint32 aIndex,
+                                PRInt32 aValue)
+{
+  nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindInt64ByIndex(PRUint32 aIndex,
+                                PRInt64 aValue)
+{
+  nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindNullByIndex(PRUint32 aIndex)
+{
+  nsCOMPtr<nsIVariant> value(new NullVariant());
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindBlobByIndex(PRUint32 aIndex,
+                               const PRUint8 *aValue,
+                               PRUint32 aValueSize)
+{
+  NS_ENSURE_ARG_MAX(aValueSize, INT_MAX);
+  std::pair<const void *, int> data(
+    static_cast<const void *>(aValue),
+    int(aValueSize)
+  );
+  nsCOMPtr<nsIVariant> value(new BlobVariant(data));
+  NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+  return BindByIndex(aIndex, value);
+}
+
+} // namespace storage
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/storage/src/mozStorageBindingParams.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * 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 ***** */
+
+#ifndef _mozStorageBindingParams_h_
+#define _mozStorageBindingParams_h_
+
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsIVariant.h"
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozStorageStatement.h"
+#include "mozIStorageBindingParams.h"
+
+class mozIStorageError;
+struct sqlite3_stmt;
+
+namespace mozilla {
+namespace storage {
+
+class BindingParams : public mozIStorageBindingParams
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEBINDINGPARAMS
+
+  /**
+   * Locks the parameters and prevents further modification to it (such as
+   * binding more elements to it).
+   */
+  void lock();
+
+  /**
+   * @returns the pointer to the owning BindingParamsArray.
+   */
+  const BindingParamsArray *getOwner() const;
+
+  /**
+   * Binds our stored data to the statement.
+   *
+   * @param aStatement
+   *        The statement to bind our data to.
+   * @returns nsnull on success, or a mozIStorageError object if an error
+   *          occurred.
+   */
+  already_AddRefed<mozIStorageError> bind(sqlite3_stmt *aStatement);
+
+  BindingParams(BindingParamsArray *aOwningArray,
+                Statement *aOwningStatement);
+
+private:
+  nsRefPtr<BindingParamsArray> mOwningArray;
+  Statement *mOwningStatement;
+  nsCOMArray<nsIVariant> mParameters;
+  PRUint32 mParamCount;
+  bool mLocked;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // _mozStorageBindingParams_h_
new file mode 100644
--- /dev/null
+++ b/storage/src/mozStorageBindingParamsArray.cpp
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * 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 ***** */
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozStorageBindingParams.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// BindingParamsArray
+
+BindingParamsArray::BindingParamsArray(Statement *aOwningStatement)
+: mOwningStatement(aOwningStatement)
+, mLocked(false)
+{
+}
+
+void
+BindingParamsArray::lock()
+{
+  NS_ASSERTION(mLocked == false, "Array has already been locked!");
+  mLocked = true;
+
+  // We also no longer need to hold a reference to our statement since it owns
+  // us.
+  mOwningStatement = nsnull;
+}
+
+const Statement *
+BindingParamsArray::getOwner() const
+{
+  return mOwningStatement;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(
+  BindingParamsArray,
+  mozIStorageBindingParamsArray
+)
+
+///////////////////////////////////////////////////////////////////////////////
+//// mozIStorageBindingParamsArray
+
+NS_IMETHODIMP
+BindingParamsArray::NewBindingParams(mozIStorageBindingParams **_params)
+{
+  NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<mozIStorageBindingParams> params =
+    new BindingParams(this, mOwningStatement);
+  NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
+
+  params.forget(_params);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BindingParamsArray::AddParams(mozIStorageBindingParams *aParameters)
+{
+  NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+
+  BindingParams *params = static_cast<BindingParams *>(aParameters);
+
+  // Check to make sure that this set of parameters was created with us.
+  if (params->getOwner() != this)
+    return NS_ERROR_UNEXPECTED;
+
+  NS_ENSURE_TRUE(mArray.AppendElement(params), NS_ERROR_OUT_OF_MEMORY);
+
+  // Lock the parameters only after we've successfully added them.
+  params->lock();
+
+  return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/storage/src/mozStorageBindingParamsArray.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * 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 ***** */
+
+#ifndef _mozStorageBindingParamsArray_h_
+#define _mozStorageBindingParamsArray_h_
+
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+
+#include "mozIStorageBindingParamsArray.h"
+
+namespace mozilla {
+namespace storage {
+
+class BindingParams;
+class Statement;
+
+class BindingParamsArray : public mozIStorageBindingParamsArray
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEBINDINGPARAMSARRAY
+
+  BindingParamsArray(Statement *aOwningStatement);
+
+  /**
+   * Locks the array and prevents further modification to it (such as adding
+   * more elements to it).
+   */
+  void lock();
+
+  /**
+   * @returns the pointer to the owning BindingParamsArray.
+   */
+  const Statement *getOwner() const;
+
+  class iterator {
+  public:
+    iterator(BindingParamsArray *aArray,
+             PRUint32 aIndex)
+    : mArray(aArray)
+    , mIndex(aIndex)
+    {
+    }
+
+    iterator &operator++(int)
+    {
+      mIndex++;
+      return *this;
+    }
+
+    bool operator==(const iterator &aOther) const
+    {
+      return mIndex == aOther.mIndex;
+    }
+    bool operator!=(const iterator &aOther) const
+    {
+      return !(*this == aOther);
+    }
+    BindingParams *operator*()
+    {
+      NS_ASSERTION(mIndex < mArray->mArray.Length(),
+                   "Dereferenceing an invalid value!");
+      return mArray->mArray[mIndex].get();
+    }
+  private:
+    void operator--() { }
+    BindingParamsArray *mArray;
+    PRUint32 mIndex;
+  };
+
+  /**
+   * Obtains an iterator pointing to the beginning of the array.
+   */
+  inline iterator begin()
+  {
+    NS_ASSERTION(mLocked, "Obtaining an iterator when we are not locked!");
+    return iterator(this, 0);
+  }
+
+  /**
+   * Obtains an iterator pointing to the end of the array.
+   */
+  inline iterator end()
+  {
+    NS_ASSERTION(mLocked, "Obtaining an iterator when we are not locked!");
+    return iterator(this, mArray.Length());
+  }
+private:
+  nsRefPtr<Statement> mOwningStatement;
+  nsTArray< nsRefPtr<BindingParams> > mArray;
+  bool mLocked;
+
+  friend class iterator;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // _mozStorageBindingParamsArray_h_
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -57,16 +57,17 @@
 
 #include "mozStorageAsyncStatementExecution.h"
 #include "mozStorageSQLFunctions.h"
 #include "mozStorageConnection.h"
 #include "mozStorageService.h"
 #include "mozStorageStatement.h"
 #include "mozStorageArgValueArray.h"
 #include "mozStoragePrivateHelpers.h"
+#include "mozStorageStatementData.h"
 
 #include "prlog.h"
 #include "prprf.h"
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gStorageLog = nsnull;
 #endif
 
@@ -622,17 +623,17 @@ Connection::ExecuteSimpleSQL(const nsACS
 
 nsresult
 Connection::ExecuteAsync(mozIStorageStatement **aStatements,
                          PRUint32 aNumStatements,
                          mozIStorageStatementCallback *aCallback,
                          mozIStoragePendingStatement **_handle)
 {
   int rc = SQLITE_OK;
-  nsTArray<sqlite3_stmt *> stmts(aNumStatements);
+  nsTArray<StatementData> stmts(aNumStatements);
   for (PRUint32 i = 0; i < aNumStatements && rc == SQLITE_OK; i++) {
     sqlite3_stmt *old_stmt =
         static_cast<Statement *>(aStatements[i])->nativeStatement();
     if (!old_stmt) {
       rc = SQLITE_MISUSE;
       break;
     }
     NS_ASSERTION(::sqlite3_db_handle(old_stmt) == mDBConn,
@@ -652,17 +653,19 @@ Connection::ExecuteAsync(mozIStorageStat
            ("Cloned statement 0x%p to 0x%p", old_stmt, new_stmt));
 #endif
 
     // Transfer the bindings
     rc = sqlite3_transfer_bindings(old_stmt, new_stmt);
     if (rc != SQLITE_OK)
       break;
 
-    if (!stmts.AppendElement(new_stmt)) {
+    Statement *storageStmt = static_cast<Statement *>(aStatements[i]);
+    StatementData data(new_stmt, storageStmt->bindingParamsArray());
+    if (!stmts.AppendElement(data)) {
       rc = SQLITE_NOMEM;
       break;
     }
   }
 
   // Dispatch to the background
   nsresult rv = NS_OK;
   if (rc == SQLITE_OK)
--- a/storage/src/mozStorageStatement.cpp
+++ b/storage/src/mozStorageStatement.cpp
@@ -501,16 +501,43 @@ Statement::BindBlobParameter(PRUint32 aP
     return NS_ERROR_NOT_INITIALIZED;
 
   int srv = ::sqlite3_bind_blob(mDBStatement, aParamIndex + 1, aValue,
                                 aValueSize, SQLITE_TRANSIENT);
   return convertResultCode(srv);
 }
 
 NS_IMETHODIMP
+Statement::BindParameters(mozIStorageBindingParamsArray *aParameters)
+{
+  if (!mDBStatement)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters);
+  if (array->getOwner() != this)
+    return NS_ERROR_UNEXPECTED;
+
+  mParamsArray = array;
+  mParamsArray->lock();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::NewBindingParamsArray(mozIStorageBindingParamsArray **_array)
+{
+  nsCOMPtr<mozIStorageBindingParamsArray> array =
+    new BindingParamsArray(this);
+  NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+  array.forget(_array);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 Statement::Execute()
 {
   if (!mDBStatement)
     return NS_ERROR_NOT_INITIALIZED;
 
   PRBool ret;
   nsresult rv = ExecuteStep(&ret);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/storage/src/mozStorageStatement.h
+++ b/storage/src/mozStorageStatement.h
@@ -40,16 +40,17 @@
 #ifndef _mozStorageStatement_h_
 #define _mozStorageStatement_h_
 
 #include "nsAutoPtr.h"
 #include "nsString.h"
 
 #include "nsTArray.h"
 
+#include "mozStorageBindingParamsArray.h"
 #include "mozIStorageStatement.h"
 
 class nsIXPConnectJSObjectHolder;
 struct sqlite3_stmt;
 
 namespace mozilla {
 namespace storage {
 class StatementJSHelper;
@@ -77,27 +78,42 @@ public:
                       const nsACString &aSQLStatement);
 
 
   /**
    * Obtains the native statement pointer.
    */
   inline sqlite3_stmt *nativeStatement() { return mDBStatement; }
 
+  /**
+   * Obtains and transfers ownership of the array of parameters that are bound
+   * to this statment.  This can be null.
+   */
+  inline already_AddRefed<BindingParamsArray> bindingParamsArray()
+  {
+    return mParamsArray.forget();
+  }
+
 private:
     ~Statement();
 
     nsRefPtr<Connection> mDBConnection;
     sqlite3_stmt *mDBStatement;
     PRUint32 mParamCount;
     PRUint32 mResultColumnCount;
     nsTArray<nsCString> mColumnNames;
     bool mExecuting;
 
     /**
+     * Holds the array of parameters to bind to this statement when we execute
+     * it asynchronously.
+     */
+    nsRefPtr<BindingParamsArray> mParamsArray;
+
+    /**
      * The following two members are only used with the JS helper.  They cache
      * the row and params objects.
      */
     nsCOMPtr<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
     nsCOMPtr<nsIXPConnectJSObjectHolder> mStatementRowHolder;
 
     friend class StatementJSHelper;
 };
new file mode 100644
--- /dev/null
+++ b/storage/src/mozStorageStatementData.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * 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 ***** */
+
+#ifndef _mozStorageStatementData_h_
+#define _mozStorageStatementData_h_
+
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+
+#include "mozStorageBindingParamsArray.h"
+
+struct sqlite3_stmt;
+
+namespace mozilla {
+namespace storage {
+
+class StatementData
+{
+public:
+  StatementData(sqlite3_stmt *aStatement,
+                already_AddRefed<BindingParamsArray> aParamsArray)
+  : mStatement(aStatement)
+  , mParamsArray(aParamsArray)
+  {
+  }
+  StatementData(const StatementData &aSource)
+  : mStatement(aSource.mStatement)
+  , mParamsArray(aSource.mParamsArray)
+  {
+  }
+  StatementData()
+  {
+  }
+
+  operator sqlite3_stmt *() const
+  {
+    NS_ASSERTION(mStatement, "NULL sqlite3_stmt being handed off!");
+    return mStatement;
+  }
+  operator BindingParamsArray *() const { return mParamsArray; }
+
+  /**
+   * Finalizes and NULLs out our sqlite3_stmt.  Also releases our parameter
+   * array since we'll no longer need it.
+   */
+  inline void finalize()
+  {
+    (void)::sqlite3_finalize(mStatement);
+    mStatement = NULL;
+    mParamsArray = nsnull;
+  }
+
+  /**
+   * Indicates if this statement has parameters to be bound before it is
+   * executed.
+   *
+   * @returns true if the statement has parameters to bind against, false
+   *          otherwise.
+   */
+  inline bool hasParametersToBeBound() const { return mParamsArray != nsnull; }
+
+private:
+  sqlite3_stmt *mStatement;
+  nsRefPtr<BindingParamsArray> mParamsArray;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // _mozStorageStatementData_h_
--- a/storage/test/unit/test_connection_executeAsync.js
+++ b/storage/test/unit/test_connection_executeAsync.js
@@ -39,21 +39,19 @@
 
 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, " +
+      "id INTEGER, " +
       "string TEXT, " +
       "number REAL, " +
       "nuller NULL, " +
       "blober BLOB" +
     ")"
   );
 
   let stmts = [];
@@ -68,17 +66,16 @@ function test_create_and_add()
   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)
     {
@@ -118,63 +115,153 @@ function test_create_and_add()
       try {
         do_check_true(stmt.executeStep());
         do_check_eq(2, stmt.getInt32(0));
       }
       finally {
         stmt.finalize();
       }
 
-      do_test_finished();
+      // Run the next test.
+      run_next_test();
     }
   });
   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();
+
+      // 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 = [];
+  // We run the same statement twice, and should insert 2 * AMOUNT_TO_ADD.
+  for (let i = 0; i < ITERATIONS; i++) {
+    stmts[i] = getOpenedDatabase().createStatement(
+      "INSERT INTO test (id, string, number, nuller, blober) " +
+      "VALUES (:int, :text, :real, :null, :blob)"
+    );
+    let params = stmts[i].newBindingParamsArray()
+    for (let j = 0; j < AMOUNT_TO_ADD; j++) {
+      let bp = params.newBindingParams();
+      bp.bindByName("int", INTEGER);
+      bp.bindByName("text", TEXT);
+      bp.bindByName("real", REAL);
+      bp.bindByName("null", null);
+      bp.bindBlobByName("blob", BLOB, BLOB.length);
+      params.addParams(bp);
+    }
+    stmts[i].bindParameters(params);
+  }
+
+  // Get our current number of rows in the table.
+  let currentRows = 0;
+  let countStmt = getOpenedDatabase().createStatement(
+    "SELECT COUNT(1) AS count FROM test"
+  );
+  try {
+    do_check_true(countStmt.executeStep());
+    currentRows = countStmt.row.count;
+  }
+  finally {
+    countStmt.reset();
+  }
+
+  // Execute asynchronously.
+  getOpenedDatabase().executeAsync(stmts, stmts.length, {
+    handleResult: function(aResultSet)
+    {
+      do_throw("Unexpected call to handleResult!");
+    },
+    handleError: function(aError)
+    {
+      print("Error code " + aError.result + " with message '" +
+            aError.message + "' returned.");
+      do_throw("Unexpected error!");
+    },
+    handleCompletion: function(aReason)
+    {
+      print("handleCompletion(" + aReason +
+            ") for test_multiple_bindings_on_statements");
+      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
+
+      // Check to make sure we added all of our rows.
+      try {
+        do_check_true(countStmt.executeStep());
+        do_check_eq(currentRows + (ITERATIONS * AMOUNT_TO_ADD),
+                    countStmt.row.count);
+      }
+      finally {
+        countStmt.finalize();
+      }
+
+      // Run the next test.
+      run_next_test();
+    }
+  });
+  stmts.forEach(function(stmt) stmt.finalize());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Runner
 
 let tests =
 [
   test_create_and_add,
   test_transaction_created,
+  test_multiple_bindings_on_statements,
 ];
+let index = 0;
+
+function run_next_test()
+{
+  if (index < tests.length) {
+    do_test_pending();
+    print("Running the next test: " + tests[index].name);
+    tests[index++]();
+  }
+
+  do_test_finished();
+}
 
 function run_test()
 {
   cleanup();
 
-  for (let i = 0; i < tests.length; i++)
-    tests[i]();
+  do_test_pending();
+  run_next_test();
 }
rename from storage/test/unit/test_storage_statement_executeAsync.js
rename to storage/test/unit/test_statement_executeAsync.js
--- a/storage/test/unit/test_storage_statement_executeAsync.js
+++ b/storage/test/unit/test_statement_executeAsync.js
@@ -39,95 +39,88 @@
 
 const INTEGER = 1;
 const TEXT = "this is test text";
 const REAL = 3.23;
 const BLOB = [1, 2];
 
 function test_create_table()
 {
-  dump("test_create_table()\n");
-
   // Ensure our table doesn't exists
   do_check_false(getOpenedDatabase().tableExists("test"));
 
   var stmt = getOpenedDatabase().createStatement(
     "CREATE TABLE test (" +
-      "id INTEGER PRIMARY KEY, " +
+      "id INTEGER, " +
       "string TEXT, " +
       "number REAL, " +
       "nuller NULL, " +
       "blober BLOB" +
     ")"
   );
 
-  do_test_pending();
   stmt.executeAsync({
     handleResult: function(aResultSet)
     {
       dump("handleResult("+aResultSet+");\n");
       do_throw("unexpected results obtained!");
     },
     handleError: function(aError)
     {
-      dump("handleError("+aError+");\n");
+      print("error code " + aerror.result + " with message '" +
+            aerror.message + "' returned.");
       do_throw("unexpected error!");
     },
     handleCompletion: function(aReason)
     {
-      dump("handleCompletion("+aReason+");\n");
+      print("handleCompletion(" + aReason + ") for test_create_table");
       do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
 
       // Check that the table has been created
       do_check_true(getOpenedDatabase().tableExists("test"));
 
       // Verify that it's created correctly (this will throw if it wasn't)
       var stmt = getOpenedDatabase().createStatement(
         "SELECT id, string, number, nuller, blober FROM test"
       );
       stmt.finalize();
 
-      // Now we run the rest of the tests
-      for (var i = 0; i < tests.length; i++)
-        tests[i]();
-
-      do_test_finished();
+      // Run the next test.
+      run_next_test();
     }
   });
   stmt.finalize();
 }
 
 function test_add_data()
 {
-  dump("test_add_data()\n");
-
   var stmt = getOpenedDatabase().createStatement(
-    "INSERT INTO test (id, string, number, nuller, blober) VALUES (?, ?, ?, ?, ?)"
+    "INSERT INTO test (id, string, number, nuller, blober) " +
+    "VALUES (?, ?, ?, ?, ?)"
   );
   stmt.bindInt32Parameter(0, INTEGER);
   stmt.bindStringParameter(1, TEXT);
   stmt.bindDoubleParameter(2, REAL);
   stmt.bindNullParameter(3);
   stmt.bindBlobParameter(4, BLOB, BLOB.length);
 
-  do_test_pending();
   stmt.executeAsync({
     handleResult: function(aResultSet)
     {
-      dump("handleResult("+aResultSet+");\n");
       do_throw("unexpected results obtained!");
     },
     handleError: function(aError)
     {
-      dump("handleError("+aError+");\n");
+      print("error code " + aerror.result + " with message '" +
+            aerror.message + "' returned.");
       do_throw("unexpected error!");
     },
     handleCompletion: function(aReason)
     {
-      dump("handleCompletion("+aReason+");\n");
+      print("handleCompletion(" + aReason + ") for test_add_data");
       do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
 
       // Check that the result is in the table
       var stmt = getOpenedDatabase().createStatement(
         "SELECT string, number, nuller, blober FROM test WHERE id = ?"
       );
       stmt.bindInt32Parameter(0, INTEGER);
       try {
@@ -142,32 +135,30 @@ function test_add_data()
         for (var i = 0; i < BLOB.length; i++)
           do_check_eq(BLOB[i], blob.value[i]);
       }
       finally {
         stmt.reset();
         stmt.finalize();
       }
 
-      do_test_finished();
+      // Run the next test.
+      run_next_test();
     }
   });
   stmt.finalize();
 }
 
 function test_get_data()
 {
-  dump("test_get_data()\n");
-
   var stmt = getOpenedDatabase().createStatement(
     "SELECT string, number, nuller, blober, id FROM test WHERE id = ?"
   );
   stmt.bindInt32Parameter(0, 1);
 
-  do_test_pending();
   stmt.executeAsync({
     resultObtained: false,
     handleResult: function(aResultSet)
     {
       dump("handleResult("+aResultSet+");\n");
       do_check_false(this.resultObtained);
       this.resultObtained = true;
 
@@ -214,39 +205,39 @@ function test_get_data()
                   tuple.getTypeOfIndex(4));
 
       // check that we have no more results
       tuple = aResultSet.getNextRow();
       do_check_eq(null, tuple);
     },
     handleError: function(aError)
     {
-      dump("handleError("+aError+");\n");
+      print("error code " + aerror.result + " with message '" +
+            aerror.message + "' returned.");
       do_throw("unexpected error!");
     },
     handleCompletion: function(aReason)
     {
-      dump("handleCompletion("+aReason+");\n");
+      print("handleCompletion(" + aReason + ") for test_get_data");
       do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
       do_check_true(this.resultObtained);
-      do_test_finished();
+
+      // Run the next test.
+      run_next_test();
     }
   });
   stmt.finalize();
 }
 
 function test_tuple_out_of_bounds()
 {
-  dump("test_tuple_out_of_bounds()\n");
-
   var stmt = getOpenedDatabase().createStatement(
     "SELECT string FROM test"
   );
 
-  do_test_pending();
   stmt.executeAsync({
     resultObtained: false,
     handleResult: function(aResultSet)
     {
       dump("handleResult("+aResultSet+");\n");
       do_check_false(this.resultObtained);
       this.resultObtained = true;
 
@@ -282,71 +273,75 @@ function test_tuple_out_of_bounds()
         do_throw("did not throw :(");
       }
       catch (e) {
         do_check_eq(Cr.NS_ERROR_ILLEGAL_VALUE, e.result);
       }
     },
     handleError: function(aError)
     {
-      dump("handleError("+aError+");\n");
+      print("Error code " + aError.result + " with message '" +
+            aError.message + "' returned.");
       do_throw("unexpected error!");
     },
     handleCompletion: function(aReason)
     {
-      dump("handleCompletion("+aReason+");\n");
+      print("handleCompletion(" + aReason +
+            ") for test_tuple_out_of_bounds");
       do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
       do_check_true(this.resultObtained);
-      do_test_finished();
+
+      // Run the next test.
+      run_next_test();
     }
   });
   stmt.finalize();
 }
 
 function test_no_listener_works_on_success()
 {
-  dump("test_no_listener_works_on_success()\n");
-
   var stmt = getOpenedDatabase().createStatement(
     "DELETE FROM test WHERE id = ?"
   );
   stmt.bindInt32Parameter(0, 0);
   stmt.executeAsync();
   stmt.finalize();
+
+  // Run the next test.
+  run_next_test();
 }
 
 function test_no_listener_works_on_results()
 {
-  dump("test_no_listener_works_on_results()\n");
-
   var stmt = getOpenedDatabase().createStatement(
     "SELECT ?"
   );
   stmt.bindInt32Parameter(0, 1);
   stmt.executeAsync();
   stmt.finalize();
+
+  // Run the next test.
+  run_next_test();
 }
 
 function test_no_listener_works_on_error()
 {
-  return;
-  dump("test_no_listener_works_on_error()\n");
-
   // commit without a transaction will trigger an error
   var stmt = getOpenedDatabase().createStatement(
     "COMMIT"
   );
   stmt.executeAsync();
   stmt.finalize();
+
+  // Run the next test.
+  run_next_test();
 }
 
 function test_partial_listener_works()
 {
-  dump("test_partial_listener_works()\n");
-
   var stmt = getOpenedDatabase().createStatement(
     "DELETE FROM test WHERE id = ?"
   );
   stmt.bindInt32Parameter(0, 0);
   stmt.executeAsync({
     handleResult: function(aResultSet)
     {
     }
@@ -357,84 +352,87 @@ function test_partial_listener_works()
     }
   });
   stmt.executeAsync({
     handleCompletion: function(aReason)
     {
     }
   });
   stmt.finalize();
+
+  // Run the next test.
+  run_next_test();
 }
 
 function test_immediate_cancellation()
 {
-  dump("test_immediate_cancelation()\n");
-
   var stmt = getOpenedDatabase().createStatement(
     "DELETE FROM test WHERE id = ?"
   );
   stmt.bindInt32Parameter(0, 0);
   let reason = Ci.mozIStorageStatementCallback.REASON_CANCELED;
   var pendingStatement = stmt.executeAsync({
     handleResult: function(aResultSet)
     {
-      dump("handleResult("+aResultSet+");\n");
       do_throw("unexpected result!");
     },
     handleError: function(aError)
     {
-      dump("handleError("+aError+");\n");
+      print("Error code " + aError.result + " with message '" +
+            aError.message + "' returned.");
       do_throw("unexpected error!");
     },
     handleCompletion: function(aReason)
     {
-      dump("handleCompletion("+aReason+");\n");
+      print("handleCompletion(" + aReason +
+            ") for test_immediate_cancellation");
       do_check_eq(reason, aReason);
-      do_test_finished();
+
+      // Run the next test.
+      run_next_test();
     }
   });
-  do_test_pending();
 
   // Cancel immediately
   if (!pendingStatement.cancel()) {
     // It is possible that we finished before we canceled
     reason = Ci.mozIStorageStatementCallback.REASON_FINISHED;
   }
 
   stmt.finalize();
 }
 
 function test_double_cancellation()
 {
-  dump("test_double_cancelation()\n");
-
   var stmt = getOpenedDatabase().createStatement(
     "DELETE FROM test WHERE id = ?"
   );
   stmt.bindInt32Parameter(0, 0);
   let reason = Ci.mozIStorageStatementCallback.REASON_CANCELED;
   var pendingStatement = stmt.executeAsync({
     handleResult: function(aResultSet)
     {
-      dump("handleResult("+aResultSet+");\n");
       do_throw("unexpected result!");
     },
     handleError: function(aError)
     {
-      dump("handleError("+aError+");\n");
+      print("Error code " + aError.result + " with message '" +
+            aError.message + "' returned.");
       do_throw("unexpected error!");
     },
     handleCompletion: function(aReason)
     {
-      dump("handleCompletion("+aReason+");\n");
+      print("handleCompletion(" + aReason +
+            ") for test_double_cancellation");
       do_check_eq(reason, aReason);
-      do_test_finished();
+
+      // Run the next test.
+      run_next_test();
     }
   });
-  do_test_pending();
 
   // Cancel immediately
   if (!pendingStatement.cancel()) {
     // It is possible that we finished before we canceled
     reason = Ci.mozIStorageStatementCallback.REASON_FINISHED;
   }
 
   // And cancel again - expect an exception
@@ -446,76 +444,563 @@ function test_double_cancellation()
     do_check_eq(Cr.NS_ERROR_UNEXPECTED, e.result);
   }
 
   stmt.finalize();
 }
 
 function test_double_execute()
 {
-  dump("test_double_execute()\n");
-
   var stmt = getOpenedDatabase().createStatement(
     "SELECT * FROM test"
   );
 
   var listener = {
+    _timesCompleted: 0,
+    _hasResults: false,
     handleResult: function(aResultSet)
     {
-      dump("handleResult("+aResultSet+");\n");
+      do_check_false(this._hasResults);
+      this._hasResults = true;
     },
     handleError: function(aError)
     {
-      dump("handleError("+aError+");\n");
+      print("Error code " + aError.result + " with message '" +
+            aError.message + "' returned.");
       do_throw("unexpected error!");
     },
     handleCompletion: function(aReason)
     {
-      dump("handleCompletion("+aReason+");\n");
+      print("handleCompletion(" + aReason +
+            ") for test_double_execute (iteration " +
+            (this._timesCompleted + 1) + ")");
       do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
-      do_test_finished();
+      do_check_true(this._hasResults);
+      this._hasResults = false;
+      this._timesCompleted++;
+
+      // Run the next test.
+      if (this._timesCompleted == 2)
+        run_next_test();
     }
   }
-  do_test_pending();
   stmt.executeAsync(listener);
-  do_test_pending();
   stmt.executeAsync(listener);
   stmt.finalize();
 }
 
 function test_finalized_statement_does_not_crash()
 {
-  dump("test_finalized_statement_does_not_crash()\n");
-
   var stmt = getOpenedDatabase().createStatement(
     "SELECT * FROM TEST"
   );
   stmt.finalize();
   // we are concerned about a crash here; an error is fine.
   try {
     stmt.executeAsync();
   }
   catch (ex) {}
+
+  // Run the next test.
+  run_next_test();
 }
 
+function test_bind_multiple_rows_by_index()
+{
+  const AMOUNT_TO_ADD = 5;
+  var stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id, string, number, nuller, blober) " +
+    "VALUES (?, ?, ?, ?, ?)"
+  );
+  var array = stmt.newBindingParamsArray();
+  for (let i = 0; i < AMOUNT_TO_ADD; i++) {
+    let bp = array.newBindingParams();
+    bp.bindByIndex(0, INTEGER);
+    bp.bindByIndex(1, TEXT);
+    bp.bindByIndex(2, REAL);
+    bp.bindByIndex(3, null);
+    bp.bindBlobByIndex(4, BLOB, BLOB.length);
+    array.addParams(bp);
+  }
+  stmt.bindParameters(array);
+
+  // Get our current number of rows in the table.
+  var currentRows = 0;
+  var countStmt = getOpenedDatabase().createStatement(
+    "SELECT COUNT(1) AS count FROM test"
+  );
+  try {
+    do_check_true(countStmt.executeStep());
+    currentRows = countStmt.row.count;
+    print("We have " + currentRows + " rows in test_bind_multiple_rows_by_index");
+  }
+  finally {
+    countStmt.reset();
+  }
+
+  // Execute asynchronously.
+  stmt.executeAsync({
+    handleResult: function(aResultSet)
+    {
+      do_throw("Unexpected call to handleResult!");
+    },
+    handleError: function(aError)
+    {
+      print("Error code " + aError.result + " with message '" +
+            aError.message + "' returned.");
+      do_throw("unexpected error!");
+    },
+    handleCompletion: function(aReason)
+    {
+      print("handleCompletion(" + aReason +
+            ") for test_bind_multiple_rows_by_index");
+      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
+
+      // Check to make sure we added all of our rows.
+      try {
+        do_check_true(countStmt.executeStep());
+        print("We now have " + currentRows +
+              " rows in test_bind_multiple_rows_by_index");
+        do_check_eq(currentRows + AMOUNT_TO_ADD, countStmt.row.count);
+      }
+      finally {
+        countStmt.finalize();
+      }
+
+      // Run the next test.
+      run_next_test();
+    }
+  });
+  stmt.finalize();
+}
+
+function test_bind_multiple_rows_by_name()
+{
+  const AMOUNT_TO_ADD = 5;
+  var stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id, string, number, nuller, blober) " +
+    "VALUES (:int, :text, :real, :null, :blob)"
+  );
+  var array = stmt.newBindingParamsArray();
+  for (let i = 0; i < AMOUNT_TO_ADD; i++) {
+    let bp = array.newBindingParams();
+    bp.bindByName("int", INTEGER);
+    bp.bindByName("text", TEXT);
+    bp.bindByName("real", REAL);
+    bp.bindByName("null", null);
+    bp.bindBlobByName("blob", BLOB, BLOB.length);
+    array.addParams(bp);
+  }
+  stmt.bindParameters(array);
+
+  // Get our current number of rows in the table.
+  var currentRows = 0;
+  var countStmt = getOpenedDatabase().createStatement(
+    "SELECT COUNT(1) AS count FROM test"
+  );
+  try {
+    do_check_true(countStmt.executeStep());
+    currentRows = countStmt.row.count;
+    print("We have " + currentRows + " rows in test_bind_multiple_rows_by_name");
+  }
+  finally {
+    countStmt.reset();
+  }
+
+  // Execute asynchronously.
+  stmt.executeAsync({
+    handleResult: function(aResultSet)
+    {
+      do_throw("Unexpected call to handleResult!");
+    },
+    handleError: function(aError)
+    {
+      print("Error code " + aError.result + " with message '" +
+            aError.message + "' returned.");
+      do_throw("unexpected error!");
+    },
+    handleCompletion: function(aReason)
+    {
+      print("handleCompletion(" + aReason +
+            ") for test_bind_multiple_rows_by_name");
+      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
+
+      // Check to make sure we added all of our rows.
+      try {
+        do_check_true(countStmt.executeStep());
+        print("We now have " + currentRows +
+              " rows in test_bind_multiple_rows_by_name");
+        do_check_eq(currentRows + AMOUNT_TO_ADD, countStmt.row.count);
+      }
+      finally {
+        countStmt.finalize();
+      }
+
+      // Run the next test.
+      run_next_test();
+    }
+  });
+  stmt.finalize();
+}
+
+function test_bind_out_of_bounds()
+{
+  let stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (?)"
+  );
+
+  let array = stmt.newBindingParamsArray();
+  let bp = array.newBindingParams();
+
+  // Check variant binding.
+  let exceptionCaught = false;
+  try {
+    bp.bindByIndex(1, INTEGER);
+    do_throw("we should have an exception!");
+  }
+  catch(e) {
+    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+    exceptionCaught = true;
+  }
+  do_check_true(exceptionCaught);
+
+  // Check blob binding.
+  exceptionCaught = false;
+  try {
+    bp.bindBlobByIndex(1, BLOB, BLOB.length);
+    do_throw("we should have an exception!");
+  }
+  catch(e) {
+    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+    exceptionCaught = true;
+  }
+  do_check_true(exceptionCaught);
+
+  stmt.finalize();
+
+  // Run the next test.
+  run_next_test();
+}
+
+function test_bind_no_such_name()
+{
+  let stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (:foo)"
+  );
+
+  let array = stmt.newBindingParamsArray();
+  let bp = array.newBindingParams();
+
+  // Check variant binding.
+  let exceptionCaught = false;
+  try {
+    bp.bindByName("doesnotexist", INTEGER);
+    do_throw("we should have an exception!");
+  }
+  catch(e) {
+    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+    exceptionCaught = true;
+  }
+  do_check_true(exceptionCaught);
+
+  // Check blob binding.
+  exceptionCaught = false;
+  try {
+    bp.bindBlobByName("doesnotexist", BLOB, BLOB.length);
+    do_throw("we should have an exception!");
+  }
+  catch(e) {
+    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+    exceptionCaught = true;
+  }
+  do_check_true(exceptionCaught);
+
+  stmt.finalize();
+
+  // Run the next test.
+  run_next_test();
+}
+
+function test_bind_bogus_type_by_index()
+{
+  // We try to bind a JS Object here that should fail to bind.
+  let stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (blober) " +
+    "VALUES (?)"
+  );
+
+  // We get an error after calling executeAsync, not when we bind.
+  let array = stmt.newBindingParamsArray();
+  let bp = array.newBindingParams();
+  bp.bindByIndex(0, run_test);
+  array.addParams(bp);
+  stmt.bindParameters(array);
+
+  stmt.executeAsync({
+    _errorObtained: false,
+    handleResult: function(aResultSet)
+    {
+      do_throw("Unexpected call to handleResult!");
+    },
+    handleError: function(aError)
+    {
+      print("Error code " + aError.result + " with message '" +
+            aError.message + "' returned.");
+      this._errorObtained = true;
+      do_check_eq(aError.result, Ci.mozIStorageError.MISMATCH);
+    },
+    handleCompletion: function(aReason)
+    {
+      print("handleCompletion(" + aReason +
+            ") for test_bind_bogus_type_by_index");
+      do_check_eq(Ci.mozIStorageStatementCallback.REASON_ERROR, aReason);
+      do_check_true(this._errorObtained);
+
+      // Run the next test.
+      run_next_test();
+    }
+  });
+  stmt.finalize();
+}
+
+function test_bind_bogus_type_by_name()
+{
+  // We try to bind a JS Object here that should fail to bind.
+  let stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (blober) " +
+    "VALUES (:blob)"
+  );
+
+  // We get an error after calling executeAsync, not when we bind.
+  let array = stmt.newBindingParamsArray();
+  let bp = array.newBindingParams();
+  bp.bindByName("blob", run_test);
+  array.addParams(bp);
+  stmt.bindParameters(array);
+
+  stmt.executeAsync({
+    _errorObtained: false,
+    handleResult: function(aResultSet)
+    {
+      do_throw("Unexpected call to handleResult!");
+    },
+    handleError: function(aError)
+    {
+      print("Error code " + aError.result + " with message '" +
+            aError.message + "' returned.");
+      this._errorObtained = true;
+      do_check_eq(aError.result, Ci.mozIStorageError.MISMATCH);
+    },
+    handleCompletion: function(aReason)
+    {
+      print("handleCompletion(" + aReason +
+            ") for test_bind_bogus_type_by_name");
+      do_check_eq(Ci.mozIStorageStatementCallback.REASON_ERROR, aReason);
+      do_check_true(this._errorObtained);
+
+      // Run the next test.
+      run_next_test();
+    }
+  });
+  stmt.finalize();
+}
+
+function test_bind_params_already_locked()
+{
+  let stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (:int)"
+  );
+
+  let array = stmt.newBindingParamsArray();
+  let bp = array.newBindingParams();
+  bp.bindByName("int", INTEGER);
+  array.addParams(bp);
+
+  // We should get an error after we call addParams and try to bind again.
+  let exceptionCaught = false;
+  try {
+    bp.bindByName("int", INTEGER);
+    do_throw("we should have an exception!");
+  }
+  catch(e) {
+    do_check_eq(e.result, Cr.NS_ERROR_UNEXPECTED);
+    exceptionCaught = true;
+  }
+  do_check_true(exceptionCaught);
+
+  // Run the next test.
+  run_next_test();
+}
+
+function test_bind_params_array_already_locked()
+{
+  let stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (:int)"
+  );
+
+  let array = stmt.newBindingParamsArray();
+  let bp1 = array.newBindingParams();
+  bp1.bindByName("int", INTEGER);
+  array.addParams(bp1);
+  let bp2 = array.newBindingParams();
+  stmt.bindParameters(array);
+  bp2.bindByName("int", INTEGER);
+
+  // We should get an error after we have bound the array to the statement.
+  let exceptionCaught = false;
+  try {
+    array.addParams(bp2);
+    do_throw("we should have an exception!");
+  }
+  catch(e) {
+    do_check_eq(e.result, Cr.NS_ERROR_UNEXPECTED);
+    exceptionCaught = true;
+  }
+  do_check_true(exceptionCaught);
+
+  // Run the next test.
+  run_next_test();
+}
+
+function test_no_binding_params_from_locked_array()
+{
+  let stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (:int)"
+  );
+
+  let array = stmt.newBindingParamsArray();
+  let bp = array.newBindingParams();
+  bp.bindByName("int", INTEGER);
+  array.addParams(bp);
+  stmt.bindParameters(array);
+
+  // We should not be able to get a new BindingParams object after we have bound
+  // to the statement.
+  let exceptionCaught = false;
+  try {
+    bp = array.newBindingParams();
+    do_throw("we should have an exception!");
+  }
+  catch(e) {
+    do_check_eq(e.result, Cr.NS_ERROR_UNEXPECTED);
+    exceptionCaught = true;
+  }
+  do_check_true(exceptionCaught);
+
+  // Run the next test.
+  run_next_test();
+}
+
+function test_not_right_owning_array()
+{
+  let stmt = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (:int)"
+  );
+
+  let array1 = stmt.newBindingParamsArray();
+  let array2 = stmt.newBindingParamsArray();
+  let bp = array1.newBindingParams();
+  bp.bindByName("int", INTEGER);
+
+  // We should not be able to add bp to array2 since it was created from array1.
+  let exceptionCaught = false;
+  try {
+    array2.addParams(bp);
+    do_throw("we should have an exception!");
+  }
+  catch(e) {
+    do_check_eq(e.result, Cr.NS_ERROR_UNEXPECTED);
+    exceptionCaught = true;
+  }
+  do_check_true(exceptionCaught);
+
+  // Run the next test.
+  run_next_test();
+}
+
+function test_not_right_owning_statement()
+{
+  let stmt1 = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (:int)"
+  );
+  let stmt2 = getOpenedDatabase().createStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (:int)"
+  );
+
+  let array1 = stmt1.newBindingParamsArray();
+  let array2 = stmt2.newBindingParamsArray();
+  let bp = array1.newBindingParams();
+  bp.bindByName("int", INTEGER);
+  array1.addParams(bp);
+
+  // We should not be able to bind array1 since it was created from stmt1.
+  let exceptionCaught = false;
+  try {
+    stmt2.bindParameters(array1);
+    do_throw("we should have an exception!");
+  }
+  catch(e) {
+    do_check_eq(e.result, Cr.NS_ERROR_UNEXPECTED);
+    exceptionCaught = true;
+  }
+  do_check_true(exceptionCaught);
+
+  // Run the next test.
+  run_next_test();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Runner
+
 var tests =
 [
+  test_create_table,
   test_add_data,
   test_get_data,
   test_tuple_out_of_bounds,
   test_no_listener_works_on_success,
   test_no_listener_works_on_results,
   test_no_listener_works_on_error,
   test_partial_listener_works,
   test_immediate_cancellation,
   test_double_cancellation,
   test_double_execute,
   test_finalized_statement_does_not_crash,
+  test_bind_multiple_rows_by_index,
+  test_bind_multiple_rows_by_name,
+  test_bind_out_of_bounds,
+  test_bind_no_such_name,
+  test_bind_bogus_type_by_index,
+  test_bind_bogus_type_by_name,
+  test_bind_params_already_locked,
+  test_bind_params_array_already_locked,
+  test_no_binding_params_from_locked_array,
+  test_not_right_owning_array,
+  test_not_right_owning_statement,
 ];
+let index = 0;
+
+function run_next_test()
+{
+  if (index < tests.length) {
+    do_test_pending();
+    print("Running the next test: " + tests[index].name);
+    tests[index++]();
+  }
+
+  do_test_finished();
+}
 
 function run_test()
 {
   cleanup();
 
-  // This test has to run first and run to completion.  When it is done, it will
-  // run the rest of the tests.
-  test_create_table();
+  do_test_pending();
+  run_next_test();
 }