Bug 507414 - Add a createAsyncStatement API on mozIStorageConnection. r=sdwilsh, sr=vlad.
authorAndrew Sutherland <asutherland@asutherland.org>
Wed, 24 Mar 2010 00:32:40 -0700
changeset 39769 9f3391b5ca0ca5e00b39f440639dd7c9009e63cc
parent 39768 add0b286ced6621754d5d65ae67d576f2f8802d4
child 39770 c313d868c8ac223616b9891e032edd53e8194829
push idunknown
push userunknown
push dateunknown
reviewerssdwilsh, vlad
bugs507414
milestone1.9.3a4pre
Bug 507414 - Add a createAsyncStatement API on mozIStorageConnection. r=sdwilsh, sr=vlad.
storage/public/Makefile.in
storage/public/mozIStorageAsyncStatement.idl
storage/public/mozIStorageBaseStatement.idl
storage/public/mozIStorageConnection.idl
storage/public/mozIStorageError.idl
storage/public/mozIStorageFunction.idl
storage/public/mozIStorageStatement.idl
storage/public/mozIStorageValueArray.idl
storage/src/IStorageBindingParamsInternal.h
storage/src/Makefile.in
storage/src/StorageBaseStatementInternal.cpp
storage/src/StorageBaseStatementInternal.h
storage/src/mozStorageAsyncStatement.cpp
storage/src/mozStorageAsyncStatement.h
storage/src/mozStorageAsyncStatementExecution.cpp
storage/src/mozStorageAsyncStatementExecution.h
storage/src/mozStorageAsyncStatementJSHelper.cpp
storage/src/mozStorageAsyncStatementJSHelper.h
storage/src/mozStorageAsyncStatementParams.cpp
storage/src/mozStorageAsyncStatementParams.h
storage/src/mozStorageBindingParams.cpp
storage/src/mozStorageBindingParams.h
storage/src/mozStorageBindingParamsArray.cpp
storage/src/mozStorageBindingParamsArray.h
storage/src/mozStorageConnection.cpp
storage/src/mozStorageConnection.h
storage/src/mozStoragePrivateHelpers.cpp
storage/src/mozStoragePrivateHelpers.h
storage/src/mozStorageStatement.cpp
storage/src/mozStorageStatement.h
storage/src/mozStorageStatementData.h
storage/src/mozStorageStatementJSHelper.cpp
storage/src/mozStorageStatementParams.cpp
storage/src/mozStorageStatementRow.cpp
storage/src/mozStorageStatementWrapper.cpp
storage/style.txt
storage/test/Makefile.in
storage/test/test_true_async.cpp
storage/test/unit/head_storage.js
storage/test/unit/test_connection_executeAsync.js
storage/test/unit/test_statement_executeAsync.js
toolkit/components/places/src/nsFaviconService.cpp
toolkit/components/places/src/nsNavHistory.cpp
toolkit/components/places/src/nsNavHistory.h
--- a/storage/public/Makefile.in
+++ b/storage/public/Makefile.in
@@ -60,16 +60,18 @@ XPIDLSRCS = \
 	mozIStorageResultSet.idl \
 	mozIStorageRow.idl \
   mozIStorageError.idl \
   mozIStorageStatementCallback.idl \
   mozIStoragePendingStatement.idl \
   mozIStorageBindingParamsArray.idl \
   mozIStorageBindingParams.idl \
   mozIStorageCompletionCallback.idl \
+  mozIStorageBaseStatement.idl \
+  mozIStorageAsyncStatement.idl \
 	$(NULL)
 
 EXPORTS_NAMESPACES = mozilla
 
 EXPORTS = \
 	mozStorageHelper.h \
 	mozStorage.h \
 	$(NULL)
new file mode 100644
--- /dev/null
+++ b/storage/public/mozIStorageAsyncStatement.idl
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** 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 the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * 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 "mozIStorageBaseStatement.idl"
+
+/**
+ * An asynchronous SQL statement.  This differs from mozIStorageStatement by
+ * only being usable for asynchronous execution.  (mozIStorageStatement can
+ * be used for both synchronous and asynchronous purposes.)  This specialization
+ * for asynchronous operation allows us to avoid needing to acquire
+ * synchronization primitives also used by the asynchronous execution thread.
+ * In contrast, mozIStorageStatement may need to acquire the primitives and
+ * consequently can cause the main thread to lock for extended intervals while
+ * the asynchronous thread performs some long-running operation.
+ */
+[scriptable, uuid(2400f64d-2cb3-49a9-b01e-f03cacb8aa6e)]
+interface mozIStorageAsyncStatement : mozIStorageBaseStatement {
+  /*
+   * 'params' provides a magic JS helper that lets you assign parameters by
+   * name.  Unlike the helper on mozIStorageStatement, you cannot enumerate
+   * in order to find out what parameters are legal.
+   *
+   * This does not work for BLOBs.  You must use an explicit binding API for
+   * that.
+   *
+   * example:
+   *  stmt.params.foo = 1;
+   *  stmt.params["bar"] = 2;
+   *  let argName = "baz";
+   *  stmt.params[argName] = 3;
+   *
+   * readonly attribute nsIMagic params;
+   */
+};
new file mode 100644
--- /dev/null
+++ b/storage/public/mozIStorageBaseStatement.idl
@@ -0,0 +1,182 @@
+/* -*- 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 mozStorage.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
+ *   Shawn Wilsher <me@shawnwilsher.com>
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * 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"
+#include "mozIStorageBindingParams.idl"
+
+interface mozIStorageConnection;
+interface mozIStorageStatementCallback;
+interface mozIStoragePendingStatement;
+interface mozIStorageBindingParams;
+interface mozIStorageBindingParamsArray;
+
+/**
+ * The base interface for both pure asynchronous storage statements 
+ * (mozIStorageAsyncStatement) and 'classic' storage statements
+ * (mozIStorageStatement) that can be used for both synchronous and asynchronous
+ * purposes.
+ */
+[scriptable, uuid(da2ec336-fbbb-4ba1-9778-8c9825980d01)]
+interface mozIStorageBaseStatement : mozIStorageBindingParams {
+  /**
+   * Finalizes a statement so you can successfully close a database connection.
+   * Once a statement has been finalized it can no longer be used for any
+   * purpose.
+   * 
+   * Statements are implicitly finalized when their reference counts hits zero.
+   * If you are a native (C++) caller this is accomplished by setting all of
+   * your nsCOMPtr instances to be NULL.  If you are operating from JavaScript
+   * code then you cannot rely on this behavior because of the involvement of
+   * garbage collection.
+   *
+   * When finalizing an asynchronous statement you do not need to worry about
+   * whether the statement has actually been executed by the asynchronous
+   * thread; you just need to call finalize after your last call to executeAsync
+   * involving the statement.  However, you do need to use asyncClose instead of
+   * close on the connection if any statements have been used asynchronously.
+   */
+  void finalize();
+
+  /**
+   * Bind the given value at the given numeric index.
+   *
+   * @param aParamIndex
+   *        0-based index, 0 corresponding to the first numbered argument or
+   *        "?1".
+   * @param aValue
+   *        Argument value.
+   * @param aValueSize
+   *        Length of aValue in bytes.
+   * @{
+   */
+  [deprecated] void bindUTF8StringParameter(in unsigned long aParamIndex,
+                                            in AUTF8String aValue);
+  [deprecated] void bindStringParameter(in unsigned long aParamIndex,
+                                        in AString aValue);
+  [deprecated] void bindDoubleParameter(in unsigned long aParamIndex,
+                                        in double aValue);
+  [deprecated] void bindInt32Parameter(in unsigned long aParamIndex,
+                                       in long aValue);
+  [deprecated] void bindInt64Parameter(in unsigned long aParamIndex,
+                                       in long long aValue);
+  [deprecated] void bindNullParameter(in unsigned long aParamIndex);
+  [deprecated] 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.
+   *
+   * @note This is only works on statements being used asynchronously.
+   */
+  void bindParameters(in mozIStorageBindingParamsArray aParameters);
+
+  /**
+   * Creates a new mozIStorageBindingParamsArray that can be used to bind
+   * multiple sets of data to a statement with bindParameters.
+   *
+   * @return a mozIStorageBindingParamsArray that multiple sets of parameters
+   *         can be bound to.
+   *
+   * @note This is only useful for statements being used asynchronously.
+   */
+  mozIStorageBindingParamsArray newBindingParamsArray();
+
+  /**
+   * Execute a query asynchronously using any currently bound parameters.  This
+   * statement can be reused immediately, and reset does not need to be called.
+   *
+   * @note If you have any custom defined functions, they must be re-entrant
+   *       since they can be called on multiple threads.
+   *
+   * @param aCallback [optional]
+   *        The callback object that will be notified of progress, errors, and
+   *        completion.
+   * @return an object that can be used to cancel the statements execution.
+   */
+  mozIStoragePendingStatement executeAsync(
+    [optional] in mozIStorageStatementCallback aCallback
+  );
+
+  /**
+   * The statement is not usable, either because it failed to initialize or
+   * was explicitly finalized.
+   */
+  const long MOZ_STORAGE_STATEMENT_INVALID = 0;
+  /**
+   * The statement is usable.
+   */
+  const long MOZ_STORAGE_STATEMENT_READY = 1;
+  /**
+   * Indicates that the statement is executing and the row getters may be used.
+   *
+   * @note This is only relevant for mozIStorageStatement instances being used
+   *       in a synchronous fashion.
+   */
+  const long MOZ_STORAGE_STATEMENT_EXECUTING = 2;
+
+  /**
+   * Find out whether the statement is usable (has not been finalized).
+   */
+  readonly attribute long state;
+
+  /**
+   * Escape a string for SQL LIKE search.
+   *
+   * @note Consumers will have to use same escape char when doing statements
+   *       such as:   ...LIKE '?1' ESCAPE '/'...
+   *
+   * @param aValue
+   *        The string to escape for SQL LIKE.
+   * @param aEscapeChar
+   *        The escape character.
+   * @return an AString of an escaped version of aValue
+   *         (%, _ and the escape char are escaped with the escape char)
+   *         For example, we will convert "foo/bar_baz%20cheese" 
+   *         into "foo//bar/_baz/%20cheese" (if the escape char is '/').
+   */
+  AString escapeStringForLIKE(in AString aValue, in wchar aEscapeChar);
+};
--- a/storage/public/mozIStorageConnection.idl
+++ b/storage/public/mozIStorageConnection.idl
@@ -40,35 +40,36 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface mozIStorageAggregateFunction;
 interface mozIStorageCompletionCallback;
 interface mozIStorageFunction;
 interface mozIStorageProgressHandler;
+interface mozIStorageBaseStatement;
 interface mozIStorageStatement;
+interface mozIStorageAsyncStatement;
 interface mozIStorageStatementCallback;
 interface mozIStoragePendingStatement;
 interface nsIFile;
 
 /**
  * mozIStorageConnection represents a database connection attached to
  * a specific file or to the in-memory data storage.  It is the
  * primary interface for interacting with a database, including
  * creating prepared statements, executing SQL, and examining database
  * errors.
  *
  * @threadsafe
  */
