--- 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);