-[scriptable, uuid(5a06b207-1977-47d4-b140-271cb851bb26)]
+[scriptable, uuid(32b43ec6-e905-4070-9cfe-370c45f7c1bf)]
 interface mozIStorageConnection : nsISupports {
-  /*
-   * Initialization and status
-   */
+  //////////////////////////////////////////////////////////////////////////////
+  //// Initialization and status
 
   /**
    * Closes a database connection.  Callers must finalize all statements created
    * for this connection prior to calling this method.  It is illegal to use
    * call this method if any asynchronous statements have been executed on this
    * connection.
    *
    * @throws NS_ERROR_UNEXPECTED
@@ -120,33 +121,48 @@ interface mozIStorageConnection : nsISup
   readonly attribute AUTF8String lastErrorString;
 
   /**
    * The schema version of the database.  This should not be used until the 
    * database is ready.  The schema will be reported as zero if it is not set.
    */
   attribute long schemaVersion;
 
-  /*
-   * Statement creation
-   */
+  //////////////////////////////////////////////////////////////////////////////
+  //// Statement creation
 
   /**
    * Create a mozIStorageStatement for the given SQL expression.  The
    * expression may use ? to indicate sequential numbered arguments,
    * ?1, ?2 etc. to indicate specific numbered arguments or :name and 
    * $var to indicate named arguments.
    *
-   * @param aSQLStatement  The SQL statement to execute
-   *
-   * @returns a new mozIStorageStatement
+   * @param aSQLStatement
+   *        The SQL statement to execute.
+   * @return a new mozIStorageStatement
    */
   mozIStorageStatement createStatement(in AUTF8String aSQLStatement);
 
   /**
+   * Create an asynchronous statement (mozIStorageAsyncStatement) for the given
+   * SQL expression.  An asynchronous statement can only be used to dispatch
+   * asynchronous requests to the asynchronous execution thread and cannot be
+   * used to take any synchronous actions on the database.
+   *
+   * The expression may use ? to indicate sequential numbered arguments,
+   * ?1, ?2 etc. to indicate specific numbered arguments or :name and
+   * $var to indicate named arguments.
+   *
+   * @param aSQLStatement
+   *        The SQL statement to execute.
+   * @return a new mozIStorageAsyncStatement
+   */
+  mozIStorageAsyncStatement createAsyncStatement(in AUTF8String aSQLStatement);
+
+  /**
    * Execute a SQL expression, expecting no arguments.
    *
    * @param aSQLStatement  The SQL statement to execute
    */
   void executeSimpleSQL(in AUTF8String aSQLStatement);
 
   /**
    * Execute an array of queries created with this connection asynchronously
@@ -160,44 +176,43 @@ interface mozIStorageConnection : nsISup
    * @param aStatements
    *        The array of statements to execute asynchronously, in the order they
    *        are given in the array.
    * @param aNumStatements
    *        The number of statements in aStatements.
    * @param aCallback [optional]
    *        The callback object that will be notified of progress, errors, and
    *        completion.
-   * @returns an object that can be used to cancel the statements execution.
+   * @return an object that can be used to cancel the statements execution.
    */
   mozIStoragePendingStatement executeAsync(
-    [array, size_is(aNumStatements)] in mozIStorageStatement aStatements,
+    [array, size_is(aNumStatements)] in mozIStorageBaseStatement aStatements,
     in unsigned long aNumStatements,
     [optional] in mozIStorageStatementCallback aCallback
   );
 
   /**
    * Check if the given table exists.
    *
-   * @param aTableName   The table to check
-   * @returns TRUE if table exists, FALSE otherwise.
+   * @param aTableName
+   *        The table to check
+   * @return TRUE if table exists, FALSE otherwise.
    */
   boolean tableExists(in AUTF8String aTableName);
 
   /**
    * Check if the given index exists.
    *
    * @param aIndexName   The index to check
-   * @returns TRUE if the index exists, FALSE otherwise.
+   * @return TRUE if the index exists, FALSE otherwise.
    */
   boolean indexExists(in AUTF8String aIndexName);
 
-
-  /*
-   * Transactions
-   */
+  //////////////////////////////////////////////////////////////////////////////
+  //// Transactions
 
   /**
    * Returns true if a transaction is active on this connection.
    */
   readonly attribute boolean transactionInProgress;
 
   /**
    * Begin a new transaction.  sqlite default transactions are deferred.
@@ -220,92 +235,97 @@ interface mozIStorageConnection : nsISup
   void commitTransaction();
 
   /**
    * Rolls back the current transaction.  If no transaction is active,
    * @throws NS_ERROR_STORAGE_NO_TRANSACTION.
    */
   void rollbackTransaction();
 
-  /*
-   * Tables
-   */
+  //////////////////////////////////////////////////////////////////////////////
+  //// Tables
 
   /**
    * Create the table with the given name and schema.
    *
    * If the table already exists, NS_ERROR_FAILURE is thrown.
    * (XXX at some point in the future it will check if the schema is
    * the same as what is specified, but that doesn't happen currently.)
    *
-   * @param aTableName the table name to be created, consisting of
-   * [A-Za-z0-9_], and beginning with a letter.
+   * @param aTableName
+   *        The table name to be created, consisting of [A-Za-z0-9_], and
+   *        beginning with a letter.
+   * @param aTableSchema
+   *        The schema of the table; what would normally go between the parens
+   *        in a CREATE TABLE statement: e.g., "foo  INTEGER, bar STRING".
    *
-   * @param aTableSchema the schema of the table; what would normally
-   * go between the parens in a CREATE TABLE statement: e.g., "foo
-   * INTEGER, bar STRING".
-   *
-   * @throws NS_ERROR_FAILURE if the table already exists or could not
-   * be created for any other reason.
-   *
+   * @throws NS_ERROR_FAILURE
+   *         If the table already exists or could not be created for any other
+   *         reason.
    */
   void createTable(in string aTableName,
                    in string aTableSchema);
 
-  /*
-   * Functions
-   */
+  //////////////////////////////////////////////////////////////////////////////
+  //// Functions
 
   /**
    * Create a new SQL function.  If you use your connection on multiple threads,
    * your function needs to be threadsafe, or it should only be called on one
    * thread.
    *
-   * @param aFunctionName  The name of function to create, as seen in SQL.
-   * @param aNumArguments  The number of arguments the function takes. Pass
-   *                       -1 for variable-argument functions.
-   * @param aFunction      The instance of mozIStorageFunction, which implements
-   *                       the function in question.
+   * @param aFunctionName
+   *        The name of function to create, as seen in SQL.
+   * @param aNumArguments
+   *        The number of arguments the function takes. Pass -1 for
+   *        variable-argument functions.
+   * @param aFunction
+   *        The instance of mozIStorageFunction, which implements the function
+   *        in question.
    */
   void createFunction(in AUTF8String aFunctionName,
                       in long aNumArguments,
                       in mozIStorageFunction aFunction);
 
   /**
    * Create a new SQL aggregate function.  If you use your connection on
    * multiple threads, your function needs to be threadsafe, or it should only
    * be called on one thread.
    *
-   * @param aFunctionName  The name of aggregate function to create, as seen
-   *                       in SQL.
-   * @param aNumArguments  The number of arguments the function takes. Pass
-   *                       -1 for variable-argument functions.
-   * @param aFunction      The instance of mozIStorageAggreagteFunction,
-   *                       which implements the function in question.
+   * @param aFunctionName
+   *        The name of aggregate function to create, as seen in SQL.
+   * @param aNumArguments
+   *        The number of arguments the function takes. Pass -1 for
+   *        variable-argument functions.
+   * @param aFunction
+   *        The instance of mozIStorageAggreagteFunction, which implements the
+   *        function in question.
    */
   void createAggregateFunction(in AUTF8String aFunctionName,
                                in long aNumArguments,
                                in mozIStorageAggregateFunction aFunction);
   /**
    * Delete custom SQL function (simple or aggregate one).
    *
-   * @param aFunctionName  The name of function to remove.
+   * @param aFunctionName
+   *        The name of function to remove.
    */
   void removeFunction(in AUTF8String aFunctionName);
 
   /**
    * Sets a progress handler. Only one handler can be registered at a time.
    * If you need more than one, you need to chain them yourself.  This progress
    * handler should be threadsafe if you use this connection object on more than
    * one thread.
    *
-   * @param aGranularity   The number of SQL virtual machine steps between
-   *                       progress handler callbacks.
-   * @param aHandler       The instance of mozIStorageProgressHandler.
-   *
+   * @param aGranularity
+   *        The number of SQL virtual machine steps between progress handler
+   *        callbacks.
+   * @param aHandler
+   *        The instance of mozIStorageProgressHandler.
    * @return previous registered handler.
    */
   mozIStorageProgressHandler setProgressHandler(in PRInt32 aGranularity,
                                                 in mozIStorageProgressHandler aHandler);
 
   /**
    * Remove a progress handler.
    *
--- a/storage/public/mozIStorageError.idl
+++ b/storage/public/mozIStorageError.idl
@@ -154,16 +154,22 @@ interface mozIStorageError : nsISupports
   const long AUTH = 23;
 
   /**
    * Auxiliary database format error.
    */
   const long FORMAT = 24;
 
   /**
+   * Attempt to bind a parameter using an out-of-range index or nonexistent
+   * named parameter name.
+   */
+  const long RANGE = 25;
+
+  /**
    * File opened that is not a database file.
    */
   const long NOTADB = 26;
 
 
   /**
    * Indicates what type of error occurred.
    */
--- a/storage/public/mozIStorageFunction.idl
+++ b/storage/public/mozIStorageFunction.idl
@@ -34,18 +34,19 @@
  * 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"
 
+#include "mozIStorageValueArray.idl"
+
 interface mozIStorageConnection;
-interface mozIStorageValueArray;
 interface nsIArray;
 interface nsIVariant;
 
 /**
  * mozIStorageFunction is to be implemented by storage consumers that
  * wish to receive callbacks during the request execution.
  *
  * SQL can apply functions to values from tables. Examples of
--- a/storage/public/mozIStorageStatement.idl
+++ b/storage/public/mozIStorageStatement.idl
@@ -17,48 +17,43 @@
  *
  * The Initial Developer of the Original Code is
  *  Oracle Corporation
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
+ *   Shawn Wilsher <me@shawnwilsher.com>
+ *   Andrew Sutherland <asutherland@asutherland.org>
  *
  * 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"
-#include "mozIStorageValueArray.idl"
+#include "mozIStorageBaseStatement.idl"
 
-interface mozIStorageConnection;
-interface mozIStorageStatementCallback;
-interface mozIStoragePendingStatement;
-interface mozIStorageBindingParamsArray;
+[ptr] native octetPtr(PRUint8);
 
-[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();
-
+/**
+ * A SQL statement that can be used for both synchronous and asynchronous
+ * purposes.
+ */
+[scriptable, uuid(57ec7be1-36cf-4510-b938-7d1c9ee8cec5)]
+interface mozIStorageStatement : mozIStorageBaseStatement {
   /**
    * Create a clone of this statement, by initializing a new statement
    * with the same connection and same SQL statement as this one.  It
    * does not preserve statement state; that is, if a statement is
    * being executed when it is cloned, the new statement will not be
    * executing.
    */
   mozIStorageStatement clone();
@@ -74,114 +69,82 @@ interface mozIStorageStatement : mozISto
   AUTF8String getParameterName(in unsigned long aParamIndex);
 
   /**
    * Returns the index of the named parameter.
    *
    * @param aName
    *        The name of the parameter you want the index for.  This does not
    *        include the leading ':'.
-   * @returns the index of the named parameter.
+   * @return the index of the named parameter.
    */
   unsigned long getParameterIndex(in AUTF8String aName);
 
   /**
    * Number of columns returned
    */
   readonly attribute unsigned long columnCount;
 
   /**
    * Name of nth column
    */
   AUTF8String getColumnName(in unsigned long aColumnIndex);
 
   /**
    * Obtains the index of the column with the specified name.
    *
-   * @param aName The name of the column.
+   * @param aName
+   *        The name of the column.
    * @return The index of the column with the specified name.
    */
   unsigned long getColumnIndex(in AUTF8String aName);
 
   /**
    * Obtains the declared column type of a prepared statement.
    *
-   * @param aParamIndex The zero-based index of the column who's declared type
-   *                    we are interested in.
-   * @returns the declared index type.
+   * @param aParamIndex
+   *        The zero-based index of the column who's declared type we are
+   *        interested in.
+   * @return the declared index type.
    */
   AUTF8String getColumnDecltype(in unsigned long aParamIndex);
 
   /**
    * Reset parameters/statement execution
    */
   void reset();
 
   /**
-   * Bind the given value to the parameter at aParamIndex. Index 0
-   * denotes the first numbered argument or ?1.
-   */
-  void bindUTF8StringParameter(in unsigned long aParamIndex,
-                               in AUTF8String aValue);
-  void bindStringParameter(in unsigned long aParamIndex, in AString aValue);
-  void bindDoubleParameter(in unsigned long aParamIndex, in double aValue);
-  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 executeStep() once, and then calling reset().
    *
    * Error and last insert info, etc. are available from
    * the mozStorageConnection.
    */
   void execute();
 
   /**
    * Execute a query, using any currently-bound parameters.  Reset
    * must be called on the statement after the last call of
    * executeStep.
    *
-   * @returns a boolean indicating whether there are more rows or not;
-   * row data may be accessed using mozIStorageValueArray methods on
-   * the statement.
-   *
+   * @return a boolean indicating whether there are more rows or not;
+   *         row data may be accessed using mozIStorageValueArray methods on
+   *         the statement.
    */
   boolean executeStep();
 
   /**
    * Execute a query, using any currently-bound parameters.  Reset is called
    * when no more data is returned.  This method is only available to JavaScript
    * consumers.
    *
    * @deprecated As of Mozilla 1.9.2 in favor of executeStep().
    *
-   * @returns a boolean indicating whether there are more rows or not.
+   * @return a boolean indicating whether there are more rows or not.
    *
    * [deprecated] boolean step();
    */
 
   /**
    * Obtains the current list of named parameters, which are settable.  This
    * property is only available to JavaScript consumers.
    *
@@ -190,48 +153,157 @@ interface mozIStorageStatement : mozISto
 
   /**
    * Obtains the current row, with access to all the data members by name.  This
    * property is only available to JavaScript consumers.
    *
    * readonly attribute mozIStorageStatementRow row;
    */
 
+  //////////////////////////////////////////////////////////////////////////////
+  //// Copied contents of mozIStorageValueArray
+
   /**
-   * Execute a query asynchronously using any currently bound parameters.  This
-   * statement can be reused immediately, and reset does not need to be called.
-   *
-   * Note:  If you have any custom defined functions, they must be re-entrant
-   *        since they can be called on multiple threads.
+   * These type values are returned by getTypeOfIndex
+   * to indicate what type of value is present at
+   * a given column.
+   */
+  const long VALUE_TYPE_NULL = 0;
+  const long VALUE_TYPE_INTEGER = 1;
+  const long VALUE_TYPE_FLOAT = 2;
+  const long VALUE_TYPE_TEXT = 3;
+  const long VALUE_TYPE_BLOB = 4;
+
+  /**
+   * The number of entries in the array (each corresponding to a column in the
+   * database row)
+   */
+  readonly attribute unsigned long numEntries;
+
+  /**
+   * Indicate the data type of the current result row for the the given column.
+   * SQLite will perform type conversion if you ask for a value as a different
+   * type than it is stored as.
    *
-   * @param aCallback [optional]
-   *        The callback object that will be notified of progress, errors, and
-   *        completion.
-   * @returns an object that can be used to cancel the statements execution.
+   * @param aIndex
+   *        0-based column index.
+   * @return The type of the value at the given column index; one of
+   *         VALUE_TYPE_NULL, VALUE_TYPE_INTEGER, VALUE_TYPE_FLOAT,
+   *         VALUE_TYPE_TEXT, VALUE_TYPE_BLOB.
+   */
+  long getTypeOfIndex(in unsigned long aIndex);
+
+  /**
+   * Retrieve the contents of a column from the current result row as an
+   * integer.
+   *
+   * @param aIndex
+   *        0-based colummn index.
+   * @return Column value interpreted as an integer per type conversion rules.
+   * @{
    */
-  mozIStoragePendingStatement executeAsync(
-    [optional] in mozIStorageStatementCallback aCallback
-  );
+  long getInt32(in unsigned long aIndex);
+  long long getInt64(in unsigned long aIndex);
+  /** @} */
+  /**
+   * Retrieve the contents of a column from the current result row as a
+   * floating point double.
+   *
+   * @param aIndex
+   *        0-based colummn index.
+   * @return Column value interpreted as a double per type conversion rules.
+   */
+  double getDouble(in unsigned long aIndex);
+  /**
+   * Retrieve the contents of a column from the current result row as a
+   * string.
+   *
+   * @param aIndex
+   *        0-based colummn index.
+   * @return The value for the result column interpreted as a string.  If the
+   *         stored value was NULL, you will get an empty string with IsVoid set
+   *         to distinguish it from an explicitly set empty string.
+   * @{
+   */
+  AUTF8String getUTF8String(in unsigned long aIndex);
+  AString getString(in unsigned long aIndex);
+  /** @} */
 
   /**
-   * The current state.  Row getters are only valid while
-   * the statement is in the "executing" state.
+   * Retrieve the contents of a column from the current result row as a
+   * blob.
+   *
+   * @param aIndex
+   *        0-based colummn index.
+   * @param[out] aDataSize
+   *             The number of bytes in the blob.
+   * @param[out] aData
+   *             The contents of the BLOB.  This will be NULL if aDataSize == 0.
    */
-  const long MOZ_STORAGE_STATEMENT_INVALID = 0;
-  const long MOZ_STORAGE_STATEMENT_READY = 1;
-  const long MOZ_STORAGE_STATEMENT_EXECUTING = 2;
-
-  readonly attribute long state;
+  void getBlob(in unsigned long aIndex, out unsigned long aDataSize, [array,size_is(aDataSize)] out octet aData);
+  /**
+   * Check whether the given column in the current result row is NULL.
+   *
+   * @param aIndex
+   *        0-based colummn index.
+   * @return true if the value for the result column is null.
+   */
+  boolean getIsNull(in unsigned long aIndex);
 
   /**
-   * Escape a string for SQL LIKE search.
+   * Returns a shared string pointer
+   */
+  [noscript] void getSharedUTF8String(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out string aResult);
+  [noscript] void getSharedString(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out wstring aResult);
+  [noscript] void getSharedBlob(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out octetPtr aResult);
+
+%{C++
+  /**
+   * Getters for native code that return their values as
+   * the return type, for convenience and sanity.
    *
-   * @param     aValue the string to escape for SQL LIKE 
-   * @param     aEscapeChar the escape character
-   * @returns   an AString of an escaped version of aValue
-   *            (%, _ and the escape char are escaped with the escape char)
-   *            For example, we will convert "foo/bar_baz%20cheese" 
-   *            into "foo//bar/_baz/%20cheese" (if the escape char is '/').
-   * @note      consumers will have to use same escape char
-   *            when doing statements such as:   ...LIKE '?1' ESCAPE '/'...
+   * Not virtual; no vtable bloat.
    */
-  AString escapeStringForLIKE(in AString aValue, in wchar aEscapeChar);
+
+  inline PRInt32 AsInt32(PRUint32 idx) {
+    PRInt32 v;
+    GetInt32(idx, &v);
+    return v;
+  }
+
+  inline PRInt64 AsInt64(PRUint32 idx) {
+    PRInt64 v;
+    GetInt64(idx, &v);
+    return v;
+  }
+
+  inline double AsDouble(PRUint32 idx) {
+    double v;
+    GetDouble(idx, &v);
+    return v;
+  }
+
+  inline const char* AsSharedUTF8String(PRUint32 idx, PRUint32 *len) {
+    const char *str = nsnull;
+    GetSharedUTF8String(idx, len, &str);
+    return str;
+  }
+
+  inline const PRUnichar* AsSharedWString(PRUint32 idx, PRUint32 *len) {
+    const PRUnichar *str = nsnull;
+    GetSharedString(idx, len, &str);
+    return str;
+  }
+
+  inline const PRUint8* AsSharedBlob(PRUint32 idx, PRUint32 *len) {
+    const PRUint8 *blob = nsnull;
+    GetSharedBlob(idx, len, &blob);
+    return blob;
+  }
+
+  inline PRBool IsNull(PRUint32 idx) {
+    PRBool b = PR_FALSE;
+    GetIsNull(idx, &b);
+    return b;
+  }
+
+%}
 };
--- a/storage/public/mozIStorageValueArray.idl
+++ b/storage/public/mozIStorageValueArray.idl
@@ -36,18 +36,18 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 [ptr] native octetPtr(PRUint8);
 
 /**
- * mozIStorageValueArray wraps an array of SQL values,
- * such as a single database row.
+ * mozIStorageValueArray wraps an array of SQL values, such as a single database
+ * row.
  */
 [scriptable, uuid(07b5b93e-113c-4150-863c-d247b003a55d)]
 interface mozIStorageValueArray : nsISupports {
   /**
    * These type values are returned by getTypeOfIndex
    * to indicate what type of value is present at
    * a given column.
    */
new file mode 100644
--- /dev/null
+++ b/storage/src/IStorageBindingParamsInternal.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** 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 the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * 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 mozilla_storage_IStorageBindingParamsInternal_h_
+#define mozilla_storage_IStorageBindingParamsInternal_h_
+
+#include "nsISupports.h"
+
+struct sqlite3_stmt;
+class mozIStorageError;
+
+namespace mozilla {
+namespace storage {
+
+#define ISTORAGEBINDINGPARAMSINTERNAL_IID \
+  {0x4c43d33a, 0xc620, 0x41b8, {0xba, 0x1d, 0x50, 0xc5, 0xb1, 0xe9, 0x1a, 0x04}}
+
+/**
+ * Implementation-only interface for mozIStorageBindingParams.  This defines the
+ * set of methods required by the asynchronous execution code in order to
+ * consume the contents stored in mozIStorageBindingParams instances.
+ */
+class IStorageBindingParamsInternal : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(ISTORAGEBINDINGPARAMSINTERNAL_IID)
+
+  /**
+   * Binds our stored data to the statement.
+   *
+   * @param aStatement
+   *        The statement to bind our data to.
+   * @return nsnull on success, or a mozIStorageError object if an error
+   *         occurred.
+   */
+  virtual already_AddRefed<mozIStorageError> bind(sqlite3_stmt *aStatement) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IStorageBindingParamsInternal,
+                              ISTORAGEBINDINGPARAMSINTERNAL_IID)
+
+#define NS_DECL_ISTORAGEBINDINGPARAMSINTERNAL \
+  already_AddRefed<mozIStorageError> bind(sqlite3_stmt *aStatement);
+
+} // storage
+} // mozilla
+
+#endif // mozilla_storage_IStorageBindingParamsInternal_h_
--- a/storage/src/Makefile.in
+++ b/storage/src/Makefile.in
@@ -70,16 +70,20 @@ CPPSRCS = \
   mozStorageRow.cpp \
   mozStorageResultSet.cpp \
   mozStorageError.cpp \
   mozStorageAsyncStatementExecution.cpp \
   mozStorageStatementJSHelper.cpp \
   mozStoragePrivateHelpers.cpp \
   mozStorageBindingParamsArray.cpp \
   mozStorageBindingParams.cpp \
+  mozStorageAsyncStatement.cpp \
+  mozStorageAsyncStatementJSHelper.cpp \
+  mozStorageAsyncStatementParams.cpp \
+  StorageBaseStatementInternal.cpp \
   SQLCollations.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.
new file mode 100644
--- /dev/null
+++ b/storage/src/StorageBaseStatementInternal.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** 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 the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * 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 "StorageBaseStatementInternal.h"
+
+#include "nsProxyRelease.h"
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozStorageStatementData.h"
+#include "mozStorageAsyncStatementExecution.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Local Classes
+
+/**
+ * Used to finalize an asynchronous statement on the background thread.
+ */
+class AsyncStatementFinalizer : public nsRunnable
+{
+public:
+  /**
+   * Constructor for the event.
+   *
+   * @param aStatement
+   *        We need the AsyncStatement to be able to get at the sqlite3_stmt;
+   *        we only access/create it on the async thread.
+   * @param aConnection
+   *        We need the connection to know what thread to release the statement
+   *        on.  We release the statement on that thread since releasing the
+   *        statement might end up releasing the connection too.
+   */
+  AsyncStatementFinalizer(StorageBaseStatementInternal *aStatement,
+                          Connection *aConnection)
+  : mStatement(aStatement)
+  , mConnection(aConnection)
+  {
+  }
+
+  NS_IMETHOD Run()
+  {
+    mStatement->internalAsyncFinalize();
+    (void)::NS_ProxyRelease(mConnection->threadOpenedOn, mStatement);
+    return NS_OK;
+  }
+private:
+  // It is vital that this remain an nsCOMPtr for NS_ProxyRelease's benefit.
+  nsCOMPtr<StorageBaseStatementInternal> mStatement;
+  nsRefPtr<Connection> mConnection;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+StorageBaseStatementInternal::StorageBaseStatementInternal()
+: mAsyncStatement(NULL)
+{
+}
+
+void
+StorageBaseStatementInternal::asyncFinalize()
+{
+  nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget();
+  if (!target) {
+    // If we cannot get the background thread, we have to assume it has been
+    // shutdown (or is in the process of doing so).  As a result, we should
+    // just finalize it here and now.
+    internalAsyncFinalize();
+  }
+  else {
+    nsCOMPtr<nsIRunnable> event =
+      new AsyncStatementFinalizer(this, mDBConnection);
+
+    // If the dispatching did not go as planned, finalize now.
+    if (!event ||
+        NS_FAILED(target->Dispatch(event, NS_DISPATCH_NORMAL))) {
+      internalAsyncFinalize();
+    }
+  }
+}
+
+void
+StorageBaseStatementInternal::internalAsyncFinalize()
+{
+  if (mAsyncStatement) {
+    (void)::sqlite3_finalize(mAsyncStatement);
+    mAsyncStatement = nsnull;
+  }
+}
+
+NS_IMETHODIMP
+StorageBaseStatementInternal::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
+StorageBaseStatementInternal::ExecuteAsync(
+  mozIStorageStatementCallback *aCallback,
+  mozIStoragePendingStatement **_stmt
+)
+{
+  // We used to call Connection::ExecuteAsync but it takes a
+  // mozIStorageBaseStatement signature because it is also a public API.  Since
+  // our 'this' has no static concept of mozIStorageBaseStatement and Connection
+  // would just QI it back across to a StorageBaseStatementInternal and the
+  // actual logic is very simple, we now roll our own.
+  nsTArray<StatementData> stmts(1);
+  StatementData data;
+  nsresult rv = getAsynchronousStatementData(data);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(stmts.AppendElement(data), NS_ERROR_OUT_OF_MEMORY);
+
+  // Dispatch to the background
+  return AsyncExecuteStatements::execute(stmts, mDBConnection, aCallback,
+                                         _stmt);
+}
+
+NS_IMETHODIMP
+StorageBaseStatementInternal::EscapeStringForLIKE(
+  const nsAString &aValue,
+  const PRUnichar aEscapeChar,
+  nsAString &_escapedString
+)
+{
+  const PRUnichar MATCH_ALL('%');
+  const PRUnichar MATCH_ONE('_');
+
+  _escapedString.Truncate(0);
+
+  for (PRUint32 i = 0; i < aValue.Length(); i++) {
+    if (aValue[i] == aEscapeChar || aValue[i] == MATCH_ALL ||
+        aValue[i] == MATCH_ONE) {
+      _escapedString += aEscapeChar;
+    }
+    _escapedString += aValue[i];
+  }
+  return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/storage/src/StorageBaseStatementInternal.h
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** 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 the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * 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 mozilla_storage_StorageBaseStatementInternal_h_
+#define mozilla_storage_StorageBaseStatementInternal_h_
+
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+
+struct sqlite3_stmt;
+class mozIStorageError;
+class mozIStorageBindingParamsArray;
+class mozIStorageBindingParams;
+class mozIStorageStatementCallback;
+class mozIStoragePendingStatement;
+
+namespace mozilla {
+namespace storage {
+
+#define STORAGEBASESTATEMENTINTERNAL_IID \
+  {0xd18856c9, 0xbf07, 0x4ae2, {0x94, 0x5b, 0x1a, 0xdd, 0x49, 0x19, 0x55, 0x2a}}
+
+class Connection;
+struct StatementData;
+
+class AsyncStatementFinalizer;
+
+/**
+ * Implementation-only interface and shared logix mix-in corresponding to
+ * mozIStorageBaseStatement.  Both Statement and AsyncStatement inherit from
+ * this. The interface aspect makes them look the same to implementation innards
+ * that aren't publicly accessible.  The mix-in avoids code duplication in
+ * common implementations of mozIStorageBaseStatement, albeit with some minor
+ * performance/space overhead because we have to use defines to officially
+ * implement the methods on Statement/AsyncStatement (and proxy to this base
+ * class.)
+ */
+class StorageBaseStatementInternal : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(STORAGEBASESTATEMENTINTERNAL_IID)
+
+  /**
+   * @return the connection that this statement belongs to.
+   */
+  Connection *getOwner()
+  {
+    return mDBConnection;
+  }
+
+  /**
+   * Return the asynchronous statement, creating it if required.
+   *
+   * This is for use by the asynchronous execution code for StatementData
+   * created by AsyncStatements.  Statement internally uses this method to
+   * prepopulate StatementData with the sqlite3_stmt.
+   *
+   * @param[out] stmt
+   *             The sqlite3_stmt for asynchronous use.
+   * @return The SQLite result code for creating the statement if created,
+   *         SQLITE_OK if creation was not required.
+   */
+  virtual int getAsyncStatement(sqlite3_stmt **_stmt) = 0;
+
+  /**
+   * Obtains the StatementData needed for asynchronous execution.
+   *
+   * This is for use by Connection to retrieve StatementData from statements
+   * when executeAsync is invoked.
+   *
+   * @param[out] _data
+   *             A reference to a StatementData object that will be populated
+   *             upon successful execution of this method.
+   * @return NS_OK if we were able to assemble the data, failure otherwise.
+   */
+  virtual nsresult getAsynchronousStatementData(StatementData &_data) = 0;
+
+  /**
+   * Construct a new BindingParams to be owned by the provided binding params
+   * array.  This method exists so that BindingParamsArray does not need
+   * factory logic to determine what type of BindingParams to instantiate.
+   *
+   * @param aOwner
+   *        The binding params array to own the newly created binding params.
+   * @return The new mozIStorageBindingParams instance appropriate to the
+   *         underlying statement type.
+   */
+  virtual already_AddRefed<mozIStorageBindingParams> newBindingParams(
+    mozIStorageBindingParamsArray *aOwner
+  ) = 0;
+
+protected: // mix-in bits are protected
+  StorageBaseStatementInternal();
+
+  nsRefPtr<Connection> mDBConnection;
+
+  /**
+   * Our asynchronous statement.
+   *
+   * For Statement this is populated by the first invocation to
+   * getAsyncStatement.
+   *
+   * For AsyncStatement, this is null at creation time and initialized by the
+   * async thread when it calls getAsyncStatement the first time the statement
+   * is executed.  (Or in the event of badly formed SQL, every time.)
+   */
+  sqlite3_stmt *mAsyncStatement;
+
+  /**
+   * Initiate asynchronous finalization by dispatching an event to the
+   * asynchronous thread to finalize mAsyncStatement.  This acquires a reference
+   * to this statement and proxies it back to the connection's owning thread
+   * for release purposes.
+   *
+   * In the event the asynchronous thread is already gone or we otherwise fail
+   * to dispatch an event to it we failover to invoking internalAsyncFinalize
+   * directly.  (That's what the asynchronous finalizer would have called.)
+   *
+   * @note You must not call this method from your destructor because its
+   *       operation assumes we are still alive.  Call internalAsyncFinalize
+   *       directly in that case.
+   */
+  void asyncFinalize();
+
+  /**
+   * Cleanup the async sqlite3_stmt stored in mAsyncStatement if it exists.
+   *
+   * @note Call this from your destructor, call asyncFinalize otherwise.
+   */
+  void internalAsyncFinalize();
+
+  NS_IMETHOD NewBindingParamsArray(mozIStorageBindingParamsArray **_array);
+  NS_IMETHOD ExecuteAsync(mozIStorageStatementCallback *aCallback,
+                          mozIStoragePendingStatement **_stmt);
+  NS_IMETHOD EscapeStringForLIKE(const nsAString &aValue,
+                                 const PRUnichar aEscapeChar,
+                                 nsAString &_escapedString);
+
+  // Needs access to internalAsyncFinalize
+  friend class AsyncStatementFinalizer;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(StorageBaseStatementInternal,
+                              STORAGEBASESTATEMENTINTERNAL_IID)
+
+#define NS_DECL_STORAGEBASESTATEMENTINTERNAL \
+  virtual Connection *getOwner(); \
+  virtual int getAsyncStatement(sqlite3_stmt **_stmt); \
+  virtual nsresult getAsynchronousStatementData(StatementData &_data); \
+  virtual already_AddRefed<mozIStorageBindingParams> newBindingParams( \
+    mozIStorageBindingParamsArray *aOwner);
+
+/**
+ * Helper macro to implement the proxying implementations.  Because we are
+ * implementing methods that are part of mozIStorageBaseStatement and the
+ * implementation classes already use NS_DECL_MOZISTORAGEBASESTATEMENT we don't
+ * need to provide declaration support.
+ */
+#define MIX_IMPL(_class, _optionalGuard, _method, _declArgs, _invokeArgs) \
+  NS_IMETHODIMP _class::_method _declArgs                                 \
+  {                                                                       \
+    _optionalGuard                                                        \
+    return StorageBaseStatementInternal::_method _invokeArgs;             \
+  }
+
+
+/**
+ * Define proxying implementation for the given _class.  If a state invariant
+ * needs to be checked and an early return possibly performed, pass the clause
+ * to use as _optionalGuard.
+ */
+#define MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(_class, _optionalGuard) \
+  MIX_IMPL(_class, _optionalGuard,                                      \
+           NewBindingParamsArray,                                       \
+           (mozIStorageBindingParamsArray **_array),                    \
+           (_array))                                                    \
+  MIX_IMPL(_class, _optionalGuard,                                      \
+           ExecuteAsync,                                                \
+           (mozIStorageStatementCallback *aCallback,                    \
+            mozIStoragePendingStatement **_stmt),                       \
+           (aCallback, _stmt))                                          \
+  MIX_IMPL(_class, _optionalGuard,                                      \
+           EscapeStringForLIKE,                                         \
+           (const nsAString &aValue, const PRUnichar aEscapeChar,       \
+            nsAString &_escapedString),                                 \
+           (aValue, aEscapeChar, _escapedString))
+
+/**
+ * Name-building helper for BIND_GEN_IMPL.
+ */
+#define BIND_NAME_CONCAT(_nameBit, _concatBit) \
+  Bind##_nameBit##_concatBit
+
+/**
+ * We have type-specific convenience methods for C++ implementations in
+ * 3 different forms; 2 by index, 1 by name.  The following macro allows
+ * us to avoid having to define repetitive things by hand.
+ *
+ * Because of limitations of macros and our desire to avoid requiring special
+ * permutations for the null and blob cases (whose argument count varies),
+ * we require that the argument declarations and corresponding invocation
+ * usages are passed in.
+ *
+ * @param _class
+ *        The class name.
+ * @param _guard
+ *        The guard clause to inject.
+ * @param _declName
+ *        The argument list (with parens) for the ByName variants.
+ * @param _declIndex
+ *        The argument list (with parens) for the index variants.
+ * @param _invArgs
+ *        The invocation argumment list.
+ */
+#define BIND_GEN_IMPL(_class, _guard, _name, _declName, _declIndex, _invArgs) \
+  NS_IMETHODIMP _class::BIND_NAME_CONCAT(_name, ByName) _declName             \
+  {                                                                           \
+    _guard                                                                    \
+    mozIStorageBindingParams *params = getParams();                           \
+    NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);                           \
+    return params->BIND_NAME_CONCAT(_name, ByName) _invArgs;                  \
+  }                                                                           \
+  NS_IMETHODIMP _class::BIND_NAME_CONCAT(_name, ByIndex) _declIndex           \
+  {                                                                           \
+    _guard                                                                    \
+    mozIStorageBindingParams *params = getParams();                           \
+    NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);                           \
+    return params->BIND_NAME_CONCAT(_name, ByIndex) _invArgs;                 \
+  }                                                                           \
+  NS_IMETHODIMP _class::BIND_NAME_CONCAT(_name, Parameter) _declIndex         \
+  {                                                                           \
+    _guard                                                                    \
+    mozIStorageBindingParams *params = getParams();                           \
+    NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);                           \
+    return params->BIND_NAME_CONCAT(_name, ByIndex) _invArgs;                 \
+  }
+
+/**
+ * Implement BindByName/BindByIndex for the given class.
+ *
+ * @param _class The class name.
+ * @param _optionalGuard The guard clause to inject.
+ */
+#define BIND_BASE_IMPLS(_class, _optionalGuard)             \
+  NS_IMETHODIMP _class::BindByName(const nsACString &aName, \
+                                   nsIVariant *aValue)      \
+  {                                                         \
+    _optionalGuard                                          \
+    mozIStorageBindingParams *params = getParams();         \
+    NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);         \
+    return params->BindByName(aName, aValue);               \
+  }                                                         \
+  NS_IMETHODIMP _class::BindByIndex(PRUint32 aIndex,        \
+                                    nsIVariant *aValue)     \
+  {                                                         \
+    _optionalGuard                                          \
+    mozIStorageBindingParams *params = getParams();         \
+    NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);         \
+    return params->BindByIndex(aIndex, aValue);             \
+  }
+
+/**
+ * Define the various Bind*Parameter, Bind*ByIndex, Bind*ByName stubs that just
+ * end up proxying to the params object.
+ */
+#define BOILERPLATE_BIND_PROXIES(_class, _optionalGuard) \
+  BIND_BASE_IMPLS(_class, _optionalGuard)                \
+  BIND_GEN_IMPL(_class, _optionalGuard,                  \
+                UTF8String,                              \
+                (const nsACString &aWhere,               \
+                 const nsACString &aValue),              \
+                (PRUint32 aWhere,                        \
+                 const nsACString &aValue),              \
+                (aWhere, aValue))                        \
+  BIND_GEN_IMPL(_class, _optionalGuard,                  \
+                String,                                  \
+                (const nsACString &aWhere,               \
+                 const nsAString  &aValue),              \
+                (PRUint32 aWhere,                        \
+                 const nsAString  &aValue),              \
+                (aWhere, aValue))                        \
+  BIND_GEN_IMPL(_class, _optionalGuard,                  \
+                Double,                                  \
+                (const nsACString &aWhere,               \
+                 double aValue),                         \
+                (PRUint32 aWhere,                        \
+                 double aValue),                         \
+                (aWhere, aValue))                        \
+  BIND_GEN_IMPL(_class, _optionalGuard,                  \
+                Int32,                                   \
+                (const nsACString &aWhere,               \
+                 PRInt32 aValue),                        \
+                (PRUint32 aWhere,                        \
+                 PRInt32 aValue),                        \
+                (aWhere, aValue))                        \
+  BIND_GEN_IMPL(_class, _optionalGuard,                  \
+                Int64,                                   \
+                (const nsACString &aWhere,               \
+                 PRInt64 aValue),                        \
+                (PRUint32 aWhere,                        \
+                 PRInt64 aValue),                        \
+                (aWhere, aValue))                        \
+  BIND_GEN_IMPL(_class, _optionalGuard,                  \
+                Null,                                    \
+                (const nsACString &aWhere),              \
+                (PRUint32 aWhere),                       \
+                (aWhere))                                \
+  BIND_GEN_IMPL(_class, _optionalGuard,                  \
+                Blob,                                    \
+                (const nsACString &aWhere,               \
+                 const PRUint8 *aValue,                  \
+                 PRUint32 aValueSize),                   \
+                (PRUint32 aWhere,                        \
+                 const PRUint8 *aValue,                  \
+                 PRUint32 aValueSize),                   \
+                (aWhere, aValue, aValueSize))
+
+  
+
+} // storage
+} // mozilla
+
+#endif // mozilla_storage_StorageBaseStatementInternal_h_
new file mode 100644
--- /dev/null
+++ b/storage/src/mozStorageAsyncStatement.cpp
@@ -0,0 +1,456 @@
+/* -*- 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 mozStorage.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
+ *   Shawn Wilsher <me@shawnwilsher.com>
+ *   John Zhang <jzhang@aptana.com>
+ *
+ * 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 <stdio.h>
+
+#include "nsError.h"
+#include "nsMemory.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIProgrammingLanguage.h"
+#include "Variant.h"
+
+#include "mozIStorageError.h"
+
+#include "mozStorageBindingParams.h"
+#include "mozStorageConnection.h"
+#include "mozStorageAsyncStatementJSHelper.h"
+#include "mozStorageAsyncStatementParams.h"
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageStatementRow.h"
+#include "mozStorageStatement.h"
+
+#include "prlog.h"
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo *gStorageLog;
+#endif
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIClassInfo
+
+NS_IMPL_CI_INTERFACE_GETTER4(
+  AsyncStatement
+, mozIStorageAsyncStatement
+, mozIStorageBaseStatement
+, mozIStorageBindingParams
+, mozilla::storage::StorageBaseStatementInternal
+)
+
+class AsyncStatementClassInfo : public nsIClassInfo
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHODIMP
+  GetInterfaces(PRUint32 *_count, nsIID ***_array)
+  {
+    return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_count, _array);
+  }
+
+  NS_IMETHODIMP
+  GetHelperForLanguage(PRUint32 aLanguage, nsISupports **_helper)
+  {
+    if (aLanguage == nsIProgrammingLanguage::JAVASCRIPT) {
+      static AsyncStatementJSHelper sJSHelper;
+      *_helper = &sJSHelper;
+      return NS_OK;
+    }
+
+    *_helper = nsnull;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  GetContractID(char **_contractID)
+  {
+    *_contractID = nsnull;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  GetClassDescription(char **_desc)
+  {
+    *_desc = nsnull;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  GetClassID(nsCID **_id)
+  {
+    *_id = nsnull;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  GetImplementationLanguage(PRUint32 *_language)
+  {
+    *_language = nsIProgrammingLanguage::CPLUSPLUS;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  GetFlags(PRUint32 *_flags)
+  {
+    *_flags = nsnull;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  GetClassIDNoAlloc(nsCID *_cid)
+  {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+};
+
+NS_IMETHODIMP_(nsrefcnt) AsyncStatementClassInfo::AddRef() { return 2; }
+NS_IMETHODIMP_(nsrefcnt) AsyncStatementClassInfo::Release() { return 1; }
+NS_IMPL_QUERY_INTERFACE1(AsyncStatementClassInfo, nsIClassInfo)
+
+static AsyncStatementClassInfo sAsyncStatementClassInfo;
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatement
+
+AsyncStatement::AsyncStatement()
+: StorageBaseStatementInternal()
+, mFinalized(false)
+{
+}
+
+nsresult
+AsyncStatement::initialize(Connection *aDBConnection,
+                           const nsACString &aSQLStatement)
+{
+  NS_ASSERTION(aDBConnection, "No database connection given!");
+  NS_ASSERTION(aDBConnection->GetNativeConnection(),
+               "We should never be called with a null sqlite3 database!");
+
+  mDBConnection = aDBConnection;
+  mSQLString = aSQLStatement;
+
+#ifdef PR_LOGGING
+  PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Inited async statement '%s' (0x%p)",
+                                      mSQLString.get()));
+#endif
+
+#ifdef DEBUG
+  // We want to try and test for LIKE and that consumers are using
+  // escapeStringForLIKE instead of just trusting user input.  The idea to
+  // check to see if they are binding a parameter after like instead of just
+  // using a string.  We only do this in debug builds because it's expensive!
+  const nsCaseInsensitiveCStringComparator c;
+  nsACString::const_iterator start, end, e;
+  aSQLStatement.BeginReading(start);
+  aSQLStatement.EndReading(end);
+  e = end;
+  while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) {
+    // We have a LIKE in here, so we perform our tests
+    // FindInReadable moves the iterator, so we have to get a new one for
+    // each test we perform.
+    nsACString::const_iterator s1, s2, s3;
+    s1 = s2 = s3 = start;
+
+    if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) ||
+          ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) ||
+          ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) {
+      // At this point, we didn't find a LIKE statement followed by ?, :,
+      // or @, all of which are valid characters for binding a parameter.
+      // We will warn the consumer that they may not be safely using LIKE.
+      NS_WARNING("Unsafe use of LIKE detected!  Please ensure that you "
+                 "are using mozIStorageAsyncStatement::escapeStringForLIKE "
+                 "and that you are binding that result to the statement "
+                 "to prevent SQL injection attacks.");
+    }
+
+    // resetting start and e
+    start = e;
+    e = end;
+  }
+#endif
+
+  return NS_OK;
+}
+
+mozIStorageBindingParams *
+AsyncStatement::getParams()
+{
+  nsresult rv;
+
+  // If we do not have an array object yet, make it.
+  if (!mParamsArray) {
+    nsCOMPtr<mozIStorageBindingParamsArray> array;
+    rv = NewBindingParamsArray(getter_AddRefs(array));
+    NS_ENSURE_SUCCESS(rv, nsnull);
+
+    mParamsArray = static_cast<BindingParamsArray *>(array.get());
+  }
+
+  // If there isn't already any rows added, we'll have to add one to use.
+  if (mParamsArray->length() == 0) {
+    nsRefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray));
+    NS_ENSURE_TRUE(params, nsnull);
+
+    rv = mParamsArray->AddParams(params);
+    NS_ENSURE_SUCCESS(rv, nsnull);
+
+    // We have to unlock our params because AddParams locks them.  This is safe
+    // because no reference to the params object was, or ever will be given out.
+    params->unlock(nsnull);
+
+    // We also want to lock our array at this point - we don't want anything to
+    // be added to it.
+    mParamsArray->lock();
+  }
+
+  return *mParamsArray->begin();
+}
+
+/**
+ * If we are here then we know there are no pending async executions relying on
+ * us (StatementData holds a reference to us; this also goes for our own
+ * AsyncStatementFinalizer which proxies its release to the calling thread) and
+ * so it is always safe to destroy our sqlite3_stmt if one exists.  We can be
+ * destroyed on the caller thread by garbage-collection/reference counting or on
+ * the async thread by the last execution of a statement that already lost its
+ * main-thread refs.
+ */
+AsyncStatement::~AsyncStatement()
+{
+  internalAsyncFinalize();
+  cleanupJSHelpers();
+
+  // If we are getting destroyed on the wrong thread, proxy the connection
+  // release to the right thread.  I'm not sure why we do this.
+  PRBool onCallingThread = PR_FALSE;
+  (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread);
+  if (!onCallingThread) {
+    // NS_ProxyRelase only magic forgets for us if mDBConnection is an
+    // nsCOMPtr.  Which it is not; it's an nsRefPtr.
+    Connection *forgottenConn = nsnull;
+    mDBConnection.swap(forgottenConn);
+    (void)::NS_ProxyRelease(mDBConnection->threadOpenedOn, forgottenConn);
+  }
+}
+
+void
+AsyncStatement::cleanupJSHelpers()
+{
+  // We are considered dead at this point, so any wrappers for row or params
+  // need to lose their reference to us.
+  if (mStatementParamsHolder) {
+    nsCOMPtr<nsIXPConnectWrappedNative> wrapper =
+      do_QueryInterface(mStatementParamsHolder);
+    nsCOMPtr<mozIStorageStatementParams> iParams =
+      do_QueryWrappedNative(wrapper);
+    AsyncStatementParams *params =
+      static_cast<AsyncStatementParams *>(iParams.get());
+    params->mStatement = nsnull;
+    mStatementParamsHolder = nsnull;
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_THREADSAFE_ADDREF(AsyncStatement)
+NS_IMPL_THREADSAFE_RELEASE(AsyncStatement)
+
+NS_INTERFACE_MAP_BEGIN(AsyncStatement)
+  NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement)
+  NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement)
+  NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams)
+  NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal)
+  if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
+    foundInterface = static_cast<nsIClassInfo *>(&sAsyncStatementClassInfo);
+  }
+  else
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement)
+NS_INTERFACE_MAP_END
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+Connection *
+AsyncStatement::getOwner()
+{
+  return mDBConnection;
+}
+
+int
+AsyncStatement::getAsyncStatement(sqlite3_stmt **_stmt)
+{
+#ifdef DEBUG
+  // Make sure we are never called on the connection's owning thread.
+  PRBool onOpenedThread = PR_FALSE;
+  (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
+  NS_ASSERTION(!onOpenedThread,
+               "We should only be called on the async thread!");
+#endif
+
+  if (!mAsyncStatement) {
+    int rc = ::sqlite3_prepare_v2(mDBConnection->GetNativeConnection(),
+                                  mSQLString.get(), -1,
+                                  &mAsyncStatement, NULL);
+    if (rc != SQLITE_OK) {
+#ifdef PR_LOGGING
+      PR_LOG(gStorageLog, PR_LOG_ERROR,
+             ("Sqlite statement prepare error: %d '%s'", rc,
+              ::sqlite3_errmsg(mDBConnection->GetNativeConnection())));
+      PR_LOG(gStorageLog, PR_LOG_ERROR,
+             ("Statement was: '%s'", mSQLString.get()));
+#endif
+      *_stmt = nsnull;
+      return rc;
+    }
+
+#ifdef PR_LOGGING
+    PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Initialized statement '%s' (0x%p)",
+                                        mSQLString.get(),
+                                        mAsyncStatement));
+#endif
+  }
+
+  *_stmt = mAsyncStatement;
+  return SQLITE_OK;
+}
+
+nsresult
+AsyncStatement::getAsynchronousStatementData(StatementData &_data)
+{
+  if (mFinalized)
+    return NS_ERROR_UNEXPECTED;
+
+  // Pass null for the sqlite3_stmt; it will be requested on demand from the
+  // async thread.
+  _data = StatementData(nsnull, bindingParamsArray(), this);
+
+  return NS_OK;
+}
+
+already_AddRefed<mozIStorageBindingParams>
+AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner)
+{
+  if (mFinalized)
+    return nsnull;
+
+  nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner));
+  return params.forget();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageAsyncStatement
+
+// (nothing is specific to mozIStorageAsyncStatement)
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+// proxy to StorageBaseStatementInternal using its define helper.
+MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
+  AsyncStatement,
+  if (mFinalized) return NS_ERROR_UNEXPECTED;)
+
+NS_IMETHODIMP
+AsyncStatement::Finalize()
+{
+  if (mFinalized)
+    return NS_OK;
+
+  mFinalized = true;
+
+#ifdef PR_LOGGING
+  PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Finalizing statement '%s'",
+                                      mSQLString.get()));
+#endif
+
+  asyncFinalize();
+  cleanupJSHelpers();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatement::BindParameters(mozIStorageBindingParamsArray *aParameters)
+{
+  if (mFinalized)
+    return NS_ERROR_UNEXPECTED;
+
+  BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters);
+  if (array->getOwner() != this)
+    return NS_ERROR_UNEXPECTED;
+
+  if (array->length() == 0)
+    return NS_ERROR_UNEXPECTED;
+
+  mParamsArray = array;
+  mParamsArray->lock();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatement::GetState(PRInt32 *_state)
+{
+  if (mFinalized)
+    *_state = MOZ_STORAGE_STATEMENT_INVALID;
+  else
+    *_state = MOZ_STORAGE_STATEMENT_READY;
+
+  return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageBindingParams
+
+BOILERPLATE_BIND_PROXIES(
+  AsyncStatement, 
+  if (mFinalized) return NS_ERROR_UNEXPECTED;
+)
+
+} // namespace storage
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/storage/src/mozStorageAsyncStatement.h
@@ -0,0 +1,145 @@
+/* -*- 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 Oracle Corporation code.
+ *
+ * The Initial Developer of the Original Code is
+ *  Oracle Corporation
+ * Portions created by the Initial Developer are Copyright (C) 2004
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * 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 mozilla_storage_mozStorageAsyncStatement_h_
+#define mozilla_storage_mozStorageAsyncStatement_h_
+
+#include "nsAutoPtr.h"
+#include "nsString.h"
+
+#include "nsTArray.h"
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozStorageStatementData.h"
+#include "mozIStorageAsyncStatement.h"
+#include "StorageBaseStatementInternal.h"
+
+class nsIXPConnectJSObjectHolder;
+struct sqlite3_stmt;
+
+namespace mozilla {
+namespace storage {
+
+class AsyncStatementJSHelper;
+class Connection;
+
+class AsyncStatement : public mozIStorageAsyncStatement
+                     , public StorageBaseStatementInternal
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEASYNCSTATEMENT
+  NS_DECL_MOZISTORAGEBASESTATEMENT
+  NS_DECL_MOZISTORAGEBINDINGPARAMS
+  NS_DECL_STORAGEBASESTATEMENTINTERNAL
+
+  AsyncStatement();
+
+  /**
+   * Initializes the object on aDBConnection by preparing the SQL statement
+   * given by aSQLStatement.
+   *
+   * @param aDBConnection
+   *        The Connection object this statement is associated with.
+   * @param aSQLStatement
+   *        The SQL statement to prepare that this object will represent.
+   */
+  nsresult initialize(Connection *aDBConnection,
+                      const nsACString &aSQLStatement);
+
+  /**
+   * 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:
+  ~AsyncStatement();
+
+  /**
+   * Clean up the references JS helpers hold to us.  For cycle-avoidance reasons
+   * they do not hold reference-counted references to us, so it is important
+   * we do this.
+   */
+  void cleanupJSHelpers();
+
+  /**
+   * @return a pointer to the BindingParams object to use with our Bind*
+   *         method.
+   */
+  mozIStorageBindingParams *getParams();
+
+  /**
+   * The SQL string as passed by the user.  We store it because we create the
+   * async statement on-demand on the async thread.
+   */
+  nsCString mSQLString;
+
+  /**
+   * Holds the array of parameters to bind to this statement when we execute
+   * it asynchronously.
+   */
+  nsRefPtr<BindingParamsArray> mParamsArray;
+
+  /**
+   * Caches the JS 'params' helper for this statement.
+   */
+  nsCOMPtr<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
+
+  /**
+   * Have we been explicitly finalized by the user?
+   */
+  bool mFinalized;
+
+  /**
+   * Required for access to private mStatementParamsHolder field by
+   * AsyncStatementJSHelper::getParams.
+   */
+  friend class AsyncStatementJSHelper;
+};
+
+} // storage
+} // mozilla
+
+#endif // mozilla_storage_mozStorageAsyncStatement_h_
--- a/storage/src/mozStorageAsyncStatementExecution.cpp
+++ b/storage/src/mozStorageAsyncStatementExecution.cpp
@@ -133,41 +133,48 @@ public:
 
 private:
   mozIStorageStatementCallback *mCallback;
   nsCOMPtr<mozIStorageError> mErrorObj;
   nsRefPtr<AsyncExecuteStatements> mEventStatus;
 };
 
 /**
- * Notifies the calling thread that the statement has finished executing.
+ * Notifies the calling thread that the statement has finished executing.  Keeps
+ * the AsyncExecuteStatements instance alive long enough so that it does not
+ * get destroyed on the async thread if there are no other references alive.
  */
 class CompletionNotifier : public nsRunnable
 {
 public:
   /**
    * This takes ownership of the callback.  It is released on the thread this is
    * dispatched to (which should always be the calling thread).
    */
   CompletionNotifier(mozIStorageStatementCallback *aCallback,
-                     ExecutionState aReason) :
-      mCallback(aCallback)
+                     ExecutionState aReason,
+                     AsyncExecuteStatements *aKeepAsyncAlive)
+    : mKeepAsyncAlive(aKeepAsyncAlive)
+    , mCallback(aCallback)
     , mReason(aReason)
   {
   }
 
   NS_IMETHOD Run()
   {
-    (void)mCallback->HandleCompletion(mReason);
-    NS_RELEASE(mCallback);
+    if (mCallback) {
+      (void)mCallback->HandleCompletion(mReason);
+      NS_RELEASE(mCallback);
+    }
 
     return NS_OK;
   }
 
 private:
+  nsRefPtr<AsyncExecuteStatements> mKeepAsyncAlive;
   mozIStorageStatementCallback *mCallback;
   ExecutionState mReason;
 };
 
 } // anonymous namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// AsyncExecuteStatements
@@ -180,17 +187,17 @@ AsyncExecuteStatements::execute(Statemen
                                 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);
 
   // Dispatch it to the background
-  nsCOMPtr<nsIEventTarget> target(aConnection->getAsyncExecutionTarget());
+  nsIEventTarget *target = aConnection->getAsyncExecutionTarget();
   NS_ENSURE_TRUE(target, NS_ERROR_NOT_AVAILABLE);
   nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Return it as the pending statement object and track it.
   NS_ADDREF(*_stmt = event);
   return NS_OK;
 }
@@ -202,16 +209,17 @@ AsyncExecuteStatements::AsyncExecuteStat
 , mTransactionManager(nsnull)
 , mCallback(aCallback)
 , mCallingThread(::do_GetCurrentThread())
 , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS))
 , mIntervalStart(TimeStamp::Now())
 , mState(PENDING)
 , mCancelRequested(false)
 , mMutex(aConnection->sharedAsyncExecutionMutex)
+, mDBMutex(aConnection->sharedDBMutex)
 {
   (void)mStatements.SwapElements(aStatements);
   NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
   NS_IF_ADDREF(mCallback);
 }
 
 bool
 AsyncExecuteStatements::shouldNotify()
@@ -231,43 +239,47 @@ AsyncExecuteStatements::shouldNotify()
 }
 
 bool
 AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
                                                        bool aLastStatement)
 {
   mMutex.AssertNotCurrentThreadOwns();
 
-  sqlite3_stmt *stmt(aData);
+  sqlite3_stmt *aStatement = nsnull;
+  // This cannot fail; we are only called if it's available.
+  (void)aData.getSqliteStatement(&aStatement);
+  NS_ASSERTION(aStatement, "You broke the code; do not call here like that!");
   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);
+    nsCOMPtr<IStorageBindingParamsInternal> bindingInternal = 
+      do_QueryInterface(*itr);
+    nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
     if (error) {
       // Set our error state.
       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);
+    continueProcessing = executeAndProcessStatement(aStatement, lastStatement);
 
     // Always reset our statement.
-    (void)::sqlite3_reset(stmt);
+    (void)::sqlite3_reset(aStatement);
   }
 
   return continueProcessing;
 }
 
 bool
 AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
                                                    bool aLastStatement)
@@ -322,38 +334,48 @@ AsyncExecuteStatements::executeAndProces
 }
 
 bool
 AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
 {
   mMutex.AssertNotCurrentThreadOwns();
 
   while (true) {
+    // lock the sqlite mutex so sqlite3_errmsg cannot change
+    SQLiteMutexAutoLock lockedScope(mDBMutex);
+
     int rc = ::sqlite3_step(aStatement);
     // Stop if we have no more results.
     if (rc == SQLITE_DONE)
       return false;
 
     // If we got results, we can return now.
     if (rc == SQLITE_ROW)
       return true;
 
     // Some errors are not fatal, and we can handle them and continue.
     if (rc == SQLITE_BUSY) {
+      // Don't hold the lock while we call outside our module.
+      SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
+
       // Yield, and try again
       (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
       continue;
     }
 
     // Set an error state.
     mState = ERROR;
 
-    // And notify.
-    sqlite3 *db = ::sqlite3_db_handle(aStatement);
-    (void)notifyError(rc, ::sqlite3_errmsg(db));
+    // Construct the error message before giving up the mutex (which we cannot
+    // hold during the call to notifyError).
+    sqlite3 *db = mConnection->GetNativeConnection();
+    nsCOMPtr<mozIStorageError> errorObj(new Error(rc, ::sqlite3_errmsg(db)));
+    // We cannot hold the DB mutex while calling notifyError.
+    SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
+    (void)notifyError(errorObj);
 
     // Finally, indicate that we should stop processing.
     return false;
   }
 }
 
 nsresult
 AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
@@ -418,50 +440,51 @@ AsyncExecuteStatements::notifyComplete()
     }
     else {
       (void)mTransactionManager->Rollback();
     }
     delete mTransactionManager;
     mTransactionManager = nsnull;
   }
 
-  // Notify about completion iff we have a callback.
-  if (mCallback) {
-    nsRefPtr<CompletionNotifier> completionEvent =
-      new CompletionNotifier(mCallback, mState);
-    NS_ENSURE_TRUE(completionEvent, NS_ERROR_OUT_OF_MEMORY);
+  // Always generate a completion notification; it is what guarantees that our
+  // destruction does not happen here on the async thread.
+  nsRefPtr<CompletionNotifier> completionEvent =
+    new CompletionNotifier(mCallback, mState, this);
+  NS_ENSURE_TRUE(completionEvent, NS_ERROR_OUT_OF_MEMORY);
 
-    // We no longer own mCallback (the CompletionNotifier takes ownership).
-    mCallback = nsnull;
+  // We no longer own mCallback (the CompletionNotifier takes ownership).
+  mCallback = nsnull;
 
-    (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
-  }
+  (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
 
   return NS_OK;
 }
 
 nsresult
 AsyncExecuteStatements::notifyError(PRInt32 aErrorCode,
                                     const char *aMessage)
 {
   mMutex.AssertNotCurrentThreadOwns();
+  mDBMutex.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();
+  mDBMutex.assertNotCurrentThreadOwns();
 
   if (!mCallback)
     return NS_OK;
 
   nsRefPtr<ErrorNotifier> notifier =
     new ErrorNotifier(mCallback, aError, this);
   NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY);
 
@@ -541,23 +564,46 @@ AsyncExecuteStatements::Run()
     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));
 
+    sqlite3_stmt *stmt;
+    { // lock the sqlite mutex so sqlite3_errmsg cannot change
+      SQLiteMutexAutoLock lockedScope(mDBMutex);
+
+      int rc = mStatements[i].getSqliteStatement(&stmt);
+      if (rc != SQLITE_OK) {
+        // Set our error state.
+        mState = ERROR;
+
+        // Build the error object; can't call notifyError with the lock held
+        sqlite3 *db = mConnection->GetNativeConnection();
+        nsCOMPtr<mozIStorageError> errorObj(
+          new Error(rc, ::sqlite3_errmsg(db))
+        );
+        {
+          // We cannot hold the DB mutex and call notifyError.
+          SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
+          (void)notifyError(errorObj);
+        }
+        break;
+      }
+    }
+
     // 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)) {
+    else if (!executeAndProcessStatement(stmt, finished)) {
       break;
     }
   }
 
   // If we still have results that we haven't notified about, take care of
   // them now.
   if (mResultSet)
     (void)notifyResults();
--- a/storage/src/mozStorageAsyncStatementExecution.h
+++ b/storage/src/mozStorageAsyncStatementExecution.h
@@ -42,16 +42,17 @@
 
 #include "nscore.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/TimeStamp.h"
 
+#include "SQLiteMutex.h"
 #include "mozIStoragePendingStatement.h"
 #include "mozIStorageStatementCallback.h"
 
 struct sqlite3_stmt;
 class mozStorageTransaction;
 
 namespace mozilla {
 namespace storage {
@@ -176,16 +177,17 @@ private:
    * @pre mMutex is not held
    */
   nsresult notifyComplete();
 
   /**
    * Notifies callback about an error.
    *
    * @pre mMutex is not held
+   * @pre mDBMutex 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.
    */
@@ -230,14 +232,22 @@ private:
   /**
    * This is the mutex that protects our state from changing between threads.
    * This includes the following variables:
    *   - mCancelRequested is only set on the calling thread while the lock is
    *     held.  It is always read from within the lock on the background thread,
    *     but not on the calling thread (see shouldNotify for why).
    */
   Mutex &mMutex;
+
+  /**
+   * The wrapped SQLite recursive connection mutex.  We use it whenever we call
+   * sqlite3_step and care about having reliable error messages.  By taking it
+   * prior to the call and holding it until the point where we no longer care
+   * about the error message, the user gets reliable error messages.
+   */
+  SQLiteMutex &mDBMutex;
 };
 
 } // namespace storage
 } // namespace mozilla
 
 #endif // _mozStorageAsyncStatementExecution_h_
copy from storage/src/mozStorageStatementJSHelper.cpp
copy to storage/src/mozStorageAsyncStatementJSHelper.cpp
--- a/storage/src/mozStorageStatementJSHelper.cpp
+++ b/storage/src/mozStorageAsyncStatementJSHelper.cpp
@@ -10,160 +10,76 @@
  *
  * 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 mozStorage code.
  *
- * The Initial Developer of the Original Code is
- * Mozilla Corporation.
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *   Andrew Sutherland <asutherland@asutherland.org>
  *
  * 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 "nsIXPConnect.h"
-#include "mozStorageStatement.h"
+#include "mozStorageAsyncStatement.h"
 #include "mozStorageService.h"
 
 #include "nsMemory.h"
 #include "nsString.h"
 #include "nsServiceManagerUtils.h"
 
-#include "mozStorageStatementJSHelper.h"
+#include "mozStorageAsyncStatementJSHelper.h"
 
-#include "mozStorageStatementRow.h"
-#include "mozStorageStatementParams.h"
+#include "mozStorageAsyncStatementParams.h"
 
 #include "jsapi.h"
 
 namespace mozilla {
 namespace storage {
 
 ////////////////////////////////////////////////////////////////////////////////
-//// Global Functions
-
-static
-JSBool
-stepFunc(JSContext *aCtx,
-         PRUint32,
-         jsval *_vp)
-{
-  nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
-  nsCOMPtr<nsIXPConnectWrappedNative> wrapper;
-  nsresult rv = xpc->GetWrappedNativeOfJSObject(
-    aCtx, JS_THIS_OBJECT(aCtx, _vp), getter_AddRefs(wrapper)
-  );
-  if (NS_FAILED(rv)) {
-    ::JS_ReportError(aCtx, "mozIStorageStatement::step() could not obtain native statement");
-    return JS_FALSE;
-  }
-
-  Statement *stmt = static_cast<Statement *>(wrapper->Native());
-
-#ifdef DEBUG
-  {
-    nsCOMPtr<mozIStorageStatement> isStatement(do_QueryInterface(stmt));
-    NS_ASSERTION(isStatement, "How is this not a statement?!");
-  }
-#endif
-
-  PRBool hasMore = PR_FALSE;
-  rv = stmt->ExecuteStep(&hasMore);
-  if (NS_SUCCEEDED(rv) && !hasMore) {
-    *_vp = JSVAL_FALSE;
-    (void)stmt->Reset();
-    return JS_TRUE;
-  }
-
-  if (NS_FAILED(rv)) {
-    ::JS_ReportError(aCtx, "mozIStorageStatement::step() returned an error");
-    return JS_FALSE;
-  }
-
-  *_vp = BOOLEAN_TO_JSVAL(hasMore);
-  return JS_TRUE;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//// StatementJSHelper
+//// AsyncStatementJSHelper
 
 nsresult
-StatementJSHelper::getRow(Statement *aStatement,
-                          JSContext *aCtx,
-                          JSObject *aScopeObj,
-                          jsval *_row)
+AsyncStatementJSHelper::getParams(AsyncStatement *aStatement,
+                                  JSContext *aCtx,
+                                  JSObject *aScopeObj,
+                                  jsval *_params)
 {
   nsresult rv;
 
 #ifdef DEBUG
   PRInt32 state;
   (void)aStatement->GetState(&state);
-  NS_ASSERTION(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_EXECUTING,
-               "Invalid state to get the row object - all calls will fail!");
-#endif
-
-  if (!aStatement->mStatementRowHolder) {
-    nsCOMPtr<mozIStorageStatementRow> row(new StatementRow(aStatement));
-    NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
-
-    nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
-    rv = xpc->WrapNative(
-      aCtx,
-      ::JS_GetGlobalForObject(aCtx, aScopeObj),
-      row,
-      NS_GET_IID(mozIStorageStatementRow),
-      getter_AddRefs(aStatement->mStatementRowHolder)
-    );
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  JSObject *obj = nsnull;
-  rv = aStatement->mStatementRowHolder->GetJSObject(&obj);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  *_row = OBJECT_TO_JSVAL(obj);
-  return NS_OK;
-}
-
-nsresult
-StatementJSHelper::getParams(Statement *aStatement,
-                             JSContext *aCtx,
-                             JSObject *aScopeObj,
-                             jsval *_params)
-{
-  nsresult rv;
-
-#ifdef DEBUG
-  PRInt32 state;
-  (void)aStatement->GetState(&state);
-  NS_ASSERTION(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_READY,
+  NS_ASSERTION(state == mozIStorageAsyncStatement::MOZ_STORAGE_STATEMENT_READY,
                "Invalid state to get the params object - all calls will fail!");
 #endif
 
   if (!aStatement->mStatementParamsHolder) {
     nsCOMPtr<mozIStorageStatementParams> params =
-      new StatementParams(aStatement);
+      new AsyncStatementParams(aStatement);
     NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
 
     nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
     rv = xpc->WrapNative(
       aCtx,
       ::JS_GetGlobalForObject(aCtx, aScopeObj),
       params,
       NS_GET_IID(mozIStorageStatementParams),
@@ -175,80 +91,57 @@ StatementJSHelper::getParams(Statement *
   JSObject *obj = nsnull;
   rv = aStatement->mStatementParamsHolder->GetJSObject(&obj);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *_params = OBJECT_TO_JSVAL(obj);
   return NS_OK;
 }
 
-NS_IMETHODIMP_(nsrefcnt) StatementJSHelper::AddRef() { return 2; }
-NS_IMETHODIMP_(nsrefcnt) StatementJSHelper::Release() { return 1; }
-NS_INTERFACE_MAP_BEGIN(StatementJSHelper)
+NS_IMETHODIMP_(nsrefcnt) AsyncStatementJSHelper::AddRef() { return 2; }
+NS_IMETHODIMP_(nsrefcnt) AsyncStatementJSHelper::Release() { return 1; }
+NS_INTERFACE_MAP_BEGIN(AsyncStatementJSHelper)
   NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIXPCScriptable
 
-#define XPC_MAP_CLASSNAME StatementJSHelper
-#define XPC_MAP_QUOTED_CLASSNAME "StatementJSHelper"
+#define XPC_MAP_CLASSNAME AsyncStatementJSHelper
+#define XPC_MAP_QUOTED_CLASSNAME "AsyncStatementJSHelper"
 #define XPC_MAP_WANT_GETPROPERTY
-#define XPC_MAP_WANT_NEWRESOLVE
 #define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE
 #include "xpc_map_end.h"
 
 NS_IMETHODIMP
-StatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper,
-                               JSContext *aCtx,
-                               JSObject *aScopeObj,
-                               jsval aId,
-                               jsval *_result,
-                               PRBool *_retval)
+AsyncStatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper,
+                                    JSContext *aCtx,
+                                    JSObject *aScopeObj,
+                                    jsval aId,
+                                    jsval *_result,
+                                    PRBool *_retval)
 {
   if (!JSVAL_IS_STRING(aId))
     return NS_OK;
 
-  Statement *stmt = static_cast<Statement *>(aWrapper->Native());
+  // Cast to async via mozI* since direct from nsISupports is ambiguous.
+  mozIStorageAsyncStatement *iAsyncStmt =
+    static_cast<mozIStorageAsyncStatement *>(aWrapper->Native());
+  AsyncStatement *stmt = static_cast<AsyncStatement *>(iAsyncStmt);
 
 #ifdef DEBUG
   {
-    nsCOMPtr<mozIStorageStatement> isStatement(do_QueryInterface(stmt));
-    NS_ASSERTION(isStatement, "How is this not a statement?!");
+    nsISupports *supp = aWrapper->Native();
+    nsCOMPtr<mozIStorageAsyncStatement> isStatement(do_QueryInterface(supp));
+    NS_ASSERTION(isStatement, "How is this not an async statement?!");
   }
 #endif
 
   const char *propName = ::JS_GetStringBytes(JSVAL_TO_STRING(aId));
-  if (::strcmp(propName, "row") == 0)
-    return getRow(stmt, aCtx, aScopeObj, _result);
-
   if (::strcmp(propName, "params") == 0)
     return getParams(stmt, aCtx, aScopeObj, _result);
 
   return NS_OK;
 }
 
-
-NS_IMETHODIMP
-StatementJSHelper::NewResolve(nsIXPConnectWrappedNative *aWrapper,
-                              JSContext *aCtx,
-                              JSObject *aScopeObj,
-                              jsval aId,
-                              PRUint32 aFlags,
-                              JSObject **_objp,
-                              PRBool *_retval)
-{
-  if (!JSVAL_IS_STRING(aId))
-    return NS_OK;
-
-  const char *name = ::JS_GetStringBytes(JSVAL_TO_STRING(aId));
-  if (::strcmp(name, "step") == 0) {
-    *_retval = ::JS_DefineFunction(aCtx, aScopeObj, "step", (JSNative)stepFunc,
-                                   0, JSFUN_FAST_NATIVE) != nsnull;
-    *_objp = aScopeObj;
-    return NS_OK;
-  }
-  return NS_OK;
-}
-
 } // namespace storage
 } // namespace mozilla
copy from storage/src/mozStorageStatementJSHelper.h
copy to storage/src/mozStorageAsyncStatementJSHelper.h
--- a/storage/src/mozStorageStatementJSHelper.h
+++ b/storage/src/mozStorageAsyncStatementJSHelper.h
@@ -10,55 +10,58 @@
  *
  * 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 mozStorage code.
  *
- * The Initial Developer of the Original Code is
- * Mozilla Corporation
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
- *  Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *   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 __MOZSTORAGESTATEMENTJSHELPER_H__
-#define __MOZSTORAGESTATEMENTJSHELPER_H__
+#ifndef mozilla_storage_mozStorageAsyncStatementJSHelper_h_
+#define mozilla_storage_mozStorageAsyncStatementJSHelper_h_
 
 #include "nsIXPCScriptable.h"
 
-class Statement;
+class AsyncStatement;
 
 namespace mozilla {
 namespace storage {
 
-class StatementJSHelper : public nsIXPCScriptable
+/**
+ * A modified version of StatementJSHelper that only exposes the async-specific
+ * 'params' helper.  We do not expose 'row' or 'step' as they do not apply to
+ * us.
+ */
+class AsyncStatementJSHelper : public nsIXPCScriptable
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIXPCSCRIPTABLE
 
 private:
-  nsresult getRow(Statement *, JSContext *, JSObject *, jsval *);
-  nsresult getParams(Statement *, JSContext *, JSObject *, jsval *);
+  nsresult getParams(AsyncStatement *, JSContext *, JSObject *, jsval *);
 };
 
 } // namespace storage
 } // namespace mozilla
 
-#endif // __MOZSTORAGESTATEMENTJSHELPER_H__
+#endif // mozilla_storage_mozStorageAsyncStatementJSHelper_h_
copy from storage/src/mozStorageStatementParams.cpp
copy to storage/src/mozStorageAsyncStatementParams.cpp
--- a/storage/src/mozStorageStatementParams.cpp
+++ b/storage/src/mozStorageAsyncStatementParams.cpp
@@ -35,195 +35,121 @@
  * 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 "nsMemory.h"
 #include "nsString.h"
+#include "nsCOMPtr.h"
 
 #include "mozStoragePrivateHelpers.h"
-#include "mozStorageStatementParams.h"
+#include "mozStorageAsyncStatement.h"
+#include "mozStorageAsyncStatementParams.h"
 #include "mozIStorageStatement.h"
 
 namespace mozilla {
 namespace storage {
 
 ////////////////////////////////////////////////////////////////////////////////
-//// StatementParams
+//// AsyncStatementParams
 
-StatementParams::StatementParams(mozIStorageStatement *aStatement) :
-    mStatement(aStatement)
+AsyncStatementParams::AsyncStatementParams(AsyncStatement *aStatement)
+: mStatement(aStatement)
 {
   NS_ASSERTION(mStatement != nsnull, "mStatement is null");
-  (void)mStatement->GetParameterCount(&mParamCount);
 }
 
 NS_IMPL_ISUPPORTS2(
-  StatementParams,
-  mozIStorageStatementParams,
-  nsIXPCScriptable
+  AsyncStatementParams
+, mozIStorageStatementParams
+, nsIXPCScriptable
 )
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIXPCScriptable
 
-#define XPC_MAP_CLASSNAME StatementParams
-#define XPC_MAP_QUOTED_CLASSNAME "StatementParams"
+#define XPC_MAP_CLASSNAME AsyncStatementParams
+#define XPC_MAP_QUOTED_CLASSNAME "AsyncStatementParams"
 #define XPC_MAP_WANT_SETPROPERTY
-#define XPC_MAP_WANT_NEWENUMERATE
 #define XPC_MAP_WANT_NEWRESOLVE
 #define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE
 #include "xpc_map_end.h"
 
 NS_IMETHODIMP
-StatementParams::SetProperty(nsIXPConnectWrappedNative *aWrapper,
-                             JSContext *aCtx,
-                             JSObject *aScopeObj,
-                             jsval aId,
-                             jsval *_vp,
-                             PRBool *_retval)
+AsyncStatementParams::SetProperty(
+  nsIXPConnectWrappedNative *aWrapper,
+  JSContext *aCtx,
+  JSObject *aScopeObj,
+  jsval aId,
+  jsval *_vp,
+  PRBool *_retval
+)
 {
   NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
 
   if (JSVAL_IS_INT(aId)) {
     int idx = JSVAL_TO_INT(aId);
 
-    PRBool res = bindJSValue(aCtx, mStatement, idx, *_vp);
-    NS_ENSURE_TRUE(res, NS_ERROR_UNEXPECTED);
+    nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp));
+    NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED);
+    nsresult rv = mStatement->BindByIndex(idx, variant);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
   else if (JSVAL_IS_STRING(aId)) {
     JSString *str = JSVAL_TO_STRING(aId);
     NS_ConvertUTF16toUTF8 name(reinterpret_cast<const PRUnichar *>
                                    (::JS_GetStringChars(str)),
                                ::JS_GetStringLength(str));
 
-    // check to see if there's a parameter with this name
-    PRUint32 index;
-    nsresult rv = mStatement->GetParameterIndex(name, &index);
+    nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp));
+    NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED);
+    nsresult rv = mStatement->BindByName(name, variant);
     NS_ENSURE_SUCCESS(rv, rv);
-
-    PRBool res = bindJSValue(aCtx, mStatement, index, *_vp);
-    NS_ENSURE_TRUE(res, NS_ERROR_UNEXPECTED);
   }
   else {
     return NS_ERROR_INVALID_ARG;
   }
 
   *_retval = PR_TRUE;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-StatementParams::NewEnumerate(nsIXPConnectWrappedNative *aWrapper,
-                              JSContext *aCtx,
-                              JSObject *aScopeObj,
-                              PRUint32 aEnumOp,
-                              jsval *_statep,
-                              jsid *_idp,
-                              PRBool *_retval)
-{
-  NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
-
-  switch (aEnumOp) {
-    case JSENUMERATE_INIT:
-    {
-      // Start our internal index at zero.
-      *_statep = JSVAL_ZERO;
-
-      // And set our length, if needed.
-      if (_idp)
-        *_idp = INT_TO_JSVAL(mParamCount);
-
-      break;
-    }
-    case JSENUMERATE_NEXT:
-    {
-      NS_ASSERTION(*_statep != JSVAL_NULL, "Internal state is null!");
-
-      // Make sure we are in range first.
-      PRUint32 index = static_cast<PRUint32>(JSVAL_TO_INT(*_statep));
-      if (index >= mParamCount) {
-        *_statep = JSVAL_NULL;
-        return NS_OK;
-      }
-
-      // Get the name of our parameter.
-      nsCAutoString name;
-      nsresult rv = mStatement->GetParameterName(index, name);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      // But drop the first character, which is going to be a ':'.
-      JSString *jsname = ::JS_NewStringCopyN(aCtx, &(name.get()[1]),
-                                             name.Length() - 1);
-      NS_ENSURE_TRUE(jsname, NS_ERROR_OUT_OF_MEMORY);
-
-      // Set our name.
-      if (!::JS_ValueToId(aCtx, STRING_TO_JSVAL(jsname), _idp)) {
-        *_retval = PR_FALSE;
-        return NS_OK;
-      }
-
-      // And increment our index.
-      *_statep = INT_TO_JSVAL(++index);
-
-      break;
-    }
-    case JSENUMERATE_DESTROY:
-    {
-      // Clear our state.
-      *_statep = JSVAL_NULL;
-
-      break;
-    }
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-StatementParams::NewResolve(nsIXPConnectWrappedNative *aWrapper,
-                            JSContext *aCtx,
-                            JSObject *aScopeObj,
-                            jsval aId,
-                            PRUint32 aFlags,
-                            JSObject **_objp,
-                            PRBool *_retval)
+AsyncStatementParams::NewResolve(
+  nsIXPConnectWrappedNative *aWrapper,
+  JSContext *aCtx,
+  JSObject *aScopeObj,
+  jsval aId,
+  PRUint32 aFlags,
+  JSObject **_objp,
+  PRBool *_retval
+)
 {
   NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
   // We do not throw at any point after this unless our index is out of range
   // because we want to allow the prototype chain to be checked for the
   // property.
 
   PRUint32 idx;
 
   if (JSVAL_IS_INT(aId)) {
     idx = JSVAL_TO_INT(aId);
-
-    // Ensure that our index is within range.  We do not care about the
-    // prototype chain being checked here.
-    if (idx >= mParamCount)
-      return NS_ERROR_INVALID_ARG;
+    // All indexes are good because we don't know how many parameters there
+    // really are.
   }
   else if (JSVAL_IS_STRING(aId)) {
     JSString *str = JSVAL_TO_STRING(aId);
     jschar *nameChars = ::JS_GetStringChars(str);
     size_t nameLength = ::JS_GetStringLength(str);
 
-    // Check to see if there's a parameter with this name, and if not, let
-    // the rest of the prototype chain be checked.
-    NS_ConvertUTF16toUTF8 name(reinterpret_cast<const PRUnichar *>(nameChars),
-                               nameLength);
-    nsresult rv = mStatement->GetParameterIndex(name, &idx);
-    if (NS_FAILED(rv)) {
-      *_objp = NULL;
-      return NS_OK;
-    }
-
+    // We are unable to tell if there's a parameter with this name and so
+    // we must assume that there is.  This screws the rest of the prototype
+    // chain, but people really shouldn't be depending on this anyways.
     *_retval = ::JS_DefineUCProperty(aCtx, aScopeObj, nameChars, nameLength,
                                      JSVAL_VOID, nsnull, nsnull, 0);
     NS_ENSURE_TRUE(*_retval, NS_OK);
   }
   else {
     // We do not handle other types.
     return NS_OK;
   }
copy from storage/src/mozStorageStatementParams.h
copy to storage/src/mozStorageAsyncStatementParams.h
--- a/storage/src/mozStorageStatementParams.h
+++ b/storage/src/mozStorageAsyncStatementParams.h
@@ -32,43 +32,46 @@
  * 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 _MOZSTORAGESTATEMENTPARAMS_H_
-#define _MOZSTORAGESTATEMENTPARAMS_H_
+#ifndef mozilla_storage_mozStorageAsyncStatementParams_h_
+#define mozilla_storage_mozStorageAsyncStatementParams_h_
 
 #include "mozIStorageStatementWrapper.h"
 #include "nsIXPCScriptable.h"
 
-class mozIStorageStatement;
+class mozIStorageAsyncStatement;
 
 namespace mozilla {
 namespace storage {
 
-class Statement;
+class AsyncStatement;
 
-class StatementParams : public mozIStorageStatementParams
-                      , public nsIXPCScriptable
+/*
+ * Since mozIStorageStatementParams is just a tagging interface we do not have
+ * an async variant.
+ */
+class AsyncStatementParams : public mozIStorageStatementParams
+                           , public nsIXPCScriptable
 {
 public:
-  StatementParams(mozIStorageStatement *aStatement);
+  AsyncStatementParams(AsyncStatement *aStatement);
 
   // interfaces
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGESTATEMENTPARAMS
   NS_DECL_NSIXPCSCRIPTABLE
 
 protected:
-  mozIStorageStatement *mStatement;
-  PRUint32 mParamCount;
+  AsyncStatement *mStatement;
 
-  friend class Statement;
+  friend class AsyncStatement;
 };
 
 } // namespace storage
 } // namespace mozilla
 
-#endif /* _MOZSTORAGESTATEMENTPARAMS_H_ */
+#endif // mozilla_storage_mozStorageAsyncStatementParams_h_
--- a/storage/src/mozStorageBindingParams.cpp
+++ b/storage/src/mozStorageBindingParams.cpp
@@ -17,16 +17,17 @@
  *
  * 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)
+ *   Andrew Sutherland <asutherland@asutherland.org>
  *
  * 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
@@ -131,52 +132,125 @@ sqlite3_T_blob(BindingColumnData aData,
 
 }
 
 #include "variantToSQLiteT_impl.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 //// BindingParams
 
-BindingParams::BindingParams(BindingParamsArray *aOwningArray,
+BindingParams::BindingParams(mozIStorageBindingParamsArray *aOwningArray,
                              Statement *aOwningStatement)
-: mOwningArray(aOwningArray)
+: mLocked(false)
+, mOwningArray(aOwningArray)
 , mOwningStatement(aOwningStatement)
-, mLocked(false)
 {
   (void)mOwningStatement->GetParameterCount(&mParamCount);
   (void)mParameters.SetCapacity(mParamCount);
 }
 
+BindingParams::BindingParams(mozIStorageBindingParamsArray *aOwningArray)
+: mLocked(false)
+, mOwningArray(aOwningArray)
+, mOwningStatement(nsnull)
+, mParamCount(0)
+{
+}
+
+AsyncBindingParams::AsyncBindingParams(
+  mozIStorageBindingParamsArray *aOwningArray
+)
+: BindingParams(aOwningArray)
+{
+  mNamedParameters.Init();
+}
+
 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;
 }
 
 void
-BindingParams::unlock()
+BindingParams::unlock(Statement *aOwningStatement)
 {
   NS_ASSERTION(mLocked == true, "Parameters were not yet locked!");
   mLocked = false;
+  mOwningStatement = aOwningStatement;
 }
 
-const BindingParamsArray *
+const mozIStorageBindingParamsArray *
 BindingParams::getOwner() const
 {
   return mOwningArray;
 }
 
+PLDHashOperator
+AsyncBindingParams::iterateOverNamedParameters(const nsACString &aName,
+                                               nsIVariant *aValue,
+                                               void *voidClosureThunk)
+{
+  NamedParameterIterationClosureThunk *closureThunk =
+    static_cast<NamedParameterIterationClosureThunk *>(voidClosureThunk);
+
+  // We do not accept any forms of names other than ":name", but we need to add
+  // the colon for SQLite.
+  nsCAutoString name(":");
+  name.Append(aName);
+  int oneIdx = ::sqlite3_bind_parameter_index(closureThunk->statement,
+                                              name.get());
+
+  if (oneIdx == 0) {
+    nsCAutoString errMsg(aName);
+    errMsg.Append(NS_LITERAL_CSTRING(" is not a valid named parameter."));
+    closureThunk->err = new Error(SQLITE_RANGE, errMsg.get());
+    return PL_DHASH_STOP;
+  }
+
+  // XPCVariant's AddRef and Release are not thread-safe and so we must not do
+  // anything that would invoke them here on the async thread.  As such we can't
+  // cram aValue into self->mParameters using ReplaceObjectAt so that we can
+  // freeload off of the BindingParams::Bind implementation.
+  int rc = variantToSQLiteT(BindingColumnData(closureThunk->statement,
+                                              oneIdx - 1),
+                            aValue);
+  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(closureThunk->statement));
+
+    closureThunk->err = new Error(rc, msg);
+    return PL_DHASH_STOP;
+  }
+  return PL_DHASH_NEXT;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(
+  BindingParams
+, mozIStorageBindingParams
+, IStorageBindingParamsInternal
+)
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// IStorageBindingParamsInternal
+
 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
@@ -186,24 +260,36 @@ BindingParams::bind(sqlite3_stmt *aState
       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
-)
+already_AddRefed<mozIStorageError>
+AsyncBindingParams::bind(sqlite3_stmt * aStatement)
+{
+  // We should bind by index using the super-class if there is nothing in our
+  // hashtable.
+  if (!mNamedParameters.Count())
+    return BindingParams::bind(aStatement);
+
+  // Enumerate over everyone in the map, propagating them into mParameters if
+  // we can and creating an error immediately when we cannot.
+  NamedParameterIterationClosureThunk closureThunk = {this, aStatement, nsnull};
+  (void)mNamedParameters.EnumerateRead(iterateOverNamedParameters,
+                                       (void *)&closureThunk);
+
+  return closureThunk.err.forget();
+}
+
 
 ///////////////////////////////////////////////////////////////////////////////
 //// mozIStorageBindingParams
 
 NS_IMETHODIMP
 BindingParams::BindByName(const nsACString &aName,
                           nsIVariant *aValue)
 {
@@ -213,16 +299,28 @@ BindingParams::BindByName(const nsACStri
   PRUint32 index;
   nsresult rv = mOwningStatement->GetParameterIndex(aName, &index);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return BindByIndex(index, aValue);
 }
 
 NS_IMETHODIMP
+AsyncBindingParams::BindByName(const nsACString &aName,
+                               nsIVariant *aValue)
+{
+  NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+
+  if (!mNamedParameters.Put(aName, aValue))
+    return NS_ERROR_OUT_OF_MEMORY;
+  return NS_OK;
+}
+
+
+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);
 }
@@ -301,16 +399,30 @@ BindingParams::BindByIndex(PRUint32 aInd
 
   // Store the variant for later use.
   NS_ENSURE_TRUE(mParameters.ReplaceObjectAt(aValue, aIndex),
                  NS_ERROR_OUT_OF_MEMORY);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+AsyncBindingParams::BindByIndex(PRUint32 aIndex,
+                                nsIVariant *aValue)
+{
+  NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+  // In the asynchronous case we do not know how many parameters there are to
+  // bind to, so we cannot check the validity of aIndex.
+
+  // Store the variant for later use.
+  NS_ENSURE_TRUE(mParameters.ReplaceObjectAt(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);
 }
--- a/storage/src/mozStorageBindingParams.h
+++ b/storage/src/mozStorageBindingParams.h
@@ -17,16 +17,17 @@
  *
  * 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)
+ *   Andrew Sutherland <asutherland@asutherland.org>
  *
  * 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
@@ -35,69 +36,120 @@
  * 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 "nsTHashtable.h"
 
 #include "mozStorageBindingParamsArray.h"
 #include "mozStorageStatement.h"
-#include "mozIStorageBindingParams.h"
+#include "mozStorageAsyncStatement.h"
 
-class mozIStorageError;
-struct sqlite3_stmt;
+#include "mozIStorageBindingParams.h"
+#include "IStorageBindingParamsInternal.h"
 
 namespace mozilla {
 namespace storage {
 
 class BindingParams : public mozIStorageBindingParams
+                    , public IStorageBindingParamsInternal
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGEBINDINGPARAMS
+  NS_DECL_ISTORAGEBINDINGPARAMSINTERNAL
 
   /**
    * Locks the parameters and prevents further modification to it (such as
    * binding more elements to it).
    */
   void lock();
 
   /**
    * Unlocks the parameters and allows modification to it again.
+   *
+   * @param aOwningStatement
+   *        The statement that owns us.  We cleared this when we were locked,
+   *        and our invariant requires us to have this, so you need to tell us
+   *        again.
    */
-  void unlock();
+  void unlock(Statement *aOwningStatement);
 
   /**
-   * @returns the pointer to the owning BindingParamsArray.
+   * @returns the pointer to the owning BindingParamsArray.  Used by a
+   *          BindingParamsArray to verify that we belong to it when added.
    */
-  const BindingParamsArray *getOwner() const;
+  const mozIStorageBindingParamsArray *getOwner() const;
+
+  BindingParams(mozIStorageBindingParamsArray *aOwningArray,
+                Statement *aOwningStatement);
+  virtual ~BindingParams() {}
+
+protected:
+  BindingParams(mozIStorageBindingParamsArray *aOwningArray);
+  nsCOMArray<nsIVariant> mParameters;
+  bool mLocked;
+
+private:
 
   /**
-   * 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.
+   * Track the BindingParamsArray that created us until we are added to it.
+   * (Once we are added we are locked and no one needs to look up our owner.)
+   * Ref-counted since there is no invariant that guarantees it stays alive
+   * otherwise.  This keeps mOwningStatement alive for us too since the array
+   * also holds a reference.
+   */
+  nsCOMPtr<mozIStorageBindingParamsArray> mOwningArray;
+  /**
+   * Used in the synchronous binding case to map parameter names to indices.
+   * Not reference-counted because this is only non-null as long as mOwningArray
+   * is non-null and mOwningArray also holds a statement reference.
    */
-  already_AddRefed<mozIStorageError> bind(sqlite3_stmt *aStatement);
+  Statement *mOwningStatement;
+  PRUint32 mParamCount;
+};
 
-  BindingParams(BindingParamsArray *aOwningArray,
-                Statement *aOwningStatement);
+/**
+ * Adds late resolution of named parameters so they don't get resolved until we
+ * try and bind the parameters on the async thread.  We also stop checking
+ * parameter indices for being too big since we just just don't know how many
+ * there are.
+ *
+ * We support *either* binding by name or binding by index.  Trying to do both
+ * results in only binding by name at sqlite3_stmt bind time.
+ */
+class AsyncBindingParams : public BindingParams
+{
+public:
+  NS_SCRIPTABLE NS_IMETHOD BindByName(const nsACString & aName,
+                                      nsIVariant *aValue);
+  NS_SCRIPTABLE NS_IMETHOD BindByIndex(PRUint32 aIndex, nsIVariant *aValue);
+
+  virtual already_AddRefed<mozIStorageError> bind(sqlite3_stmt * aStatement);
+
+  AsyncBindingParams(mozIStorageBindingParamsArray *aOwningArray);
+  virtual ~AsyncBindingParams() {}
 
 private:
-  nsRefPtr<BindingParamsArray> mOwningArray;
-  Statement *mOwningStatement;
-  nsCOMArray<nsIVariant> mParameters;
-  PRUint32 mParamCount;
-  bool mLocked;
+  nsInterfaceHashtable<nsCStringHashKey, nsIVariant> mNamedParameters;
+
+  struct NamedParameterIterationClosureThunk
+  {
+    AsyncBindingParams *self;
+    sqlite3_stmt *statement;
+    nsCOMPtr<mozIStorageError> err;
+  };
+
+  static PLDHashOperator iterateOverNamedParameters(const nsACString &aName,
+                                                    nsIVariant *aValue,
+                                                    void *voidClosureThunk);
 };
 
 } // namespace storage
 } // namespace mozilla
 
 #endif // _mozStorageBindingParams_h_
--- a/storage/src/mozStorageBindingParamsArray.cpp
+++ b/storage/src/mozStorageBindingParamsArray.cpp
@@ -34,41 +34,44 @@
  * 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"
+#include "StorageBaseStatementInternal.h"
 
 namespace mozilla {
 namespace storage {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// BindingParamsArray
 
-BindingParamsArray::BindingParamsArray(Statement *aOwningStatement)
+BindingParamsArray::BindingParamsArray(
+  StorageBaseStatementInternal *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 *
+const StorageBaseStatementInternal *
 BindingParamsArray::getOwner() const
 {
   return mOwningStatement;
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(
   BindingParamsArray,
   mozIStorageBindingParamsArray
@@ -77,19 +80,19 @@ NS_IMPL_THREADSAFE_ISUPPORTS1(
 ///////////////////////////////////////////////////////////////////////////////
 //// 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);
+  nsCOMPtr<mozIStorageBindingParams> params(
+    mOwningStatement->newBindingParams(this));
+  NS_ENSURE_TRUE(params, NS_ERROR_UNEXPECTED);
 
   params.forget(_params);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BindingParamsArray::AddParams(mozIStorageBindingParams *aParameters)
 {
--- a/storage/src/mozStorageBindingParamsArray.h
+++ b/storage/src/mozStorageBindingParamsArray.h
@@ -43,39 +43,38 @@
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 
 #include "mozIStorageBindingParamsArray.h"
 
 namespace mozilla {
 namespace storage {
 
-class BindingParams;
-class Statement;
+class StorageBaseStatementInternal;
 
 class BindingParamsArray : public mozIStorageBindingParamsArray
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGEBINDINGPARAMSARRAY
 
-  BindingParamsArray(Statement *aOwningStatement);
+  BindingParamsArray(StorageBaseStatementInternal *aOwningStatement);
 
   typedef nsTArray_base::size_type size_type;
 
   /**
    * Locks the array and prevents further modification to it (such as adding
    * more elements to it).
    */
   void lock();
 
   /**
    * @return the pointer to the owning BindingParamsArray.
    */
-  const Statement *getOwner() const;
+  const StorageBaseStatementInternal *getOwner() const;
 
   /**
    * @return the number of elemets the array contains.
    */
   const size_type length() const { return mArray.Length(); }
 
   class iterator {
   public:
@@ -95,17 +94,17 @@ public:
     bool operator==(const iterator &aOther) const
     {
       return mIndex == aOther.mIndex;
     }
     bool operator!=(const iterator &aOther) const
     {
       return !(*this == aOther);
     }
-    BindingParams *operator*()
+    mozIStorageBindingParams *operator*()
     {
       NS_ASSERTION(mIndex < mArray->length(),
                    "Dereferenceing an invalid value!");
       return mArray->mArray[mIndex].get();
     }
   private:
     void operator--() { }
     BindingParamsArray *mArray;
@@ -127,18 +126,18 @@ public:
    */
   inline iterator end()
   {
     NS_ASSERTION(mLocked,
                  "Obtaining an iterator to the end when we are not locked!");
     return iterator(this, length());
   }
 private:
-  nsRefPtr<Statement> mOwningStatement;
-  nsTArray< nsRefPtr<BindingParams> > mArray;
+  nsCOMPtr<StorageBaseStatementInternal> mOwningStatement;
+  nsTArray< nsCOMPtr<mozIStorageBindingParams> > mArray;
   bool mLocked;
 
   friend class iterator;
 };
 
 } // namespace storage
 } // namespace mozilla
 
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -57,19 +57,21 @@
 #include "mozIStorageCompletionCallback.h"
 #include "mozIStorageFunction.h"
 
 #include "mozStorageAsyncStatementExecution.h"
 #include "mozStorageSQLFunctions.h"
 #include "mozStorageConnection.h"
 #include "mozStorageService.h"
 #include "mozStorageStatement.h"
+#include "mozStorageAsyncStatement.h"
 #include "mozStorageArgValueArray.h"
 #include "mozStoragePrivateHelpers.h"
 #include "mozStorageStatementData.h"
+#include "StorageBaseStatementInternal.h"
 #include "SQLCollations.h"
 
 #include "prlog.h"
 #include "prprf.h"
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gStorageLog = nsnull;
 #endif
@@ -291,20 +293,20 @@ private:
 
 } // anonymous namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Connection
 
 Connection::Connection(Service *aService)
 : sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex")
+, sharedDBMutex("Connection::sharedDBMutex")
 , threadOpenedOn(do_GetCurrentThread())
 , mDBConn(nsnull)
 , mAsyncExecutionThreadShuttingDown(false)
-, mDBMutex("Connection::mDBMutex")
 , mTransactionInProgress(PR_FALSE)
 , mProgressHandler(nsnull)
 , mStorageService(aService)
 {
   mFunctions.Init();
 }
 
 Connection::~Connection()
@@ -312,17 +314,17 @@ Connection::~Connection()
   (void)Close();
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(
   Connection,
   mozIStorageConnection
 )
 
-already_AddRefed<nsIEventTarget>
+nsIEventTarget *
 Connection::getAsyncExecutionTarget()
 {
   MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
 
   // If we are shutting down the asynchronous thread, don't hand out any more
   // references to the thread.
   if (mAsyncExecutionThreadShuttingDown)
     return nsnull;
@@ -330,19 +332,17 @@ Connection::getAsyncExecutionTarget()
   if (!mAsyncExecutionThread) {
     nsresult rv = ::NS_NewThread(getter_AddRefs(mAsyncExecutionThread));
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to create async thread.");
       return nsnull;
     }
   }
 
-  nsIEventTarget *eventTarget;
-  NS_ADDREF(eventTarget = mAsyncExecutionThread);
-  return eventTarget;
+  return mAsyncExecutionThread;
 }
 
 nsresult
 Connection::initialize(nsIFile *aDatabaseFile)
 {
   NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
 
   int srv;
@@ -362,17 +362,17 @@ Connection::initialize(nsIFile *aDatabas
     srv = ::sqlite3_open(":memory:", &mDBConn);
   }
   if (srv != SQLITE_OK) {
     mDBConn = nsnull;
     return convertResultCode(srv);
   }
 
   // Properly wrap the database handle's mutex.
-  mDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn));
+  sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn));
 
 #ifdef PR_LOGGING
   if (!gStorageLog)
     gStorageLog = ::PR_NewLogModule("mozStorage");
 
   ::sqlite3_trace(mDBConn, tracefunc, this);
 
   nsCAutoString leafName(":memory");
@@ -482,33 +482,33 @@ Connection::databaseElementExists(enum D
   }
 
   return convertResultCode(srv);
 }
 
 bool
 Connection::findFunctionByInstance(nsISupports *aInstance)
 {
-  mDBMutex.assertCurrentThreadOwns();
+  sharedDBMutex.assertCurrentThreadOwns();
   FFEArguments args = { aInstance, false };
   mFunctions.EnumerateRead(findFunctionEnumerator, &args);
   return args.found;
 }
 
 /* static */ int
 Connection::sProgressHelper(void *aArg)
 {
   Connection *_this = static_cast<Connection *>(aArg);
   return _this->progressHandler();
 }
 
 int
 Connection::progressHandler()
 {
-  mDBMutex.assertCurrentThreadOwns();
+  sharedDBMutex.assertCurrentThreadOwns();
   if (mProgressHandler) {
     PRBool result;
     nsresult rv = mProgressHandler->OnProgress(this, &result);
     if (NS_FAILED(rv)) return 0; // Don't break request
     return result ? 1 : 0;
   }
   return 0;
 }
@@ -605,17 +605,17 @@ Connection::Close()
 }
 
 NS_IMETHODIMP
 Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
 {
   if (!mDBConn)
     return NS_ERROR_NOT_INITIALIZED;
 
-  nsCOMPtr<nsIEventTarget> asyncThread(getAsyncExecutionTarget());
+  nsIEventTarget *asyncThread = getAsyncExecutionTarget();
   NS_ENSURE_TRUE(asyncThread, NS_ERROR_UNEXPECTED);
 
   nsresult rv = setClosedState();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Create our callback event if we were given a callback.
   nsCOMPtr<nsIRunnable> completeEvent;
   if (aCallback) {
@@ -728,41 +728,61 @@ Connection::CreateStatement(const nsACSt
 
   Statement *rawPtr;
   statement.forget(&rawPtr);
   *_stmt = rawPtr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+Connection::CreateAsyncStatement(const nsACString &aSQLStatement,
+                                 mozIStorageAsyncStatement **_stmt)
+{
+  NS_ENSURE_ARG_POINTER(_stmt);
+  if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<AsyncStatement> statement(new AsyncStatement());
+  NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv = statement->initialize(this, aSQLStatement);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AsyncStatement *rawPtr;
+  statement.forget(&rawPtr);
+  *_stmt = rawPtr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
   int srv = ::sqlite3_exec(mDBConn, PromiseFlatCString(aSQLStatement).get(),
                            NULL, NULL, NULL);
   return convertResultCode(srv);
 }
 
-nsresult
-Connection::ExecuteAsync(mozIStorageStatement **aStatements,
+NS_IMETHODIMP
+Connection::ExecuteAsync(mozIStorageBaseStatement **aStatements,
                          PRUint32 aNumStatements,
                          mozIStorageStatementCallback *aCallback,
                          mozIStoragePendingStatement **_handle)
 {
   nsTArray<StatementData> stmts(aNumStatements);
   for (PRUint32 i = 0; i < aNumStatements; i++) {
-    Statement *stmt = static_cast<Statement *>(aStatements[i]);
+    nsCOMPtr<StorageBaseStatementInternal> stmt = 
+      do_QueryInterface(aStatements[i]);
 
     // Obtain our StatementData.
     StatementData data;
     nsresult rv = stmt->getAsynchronousStatementData(data);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    NS_ASSERTION(::sqlite3_db_handle(stmt->nativeStatement()) == mDBConn,
+    NS_ASSERTION(stmt->getOwner() == this,
                  "Statement must be from this database connection!");
 
     // Now append it to our array.
     NS_ENSURE_TRUE(stmts.AppendElement(data), NS_ERROR_OUT_OF_MEMORY);
   }
 
   // Dispatch to the background
   return AsyncExecuteStatements::execute(stmts, this, aCallback, _handle);
@@ -782,33 +802,33 @@ Connection::IndexExists(const nsACString
     return databaseElementExists(INDEX, aIndexName, _exists);
 }
 
 NS_IMETHODIMP
 Connection::GetTransactionInProgress(PRBool *_inProgress)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
-  SQLiteMutexAutoLock lockedScope(mDBMutex);
+  SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   *_inProgress = mTransactionInProgress;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::BeginTransaction()
 {
   return BeginTransactionAs(mozIStorageConnection::TRANSACTION_DEFERRED);
 }
 
 NS_IMETHODIMP
 Connection::BeginTransactionAs(PRInt32 aTransactionType)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
-  SQLiteMutexAutoLock lockedScope(mDBMutex);
+  SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   if (mTransactionInProgress)
     return NS_ERROR_FAILURE;
   nsresult rv;
   switch(aTransactionType) {
     case TRANSACTION_DEFERRED:
       rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN DEFERRED"));
       break;
     case TRANSACTION_IMMEDIATE:
@@ -825,31 +845,31 @@ Connection::BeginTransactionAs(PRInt32 a
   return rv;
 }
 
 NS_IMETHODIMP
 Connection::CommitTransaction()
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
-  SQLiteMutexAutoLock lockedScope(mDBMutex);
+  SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   if (!mTransactionInProgress)
     return NS_ERROR_FAILURE;
   nsresult rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT TRANSACTION"));
   if (NS_SUCCEEDED(rv))
     mTransactionInProgress = PR_FALSE;
   return rv;
 }
 
 NS_IMETHODIMP
 Connection::RollbackTransaction()
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
-  SQLiteMutexAutoLock lockedScope(mDBMutex);
+  SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   if (!mTransactionInProgress)
     return NS_ERROR_FAILURE;
   nsresult rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK TRANSACTION"));
   if (NS_SUCCEEDED(rv))
     mTransactionInProgress = PR_FALSE;
   return rv;
 }
 
@@ -873,17 +893,17 @@ NS_IMETHODIMP
 Connection::CreateFunction(const nsACString &aFunctionName,
                            PRInt32 aNumArguments,
                            mozIStorageFunction *aFunction)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
   // Check to see if this function is already defined.  We only check the name
   // because a function can be defined with the same body but different names.
-  SQLiteMutexAutoLock lockedScope(mDBMutex);
+  SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, NULL), NS_ERROR_FAILURE);
 
   int srv = ::sqlite3_create_function(mDBConn,
                                       nsPromiseFlatCString(aFunctionName).get(),
                                       aNumArguments,
                                       SQLITE_ANY,
                                       aFunction,
                                       basicFunctionHelper,
@@ -901,17 +921,17 @@ Connection::CreateFunction(const nsACStr
 NS_IMETHODIMP
 Connection::CreateAggregateFunction(const nsACString &aFunctionName,
                                     PRInt32 aNumArguments,
                                     mozIStorageAggregateFunction *aFunction)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
   // Check to see if this function name is already defined.
-  SQLiteMutexAutoLock lockedScope(mDBMutex);
+  SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, NULL), NS_ERROR_FAILURE);
 
   // Because aggregate functions depend on state across calls, you cannot have
   // the same instance use the same name.  We want to enumerate all functions
   // and make sure this instance is not already registered.
   NS_ENSURE_FALSE(findFunctionByInstance(aFunction), NS_ERROR_FAILURE);
 
   int srv = ::sqlite3_create_function(mDBConn,
@@ -931,17 +951,17 @@ Connection::CreateAggregateFunction(cons
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::RemoveFunction(const nsACString &aFunctionName)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
-  SQLiteMutexAutoLock lockedScope(mDBMutex);
+  SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, NULL), NS_ERROR_FAILURE);
 
   int srv = ::sqlite3_create_function(mDBConn,
                                       nsPromiseFlatCString(aFunctionName).get(),
                                       0,
                                       SQLITE_ANY,
                                       NULL,
                                       NULL,
@@ -958,17 +978,17 @@ Connection::RemoveFunction(const nsACStr
 NS_IMETHODIMP
 Connection::SetProgressHandler(PRInt32 aGranularity,
                                mozIStorageProgressHandler *aHandler,
                                mozIStorageProgressHandler **_oldHandler)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
   // Return previous one
-  SQLiteMutexAutoLock lockedScope(mDBMutex);
+  SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_IF_ADDREF(*_oldHandler = mProgressHandler);
 
   if (!aHandler || aGranularity <= 0) {
     aHandler = nsnull;
     aGranularity = 0;
   }
   mProgressHandler = aHandler;
   ::sqlite3_progress_handler(mDBConn, aGranularity, sProgressHelper, this);
@@ -977,17 +997,17 @@ Connection::SetProgressHandler(PRInt32 a
 }
 
 NS_IMETHODIMP
 Connection::RemoveProgressHandler(mozIStorageProgressHandler **_oldHandler)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
   // Return previous one
-  SQLiteMutexAutoLock lockedScope(mDBMutex);
+  SQLiteMutexAutoLock lockedScope(sharedDBMutex);
   NS_IF_ADDREF(*_oldHandler = mProgressHandler);
 
   mProgressHandler = nsnull;
   ::sqlite3_progress_handler(mDBConn, 0, NULL, NULL);
 
   return NS_OK;
 }
 
--- a/storage/src/mozStorageConnection.h
+++ b/storage/src/mozStorageConnection.h
@@ -87,27 +87,35 @@ public:
 
   /**
    * Lazily creates and returns a background execution thread.  In the future,
    * the thread may be re-claimed if left idle, so you should call this
    * method just before you dispatch and not save the reference.
    *
    * @returns an event target suitable for asynchronous statement execution.
    */
-  already_AddRefed<nsIEventTarget> getAsyncExecutionTarget();
+  nsIEventTarget *getAsyncExecutionTarget();
 
   /**
    * Mutex used by asynchronous statements to protect state.  The mutex is
    * declared on the connection object because there is no contention between
-   * asynchronous statements (they are serialized on mAsyncExecutionThread).  It also
-   * protects mPendingStatements.
+   * asynchronous statements (they are serialized on mAsyncExecutionThread).  It
+   * also protects mPendingStatements.
    */
   Mutex sharedAsyncExecutionMutex;
 
   /**
+   * Wraps the mutex that SQLite gives us from sqlite3_db_mutex.  This is public
+   * because we already expose the sqlite3* native connection and proper
+   * operation of the deadlock detector requires everyone to use the same single
+   * SQLiteMutex instance for correctness.
+   */
+  SQLiteMutex sharedDBMutex;
+
+  /**
    * References the thread this database was opened on.  This MUST be thread it is
    * closed on.
    */
   const nsCOMPtr<nsIThread> threadOpenedOn;
 
   /**
    * Closes the SQLite database, and warns about any non-finalized statements.
    */
@@ -170,21 +178,16 @@ private:
    * Set to true by Close() prior to actually shutting down the thread.  This
    * lets getAsyncExecutionTarget() know not to hand out any more thread
    * references (or to create the thread in the first place).  This variable
    * should be accessed while holding the mAsyncExecutionMutex.
    */
   bool mAsyncExecutionThreadShuttingDown;
 
   /**
-   * Wraps the mutex that SQLite gives us from sqlite3_db_mutex.
-   */
-  SQLiteMutex mDBMutex;
-
-  /**
    * Tracks if we have a transaction in progress or not.  Access protected by
    * mDBMutex.
    */
   PRBool mTransactionInProgress;
 
   /**
    * Stores the mapping of a given function by name to its instance.  Access is
    * protected by mDBMutex.
--- a/storage/src/mozStoragePrivateHelpers.cpp
+++ b/storage/src/mozStoragePrivateHelpers.cpp
@@ -43,19 +43,21 @@
 #include "jsapi.h"
 #include "jsdate.h"
 
 #include "nsPrintfCString.h"
 #include "nsString.h"
 #include "nsError.h"
 #include "nsThreadUtils.h"
 
+#include "Variant.h"
 #include "mozStoragePrivateHelpers.h"
 #include "mozIStorageStatement.h"
 #include "mozIStorageCompletionCallback.h"
+#include "mozIStorageBindingParams.h"
 
 namespace mozilla {
 namespace storage {
 
 nsresult
 convertResultCode(int aSQLiteResultCode)
 {
   switch (aSQLiteResultCode) {
@@ -117,72 +119,60 @@ checkAndLogStatementPerformance(sqlite3_
   message.Append("occurred for the SQL statement '");
   nsPrintfCString address("0x%p", aStatement);
   message.Append(address);
   message.Append("'.  See https://developer.mozilla.org/En/Storage/Warnings "
                  "details.");
   NS_WARNING(message.get());
 }
 
-bool
-bindJSValue(JSContext *aCtx,
-            mozIStorageStatement *aStatement,
-            int aIdx,
-            jsval aValue)
+nsIVariant *
+convertJSValToVariant(
+  JSContext *aCtx,
+  jsval aValue)
 {
-  if (JSVAL_IS_INT(aValue)) {
-    int v = JSVAL_TO_INT(aValue);
-    (void)aStatement->BindInt32Parameter(aIdx, v);
-    return true;
-  }
+  if (JSVAL_IS_INT(aValue))
+    return new IntegerVariant(JSVAL_TO_INT(aValue));
 
-  if (JSVAL_IS_DOUBLE(aValue)) {
-    double d = *JSVAL_TO_DOUBLE(aValue);
-    (void)aStatement->BindDoubleParameter(aIdx, d);
-    return true;
-  }
+  if (JSVAL_IS_DOUBLE(aValue))
+    return new FloatVariant(*JSVAL_TO_DOUBLE(aValue));
 
   if (JSVAL_IS_STRING(aValue)) {
     JSString *str = JSVAL_TO_STRING(aValue);
     nsDependentString value(
       reinterpret_cast<PRUnichar *>(::JS_GetStringChars(str)),
       ::JS_GetStringLength(str)
     );
-    (void)aStatement->BindStringParameter(aIdx, value);
-    return true;
+    return new TextVariant(value);
   }
 
-  if (JSVAL_IS_BOOLEAN(aValue)) {
-    (void)aStatement->BindInt32Parameter(aIdx, (aValue == JSVAL_TRUE) ? 1 : 0);
-    return true;
-  }
+  if (JSVAL_IS_BOOLEAN(aValue))
+    return new IntegerVariant((aValue == JSVAL_TRUE) ? 1 : 0);
 
-  if (JSVAL_IS_NULL(aValue)) {
-    (void)aStatement->BindNullParameter(aIdx);
-    return true;
-  }
+  if (JSVAL_IS_NULL(aValue))
+    return new NullVariant();
 
   if (JSVAL_IS_OBJECT(aValue)) {
     JSObject *obj = JSVAL_TO_OBJECT(aValue);
-    // some special things
+    // We only support Date instances, all others fail.
     if (!::js_DateIsValid(aCtx, obj))
-      return false;
+      return nsnull;
 
     double msecd = ::js_DateGetMsecSinceEpoch(aCtx, obj);
     msecd *= 1000.0;
     PRInt64 msec;
     LL_D2L(msec, msecd);
 
-    (void)aStatement->BindInt64Parameter(aIdx, msec);
-    return true;
+    return new IntegerVariant(msec);
   }
 
-  return false;
+  return nsnull;
 }
 
+
 namespace {
 class CallbackEvent : public nsRunnable
 {
 public:
   CallbackEvent(mozIStorageCompletionCallback *aCallback)
   : mCallback(aCallback)
   {
   }
--- a/storage/src/mozStoragePrivateHelpers.h
+++ b/storage/src/mozStoragePrivateHelpers.h
@@ -47,17 +47,18 @@
 
 #include "sqlite3.h"
 #include "nsIVariant.h"
 #include "mozStorage.h"
 #include "jsapi.h"
 #include "nsAutoPtr.h"
 
 class mozIStorageCompletionCallback;
-class mozIStorageStatement;
+class mozIStorageBaseStatement;
+class mozIStorageBindingParams;
 class nsIRunnable;
 
 namespace mozilla {
 namespace storage {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Macros
 
@@ -83,37 +84,36 @@ nsresult convertResultCode(int aSQLiteRe
  * made faster with the careful use of an index.
  *
  * @param aStatement
  *        The sqlite3_stmt object to check.
  */
 void checkAndLogStatementPerformance(sqlite3_stmt *aStatement);
 
 /**
- * Binds a jsval to a statement at the given index.
+ * Convert the provided jsval into a variant representation if possible.
  *
  * @param aCtx
- *        The JSContext jsval is associated with.
- * @param aStatement
- *        The statement to bind to.
- * @param aIdx
- *        The one-based index to bind aValue to.
+ *        The JSContext the value is from.
  * @param aValue
- *        The value to bind to aStatement.
- * @return true if we bound the value to the statement, false otherwise.
+ *        The JavaScript value to convert.  All primitive types are supported,
+ *        but only Date objects are supported from the Date family.  Date
+ *        objects are coerced to PRTime (nanoseconds since epoch) values.
+ * @return the variant if conversion was successful, nsnull if conversion
+ *         failed.  The caller is responsible for addref'ing if non-null.
  */
-bool bindJSValue(JSContext *aCtx, mozIStorageStatement *aStatement, int aIdx,
-                 jsval aValue);
+nsIVariant *convertJSValToVariant(JSContext *aCtx, jsval aValue);
 
 /**
  * Obtains an event that will notify a completion callback about completion.
  *
  * @param aCallback
  *        The callback to be notified.
  * @return an nsIRunnable that can be dispatched to the calling thread.
  */
-already_AddRefed<nsIRunnable>
-newCompletionEvent(mozIStorageCompletionCallback *aCallback);
+already_AddRefed<nsIRunnable> newCompletionEvent(
+  mozIStorageCompletionCallback *aCallback
+);
 
 } // namespace storage
 } // namespace mozilla
 
 #endif // _mozStoragePrivateHelpers_h_
--- a/storage/src/mozStorageStatement.cpp
+++ b/storage/src/mozStorageStatement.cpp
@@ -34,24 +34,25 @@
  * 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 <stdio.h>
 
 #include "nsError.h"
 #include "nsMemory.h"
-#include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIProgrammingLanguage.h"
+#include "Variant.h"
 
 #include "mozIStorageError.h"
 
 #include "mozStorageBindingParams.h"
 #include "mozStorageConnection.h"
 #include "mozStorageStatementJSHelper.h"
 #include "mozStoragePrivateHelpers.h"
 #include "mozStorageStatementParams.h"
@@ -63,64 +64,25 @@
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gStorageLog;
 #endif
 
 namespace mozilla {
 namespace storage {
 
 ////////////////////////////////////////////////////////////////////////////////
-//// Local Classes
-
-namespace {
-
-/**
- * Used to finalize an asynchronous statement on the background thread.
- */
-class AsyncStatementFinalizer : public nsRunnable
-{
-public:
-  /**
-   * Constructor for the event.
-   *
-   * @param aStatement
-   *        The sqlite3_stmt to finalize on the background thread.
-   * @param aConnection
-   *        The Connection that aStatement was created on.  We hold a reference
-   *        to this to ensure that if we are the last reference to the
-   *        Connection, that we release it on the proper thread.  The release
-   *        call is proxied to the appropriate thread.
-   */
-  AsyncStatementFinalizer(sqlite3_stmt *aStatement,
-                          Connection *aConnection)
-  : mStatement(aStatement)
-  , mConnection(aConnection)
-  {
-  }
-
-  NS_IMETHOD Run()
-  {
-    (void)::sqlite3_finalize(mStatement);
-    (void)::NS_ProxyRelease(mConnection->threadOpenedOn, mConnection);
-    return NS_OK;
-  }
-private:
-  sqlite3_stmt *mStatement;
-  nsCOMPtr<Connection> mConnection;
-};
-
-} // anonymous namespace
-
-////////////////////////////////////////////////////////////////////////////////
 //// nsIClassInfo
 
-NS_IMPL_CI_INTERFACE_GETTER2(
+NS_IMPL_CI_INTERFACE_GETTER5(
   Statement,
   mozIStorageStatement,
-  mozIStorageValueArray
+  mozIStorageBaseStatement,
+  mozIStorageBindingParams,
+  mozIStorageValueArray,
+  mozilla::storage::StorageBaseStatementInternal
 )
 
 class StatementClassInfo : public nsIClassInfo
 {
 public:
   NS_DECL_ISUPPORTS
 
   NS_IMETHODIMP
@@ -189,21 +151,20 @@ NS_IMETHODIMP_(nsrefcnt) StatementClassI
 NS_IMPL_QUERY_INTERFACE1(StatementClassInfo, nsIClassInfo)
 
 static StatementClassInfo sStatementClassInfo;
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Statement
 
 Statement::Statement()
-: mDBConnection(nsnull)
+: StorageBaseStatementInternal()
 , mDBStatement(NULL)
 , mColumnNames()
 , mExecuting(false)
-, mCachedAsyncStatement(NULL)
 {
 }
 
 nsresult
 Statement::initialize(Connection *aDBConnection,
                       const nsACString &aSQLStatement)
 {
   NS_ASSERTION(aDBConnection, "No database connection given!");
@@ -274,58 +235,17 @@ Statement::initialize(Connection *aDBCon
     start = e;
     e = end;
   }
 #endif
 
   return NS_OK;
 }
 
-nsresult
-Statement::getAsynchronousStatementData(StatementData &_data)
-{
-  if (!mDBStatement)
-    return NS_ERROR_UNEXPECTED;
-
-  sqlite3_stmt *stmt;
-  int rc = getAsyncStatement(&stmt);
-  if (rc != SQLITE_OK)
-    return convertResultCode(rc);
-
-  _data = StatementData(stmt, bindingParamsArray(), this);
-
-  return NS_OK;
-}
-
-int
-Statement::getAsyncStatement(sqlite3_stmt **_stmt)
-{
-  // If we have no statement, we shouldn't be calling this method!
-  NS_ASSERTION(mDBStatement != NULL, "We have no statement to clone!");
-
-  // If we do not yet have a cached async statement, clone our statement now.
-  if (!mCachedAsyncStatement) {
-    int rc = ::sqlite3_prepare_v2(mDBConnection->GetNativeConnection(),
-                                  ::sqlite3_sql(mDBStatement), -1,
-                                  &mCachedAsyncStatement, NULL);
-    if (rc != SQLITE_OK)
-      return rc;
-
-#ifdef PR_LOGGING
-    PR_LOG(gStorageLog, PR_LOG_NOTICE,
-           ("Cloned statement 0x%p to 0x%p", mDBStatement,
-            mCachedAsyncStatement));
-#endif
-  }
-
-  *_stmt = mCachedAsyncStatement;
-  return SQLITE_OK;
-}
-
-BindingParams *
+mozIStorageBindingParams *
 Statement::getParams()
 {
   nsresult rv;
 
   // If we do not have an array object yet, make it.
   if (!mParamsArray) {
     nsCOMPtr<mozIStorageBindingParamsArray> array;
     rv = NewBindingParamsArray(getter_AddRefs(array));
@@ -339,48 +259,117 @@ Statement::getParams()
     nsRefPtr<BindingParams> params(new BindingParams(mParamsArray, this));
     NS_ENSURE_TRUE(params, nsnull);
 
     rv = mParamsArray->AddParams(params);
     NS_ENSURE_SUCCESS(rv, nsnull);
 
     // We have to unlock our params because AddParams locks them.  This is safe
     // because no reference to the params object was, or ever will be given out.
-    params->unlock();
+    params->unlock(this);
 
     // We also want to lock our array at this point - we don't want anything to
     // be added to it.  Nothing has, or will ever get a reference to it, but we
     // will get additional safety checks via assertions by doing this.
     mParamsArray->lock();
   }
 
   return *mParamsArray->begin();
 }
 
 Statement::~Statement()
 {
-  (void)Finalize();
+  (void)internalFinalize(true);
 }
 
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
 NS_IMPL_THREADSAFE_ADDREF(Statement)
 NS_IMPL_THREADSAFE_RELEASE(Statement)
 
 NS_INTERFACE_MAP_BEGIN(Statement)
   NS_INTERFACE_MAP_ENTRY(mozIStorageStatement)
+  NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement)
+  NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams)
   NS_INTERFACE_MAP_ENTRY(mozIStorageValueArray)
+  NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal)
   if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
     foundInterface = static_cast<nsIClassInfo *>(&sStatementClassInfo);
   }
   else
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageStatement)
 NS_INTERFACE_MAP_END
 
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+Connection *
+Statement::getOwner()
+{
+  return mDBConnection;
+}
+
+int
+Statement::getAsyncStatement(sqlite3_stmt **_stmt)
+{
+  // If we have no statement, we shouldn't be calling this method!
+  NS_ASSERTION(mDBStatement != NULL, "We have no statement to clone!");
+
+  // If we do not yet have a cached async statement, clone our statement now.
+  if (!mAsyncStatement) {
+    int rc = ::sqlite3_prepare_v2(mDBConnection->GetNativeConnection(),
+                                  ::sqlite3_sql(mDBStatement), -1,
+                                  &mAsyncStatement, NULL);
+    if (rc != SQLITE_OK) {
+      *_stmt = nsnull;
+      return rc;
+    }
+
+#ifdef PR_LOGGING
+    PR_LOG(gStorageLog, PR_LOG_NOTICE,
+           ("Cloned statement 0x%p to 0x%p", mDBStatement, mAsyncStatement));
+#endif
+  }
+
+  *_stmt = mAsyncStatement;
+  return SQLITE_OK;
+}
+
+nsresult
+Statement::getAsynchronousStatementData(StatementData &_data)
+{
+  if (!mDBStatement)
+    return NS_ERROR_UNEXPECTED;
+
+  sqlite3_stmt *stmt;
+  int rc = getAsyncStatement(&stmt);
+  if (rc != SQLITE_OK)
+    return convertResultCode(rc);
+
+  _data = StatementData(stmt, bindingParamsArray(), this);
+
+  return NS_OK;
+}
+
+already_AddRefed<mozIStorageBindingParams>
+Statement::newBindingParams(mozIStorageBindingParamsArray *aOwner)
+{
+  nsCOMPtr<mozIStorageBindingParams> params = new BindingParams(aOwner, this);
+  return params.forget();
+}
+
+
 ////////////////////////////////////////////////////////////////////////////////
 //// mozIStorageStatement
 
+// proxy to StorageBaseStatementInternal using its define helper.
+MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(Statement, (void)0;)
+
 NS_IMETHODIMP
 Statement::Clone(mozIStorageStatement **_statement)
 {
   nsRefPtr<Statement> statement(new Statement());
   NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
 
   nsCAutoString sql(::sqlite3_sql(mDBStatement));
   nsresult rv = statement->initialize(mDBConnection, sql);
@@ -388,47 +377,40 @@ Statement::Clone(mozIStorageStatement **
 
   statement.forget(_statement);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Statement::Finalize()
 {
+  return internalFinalize(false);
+}
+
+nsresult
+Statement::internalFinalize(bool aDestructing)
+{
   if (!mDBStatement)
     return NS_OK;
 
 #ifdef PR_LOGGING
   PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Finalizing statement '%s'",
                                       ::sqlite3_sql(mDBStatement)));
 #endif
 
   int srv = ::sqlite3_finalize(mDBStatement);
   mDBStatement = NULL;
 
-  // We need to finalize our async statement too, but want to make sure that any
-  // queued up statements run first.  Dispatch an event to the background thread
-  // that will do this for us.
-  if (mCachedAsyncStatement) {
-    nsCOMPtr<nsIEventTarget> target = mDBConnection->getAsyncExecutionTarget();
-    if (!target) {
-      // However, if we cannot get the background thread, we have to assume it
-      // has been shutdown (or is in the process of doing so).  As a result, we
-      // should just finalize it here and now.
-      (void)::sqlite3_finalize(mCachedAsyncStatement);
-    }
-    else {
-      nsCOMPtr<nsIRunnable> event =
-        new AsyncStatementFinalizer(mCachedAsyncStatement, mDBConnection);
-      NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
-
-      nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
-      NS_ENSURE_SUCCESS(rv, rv);
-      mCachedAsyncStatement = NULL;
-    }
+  if (mAsyncStatement) {
+    // If the destructor called us, there are no pending async statements (they
+    // hold a reference to us) and we can/must just kill the statement directly.
+    if (aDestructing)
+      internalAsyncFinalize();
+    else
+      asyncFinalize();
   }
 
   // We are considered dead at this point, so any wrappers for row or params
   // need to lose their reference to us.
   if (mStatementParamsHolder) {
     nsCOMPtr<nsIXPConnectWrappedNative> wrapper =
         do_QueryInterface(mStatementParamsHolder);
     nsCOMPtr<mozIStorageStatementParams> iParams =
@@ -566,107 +548,16 @@ Statement::Reset()
   (void)sqlite3_clear_bindings(mDBStatement);
 
   mExecuting = false;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-Statement::BindUTF8StringParameter(PRUint32 aParamIndex,
-                                   const nsACString &aValue)
-{
-  if (!mDBStatement)
-    return NS_ERROR_NOT_INITIALIZED;
-
-  BindingParams *params = getParams();
-  NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
-
-  return params->BindUTF8StringByIndex(aParamIndex, aValue);
-}
-
-NS_IMETHODIMP
-Statement::BindStringParameter(PRUint32 aParamIndex,
-                               const nsAString &aValue)
-{
-  if (!mDBStatement)
-    return NS_ERROR_NOT_INITIALIZED;
-
-  BindingParams *params = getParams();
-  NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
-
-  return params->BindStringByIndex(aParamIndex, aValue);
-}
-
-NS_IMETHODIMP
-Statement::BindDoubleParameter(PRUint32 aParamIndex,
-                               double aValue)
-{
-  if (!mDBStatement)
-    return NS_ERROR_NOT_INITIALIZED;
-
-  BindingParams *params = getParams();
-  NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
-
-  return params->BindDoubleByIndex(aParamIndex, aValue);
-}
-
-NS_IMETHODIMP
-Statement::BindInt32Parameter(PRUint32 aParamIndex,
-                              PRInt32 aValue)
-{
-  if (!mDBStatement)
-    return NS_ERROR_NOT_INITIALIZED;
-
-  BindingParams *params = getParams();
-  NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
-
-  return params->BindInt32ByIndex(aParamIndex, aValue);
-}
-
-NS_IMETHODIMP
-Statement::BindInt64Parameter(PRUint32 aParamIndex,
-                              PRInt64 aValue)
-{
-  if (!mDBStatement)
-    return NS_ERROR_NOT_INITIALIZED;
-
-  BindingParams *params = getParams();
-  NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
-
-  return params->BindInt64ByIndex(aParamIndex, aValue);
-}
-
-NS_IMETHODIMP
-Statement::BindNullParameter(PRUint32 aParamIndex)
-{
-  if (!mDBStatement)
-    return NS_ERROR_NOT_INITIALIZED;
-
-  BindingParams *params = getParams();
-  NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
-
-  return params->BindNullByIndex(aParamIndex);
-}
-
-NS_IMETHODIMP
-Statement::BindBlobParameter(PRUint32 aParamIndex,
-                             const PRUint8 *aValue,
-                             PRUint32 aValueSize)
-{
-  if (!mDBStatement)
-    return NS_ERROR_NOT_INITIALIZED;
-
-  BindingParams *params = getParams();
-  NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
-
-  return params->BindBlobByIndex(aParamIndex, aValue, aValueSize);
-}
-
-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;
@@ -676,27 +567,16 @@ Statement::BindParameters(mozIStorageBin
 
   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);
@@ -713,18 +593,19 @@ Statement::ExecuteStep(PRBool *_moreResu
   // Bind any parameters first before executing.
   if (mParamsArray) {
     // If we have more than one row of parameters to bind, they shouldn't be
     // calling this method (and instead use executeAsync).
     if (mParamsArray->length() != 1)
       return NS_ERROR_UNEXPECTED;
 
     BindingParamsArray::iterator row = mParamsArray->begin();
-    nsCOMPtr<mozIStorageError> error;
-    error = (*row)->bind(mDBStatement);
+    nsCOMPtr<IStorageBindingParamsInternal> bindingInternal = 
+      do_QueryInterface(*row);
+    nsCOMPtr<mozIStorageError> error = bindingInternal->bind(mDBStatement);
     if (error) {
       PRInt32 srv;
       (void)error->GetResult(&srv);
       return convertResultCode(srv);
     }
 
     // We have bound, so now we can clear our array.
     mParamsArray = nsnull;
@@ -762,71 +643,44 @@ Statement::ExecuteStep(PRBool *_moreResu
            ("SQLite error after mExecuting was true!"));
 #endif
     mExecuting = PR_FALSE;
   }
 
   return convertResultCode(srv);
 }
 
-nsresult
-Statement::ExecuteAsync(mozIStorageStatementCallback *aCallback,
-                        mozIStoragePendingStatement **_stmt)
-{
-  mozIStorageStatement *stmts[1] = {this};
-  return mDBConnection->ExecuteAsync(stmts, 1, aCallback, _stmt);
-}
-
 NS_IMETHODIMP
 Statement::GetState(PRInt32 *_state)
 {
   if (!mDBStatement)
     *_state = MOZ_STORAGE_STATEMENT_INVALID;
   else if (mExecuting)
     *_state = MOZ_STORAGE_STATEMENT_EXECUTING;
   else
     *_state = MOZ_STORAGE_STATEMENT_READY;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-Statement::EscapeStringForLIKE(const nsAString &aValue,
-                               const PRUnichar aEscapeChar,
-                               nsAString &_escapedString)
-{
-  const PRUnichar MATCH_ALL('%');
-  const PRUnichar MATCH_ONE('_');
-
-  _escapedString.Truncate(0);
-
-  for (PRUint32 i = 0; i < aValue.Length(); i++) {
-    if (aValue[i] == aEscapeChar || aValue[i] == MATCH_ALL ||
-        aValue[i] == MATCH_ONE)
-      _escapedString += aEscapeChar;
-    _escapedString += aValue[i];
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 Statement::GetColumnDecltype(PRUint32 aParamIndex,
                              nsACString &_declType)
 {
   if (!mDBStatement)
     return NS_ERROR_NOT_INITIALIZED;
 
   ENSURE_INDEX_VALUE(aParamIndex, mResultColumnCount);
 
   _declType.Assign(::sqlite3_column_decltype(mDBStatement, aParamIndex));
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-//// mozIStorageValueArray
+//// mozIStorageValueArray (now part of mozIStorageStatement too)
 
 NS_IMETHODIMP
 Statement::GetNumEntries(PRUint32 *_length)
 {
   *_length = mResultColumnCount;
   return NS_OK;
 }
 
@@ -840,29 +694,29 @@ Statement::GetTypeOfIndex(PRUint32 aInde
   ENSURE_INDEX_VALUE(aIndex, mResultColumnCount);
 
   if (!mExecuting)
     return NS_ERROR_UNEXPECTED;
 
   int t = ::sqlite3_column_type(mDBStatement, aIndex);
   switch (t) {
     case SQLITE_INTEGER:
-      *_type = VALUE_TYPE_INTEGER;
+      *_type = mozIStorageStatement::VALUE_TYPE_INTEGER;
       break;
     case SQLITE_FLOAT:
-      *_type = VALUE_TYPE_FLOAT;
+      *_type = mozIStorageStatement::VALUE_TYPE_FLOAT;
       break;
     case SQLITE_TEXT:
-      *_type = VALUE_TYPE_TEXT;
+      *_type = mozIStorageStatement::VALUE_TYPE_TEXT;
       break;
     case SQLITE_BLOB:
-      *_type = VALUE_TYPE_BLOB;
+      *_type = mozIStorageStatement::VALUE_TYPE_BLOB;
       break;
     case SQLITE_NULL:
-      *_type = VALUE_TYPE_NULL;
+      *_type = mozIStorageStatement::VALUE_TYPE_NULL;
       break;
     default:
       return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
@@ -919,17 +773,17 @@ Statement::GetDouble(PRUint32 aIndex,
 NS_IMETHODIMP
 Statement::GetUTF8String(PRUint32 aIndex,
                          nsACString &_value)
 {
   // Get type of Index will check aIndex for us, so we don't have to.
   PRInt32 type;
   nsresult rv = GetTypeOfIndex(aIndex, &type);
   NS_ENSURE_SUCCESS(rv, rv);
-  if (type == VALUE_TYPE_NULL) {
+  if (type == mozIStorageStatement::VALUE_TYPE_NULL) {
     // NULL columns should have IsVod set to distinguis them from an empty
     // string.
     _value.Truncate(0);
     _value.SetIsVoid(PR_TRUE);
   }
   else {
     const char *value =
       reinterpret_cast<const char *>(::sqlite3_column_text(mDBStatement,
@@ -942,17 +796,17 @@ Statement::GetUTF8String(PRUint32 aIndex
 NS_IMETHODIMP
 Statement::GetString(PRUint32 aIndex,
                      nsAString &_value)
 {
   // Get type of Index will check aIndex for us, so we don't have to.
   PRInt32 type;
   nsresult rv = GetTypeOfIndex(aIndex, &type);
   NS_ENSURE_SUCCESS(rv, rv);
-  if (type == VALUE_TYPE_NULL) {
+  if (type == mozIStorageStatement::VALUE_TYPE_NULL) {
     // NULL columns should have IsVod set to distinguis them from an empty
     // string.
     _value.Truncate(0);
     _value.SetIsVoid(PR_TRUE);
   } else {
     const PRUnichar *value =
       static_cast<const PRUnichar *>(::sqlite3_column_text16(mDBStatement,
                                                              aIndex));
@@ -1026,14 +880,22 @@ Statement::GetSharedBlob(PRUint32 aIndex
 NS_IMETHODIMP
 Statement::GetIsNull(PRUint32 aIndex,
                      PRBool *_isNull)
 {
   // Get type of Index will check aIndex for us, so we don't have to.
   PRInt32 type;
   nsresult rv = GetTypeOfIndex(aIndex, &type);
   NS_ENSURE_SUCCESS(rv, rv);
-  *_isNull = (type == VALUE_TYPE_NULL);
+  *_isNull = (type == mozIStorageStatement::VALUE_TYPE_NULL);
   return NS_OK;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageBindingParams
+
+BOILERPLATE_BIND_PROXIES(
+  Statement, 
+  if (!mDBStatement) return NS_ERROR_NOT_INITIALIZED;
+)
+
 } // namespace storage
 } // namespace mozilla
--- a/storage/src/mozStorageStatement.h
+++ b/storage/src/mozStorageStatement.h
@@ -43,32 +43,38 @@
 #include "nsAutoPtr.h"
 #include "nsString.h"
 
 #include "nsTArray.h"
 
 #include "mozStorageBindingParamsArray.h"
 #include "mozStorageStatementData.h"
 #include "mozIStorageStatement.h"
+#include "mozIStorageValueArray.h"
+#include "StorageBaseStatementInternal.h"
 
 class nsIXPConnectJSObjectHolder;
 struct sqlite3_stmt;
 
 namespace mozilla {
 namespace storage {
 class StatementJSHelper;
 class Connection;
-class BindingParams;
 
 class Statement : public mozIStorageStatement
+                , public mozIStorageValueArray
+                , public StorageBaseStatementInternal
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGESTATEMENT
-  NS_DECL_MOZISTORAGEVALUEARRAY
+  NS_DECL_MOZISTORAGEBASESTATEMENT
+  NS_DECL_MOZISTORAGEBINDINGPARAMS
+  // NS_DECL_MOZISTORAGEVALUEARRAY (methods in mozIStorageStatement)
+  NS_DECL_STORAGEBASESTATEMENTINTERNAL
 
   Statement();
 
   /**
    * Initializes the object on aDBConnection by preparing the SQL statement
    * given by aSQLStatement.
    *
    * @param aDBConnection
@@ -89,70 +95,53 @@ public:
    * 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();
   }
 
-  /**
-   * Obtains the StatementData needed for asynchronous execution.
-   *
-   * @param _data
-   *        A reference to a StatementData object that will be populated upon
-   *        successful execution of this method.
-   * @return an nsresult indicating success or failure.
-   */
-  nsresult getAsynchronousStatementData(StatementData &_data);
-
 private:
     ~Statement();
 
-    nsRefPtr<Connection> mDBConnection;
     sqlite3_stmt *mDBStatement;
     PRUint32 mParamCount;
     PRUint32 mResultColumnCount;
     nsTArray<nsCString> mColumnNames;
     bool mExecuting;
 
     /**
      * @return a pointer to the BindingParams object to use with our Bind*
      *         method.
      */
-    BindingParams *getParams();
+    mozIStorageBindingParams *getParams();
 
     /**
      * Holds the array of parameters to bind to this statement when we execute
      * it asynchronously.
      */
     nsRefPtr<BindingParamsArray> mParamsArray;
 
     /**
-     * Holds a copy of mDBStatement that we can use asynchronously.  Access to
-     * this is serialized on the asynchronous thread, so it does not need to be
-     * protected.  We will finalize this statement in our destructor.
-     */
-    sqlite3_stmt *mCachedAsyncStatement;
-
-    /**
-     * Obtains the statement to use on the background thread.
-     *
-     * @param _stmt
-     *        An outparm where the new statement should be placed.
-     * @return a SQLite result code indicating success or failure.
-     */
-    int getAsyncStatement(sqlite3_stmt **_stmt);
-
-    /**
      * The following two members are only used with the JS helper.  They cache
      * the row and params objects.
      */
     nsCOMPtr<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
     nsCOMPtr<nsIXPConnectJSObjectHolder> mStatementRowHolder;
 
+  /**
+   * Internal version of finalize that allows us to tell it if it is being
+   * called from the destructor so it can know not to dispatch events that
+   * require a reference to us.
+   *
+   * @param aDestructing
+   *        Is the destructor calling?
+   */
+  nsresult internalFinalize(bool aDestructing);
+
     friend class StatementJSHelper;
 };
 
 } // storage
 } // mozilla
 
 #endif // _mozStorageStatement_h_
--- a/storage/src/mozStorageStatementData.h
+++ b/storage/src/mozStorageStatementData.h
@@ -41,61 +41,90 @@
 #define _mozStorageStatementData_h_
 
 #include "sqlite3.h"
 
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 
 #include "mozStorageBindingParamsArray.h"
+#include "mozIStorageBaseStatement.h"
+#include "mozStorageConnection.h"
+#include "StorageBaseStatementInternal.h"
 
 struct sqlite3_stmt;
 
 namespace mozilla {
 namespace storage {
 
 class StatementData
 {
 public:
   StatementData(sqlite3_stmt *aStatement,
                 already_AddRefed<BindingParamsArray> aParamsArray,
-                nsISupports *aStatementOwner)
+                StorageBaseStatementInternal *aStatementOwner)
   : mStatement(aStatement)
   , mParamsArray(aParamsArray)
   , mStatementOwner(aStatementOwner)
   {
   }
   StatementData(const StatementData &aSource)
   : mStatement(aSource.mStatement)
   , mParamsArray(aSource.mParamsArray)
   , mStatementOwner(aSource.mStatementOwner)
   {
   }
   StatementData()
   {
   }
 
-  operator sqlite3_stmt *() const
+  /**
+   * Return the sqlite statement, fetching it from the storage statement.  In
+   * the case of AsyncStatements this may actually create the statement 
+   */
+  inline int getSqliteStatement(sqlite3_stmt **_stmt)
   {
-    NS_ASSERTION(mStatement, "NULL sqlite3_stmt being handed off!");
-    return mStatement;
+    if (!mStatement) {
+      int rc = mStatementOwner->getAsyncStatement(&mStatement);
+      NS_ENSURE_TRUE(rc == SQLITE_OK, rc);
+    }
+    *_stmt = mStatement;
+    return SQLITE_OK;
   }
+
   operator BindingParamsArray *() const { return mParamsArray; }
 
   /**
+   * Provide the ability to coerce back to a sqlite3 * connection for purposes 
+   * of getting an error message out of it.
+   */
+  operator sqlite3 *() const
+  {
+    return mStatementOwner->getOwner()->GetNativeConnection();
+  }
+
+  /**
    * NULLs out our sqlite3_stmt (it is held by the owner) after reseting it and
-   * clear all bindings to it.  Then, NULL out the rest of our data.
+   * clear all bindings to it.  This is expected to occur on the async thread.
+   *
+   * We do not clear mParamsArray out because we only want to release
+   * mParamsArray on the calling thread because of XPCVariant addref/release
+   * thread-safety issues.  The same holds for mStatementOwner which can be
+   * holding such a reference chain as well.
    */
   inline void finalize()
   {
-    (void)::sqlite3_reset(mStatement);
-    (void)::sqlite3_clear_bindings(mStatement);
-    mStatement = NULL;
-    mParamsArray = nsnull;
-    mStatementOwner = nsnull;
+    // In the AsyncStatement case we may never have populated mStatement if the
+    // AsyncExecuteStatements got canceled or a failure occurred in constructing
+    // the statement.
+    if (mStatement) {
+      (void)::sqlite3_reset(mStatement);
+      (void)::sqlite3_clear_bindings(mStatement);
+      mStatement = NULL;
+    }
   }
 
   /**
    * 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.
@@ -114,15 +143,15 @@ public:
 private:
   sqlite3_stmt *mStatement;
   nsRefPtr<BindingParamsArray> mParamsArray;
 
   /**
    * We hold onto a reference of the statement's owner so it doesn't get
    * destroyed out from under us.
    */
-  nsCOMPtr<nsISupports> mStatementOwner;
+  nsCOMPtr<StorageBaseStatementInternal> mStatementOwner;
 };
 
 } // namespace storage
 } // namespace mozilla
 
 #endif // _mozStorageStatementData_h_
--- a/storage/src/mozStorageStatementJSHelper.cpp
+++ b/storage/src/mozStorageStatementJSHelper.cpp
@@ -69,25 +69,29 @@ stepFunc(JSContext *aCtx,
   nsresult rv = xpc->GetWrappedNativeOfJSObject(
     aCtx, JS_THIS_OBJECT(aCtx, _vp), getter_AddRefs(wrapper)
   );
   if (NS_FAILED(rv)) {
     ::JS_ReportError(aCtx, "mozIStorageStatement::step() could not obtain native statement");
     return JS_FALSE;
   }
 
-  Statement *stmt = static_cast<Statement *>(wrapper->Native());
-
 #ifdef DEBUG
   {
-    nsCOMPtr<mozIStorageStatement> isStatement(do_QueryInterface(stmt));
+    nsCOMPtr<mozIStorageStatement> isStatement(
+      do_QueryInterface(wrapper->Native())
+    );
     NS_ASSERTION(isStatement, "How is this not a statement?!");
   }
 #endif
 
+  Statement *stmt = static_cast<Statement *>(
+    static_cast<mozIStorageStatement *>(wrapper->Native())
+  );
+
   PRBool hasMore = PR_FALSE;
   rv = stmt->ExecuteStep(&hasMore);
   if (NS_SUCCEEDED(rv) && !hasMore) {
     *_vp = JSVAL_FALSE;
     (void)stmt->Reset();
     return JS_TRUE;
   }
 
@@ -203,25 +207,28 @@ StatementJSHelper::GetProperty(nsIXPConn
                                JSObject *aScopeObj,
                                jsval aId,
                                jsval *_result,
                                PRBool *_retval)
 {
   if (!JSVAL_IS_STRING(aId))
     return NS_OK;
 
-  Statement *stmt = static_cast<Statement *>(aWrapper->Native());
-
 #ifdef DEBUG
   {
-    nsCOMPtr<mozIStorageStatement> isStatement(do_QueryInterface(stmt));
+    nsCOMPtr<mozIStorageStatement> isStatement(
+                                     do_QueryInterface(aWrapper->Native()));
     NS_ASSERTION(isStatement, "How is this not a statement?!");
   }
 #endif
 
+  Statement *stmt = static_cast<Statement *>(
+    static_cast<mozIStorageStatement *>(aWrapper->Native())
+  );
+
   const char *propName = ::JS_GetStringBytes(JSVAL_TO_STRING(aId));
   if (::strcmp(propName, "row") == 0)
     return getRow(stmt, aCtx, aScopeObj, _result);
 
   if (::strcmp(propName, "params") == 0)
     return getParams(stmt, aCtx, aScopeObj, _result);
 
   return NS_OK;
--- a/storage/src/mozStorageStatementParams.cpp
+++ b/storage/src/mozStorageStatementParams.cpp
@@ -83,32 +83,32 @@ StatementParams::SetProperty(nsIXPConnec
                              jsval *_vp,
                              PRBool *_retval)
 {
   NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
 
   if (JSVAL_IS_INT(aId)) {
     int idx = JSVAL_TO_INT(aId);
 
-    PRBool res = bindJSValue(aCtx, mStatement, idx, *_vp);
-    NS_ENSURE_TRUE(res, NS_ERROR_UNEXPECTED);
+    nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp));
+    NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED);
+    nsresult rv = mStatement->BindByIndex(idx, variant);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
   else if (JSVAL_IS_STRING(aId)) {
     JSString *str = JSVAL_TO_STRING(aId);
     NS_ConvertUTF16toUTF8 name(reinterpret_cast<const PRUnichar *>
                                    (::JS_GetStringChars(str)),
                                ::JS_GetStringLength(str));
 
     // check to see if there's a parameter with this name
-    PRUint32 index;
-    nsresult rv = mStatement->GetParameterIndex(name, &index);
+    nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp));
+    NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED);
+    nsresult rv = mStatement->BindByName(name, variant);
     NS_ENSURE_SUCCESS(rv, rv);
-
-    PRBool res = bindJSValue(aCtx, mStatement, index, *_vp);
-    NS_ENSURE_TRUE(res, NS_ERROR_UNEXPECTED);
   }
   else {
     return NS_ERROR_INVALID_ARG;
   }
 
   *_retval = PR_TRUE;
   return NS_OK;
 }
--- a/storage/src/mozStorageStatementRow.cpp
+++ b/storage/src/mozStorageStatementRow.cpp
@@ -102,28 +102,30 @@ StatementRow::GetProperty(nsIXPConnectWr
       if (!::JS_NewNumberValue(aCtx, dval, _vp)) {
         *_retval = PR_FALSE;
         return NS_OK;
       }
     }
     else if (type == mozIStorageValueArray::VALUE_TYPE_TEXT) {
       PRUint32 bytes;
       const jschar *sval = reinterpret_cast<const jschar *>(
-        mStatement->AsSharedWString(idx, &bytes)
+        static_cast<mozIStorageStatement *>(mStatement)->
+          AsSharedWString(idx, &bytes)
       );
       JSString *str = ::JS_NewUCStringCopyN(aCtx, sval, bytes / 2);
       if (!str) {
         *_retval = PR_FALSE;
         return NS_OK;
       }
       *_vp = STRING_TO_JSVAL(str);
     }
     else if (type == mozIStorageValueArray::VALUE_TYPE_BLOB) {
       PRUint32 length;
-      const PRUint8 *blob = mStatement->AsSharedBlob(idx, &length);
+      const PRUint8 *blob = static_cast<mozIStorageStatement *>(mStatement)->
+        AsSharedBlob(idx, &length);
       JSObject *obj = ::JS_NewArrayObject(aCtx, length, nsnull);
       if (!obj) {
         *_retval = PR_FALSE;
         return NS_OK;
       }
       *_vp = OBJECT_TO_JSVAL(obj);
 
       // Copy the blob over to the JS array.
--- a/storage/src/mozStorageStatementWrapper.cpp
+++ b/storage/src/mozStorageStatementWrapper.cpp
@@ -198,17 +198,19 @@ StatementWrapper::Call(nsIXPConnectWrapp
     return NS_ERROR_FAILURE;
   }
 
   // reset
   (void)mStatement->Reset();
 
   // bind parameters
   for (int i = 0; i < (int)aArgc; i++) {
-    if (!bindJSValue(aCtx, mStatement, i, aArgv[i])) {
+    nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, aArgv[i]));
+    if (!variant ||
+        NS_FAILED(mStatement->BindByIndex(i, variant))) {
       *_retval = PR_FALSE;
       return NS_ERROR_INVALID_ARG;
     }
   }
 
   // if there are no results, we just execute
   if (mResultColumnCount == 0)
     (void)mStatement->Execute();
--- a/storage/style.txt
+++ b/storage/style.txt
@@ -20,26 +20,122 @@ will be enforcing them, so please obey t
 * Function arguments that are not out parameters should be prefixed with a (for
   pArameter), and use CamelCase.
 
 * Function arguments that are out parameters should be prefixed with an
   underscore and have a descriptive name.
 
 * Function declarations should include javadoc style comments.
 
+* Javadoc @param tags should have the parameter description start on a new line
+  aligned with the variable name.  See the example below.
+
+* Javadoc @return (note: non-plural) continuation lines should be lined up with
+  the initial comment.  See the example below.
+
+* Javadoc @throws, like @param, should have the exception type on the same line
+  as the @throws and the description on a new line indented to line up with
+  the type of the exception.
+
 * For function implementations, each argument should be on its own line.
 
 * All variables should use camelCase.
 
 * The use of bool is encouraged whenever the variable does not have the
   potential to go through xpconnect.
 
 * For pointer variable types, include a space after the type before the asterisk
   and no space between the asterisk and variable name.
 
 * If any part of an if-else block requires braces, all blocks need braces.
 
 * Every else should be on a newline after a brace.
 
-* Bracing should start on the line after a function and class definition.
+* Bracing should start on the line after a function and class definition.  This
+  goes for JavaScript code as well as C++ code.
 
 * If a return value is not going to be checked, the return value should be
   explicitly casted to void (C style cast).
+
+
+BIG EXAMPLE:
+
+*** Header ***
+
+/* -*- 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 *****
+...
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_storage_FILENAME_h_
+#define mozilla_storage_FILENAME_h_
+
+namespace mozilla {
+namespace storage {
+
+class Foo : public Bar
+          , public Baz
+{
+public:
+  /**
+   * Brief function summary.
+   *
+   * @param aArg1
+   *        Description description description description description etc etc
+   *        next line of description.
+   * @param aArg2
+   *        Description description description.
+   * @return Description description description description description etc etc
+   *         next line of description.
+   *
+   * @throws NS_ERROR_FAILURE
+   *         Okay, so this is for JavaScript code, but you probably get the
+   *         idea.
+   */
+  int chew(int aArg1, int aArg2);
+};
+
+} // storage
+} // mozilla
+
+#endif // mozilla_storage_FILENAME_h_
+
+
+*** Implementation ***
+
+/* -*- 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 *****
+...
+ * ***** END LICENSE BLOCK ***** */
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(
+  Foo
+, IBar
+, IBaz
+)
+
+Foo::Foo(
+  LongArgumentLineThatWouldOtherwiseOverflow *aArgument1
+)
+: mField1(0)
+, mField2(0)
+{
+  someMethodWithLotsOfParamsOrJustLongParameters(
+    mLongFieldNameThatIsJustified,
+    mMaybeThisOneIsLessJustifiedButBoyIsItLong,
+    15
+  );
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Separate sections of the file like this
+
+int
+Foo::chew(int aArg1, int aArg2)
+{
+  (void)functionReturningAnIgnoredValue();
+
+  ::functionFromGlobalNamespaceWithVoidReturnValue();
+
+  return 0;
+}
--- a/storage/test/Makefile.in
+++ b/storage/test/Makefile.in
@@ -48,16 +48,17 @@ MODULE = test_storage
 
 XPCSHELL_TESTS = unit
 
 CPP_UNIT_TESTS = \
   test_transaction_helper.cpp \
   test_statement_scoper.cpp \
   test_mutex.cpp \
   test_binding_params.cpp \
+  test_true_async.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 WINCE Darwin,$(OS_ARCH)))
 CPP_UNIT_TESTS += \
   test_deadlock_detector.cpp \
new file mode 100644
--- /dev/null
+++ b/storage/test/test_true_async.cpp
@@ -0,0 +1,447 @@
+/* -*- 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 storage test code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Andrew Sutherland <asutherland@asutherland.org> (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 "storage_test_harness.h"
+#include "prthread.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsIEventTarget.h"
+
+#include "sqlite3.h"
+
+#include "mozilla/Monitor.h"
+
+#include "mozIStorageStatementCallback.h"
+#include "mozIStorageCompletionCallback.h"
+#include "mozIStorageBindingParamsArray.h"
+#include "mozIStorageBindingParams.h"
+#include "mozIStorageAsyncStatement.h"
+#include "mozIStorageStatement.h"
+#include "mozIStoragePendingStatement.h"
+
+using mozilla::Monitor;
+using mozilla::MonitorAutoEnter;
+
+/**
+ * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on
+ * the caller (generally main) thread.  We do this by decorating the sqlite
+ * mutex logic with our own code that checks what thread it is being invoked on
+ * and sets a flag if it is invoked on the main thread.  We are able to easily
+ * decorate the SQLite mutex logic because SQLite allows us to retrieve the
+ * current function pointers being used and then provide a new set.
+ */
+
+/* ===== Mutex Watching ===== */
+
+sqlite3_mutex_methods orig_mutex_methods;
+sqlite3_mutex_methods wrapped_mutex_methods;
+
+bool mutex_used_on_watched_thread = false;
+PRThread *watched_thread = NULL;
+/**
+ * Ugly hack to let us figure out what a connection's async thread is.  If we
+ * were MOZILLA_INTERNAL_API and linked as such we could just include
+ * mozStorageConnection.h and just ask Connection directly.  But that turns out
+ * poorly.
+ *
+ * When the thread a mutex is invoked on isn't watched_thread we save it to this
+ * variable.
+ */
+PRThread *last_non_watched_thread = NULL;
+
+/**
+ * Set a flag if the mutex is used on the thread we are watching, but always
+ * call the real mutex function.
+ */
+extern "C" void wrapped_MutexEnter(sqlite3_mutex *mutex)
+{
+  PRThread *curThread = ::PR_GetCurrentThread();
+  if (curThread == watched_thread)
+    mutex_used_on_watched_thread = true;
+  else
+    last_non_watched_thread = curThread;
+  orig_mutex_methods.xMutexEnter(mutex);
+}
+
+extern "C" int wrapped_MutexTry(sqlite3_mutex *mutex)
+{
+  if (::PR_GetCurrentThread() == watched_thread)
+    mutex_used_on_watched_thread = true;
+  return orig_mutex_methods.xMutexTry(mutex);
+}
+
+
+#define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
+
+void hook_sqlite_mutex()
+{
+  // We need to initialize and teardown SQLite to get it to set up the
+  // default mutex handlers for us so we can steal them and wrap them.
+  sqlite3_initialize();
+  sqlite3_shutdown();
+  do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods));
+  do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods));
+  wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter;
+  wrapped_mutex_methods.xMutexTry = wrapped_MutexTry;
+  do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods));
+}
+
+/**
+ * Call to clear the watch state and to set the watching against this thread.
+ *
+ * Check |mutex_used_on_watched_thread| to see if the mutex has fired since
+ * this method was last called.  Since we're talking about the current thread,
+ * there are no race issues to be concerned about
+ */
+void watch_for_mutex_use_on_this_thread()
+{
+  watched_thread = ::PR_GetCurrentThread();
+  mutex_used_on_watched_thread = false;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Event Loop Spinning
+
+class AsyncStatementSpinner : public mozIStorageStatementCallback,
+                              public mozIStorageCompletionCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+  NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
+
+  AsyncStatementSpinner();
+
+  void SpinUntilCompleted();
+
+  PRUint16 completionReason;
+
+private:
+  ~AsyncStatementSpinner() {}
+  volatile bool mCompleted;
+};
+
+NS_IMPL_ISUPPORTS2(AsyncStatementSpinner,
+                   mozIStorageStatementCallback,
+                   mozIStorageCompletionCallback)
+
+AsyncStatementSpinner::AsyncStatementSpinner()
+: completionReason(0)
+, mCompleted(false)
+{
+}
+
+NS_IMETHODIMP
+AsyncStatementSpinner::HandleResult(mozIStorageResultSet *aResultSet)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatementSpinner::HandleError(mozIStorageError *aError)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatementSpinner::HandleCompletion(PRUint16 aReason)
+{
+  completionReason = aReason;
+  mCompleted = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatementSpinner::Complete()
+{
+  mCompleted = true;
+  return NS_OK;
+}
+
+void AsyncStatementSpinner::SpinUntilCompleted()
+{
+  nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
+  nsresult rv = NS_OK;
+  PRBool processed = PR_TRUE;
+  while (!mCompleted && NS_SUCCEEDED(rv)) {
+    rv = thread->ProcessNextEvent(true, &processed);
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Thread Wedgers
+
+/**
+ * A runnable that blocks until code on another thread invokes its unwedge
+ * method.  By dispatching this to a thread you can ensure that no subsequent
+ * runnables dispatched to the thread will execute until you invoke unwedge.
+ *
+ * The wedger is self-dispatching, just construct it with its target.
+ */
+class ThreadWedger : public nsRunnable
+{
+public:
+  ThreadWedger(nsIEventTarget *aTarget)
+  : mMonitor("thread wedger")
+  , unwedged(false)
+  {
+    aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL);
+  }
+
+  NS_IMETHOD Run()
+  {
+    MonitorAutoEnter automon(mMonitor);
+
+    if (!unwedged)
+      automon.Wait();
+
+    return NS_OK;
+  }
+
+  void unwedge()
+  {
+    MonitorAutoEnter automon(mMonitor);
+    unwedged = true;
+    automon.Notify();
+  }
+
+private:
+  Monitor mMonitor;
+  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
+  watch_for_mutex_use_on_this_thread();
+
+  // - statement with nothing to bind
+  nsCOMPtr<mozIStorageAsyncStatement> stmt;
+  db->CreateAsyncStatement(
+    NS_LITERAL_CSTRING("SELECT 1"),
+    getter_AddRefs(stmt));
+  blocking_async_execute(stmt);
+  stmt->Finalize();
+
+  nsCOMPtr<nsIThreadManager> threadMan =
+    do_GetService("@mozilla.org/thread-manager;1");
+  nsCOMPtr<nsIThread> asyncThread;
+  threadMan->GetThreadFromPRThread(last_non_watched_thread,
+                                   getter_AddRefs(asyncThread));
+  return asyncThread.forget();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+void
+test_TrueAsyncStatement()
+{
+  hook_sqlite_mutex();
+
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // Start watching for forbidden mutex usage.
+  watch_for_mutex_use_on_this_thread();
+
+  // - statement with nothing to bind
+  nsCOMPtr<mozIStorageAsyncStatement> stmt;
+  db->CreateAsyncStatement(
+    NS_LITERAL_CSTRING("CREATE TABLE test (id INTEGER PRIMARY KEY)"),
+    getter_AddRefs(stmt)
+  );
+  blocking_async_execute(stmt);
+  stmt->Finalize();
+  do_check_false(mutex_used_on_watched_thread);
+
+  // - statement with something to bind ordinally
+  db->CreateAsyncStatement(
+    NS_LITERAL_CSTRING("INSERT INTO test (id) VALUES (?)"),
+    getter_AddRefs(stmt)
+  );
+  stmt->BindInt32Parameter(0, 1);
+  blocking_async_execute(stmt);
+  stmt->Finalize();
+  do_check_false(mutex_used_on_watched_thread);
+  
+  // - statement with something to bind by name
+  db->CreateAsyncStatement(
+    NS_LITERAL_CSTRING("INSERT INTO test (id) VALUES (:id)"),
+    getter_AddRefs(stmt)
+  );
+  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+  stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+  nsCOMPtr<mozIStorageBindingParams> params;
+  paramsArray->NewBindingParams(getter_AddRefs(params));
+  params->BindInt32ByName(NS_LITERAL_CSTRING("id"), 2);
+  paramsArray->AddParams(params);
+  params = nsnull;
+  stmt->BindParameters(paramsArray);
+  paramsArray = nsnull;
+  blocking_async_execute(stmt);
+  stmt->Finalize();
+  do_check_false(mutex_used_on_watched_thread);
+
+  // - now, make sure creating a sync statement does trigger our guard.
+  // (If this doesn't happen, our test is bunk and it's important to know that.)
+  nsCOMPtr<mozIStorageStatement> syncStmt;
+  db->CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM test"),
+                      getter_AddRefs(syncStmt));
+  syncStmt->Finalize();
+  do_check_true(mutex_used_on_watched_thread);
+
+  blocking_async_close(db);
+}
+
+/**
+ * Test that cancellation before a statement is run successfully stops the
+ * statement from executing.
+ */
+void
+test_AsyncCancellation()
+{
+  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+  // -- wedge the thread
+  nsCOMPtr<nsIThread> target(get_conn_async_thread(db));
+  do_check_true(target);
+  nsRefPtr<ThreadWedger> wedger (new ThreadWedger(target));
+
+  // -- create statements and cancel them
+  // - async
+  nsCOMPtr<mozIStorageAsyncStatement> asyncStmt;
+  db->CreateAsyncStatement(
+    NS_LITERAL_CSTRING("CREATE TABLE asyncTable (id INTEGER PRIMARY KEY)"),
+    getter_AddRefs(asyncStmt)
+  );
+
+  nsRefPtr<AsyncStatementSpinner> asyncSpin(new AsyncStatementSpinner());
+  nsCOMPtr<mozIStoragePendingStatement> asyncPend;
+  (void)asyncStmt->ExecuteAsync(asyncSpin, getter_AddRefs(asyncPend));
+  do_check_true(asyncPend);
+  asyncPend->Cancel();
+
+  // - sync
+  nsCOMPtr<mozIStorageStatement> syncStmt;
+  db->CreateStatement(
+    NS_LITERAL_CSTRING("CREATE TABLE syncTable (id INTEGER PRIMARY KEY)"),
+    getter_AddRefs(syncStmt)
+  );
+
+  nsRefPtr<AsyncStatementSpinner> syncSpin(new AsyncStatementSpinner());
+  nsCOMPtr<mozIStoragePendingStatement> syncPend;
+  (void)syncStmt->ExecuteAsync(syncSpin, getter_AddRefs(syncPend));
+  do_check_true(syncPend);
+  syncPend->Cancel();
+
+  // -- unwedge the async thread
+  wedger->unwedge();
+
+  // -- verify that both statements report they were canceled
+  asyncSpin->SpinUntilCompleted();
+  do_check_true(asyncSpin->completionReason ==
+                mozIStorageStatementCallback::REASON_CANCELED);
+
+  syncSpin->SpinUntilCompleted();
+  do_check_true(syncSpin->completionReason ==
+                mozIStorageStatementCallback::REASON_CANCELED);
+
+  // -- verify that neither statement constructed their tables
+  nsresult rv;
+  PRBool exists;
+  rv = db->TableExists(NS_LITERAL_CSTRING("asyncTable"), &exists);
+  do_check_true(rv == NS_OK);
+  do_check_false(exists);
+  rv = db->TableExists(NS_LITERAL_CSTRING("syncTable"), &exists);
+  do_check_true(rv == NS_OK);
+  do_check_false(exists);
+
+  // -- cleanup
+  asyncStmt->Finalize();
+  syncStmt->Finalize();
+  blocking_async_close(db);
+}
+
+void (*gTests[])(void) = {
+  // this test must be first because it hooks the mutex mechanics
+  test_TrueAsyncStatement,
+  test_AsyncCancellation,
+};
+
+const char *file = __FILE__;
+#define TEST_NAME "true async statement"
+#define TEST_FILE file
+#include "storage_test_harness_tail.h"
--- a/storage/test/unit/head_storage.js
+++ b/storage/test/unit/head_storage.js
@@ -70,16 +70,44 @@ function cleanup()
 
   // removing test db
   print("*** Storage Tests: Trying to remove file!");
   var dbFile = getTestDB();
   if (dbFile.exists())
     try { dbFile.remove(false); } catch(e) { /* stupid windows box */ }
 }
 
+/**
+ * Use asyncClose to cleanup a connection.  Synchronous by means of internally
+ * spinning an event loop.
+ */
+function asyncCleanup()
+{
+  let closed = false;
+
+  // close the connection
+  print("*** Storage Tests: Trying to asyncClose!");
+  getOpenedDatabase().asyncClose(function() { closed = true; });
+
+  let curThread = Components.classes["@mozilla.org/thread-manager;1"]
+                            .getService().currentThread;
+  while (!closed)
+    curThread.processNextEvent(true);
+
+  // we need to null out the database variable to get a new connection the next
+  // time getOpenedDatabase is called
+  gDBConn = null;
+
+  // removing test db
+  print("*** Storage Tests: Trying to remove file!");
+  var dbFile = getTestDB();
+  if (dbFile.exists())
+    try { dbFile.remove(false); } catch(e) { /* stupid windows box */ }
+}
+
 function getService()
 {
   return Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService);
 }
 
 var gDBConn = null;
 
 /**
@@ -114,10 +142,122 @@ function getDatabase(aFile)
   return getService().openDatabase(aFile);
 }
 
 function createStatement(aSQL)
 {
   return getOpenedDatabase().createStatement(aSQL);
 }
 
+/**
+ * Invoke the given function and assert that it throws an exception expressing
+ * the provided error code in its 'result' attribute.  JS function expressions
+ * can be used to do this concisely.
+ *
+ * Example:
+ *  expectError(Cr.NS_ERROR_INVALID_ARG, function() explodingFunction());
+ *
+ * @param aErrorCode
+ *        The error code to expect from invocation of aFunction.
+ * @param aFunction
+ *        The function to invoke and expect an XPCOM-style error from.
+ */
+function expectError(aErrorCode, aFunction)
+{
+  let exceptionCaught = false;
+  try {
+    aFunction();
+  }
+  catch(e) {
+    if (e.result != aErrorCode) {
+      do_throw("Got an exception, but the result code was not the expected " +
+               "one.  Expected " + aErrorCode + ", got " + e.result);
+    }
+    exceptionCaught = true;
+  }
+  if (!exceptionCaught)
+    do_throw(aFunction + " should have thrown an exception but did not!");
+}
+
+/**
+ * Run a query synchronously and verify that we get back the expected results.
+ *
+ * @param aSQLString
+ *        The SQL string for the query.
+ * @param aBind
+ *        The value to bind at index 0.
+ * @param aResults
+ *        A list of the expected values returned in the sole result row.
+ *        Express blobs as lists.
+ */
+function verifyQuery(aSQLString, aBind, aResults)
+{
+  let stmt = getOpenedDatabase().createStatement(aSQLString);
+  stmt.bindByIndex(0, aBind);
+  try {
+    do_check_true(stmt.executeStep());
+    let nCols = stmt.numEntries;
+    if (aResults.length != nCols)
+      do_throw("Expected " + aResults.length + " columns in result but " +
+               "there are only " + aResults.length + "!");
+    for (let iCol = 0; iCol < nCols; iCol++) {
+      let expectedVal = aResults[iCol];
+      let valType = stmt.getTypeOfIndex(iCol);
+      if (expectedVal === null) {
+        do_check_eq(stmt.VALUE_TYPE_NULL, valType);
+        do_check_true(stmt.getIsNull(iCol));
+      }
+      else if (typeof(expectedVal) == "number") {
+        if (Math.floor(expectedVal) == expectedVal) {
+          do_check_eq(stmt.VALUE_TYPE_INTEGER, valType);
+          do_check_eq(expectedVal, stmt.getInt32(iCol));
+        }
+        else {
+          do_check_eq(stmt.VALUE_TYPE_FLOAT, valType);
+          do_check_eq(expectedVal, stmt.getDouble(iCol));
+        }
+      }
+      else if (typeof(expectedVal) == "string") {
+        do_check_eq(stmt.VALUE_TYPE_TEXT, valType);
+        do_check_eq(expectedVal, stmt.getUTF8String(iCol));
+      }
+      else { // blob
+        do_check_eq(stmt.VALUE_TYPE_BLOB, valType);
+        let count = { value: 0 }, blob = { value: null };
+        stmt.getBlob(iCol, count, blob);
+        do_check_eq(count.value, expectedVal.length);
+        for (let i = 0; i < count.value; i++) {
+          do_check_eq(expectedVal[i], blob.value[i]);
+        }
+      }
+    }
+  }
+  finally {
+    stmt.finalize();
+  }
+}
+
+/**
+ * Return the number of rows in the able with the given name using a synchronous
+ * query.
+ *
+ * @param aTableName
+ *        The name of the table.
+ * @return The number of rows.
+ */
+function getTableRowCount(aTableName)
+{
+  var currentRows = 0;
+  var countStmt = getOpenedDatabase().createStatement(
+    "SELECT COUNT(1) AS count FROM " + aTableName
+  );
+  try {
+    do_check_true(countStmt.executeStep());
+    currentRows = countStmt.row.count;
+  }
+  finally {
+    countStmt.finalize();
+  }
+  return currentRows;
+}
+
 cleanup();
 
--- a/storage/test/unit/test_connection_executeAsync.js
+++ b/storage/test/unit/test_connection_executeAsync.js
@@ -30,17 +30,20 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-// This file tests the functionality of mozIStorageConnection::executeAsync
+/*
+ * This file tests the functionality of mozIStorageConnection::executeAsync for
+ * both mozIStorageStatement and mozIStorageAsyncStatement.
+ */
 
 const INTEGER = 1;
 const TEXT = "this is test text";
 const REAL = 3.23;
 const BLOB = [1, 2];
 
 function test_create_and_add()
 {
@@ -58,17 +61,17 @@ function test_create_and_add()
   stmts[0] = getOpenedDatabase().createStatement(
     "INSERT INTO test (id, string, number, nuller, blober) VALUES (?, ?, ?, ?, ?)"
   );
   stmts[0].bindInt32Parameter(0, INTEGER);
   stmts[0].bindStringParameter(1, TEXT);
   stmts[0].bindDoubleParameter(2, REAL);
   stmts[0].bindNullParameter(3);
   stmts[0].bindBlobParameter(4, BLOB, BLOB.length);
-  stmts[1] = getOpenedDatabase().createStatement(
+  stmts[1] = getOpenedDatabase().createAsyncStatement(
     "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);
 
   getOpenedDatabase().executeAsync(stmts, stmts.length, {
@@ -126,17 +129,17 @@ function test_create_and_add()
   });
   stmts[0].finalize();
   stmts[1].finalize();
 }
 
 function test_transaction_created()
 {
   let stmts = [];
-  stmts[0] = getOpenedDatabase().createStatement(
+  stmts[0] = getOpenedDatabase().createAsyncStatement(
     "BEGIN"
   );
   stmts[1] = getOpenedDatabase().createStatement(
     "SELECT * FROM test"
   );
 
   getOpenedDatabase().executeAsync(stmts, stmts.length, {
     handleResult: function(aResultSet)
@@ -164,23 +167,28 @@ function test_transaction_created()
 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 = [];
+  let db = getOpenedDatabase();
+  let sqlString = "INSERT INTO test (id, string, number, nuller, blober) " +
+                    "VALUES (:int, :text, :real, :null, :blob)";
   // 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()
+    // alternate the type of statement we create
+    if (i % 2)
+      stmts[i] = db.createStatement(sqlString);
+    else
+      stmts[i] = db.createAsyncStatement(sqlString);
+
+    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);
--- a/storage/test/unit/test_statement_executeAsync.js
+++ b/storage/test/unit/test_statement_executeAsync.js
@@ -15,160 +15,258 @@
  *
  * The Initial Developer of the Original Code is
  * Mozilla Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *   Andrew Sutherland <asutherland@asutherland.org>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-// This file tests the functionality of mozIStorageStatement::executeAsync
+/*
+ * This file tests the functionality of mozIStorageBaseStatement::executeAsync
+ * for both mozIStorageStatement and mozIStorageAsyncStatement.
+ */
 
 const INTEGER = 1;
 const TEXT = "this is test text";
 const REAL = 3.23;
 const BLOB = [1, 2];
 
+/**
+ * Execute the given statement asynchronously, spinning an event loop until the
+ * async statement completes.
+ *
+ * @param aStmt
+ *        The statement to execute.
+ * @param [aOptions={}]
+ * @param [aOptions.error=false]
+ *        If true we should expect an error whose code we do not care about.  If
+ *        a numeric value, that's the error code we expect and require.  If we
+ *        are expecting an error, we expect a completion reason of REASON_ERROR.
+ *        Otherwise we expect no error notification and a completion reason of
+ *        REASON_FINISHED.
+ * @param [aOptions.cancel]
+ *        If true we cancel the pending statement and additionally return the
+ *        pending statement in case you want to further manipulate it.
+ * @param [aOptions.returnPending=false]
+ *        If true we keep the pending statement around and return it to you.  We
+ *        normally avoid doing this to try and minimize the amount of time a
+ *        reference is held to the returned pending statement.
+ * @param [aResults]
+ *        If omitted, we assume no results rows are expected.  If it is a
+ *        number, we assume it is the number of results rows expected.  If it is
+ *        a function, we assume it is a function that takes the 1) result row
+ *        number, 2) result tuple, 3) call stack for the original call to
+ *        execAsync as arguments.  If it is a list, we currently assume it is a
+ *        list of functions where each function is intended to evaluate the
+ *        result row at that ordinal position and takes the result tuple and
+ *        the call stack for the original call.
+ */
+function execAsync(aStmt, aOptions, aResults)
+{
+  let caller = Components.stack.caller;
+  if (aOptions == null)
+    aOptions = {};
+
+  let resultsExpected;
+  let resultsChecker;
+  if (aResults == null) {
+    resultsExpected = 0;
+  }
+  else if (typeof(aResults) == "number") {
+    resultsExpected = aResults;
+  }
+  else if (typeof(aResults) == "function") {
+    resultsChecker = aResults;
+  }
+  else { // array
+    resultsExpected = aResults.length;
+    resultsChecker = function(aResultNum, aTup, aCaller) {
+      aResults[aResultNum](aTup, aCaller);
+    };
+  }
+  let resultsSeen = 0;
+
+  let errorCodeExpected = false;
+  let reasonExpected = Ci.mozIStorageStatementCallback.REASON_FINISHED;
+  let altReasonExpected = null;
+  if ("error" in aOptions) {
+    errorCodeExpected = aOptions.error;
+    if (errorCodeExpected)
+      reasonExpected = Ci.mozIStorageStatementCallback.REASON_ERROR;
+  }
+  let errorCodeSeen = false;
+
+  if ("cancel" in aOptions && aOptions.cancel)
+    altReasonExpected = Ci.mozIStorageStatementCallback.REASON_CANCELED;
+
+  let completed = false;
+
+  let listener = {
+    handleResult: function(aResultSet)
+    {
+      let row, resultsSeenThisCall = 0;
+      while ((row = aResultSet.getNextRow()) != null) {
+        if (resultsChecker)
+          resultsChecker(resultsSeen, row, caller);
+        resultsSeen++;
+        resultsSeenThisCall++;
+      }
+
+      if (!resultsSeenThisCall)
+        do_throw("handleResult invoked with 0 result rows!");
+    },
+    handleError: function(aError)
+    {
+      if (errorCodeSeen != false)
+        do_throw("handleError called when we already had an error!");
+      errorCodeSeen = aError.result;
+    },
+    handleCompletion: function(aReason)
+    {
+      if (completed) // paranoia check
+        do_throw("Received a second handleCompletion notification!", caller);
+
+      if (resultsSeen != resultsExpected)
+        do_throw("Expected " + resultsExpected + " rows of results but " +
+                 "got " + resultsSeen + " rows!", caller);
+
+      if (errorCodeExpected == true && errorCodeSeen == false)
+        do_throw("Expected an error, but did not see one.", caller);
+      else if (errorCodeExpected != errorCodeSeen)
+        do_throw("Expected error code " + errorCodeExpected + " but got " +
+                 errorCodeSeen, caller);
+
+      if (aReason != reasonExpected && aReason != altReasonExpected)
+        do_throw("Expected reason " + reasonExpected +
+                 (altReasonExpected ? (" or " + altReasonExpected) : "") +
+                 " but got " + aReason, caller);
+
+      completed = true;
+    }
+  };
+
+  let pending;
+  // Only get a pending reference if we're supposed to do.
+  // (note: This does not stop XPConnect from holding onto one currently.)
+  if (("cancel" in aOptions && aOptions.cancel) ||
+      ("returnPending" in aOptions && aOptions.returnPending)) {
+    pending = aStmt.executeAsync(listener);
+  }
+  else {
+    aStmt.executeAsync(listener);
+  }
+
+  if ("cancel" in aOptions && aOptions.cancel)
+    pending.cancel();
+
+  let curThread = Components.classes["@mozilla.org/thread-manager;1"]
+                            .getService().currentThread;
+  while (!completed && !_quit)
+    curThread.processNextEvent(true);
+
+  return pending;
+}
+
+/**
+ * Make sure that illegal SQL generates the expected runtime error and does not
+ * result in any crashes.  Async-only since the synchronous case generates the
+ * error synchronously (and is tested elsewhere).
+ */
+function test_illegal_sql_async_deferred()
+{
+  // gibberish
+  let stmt = makeTestStatement("I AM A ROBOT. DO AS I SAY.");
+  execAsync(stmt, {error: Ci.mozIStorageError.ERROR});
+  stmt.finalize();
+
+  // legal SQL syntax, but with semantics issues.
+  stmt = makeTestStatement("SELECT destination FROM funkytown");
+  execAsync(stmt, {error: Ci.mozIStorageError.ERROR});
+  stmt.finalize();
+
+  run_next_test();
+}
+test_illegal_sql_async_deferred.asyncOnly = true;
+
 function test_create_table()
 {
-  // Ensure our table doesn't exists
+  // Ensure our table doesn't exist
   do_check_false(getOpenedDatabase().tableExists("test"));
 
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "CREATE TABLE test (" +
       "id INTEGER, " +
       "string TEXT, " +
       "number REAL, " +
       "nuller NULL, " +
       "blober BLOB" +
     ")"
   );
+  execAsync(stmt);
+  stmt.finalize();
 
-  stmt.executeAsync({
-    handleResult: function(aResultSet)
-    {
-      dump("handleResult("+aResultSet+");\n");
-      do_throw("unexpected results obtained!");
-    },
-    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_create_table");
-      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
+  // Check that the table has been created
+  do_check_true(getOpenedDatabase().tableExists("test"));
 
-      // 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();
-
-      // Run the next test.
-      run_next_test();
-    }
-  });
-  stmt.finalize();
+  // Verify that it's created correctly (this will throw if it wasn't)
+  let checkStmt = getOpenedDatabase().createStatement(
+    "SELECT id, string, number, nuller, blober FROM test"
+  );
+  checkStmt.finalize();
+  run_next_test();
 }
 
 function test_add_data()
 {
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "INSERT INTO test (id, string, number, nuller, blober) " +
     "VALUES (?, ?, ?, ?, ?)"
   );
   stmt.bindBlobParameter(4, BLOB, BLOB.length);
   stmt.bindNullParameter(3);
   stmt.bindDoubleParameter(2, REAL);
   stmt.bindStringParameter(1, TEXT);
   stmt.bindInt32Parameter(0, INTEGER);
 
-  stmt.executeAsync({
-    handleResult: function(aResultSet)
-    {
-      do_throw("unexpected results obtained!");
-    },
-    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_add_data");
-      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
+  execAsync(stmt);
+  stmt.finalize();
 
-      // 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 {
-        do_check_true(stmt.executeStep());
-        do_check_eq(TEXT, stmt.getString(0));
-        do_check_eq(REAL, stmt.getDouble(1));
-        do_check_true(stmt.getIsNull(2));
-        var count = { value: 0 };
-        var blob = { value: null };
-        stmt.getBlob(3, count, blob);
-        do_check_eq(BLOB.length, count.value);
-        for (var i = 0; i < BLOB.length; i++)
-          do_check_eq(BLOB[i], blob.value[i]);
-      }
-      finally {
-        stmt.reset();
-        stmt.finalize();
-      }
-
-      // Run the next test.
-      run_next_test();
-    }
-  });
-  stmt.finalize();
+  // Check that the result is in the table
+  verifyQuery("SELECT string, number, nuller, blober FROM test WHERE id = ?",
+              INTEGER,
+              [TEXT, REAL, null, BLOB]);
+  run_next_test();
 }
 
 function test_get_data()
 {
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "SELECT string, number, nuller, blober, id FROM test WHERE id = ?"
   );
   stmt.bindInt32Parameter(0, INTEGER);
-
-  stmt.executeAsync({
-    resultObtained: false,
-    handleResult: function(aResultSet)
+  execAsync(stmt, {}, [
+    function(tuple)
     {
-      dump("handleResult("+aResultSet+");\n");
-      do_check_false(this.resultObtained);
-      this.resultObtained = true;
-
-      // Check that we have a result
-      var tuple = aResultSet.getNextRow();
       do_check_neq(null, tuple);
 
       // Check that it's what we expect
       do_check_false(tuple.getIsNull(0));
       do_check_eq(tuple.getResultByName("string"), tuple.getResultByIndex(0));
       do_check_eq(TEXT, tuple.getResultByName("string"));
       do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_TEXT,
                   tuple.getTypeOfIndex(0));
@@ -203,56 +301,28 @@ function test_get_data()
       do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_BLOB,
                   tuple.getTypeOfIndex(3));
 
       do_check_false(tuple.getIsNull(4));
       do_check_eq(tuple.getResultByName("id"), tuple.getResultByIndex(4));
       do_check_eq(INTEGER, tuple.getResultByName("id"));
       do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER,
                   tuple.getTypeOfIndex(4));
-
-      // check that we have no more results
-      tuple = aResultSet.getNextRow();
-      do_check_eq(null, tuple);
-    },
-    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_get_data");
-      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
-      do_check_true(this.resultObtained);
-
-      // Run the next test.
-      run_next_test();
-    }
-  });
+    }]);
   stmt.finalize();
+  run_next_test();
 }
 
 function test_tuple_out_of_bounds()
 {
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "SELECT string FROM test"
   );
-
-  stmt.executeAsync({
-    resultObtained: false,
-    handleResult: function(aResultSet)
-    {
-      dump("handleResult("+aResultSet+");\n");
-      do_check_false(this.resultObtained);
-      this.resultObtained = true;
-
-      // Check that we have a result
-      var tuple = aResultSet.getNextRow();
+  execAsync(stmt, {}, [
+    function(tuple) {
       do_check_neq(null, tuple);
 
       // Check all out of bounds - should throw
       var methods = [
         "getTypeOfIndex",
         "getInt32",
         "getInt64",
         "getDouble",
@@ -275,79 +345,63 @@ function test_tuple_out_of_bounds()
         var blob = { value: null };
         var size = { value: 0 };
         tuple.getBlob(tuple.numEntries, blob, size);
         do_throw("did not throw :(");
       }
       catch (e) {
         do_check_eq(Cr.NS_ERROR_ILLEGAL_VALUE, e.result);
       }
-    },
-    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_tuple_out_of_bounds");
-      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
-      do_check_true(this.resultObtained);
-
-      // Run the next test.
-      run_next_test();
-    }
-  });
+    }]);
   stmt.finalize();
+  run_next_test();
 }
 
 function test_no_listener_works_on_success()
 {
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "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()
 {
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "SELECT ?"
   );
   stmt.bindInt32Parameter(0, 1);
   stmt.executeAsync();
   stmt.finalize();
 
   // Run the next test.
   run_next_test();
 }
 
 function test_no_listener_works_on_error()
 {
   // commit without a transaction will trigger an error
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "COMMIT"
   );
   stmt.executeAsync();
   stmt.finalize();
 
   // Run the next test.
   run_next_test();
 }
 
 function test_partial_listener_works()
 {
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "DELETE FROM test WHERE id = ?"
   );
   stmt.bindInt32Parameter(0, 0);
   stmt.executeAsync({
     handleResult: function(aResultSet)
     {
     }
   });
@@ -362,728 +416,635 @@ function test_partial_listener_works()
     }
   });
   stmt.finalize();
 
   // Run the next test.
   run_next_test();
 }
 
+/**
+ * Dubious cancellation test that depends on system loading may or may not
+ * succeed in canceling things.  It does at least test if calling cancel blows
+ * up.  test_AsyncCancellation in test_true_async.cpp is our test that canceling
+ * actually works correctly.
+ */
 function test_immediate_cancellation()
 {
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "DELETE FROM test WHERE id = ?"
   );
   stmt.bindInt32Parameter(0, 0);
-  var pendingStatement = stmt.executeAsync({
-    handleResult: function(aResultSet)
-    {
-      do_throw("unexpected result!");
-    },
-    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_immediate_cancellation");
-      // It is possible that we finished before we canceled.
-      do_check_true(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ||
-                    aReason == Ci.mozIStorageStatementCallback.REASON_CANCELED);
-
-      // Run the next test.
-      run_next_test();
-    }
-  });
-
-  // Cancel immediately
-  pendingStatement.cancel()
-
+  execAsync(stmt, {cancel: true});
   stmt.finalize();
+  run_next_test();
 }
 
+/**
+ * Test that calling cancel twice throws the second time.
+ */
 function test_double_cancellation()
 {
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "DELETE FROM test WHERE id = ?"
   );
   stmt.bindInt32Parameter(0, 0);
-  var pendingStatement = stmt.executeAsync({
-    handleResult: function(aResultSet)
-    {
-      do_throw("unexpected result!");
-    },
-    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_double_cancellation");
-      // It is possible that we finished before we canceled.
-      do_check_true(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ||
-                    aReason == Ci.mozIStorageStatementCallback.REASON_CANCELED);
-
-      // Run the next test.
-      run_next_test();
-    }
-  });
-
-  // Cancel immediately
-  pendingStatement.cancel()
-
+  let pendingStatement = execAsync(stmt, {cancel: true});
   // And cancel again - expect an exception
-  try {
-    pendingStatement.cancel();
-    do_throw("function call should have thrown!");
-  }
-  catch (e) {
-    do_check_eq(Cr.NS_ERROR_UNEXPECTED, e.result);
-  }
+  expectError(Cr.NS_ERROR_UNEXPECTED,
+              function() pendingStatement.cancel());
 
   stmt.finalize();
+  run_next_test();
 }
 
+/**
+ * Verify that nothing untoward happens if we try and cancel something after it
+ * has fully run to completion.
+ */
+function test_cancellation_after_execution()
+{
+  var stmt = makeTestStatement(
+    "DELETE FROM test WHERE id = ?"
+  );
+  stmt.bindInt32Parameter(0, 0);
+  let pendingStatement = execAsync(stmt, {returnPending: true});
+  // (the statement has fully executed at this point)
+  // canceling after the statement has run to completion should not throw!
+  pendingStatement.cancel();
+
+  stmt.finalize();
+  run_next_test();
+}
+
+/**
+ * Verifies that a single statement can be executed more than once.  Might once
+ * have been intended to also ensure that callback notifications were not
+ * incorrectly interleaved, but that part was brittle (it's totally fine for
+ * handleResult to get called multiple times) and not comprehensive.
+ */
 function test_double_execute()
 {
-  var stmt = getOpenedDatabase().createStatement(
-    "SELECT * FROM test"
+  var stmt = makeTestStatement(
+    "SELECT 1"
   );
-
-  var listener = {
-    _timesCompleted: 0,
-    _hasResults: false,
-    handleResult: function(aResultSet)
-    {
-      do_check_false(this._hasResults);
-      this._hasResults = true;
-    },
-    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_double_execute (iteration " +
-            (this._timesCompleted + 1) + ")");
-      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
-      do_check_true(this._hasResults);
-      this._hasResults = false;
-      this._timesCompleted++;
-
-      // Run the next test.
-      if (this._timesCompleted == 2)
-        run_next_test();
-    }
-  }
-  stmt.executeAsync(listener);
-  stmt.executeAsync(listener);
+  execAsync(stmt, null, 1);
+  execAsync(stmt, null, 1);
   stmt.finalize();
+  run_next_test();
 }
 
 function test_finalized_statement_does_not_crash()
 {
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "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();
 }
 
+/**
+ * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by index.
+ */
+function test_bind_direct_binding_params_by_index()
+{
+  var stmt = makeTestStatement(
+    "INSERT INTO test (id, string, number, nuller, blober) " +
+    "VALUES (?, ?, ?, ?, ?)"
+  );
+  let insertId = nextUniqueId++;
+  stmt.bindByIndex(0, insertId);
+  stmt.bindByIndex(1, TEXT);
+  stmt.bindByIndex(2, REAL);
+  stmt.bindByIndex(3, null);
+  stmt.bindBlobByIndex(4, BLOB, BLOB.length);
+  execAsync(stmt);
+  stmt.finalize();
+  verifyQuery("SELECT string, number, nuller, blober FROM test WHERE id = ?",
+              insertId,
+              [TEXT, REAL, null, BLOB]);
+  run_next_test();
+}
+
+/**
+ * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by name.
+ */
+function test_bind_direct_binding_params_by_name()
+{
+  var stmt = makeTestStatement(
+    "INSERT INTO test (id, string, number, nuller, blober) " +
+    "VALUES (:int, :text, :real, :null, :blob)"
+  );
+  let insertId = nextUniqueId++;
+  stmt.bindByName("int", insertId);
+  stmt.bindByName("text", TEXT);
+  stmt.bindByName("real", REAL);
+  stmt.bindByName("null", null);
+  stmt.bindBlobByName("blob", BLOB, BLOB.length);
+  execAsync(stmt);
+  stmt.finalize();
+  verifyQuery("SELECT string, number, nuller, blober FROM test WHERE id = ?",
+              insertId,
+              [TEXT, REAL, null, BLOB]);
+  run_next_test();
+}
+
+function test_bind_js_params_helper_by_index()
+{
+  var stmt = makeTestStatement(
+    "INSERT INTO test (id, string, number, nuller, blober) " +
+    "VALUES (?, ?, ?, ?, NULL)"
+  );
+  let insertId = nextUniqueId++;
+  // we cannot bind blobs this way; no blober
+  stmt.params[3] = null;
+  stmt.params[2] = REAL;
+  stmt.params[1] = TEXT;
+  stmt.params[0] = insertId;
+  execAsync(stmt);
+  stmt.finalize();
+  verifyQuery("SELECT string, number, nuller FROM test WHERE id = ?", insertId,
+              [TEXT, REAL, null]);
+  run_next_test();
+}
+
+function test_bind_js_params_helper_by_name()
+{
+  var stmt = makeTestStatement(
+    "INSERT INTO test (id, string, number, nuller, blober) " +
+    "VALUES (:int, :text, :real, :null, NULL)"
+  );
+  let insertId = nextUniqueId++;
+  // we cannot bind blobs this way; no blober
+  stmt.params.null = null;
+  stmt.params.real = REAL;
+  stmt.params.text = TEXT;
+  stmt.params.int = insertId;
+  execAsync(stmt);
+  stmt.finalize();
+  verifyQuery("SELECT string, number, nuller FROM test WHERE id = ?", insertId,
+              [TEXT, REAL, null]);
+  run_next_test();
+}
+
 function test_bind_multiple_rows_by_index()
 {
   const AMOUNT_TO_ADD = 5;
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "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);
     do_check_eq(array.length, i + 1);
   }
   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();
-    }
-  });
+  let rowCount = getTableRowCount("test");
+  execAsync(stmt);
+  do_check_eq(rowCount + AMOUNT_TO_ADD, getTableRowCount("test"));
   stmt.finalize();
+  run_next_test();
 }
 
 function test_bind_multiple_rows_by_name()
 {
   const AMOUNT_TO_ADD = 5;
-  var stmt = getOpenedDatabase().createStatement(
+  var stmt = makeTestStatement(
     "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);
     do_check_eq(array.length, i + 1);
   }
   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();
-    }
-  });
+  let rowCount = getTableRowCount("test");
+  execAsync(stmt);
+  do_check_eq(rowCount + AMOUNT_TO_ADD, getTableRowCount("test"));
   stmt.finalize();
+  run_next_test();
 }
 
-function test_bind_out_of_bounds()
+/**
+ * Verify that a mozIStorageStatement instance throws immediately when we
+ * try and bind to an illegal index.
+ */
+function test_bind_out_of_bounds_sync_immediate()
 {
-  let stmt = getOpenedDatabase().createStatement(
+  let stmt = makeTestStatement(
     "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);
-
+  expectError(Cr.NS_ERROR_INVALID_ARG,
+              function() bp.bindByIndex(1, INTEGER));
   // 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);
+  expectError(Cr.NS_ERROR_INVALID_ARG,
+              function() bp.bindBlobByIndex(1, BLOB, BLOB.length));
 
   stmt.finalize();
-
-  // Run the next test.
   run_next_test();
 }
+test_bind_out_of_bounds_sync_immediate.syncOnly = true;
 
-function test_bind_no_such_name()
+/**
+ * Verify that a mozIStorageAsyncStatement reports an error asynchronously when
+ * we bind to an illegal index.
+ */
+function test_bind_out_of_bounds_async_deferred()
 {
-  let stmt = getOpenedDatabase().createStatement(
+  let stmt = makeTestStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (?)"
+  );
+
+  let array = stmt.newBindingParamsArray();
+  let bp = array.newBindingParams();
+
+  // There is no difference between variant and blob binding for async purposes.
+  bp.bindByIndex(1, INTEGER);
+  array.addParams(bp);
+  stmt.bindParameters(array);
+  execAsync(stmt, {error: Ci.mozIStorageError.RANGE});
+
+  stmt.finalize();
+  run_next_test();
+}
+test_bind_out_of_bounds_async_deferred.asyncOnly = true;
+
+function test_bind_no_such_name_sync_immediate()
+{
+  let stmt = makeTestStatement(
     "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);
-
+  expectError(Cr.NS_ERROR_INVALID_ARG,
+              function() bp.bindByName("doesnotexist", INTEGER));
   // 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);
+  expectError(Cr.NS_ERROR_INVALID_ARG,
+              function() bp.bindBlobByName("doesnotexist", BLOB, BLOB.length));
 
   stmt.finalize();
-
-  // Run the next test.
   run_next_test();
 }
+test_bind_no_such_name_sync_immediate.syncOnly = true;
+
+function test_bind_no_such_name_async_deferred()
+{
+  let stmt = makeTestStatement(
+    "INSERT INTO test (id) " +
+    "VALUES (:foo)"
+  );
+
+  let array = stmt.newBindingParamsArray();
+  let bp = array.newBindingParams();
+
+  bp.bindByName("doesnotexist", INTEGER);
+  array.addParams(bp);
+  stmt.bindParameters(array);
+  execAsync(stmt, {error: Ci.mozIStorageError.RANGE});
+
+  stmt.finalize();
+  run_next_test();
+}
+test_bind_no_such_name_async_deferred.asyncOnly = true;
 
 function test_bind_bogus_type_by_index()
 {
   // We try to bind a JS Object here that should fail to bind.
-  let stmt = getOpenedDatabase().createStatement(
+  let stmt = makeTestStatement(
     "INSERT INTO test (blober) " +
     "VALUES (?)"
   );
 
-  // We get an error after calling executeAsync, not when we bind.
   let array = stmt.newBindingParamsArray();
   let bp = array.newBindingParams();
+  // We get an error after calling executeAsync, not when we bind.
   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);
+  execAsync(stmt, {error: Ci.mozIStorageError.MISMATCH});
 
-      // Run the next test.
-      run_next_test();
-    }
-  });
   stmt.finalize();
+  run_next_test();
 }
 
 function test_bind_bogus_type_by_name()
 {
   // We try to bind a JS Object here that should fail to bind.
-  let stmt = getOpenedDatabase().createStatement(
+  let stmt = makeTestStatement(
     "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();
+  // We get an error after calling executeAsync, not when we bind.
   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);
+  execAsync(stmt, {error: Ci.mozIStorageError.MISMATCH});
 
-      // Run the next test.
-      run_next_test();
-    }
-  });
   stmt.finalize();
+  run_next_test();
 }
 
 function test_bind_params_already_locked()
 {
-  let stmt = getOpenedDatabase().createStatement(
+  let stmt = makeTestStatement(
     "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);
+  expectError(Cr.NS_ERROR_UNEXPECTED,
+              function() bp.bindByName("int", INTEGER));
 
-  // Run the next test.
+  stmt.finalize();
   run_next_test();
 }
 
 function test_bind_params_array_already_locked()
 {
-  let stmt = getOpenedDatabase().createStatement(
+  let stmt = makeTestStatement(
     "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);
+  expectError(Cr.NS_ERROR_UNEXPECTED,
+              function() array.addParams(bp2));
 
-  // Run the next test.
+  stmt.finalize();
   run_next_test();
 }
 
 function test_no_binding_params_from_locked_array()
 {
-  let stmt = getOpenedDatabase().createStatement(
+  let stmt = makeTestStatement(
     "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);
+  expectError(Cr.NS_ERROR_UNEXPECTED,
+              function() array.newBindingParams());
 
-  // Run the next test.
+  stmt.finalize();
   run_next_test();
 }
 
 function test_not_right_owning_array()
 {
-  let stmt = getOpenedDatabase().createStatement(
+  let stmt = makeTestStatement(
     "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);
+  expectError(Cr.NS_ERROR_UNEXPECTED,
+              function() array2.addParams(bp));
 
-  // Run the next test.
+  stmt.finalize();
   run_next_test();
 }
 
 function test_not_right_owning_statement()
 {
-  let stmt1 = getOpenedDatabase().createStatement(
+  let stmt1 = makeTestStatement(
     "INSERT INTO test (id) " +
     "VALUES (:int)"
   );
-  let stmt2 = getOpenedDatabase().createStatement(
+  let stmt2 = makeTestStatement(
     "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);
+  expectError(Cr.NS_ERROR_UNEXPECTED,
+              function() stmt2.bindParameters(array1));
 
-  // Run the next test.
+  stmt1.finalize();
+  stmt2.finalize();
   run_next_test();
 }
 
 function test_bind_empty_array()
 {
-  let stmt = getOpenedDatabase().createStatement(
+  let stmt = makeTestStatement(
     "INSERT INTO test (id) " +
     "VALUES (:int)"
   );
 
   let paramsArray = stmt.newBindingParamsArray();
 
   // We should not be able to bind this array to the statement because it is
   // empty.
-  let exceptionCaught = false;
-  try {
-    stmt.bindParameters(paramsArray);
-    do_throw("we should have an exception!");
-  }
-  catch(e) {
-    do_check_eq(e.result, Cr.NS_ERROR_UNEXPECTED);
-    exceptionCaught = true;
-  }
-  do_check_true(exceptionCaught);
+  expectError(Cr.NS_ERROR_UNEXPECTED,
+              function() stmt.bindParameters(paramsArray));
 
-  // Run the next test.
+  stmt.finalize();
   run_next_test();
 }
 
 function test_multiple_results()
 {
-  // First, we need to know how many rows we are expecting.
-  let stmt = createStatement("SELECT COUNT(1) FROM test");
-  try {
-    do_check_true(stmt.executeStep());
-    var expectedResults = stmt.getInt32(0);
-  }
-  finally {
-    stmt.finalize();
-  }
-
+  let expectedResults = getTableRowCount("test");
   // Sanity check - we should have more than one result, but let's be sure.
   do_check_true(expectedResults > 1);
 
   // Now check that we get back two rows of data from our async query.
-  stmt = createStatement("SELECT * FROM test");
-  stmt.executeAsync({
-    _results: 0,
-    handleResult: function(aResultSet)
-    {
-      while (aResultSet.getNextRow())
-        this._results++;
-    },
-    handleError: function(aError)
-    {
-      print("Error code " + aError.result + " with message '" +
-            aError.message + "' returned.");
-      do_throw("Unexpected call to handleError!");
-    },
-    handleCompletion: function(aReason)
-    {
-      print("handleCompletion(" + aReason +
-            ") for test_multiple_results");
-      do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
+  let stmt = makeTestStatement("SELECT * FROM test");
+  execAsync(stmt, {}, expectedResults);
 
-      // Make sure we have the right number of results.
-      do_check_eq(this._results, expectedResults);
-
-      // Run the next test.
-      run_next_test();
-    }
-  });
   stmt.finalize();
+  run_next_test();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Test Runner
 
+
+const TEST_PASS_SYNC = 0;
+const TEST_PASS_ASYNC = 1;
+/**
+ * We run 2 passes against the test.  One where makeTestStatement generates
+ * synchronous (mozIStorageStatement) statements and one where it generates
+ * asynchronous (mozIStorageAsyncStatement) statements.
+ *
+ * Because of differences in the ability to know the number of parameters before
+ * dispatching, some tests are sync/async specific.  These functions are marked
+ * with 'syncOnly' or 'asyncOnly' attributes and run_next_test knows what to do.
+ */
+let testPass = TEST_PASS_SYNC;
+
+/**
+ * Create a statement of the type under test per testPass.
+ *
+ * @param aSQL
+ *        The SQL string from which to build a statement.
+ * @return a statement of the type under test per testPass.
+ */
+function makeTestStatement(aSQL) {
+  if (testPass == TEST_PASS_SYNC)
+    return getOpenedDatabase().createStatement(aSQL);
+  else
+    return getOpenedDatabase().createAsyncStatement(aSQL);
+}
+
 var tests =
 [
+  test_illegal_sql_async_deferred,
   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_cancellation_after_execution,
   test_double_execute,
   test_finalized_statement_does_not_crash,
+  test_bind_direct_binding_params_by_index,
+  test_bind_direct_binding_params_by_name,
+  test_bind_js_params_helper_by_index,
+  test_bind_js_params_helper_by_name,
   test_bind_multiple_rows_by_index,
   test_bind_multiple_rows_by_name,
-  test_bind_out_of_bounds,
-  test_bind_no_such_name,
+  test_bind_out_of_bounds_sync_immediate,
+  test_bind_out_of_bounds_async_deferred,
+  test_bind_no_such_name_sync_immediate,
+  test_bind_no_such_name_async_deferred,
   test_bind_bogus_type_by_index,
   test_bind_bogus_type_by_name,
   test_bind_params_already_locked,
   test_bind_params_array_already_locked,
   test_bind_empty_array,
   test_no_binding_params_from_locked_array,
   test_not_right_owning_array,
   test_not_right_owning_statement,
   test_multiple_results,
 ];
 let index = 0;
 
+const STARTING_UNIQUE_ID = 2;
+let nextUniqueId = STARTING_UNIQUE_ID;
+
 function run_next_test()
 {
-  if (index < tests.length) {
-    do_test_pending();
-    print("Running the next test: " + tests[index].name);
+  function _run_next_test() {
+    // use a loop so we can skip tests...
+    while (index < tests.length) {
+      let test = tests[index++];
+      // skip tests not appropriate to the current test pass
+      if ((testPass == TEST_PASS_SYNC && ("asyncOnly" in test)) ||
+          (testPass == TEST_PASS_ASYNC && ("syncOnly" in test)))
+        continue;
 
-    // Asynchronous tests means that exceptions don't kill the test.
-    try {
-      tests[index++]();
+      // Asynchronous tests means that exceptions don't kill the test.
+      try {
+        print("****** Running the next test: " + test.name);
+        test();
+        return;
+      }
+      catch (e) {
+        do_throw(e);
+      }
     }
-    catch (e) {
-      do_throw(e);
+
+    // if we only completed the first pass, move to the next pass
+    if (testPass == TEST_PASS_SYNC) {
+      print("********* Beginning mozIStorageAsyncStatement pass.");
+      testPass++;
+      index = 0;
+      // a new pass demands a new database
+      asyncCleanup();
+      nextUniqueId = STARTING_UNIQUE_ID;
+      _run_next_test();
+      return;
     }
+
+    // we did some async stuff; we need to clean up.
+    asyncCleanup();
+    do_test_finished();
   }
 
-  do_test_finished();
+  // Don't actually schedule another test if we're quitting.
+  if (!_quit) {
+    // For saner stacks, we execute this code RSN.
+    do_execute_soon(_run_next_test);
+  }
 }
 
 function run_test()
 {
   cleanup();
 
   do_test_pending();
   run_next_test();
--- a/toolkit/components/places/src/nsFaviconService.cpp
+++ b/toolkit/components/places/src/nsFaviconService.cpp
@@ -451,17 +451,17 @@ NS_IMETHODIMP
 nsFaviconService::ExpireAllFavicons()
 {
   mFaviconsExpirationRunning = true;
 
   // We do this in 2 steps, first we null-out all favicons in the disk table,
   // then we do the same in the temp table.  This is because the view UPDATE
   // trigger does not allow setting a NULL value to prevent dataloss.
 
-  mozIStorageStatement *stmts[] = {
+  mozIStorageBaseStatement *stmts[] = {
     GetStatement(mDBRemoveOnDiskReferences),
     GetStatement(mDBRemoveTempReferences),
     GetStatement(mDBRemoveAllFavicons),
   };
   NS_ENSURE_STATE(stmts[0] && stmts[1] && stmts[2]);
   nsCOMPtr<mozIStoragePendingStatement> ps;
   nsCOMPtr<ExpireFaviconsStatementCallbackNotifier> callback =
     new ExpireFaviconsStatementCallbackNotifier(&mFaviconsExpirationRunning);
--- a/toolkit/components/places/src/nsNavHistory.cpp
+++ b/toolkit/components/places/src/nsNavHistory.cpp
@@ -5848,17 +5848,17 @@ nsNavHistory::VacuumDatabase()
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<mozIStorageStatement> journalToDefault;
     rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
         "PRAGMA journal_mode = " DATABASE_JOURNAL_MODE),
       getter_AddRefs(journalToDefault));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mozIStorageStatement *stmts[] = {
+    mozIStorageBaseStatement *stmts[] = {
       journalToMemory,
       vacuum,
       journalToDefault
     };
     nsCOMPtr<mozIStoragePendingStatement> ps;
     rv = mDBConn->ExecuteAsync(stmts, NS_ARRAY_LENGTH(stmts), nsnull,
                                getter_AddRefs(ps));
     NS_ENSURE_SUCCESS(rv, rv);
@@ -5902,17 +5902,17 @@ nsNavHistory::DecayFrecency()
 
   // Delete any adaptive entries that won't help in ordering anymore.
   nsCOMPtr<mozIStorageStatement> deleteAdaptive;
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "DELETE FROM moz_inputhistory WHERE use_count < .01"),
     getter_AddRefs(deleteAdaptive));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mozIStorageStatement *stmts[] = {
+  mozIStorageBaseStatement *stmts[] = {
     decayFrecency,
     decayAdaptive,
     deleteAdaptive
   };
   nsCOMPtr<mozIStoragePendingStatement> ps;
   rv = mDBConn->ExecuteAsync(stmts, NS_ARRAY_LENGTH(stmts), nsnull,
                              getter_AddRefs(ps));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -6380,23 +6380,21 @@ nsNavHistory::BindQueryClauseParameters(
 //
 
 nsresult
 nsNavHistory::ResultsAsList(mozIStorageStatement* statement,
                             nsNavHistoryQueryOptions* aOptions,
                             nsCOMArray<nsNavHistoryResultNode>* aResults)
 {
   nsresult rv;
-  nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool hasMore = PR_FALSE;
   while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) {
     nsRefPtr<nsNavHistoryResultNode> result;
-    rv = RowToResult(row, aOptions, getter_AddRefs(result));
+    rv = RowToResult(statement, aOptions, getter_AddRefs(result));
     NS_ENSURE_SUCCESS(rv, rv);
     aResults->AppendObject(result);
   }
   return NS_OK;
 }
 
 static PRInt64
 GetAgeInDays(PRTime aNormalizedNow, PRTime aDate)
@@ -6773,17 +6771,17 @@ nsNavHistory::GetRedirectFor(const nsACS
 
 
 // nsNavHistory::RowToResult
 //
 //    Here, we just have a generic row. It could be a query, URL, visit,
 //    or full visit.
 
 nsresult
-nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
+nsNavHistory::RowToResult(mozIStorageStatement* aRow,
                           nsNavHistoryQueryOptions* aOptions,
                           nsNavHistoryResultNode** aResult)
 {
   *aResult = nsnull;
   NS_ASSERTION(aRow && aOptions && aResult, "Null pointer in RowToResult");
 
   // URL
   nsCAutoString url;
--- a/toolkit/components/places/src/nsNavHistory.h
+++ b/toolkit/components/places/src/nsNavHistory.h
@@ -250,17 +250,17 @@ public:
   // nsNavHistoryQueryResultNode
   nsresult GetQueryResults(nsNavHistoryQueryResultNode *aResultNode,
                            const nsCOMArray<nsNavHistoryQuery>& aQueries,
                            nsNavHistoryQueryOptions *aOptions,
                            nsCOMArray<nsNavHistoryResultNode>* aResults);
 
   // Take a row of kGetInfoIndex_* columns and construct a ResultNode.
   // The row must contain the full set of columns.
-  nsresult RowToResult(mozIStorageValueArray* aRow,
+  nsresult RowToResult(mozIStorageStatement* aRow,
                        nsNavHistoryQueryOptions* aOptions,
                        nsNavHistoryResultNode** aResult);
   nsresult QueryRowToResult(PRInt64 aItemId, const nsACString& aURI,
                             const nsACString& aTitle,
                             PRUint32 aAccessCount, PRTime aTime,
                             const nsACString& aFavicon,
                             nsNavHistoryResultNode** aNode);