diff --git a/storage/public/Makefile.in b/storage/public/Makefile.in index f3a69eda4d9..339aabf288d 100644 --- a/storage/public/Makefile.in +++ b/storage/public/Makefile.in @@ -65,6 +65,8 @@ XPIDLSRCS = \ mozIStorageBindingParamsArray.idl \ mozIStorageBindingParams.idl \ mozIStorageCompletionCallback.idl \ + mozIStorageBaseStatement.idl \ + mozIStorageAsyncStatement.idl \ $(NULL) EXPORTS_NAMESPACES = mozilla diff --git a/storage/public/mozIStorageAsyncStatement.idl b/storage/public/mozIStorageAsyncStatement.idl new file mode 100644 index 00000000000..e067c92aa7f --- /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 + * + * 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; + */ +}; diff --git a/storage/public/mozIStorageBaseStatement.idl b/storage/public/mozIStorageBaseStatement.idl new file mode 100644 index 00000000000..72d9f1c9e37 --- /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 + * Shawn Wilsher + * Andrew Sutherland + * + * 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); +}; diff --git a/storage/public/mozIStorageConnection.idl b/storage/public/mozIStorageConnection.idl index 1ff0e61915f..16b02983934 100644 --- a/storage/public/mozIStorageConnection.idl +++ b/storage/public/mozIStorageConnection.idl @@ -45,7 +45,9 @@ interface mozIStorageAggregateFunction; interface mozIStorageCompletionCallback; interface mozIStorageFunction; interface mozIStorageProgressHandler; +interface mozIStorageBaseStatement; interface mozIStorageStatement; +interface mozIStorageAsyncStatement; interface mozIStorageStatementCallback; interface mozIStoragePendingStatement; interface nsIFile; @@ -59,11 +61,10 @@ interface nsIFile; * * @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 @@ -125,9 +126,8 @@ interface mozIStorageConnection : nsISupports { */ attribute long schemaVersion; - /* - * Statement creation - */ + ////////////////////////////////////////////////////////////////////////////// + //// Statement creation /** * Create a mozIStorageStatement for the given SQL expression. The @@ -135,12 +135,28 @@ interface mozIStorageConnection : nsISupports { * ?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. * @@ -165,10 +181,10 @@ interface mozIStorageConnection : nsISupports { * @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 ); @@ -176,8 +192,9 @@ interface mozIStorageConnection : nsISupports { /** * 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); @@ -185,14 +202,12 @@ interface mozIStorageConnection : nsISupports { * 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. @@ -225,9 +240,8 @@ interface mozIStorageConnection : nsISupports { */ void rollbackTransaction(); - /* - * Tables - */ + ////////////////////////////////////////////////////////////////////////////// + //// Tables /** * Create the table with the given name and schema. @@ -236,34 +250,36 @@ interface mozIStorageConnection : nsISupports { * (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 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. + * @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". * + * @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, @@ -274,12 +290,14 @@ interface mozIStorageConnection : nsISupports { * 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, @@ -287,7 +305,8 @@ interface mozIStorageConnection : nsISupports { /** * 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); @@ -297,10 +316,11 @@ interface mozIStorageConnection : nsISupports { * 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, diff --git a/storage/public/mozIStorageError.idl b/storage/public/mozIStorageError.idl index 2b884a9abbc..36d19d1f193 100644 --- a/storage/public/mozIStorageError.idl +++ b/storage/public/mozIStorageError.idl @@ -158,6 +158,12 @@ interface mozIStorageError : nsISupports { */ 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. */ diff --git a/storage/public/mozIStorageFunction.idl b/storage/public/mozIStorageFunction.idl index adeab7aa663..9c9800a6cc4 100644 --- a/storage/public/mozIStorageFunction.idl +++ b/storage/public/mozIStorageFunction.idl @@ -39,8 +39,9 @@ #include "nsISupports.idl" +#include "mozIStorageValueArray.idl" + interface mozIStorageConnection; -interface mozIStorageValueArray; interface nsIArray; interface nsIVariant; diff --git a/storage/public/mozIStorageStatement.idl b/storage/public/mozIStorageStatement.idl index 0c409190a89..f74eacbec9d 100644 --- a/storage/public/mozIStorageStatement.idl +++ b/storage/public/mozIStorageStatement.idl @@ -22,6 +22,8 @@ * * Contributor(s): * Vladimir Vukicevic + * Shawn Wilsher + * Andrew Sutherland * * 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 @@ -37,23 +39,16 @@ * * ***** END LICENSE BLOCK ***** */ -#include "nsISupports.idl" -#include "mozIStorageValueArray.idl" +#include "mozIStorageBaseStatement.idl" -interface mozIStorageConnection; -interface mozIStorageStatementCallback; -interface mozIStoragePendingStatement; -interface mozIStorageBindingParamsArray; - -[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(); +[ptr] native octetPtr(PRUint8); +/** + * 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 @@ -79,7 +74,7 @@ interface mozIStorageStatement : mozIStorageValueArray { * @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); @@ -96,7 +91,8 @@ interface mozIStorageStatement : mozIStorageValueArray { /** * 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); @@ -104,9 +100,10 @@ interface mozIStorageStatement : mozIStorageValueArray { /** * 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); @@ -115,39 +112,6 @@ interface mozIStorageStatement : mozIStorageValueArray { */ 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(). @@ -162,10 +126,9 @@ interface mozIStorageStatement : mozIStorageValueArray { * 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(); @@ -176,7 +139,7 @@ interface mozIStorageStatement : mozIStorageValueArray { * * @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(); */ @@ -195,43 +158,152 @@ interface mozIStorageStatement : mozIStorageValueArray { * readonly attribute mozIStorageStatementRow row; */ - /** - * 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. - * @returns an object that can be used to cancel the statements execution. - */ - mozIStoragePendingStatement executeAsync( - [optional] in mozIStorageStatementCallback aCallback - ); + ////////////////////////////////////////////////////////////////////////////// + //// Copied contents of mozIStorageValueArray /** - * The current state. Row getters are only valid while - * the statement is in the "executing" state. + * These type values are returned by getTypeOfIndex + * to indicate what type of value is present at + * a given column. */ - 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; + 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; /** - * Escape a string for SQL LIKE search. - * - * @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 '/'... + * The number of entries in the array (each corresponding to a column in the + * database row) */ - AString escapeStringForLIKE(in AString aValue, in wchar aEscapeChar); + 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 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. + * @{ + */ + 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); + /** @} */ + + /** + * 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. + */ + 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); + + /** + * 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. + * + * Not virtual; no vtable bloat. + */ + + 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; + } + +%} }; diff --git a/storage/public/mozIStorageValueArray.idl b/storage/public/mozIStorageValueArray.idl index 020c8f38610..c43b9466465 100644 --- a/storage/public/mozIStorageValueArray.idl +++ b/storage/public/mozIStorageValueArray.idl @@ -41,8 +41,8 @@ [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 { diff --git a/storage/src/IStorageBindingParamsInternal.h b/storage/src/IStorageBindingParamsInternal.h new file mode 100644 index 00000000000..f4ef374d738 --- /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 + * + * 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 bind(sqlite3_stmt *aStatement) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IStorageBindingParamsInternal, + ISTORAGEBINDINGPARAMSINTERNAL_IID) + +#define NS_DECL_ISTORAGEBINDINGPARAMSINTERNAL \ + already_AddRefed bind(sqlite3_stmt *aStatement); + +} // storage +} // mozilla + +#endif // mozilla_storage_IStorageBindingParamsInternal_h_ diff --git a/storage/src/Makefile.in b/storage/src/Makefile.in index 91d9a655132..9dd384e7563 100644 --- a/storage/src/Makefile.in +++ b/storage/src/Makefile.in @@ -75,6 +75,10 @@ CPPSRCS = \ mozStoragePrivateHelpers.cpp \ mozStorageBindingParamsArray.cpp \ mozStorageBindingParams.cpp \ + mozStorageAsyncStatement.cpp \ + mozStorageAsyncStatementJSHelper.cpp \ + mozStorageAsyncStatementParams.cpp \ + StorageBaseStatementInternal.cpp \ SQLCollations.cpp \ $(NULL) diff --git a/storage/src/StorageBaseStatementInternal.cpp b/storage/src/StorageBaseStatementInternal.cpp new file mode 100644 index 00000000000..6e69a341c10 --- /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 + * + * 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 mStatement; + nsRefPtr 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 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 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 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 diff --git a/storage/src/StorageBaseStatementInternal.h b/storage/src/StorageBaseStatementInternal.h new file mode 100644 index 00000000000..415424a4c06 --- /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 + * + * 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 newBindingParams( + mozIStorageBindingParamsArray *aOwner + ) = 0; + +protected: // mix-in bits are protected + StorageBaseStatementInternal(); + + nsRefPtr 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 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_ diff --git a/storage/src/mozStorageAsyncStatement.cpp b/storage/src/mozStorageAsyncStatement.cpp new file mode 100644 index 00000000000..b3a44dea614 --- /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 + * Shawn Wilsher + * John Zhang + * + * 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 +#include + +#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 array; + rv = NewBindingParamsArray(getter_AddRefs(array)); + NS_ENSURE_SUCCESS(rv, nsnull); + + mParamsArray = static_cast(array.get()); + } + + // If there isn't already any rows added, we'll have to add one to use. + if (mParamsArray->length() == 0) { + nsRefPtr 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 wrapper = + do_QueryInterface(mStatementParamsHolder); + nsCOMPtr iParams = + do_QueryWrappedNative(wrapper); + AsyncStatementParams *params = + static_cast(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(&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 +AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner) +{ + if (mFinalized) + return nsnull; + + nsCOMPtr 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(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 diff --git a/storage/src/mozStorageAsyncStatement.h b/storage/src/mozStorageAsyncStatement.h new file mode 100644 index 00000000000..108d961ac03 --- /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 + * Andrew Sutherland + * + * 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() + { + 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 mParamsArray; + + /** + * Caches the JS 'params' helper for this statement. + */ + nsCOMPtr 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_ diff --git a/storage/src/mozStorageAsyncStatementExecution.cpp b/storage/src/mozStorageAsyncStatementExecution.cpp index e23528f682a..0f5aa0f3f7a 100644 --- a/storage/src/mozStorageAsyncStatementExecution.cpp +++ b/storage/src/mozStorageAsyncStatementExecution.cpp @@ -138,7 +138,9 @@ private: }; /** - * 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 { @@ -148,21 +150,26 @@ public: * 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 mKeepAsyncAlive; mozIStorageStatementCallback *mCallback; ExecutionState mReason; }; @@ -185,7 +192,7 @@ AsyncExecuteStatements::execute(StatementDataArray &aStatements, NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); // Dispatch it to the background - nsCOMPtr 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); @@ -207,6 +214,7 @@ AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements, , mState(PENDING) , mCancelRequested(false) , mMutex(aConnection->sharedAsyncExecutionMutex) +, mDBMutex(aConnection->sharedDBMutex) { (void)mStatements.SwapElements(aStatements); NS_ASSERTION(mStatements.Length(), "We weren't given any statements!"); @@ -236,7 +244,10 @@ AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData, { 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. @@ -245,8 +256,9 @@ AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData, BindingParamsArray::iterator end = paramsArray->end(); while (itr != end && continueProcessing) { // Bind the data to our statement. - nsCOMPtr error; - error = (*itr)->bind(stmt); + nsCOMPtr bindingInternal = + do_QueryInterface(*itr); + nsCOMPtr error = bindingInternal->bind(aStatement); if (error) { // Set our error state. mState = ERROR; @@ -259,10 +271,10 @@ AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData, // 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; @@ -327,6 +339,9 @@ 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) @@ -338,6 +353,9 @@ AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement) // 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; @@ -346,9 +364,13 @@ AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement) // 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 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; @@ -423,17 +445,16 @@ AsyncExecuteStatements::notifyComplete() mTransactionManager = nsnull; } - // Notify about completion iff we have a callback. - if (mCallback) { - nsRefPtr 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 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; } @@ -443,6 +464,7 @@ AsyncExecuteStatements::notifyError(PRInt32 aErrorCode, const char *aMessage) { mMutex.AssertNotCurrentThreadOwns(); + mDBMutex.assertNotCurrentThreadOwns(); if (!mCallback) return NS_OK; @@ -457,6 +479,7 @@ nsresult AsyncExecuteStatements::notifyError(mozIStorageError *aError) { mMutex.AssertNotCurrentThreadOwns(); + mDBMutex.assertNotCurrentThreadOwns(); if (!mCallback) return NS_OK; @@ -546,13 +569,36 @@ AsyncExecuteStatements::Run() 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 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; } } diff --git a/storage/src/mozStorageAsyncStatementExecution.h b/storage/src/mozStorageAsyncStatementExecution.h index 2904ba1ebf4..46cedfe4b8b 100644 --- a/storage/src/mozStorageAsyncStatementExecution.h +++ b/storage/src/mozStorageAsyncStatementExecution.h @@ -47,6 +47,7 @@ #include "mozilla/Mutex.h" #include "mozilla/TimeStamp.h" +#include "SQLiteMutex.h" #include "mozIStoragePendingStatement.h" #include "mozIStorageStatementCallback.h" @@ -181,6 +182,7 @@ private: * 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. @@ -235,6 +237,14 @@ private: * 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 diff --git a/storage/src/mozStorageAsyncStatementJSHelper.cpp b/storage/src/mozStorageAsyncStatementJSHelper.cpp new file mode 100644 index 00000000000..688877fb165 --- /dev/null +++ b/storage/src/mozStorageAsyncStatementJSHelper.cpp @@ -0,0 +1,147 @@ +/* -*- 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 code. + * + * 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 (Original Author) + * Andrew Sutherland + * + * 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 "mozStorageAsyncStatement.h" +#include "mozStorageService.h" + +#include "nsMemory.h" +#include "nsString.h" +#include "nsServiceManagerUtils.h" + +#include "mozStorageAsyncStatementJSHelper.h" + +#include "mozStorageAsyncStatementParams.h" + +#include "jsapi.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// AsyncStatementJSHelper + +nsresult +AsyncStatementJSHelper::getParams(AsyncStatement *aStatement, + JSContext *aCtx, + JSObject *aScopeObj, + jsval *_params) +{ + nsresult rv; + +#ifdef DEBUG + PRInt32 state; + (void)aStatement->GetState(&state); + NS_ASSERTION(state == mozIStorageAsyncStatement::MOZ_STORAGE_STATEMENT_READY, + "Invalid state to get the params object - all calls will fail!"); +#endif + + if (!aStatement->mStatementParamsHolder) { + nsCOMPtr params = + new AsyncStatementParams(aStatement); + NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr xpc(Service::getXPConnect()); + rv = xpc->WrapNative( + aCtx, + ::JS_GetGlobalForObject(aCtx, aScopeObj), + params, + NS_GET_IID(mozIStorageStatementParams), + getter_AddRefs(aStatement->mStatementParamsHolder) + ); + NS_ENSURE_SUCCESS(rv, rv); + } + + JSObject *obj = nsnull; + rv = aStatement->mStatementParamsHolder->GetJSObject(&obj); + NS_ENSURE_SUCCESS(rv, rv); + + *_params = OBJECT_TO_JSVAL(obj); + return NS_OK; +} + +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 AsyncStatementJSHelper +#define XPC_MAP_QUOTED_CLASSNAME "AsyncStatementJSHelper" +#define XPC_MAP_WANT_GETPROPERTY +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" + +NS_IMETHODIMP +AsyncStatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + jsval aId, + jsval *_result, + PRBool *_retval) +{ + if (!JSVAL_IS_STRING(aId)) + return NS_OK; + + // Cast to async via mozI* since direct from nsISupports is ambiguous. + mozIStorageAsyncStatement *iAsyncStmt = + static_cast(aWrapper->Native()); + AsyncStatement *stmt = static_cast(iAsyncStmt); + +#ifdef DEBUG + { + nsISupports *supp = aWrapper->Native(); + nsCOMPtr 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, "params") == 0) + return getParams(stmt, aCtx, aScopeObj, _result); + + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/storage/src/mozStorageAsyncStatementJSHelper.h b/storage/src/mozStorageAsyncStatementJSHelper.h new file mode 100644 index 00000000000..1c44e2d23d9 --- /dev/null +++ b/storage/src/mozStorageAsyncStatementJSHelper.h @@ -0,0 +1,67 @@ +/* -*- 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 code. + * + * 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 (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 mozilla_storage_mozStorageAsyncStatementJSHelper_h_ +#define mozilla_storage_mozStorageAsyncStatementJSHelper_h_ + +#include "nsIXPCScriptable.h" + +class AsyncStatement; + +namespace mozilla { +namespace storage { + +/** + * 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 getParams(AsyncStatement *, JSContext *, JSObject *, jsval *); +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_mozStorageAsyncStatementJSHelper_h_ diff --git a/storage/src/mozStorageAsyncStatementParams.cpp b/storage/src/mozStorageAsyncStatementParams.cpp new file mode 100644 index 00000000000..20343c1da67 --- /dev/null +++ b/storage/src/mozStorageAsyncStatementParams.cpp @@ -0,0 +1,165 @@ +/* -*- 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 + * Shawn Wilsher + * + * 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 "nsMemory.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +#include "mozStoragePrivateHelpers.h" +#include "mozStorageAsyncStatement.h" +#include "mozStorageAsyncStatementParams.h" +#include "mozIStorageStatement.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// AsyncStatementParams + +AsyncStatementParams::AsyncStatementParams(AsyncStatement *aStatement) +: mStatement(aStatement) +{ + NS_ASSERTION(mStatement != nsnull, "mStatement is null"); +} + +NS_IMPL_ISUPPORTS2( + AsyncStatementParams +, mozIStorageStatementParams +, nsIXPCScriptable +) + +//////////////////////////////////////////////////////////////////////////////// +//// nsIXPCScriptable + +#define XPC_MAP_CLASSNAME AsyncStatementParams +#define XPC_MAP_QUOTED_CLASSNAME "AsyncStatementParams" +#define XPC_MAP_WANT_SETPROPERTY +#define XPC_MAP_WANT_NEWRESOLVE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" + +NS_IMETHODIMP +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); + + nsCOMPtr 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 + (::JS_GetStringChars(str)), + ::JS_GetStringLength(str)); + + nsCOMPtr variant(convertJSValToVariant(aCtx, *_vp)); + NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED); + nsresult rv = mStatement->BindByName(name, variant); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + return NS_ERROR_INVALID_ARG; + } + + *_retval = PR_TRUE; + return NS_OK; +} + +NS_IMETHODIMP +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); + // 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); + + // 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; + } + + *_retval = ::JS_DefineElement(aCtx, aScopeObj, idx, JSVAL_VOID, nsnull, + nsnull, 0); + if (*_retval) + *_objp = aScopeObj; + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/storage/src/mozStorageAsyncStatementParams.h b/storage/src/mozStorageAsyncStatementParams.h new file mode 100644 index 00000000000..ef04de13315 --- /dev/null +++ b/storage/src/mozStorageAsyncStatementParams.h @@ -0,0 +1,77 @@ +/* -*- 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 + * + * 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_mozStorageAsyncStatementParams_h_ +#define mozilla_storage_mozStorageAsyncStatementParams_h_ + +#include "mozIStorageStatementWrapper.h" +#include "nsIXPCScriptable.h" + +class mozIStorageAsyncStatement; + +namespace mozilla { +namespace storage { + +class AsyncStatement; + +/* + * Since mozIStorageStatementParams is just a tagging interface we do not have + * an async variant. + */ +class AsyncStatementParams : public mozIStorageStatementParams + , public nsIXPCScriptable +{ +public: + AsyncStatementParams(AsyncStatement *aStatement); + + // interfaces + NS_DECL_ISUPPORTS + NS_DECL_MOZISTORAGESTATEMENTPARAMS + NS_DECL_NSIXPCSCRIPTABLE + +protected: + AsyncStatement *mStatement; + + friend class AsyncStatement; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_mozStorageAsyncStatementParams_h_ diff --git a/storage/src/mozStorageBindingParams.cpp b/storage/src/mozStorageBindingParams.cpp index cf6298332e5..902244a7cc9 100644 --- a/storage/src/mozStorageBindingParams.cpp +++ b/storage/src/mozStorageBindingParams.cpp @@ -22,6 +22,7 @@ * * Contributor(s): * Shawn Wilsher (Original Author) + * Andrew Sutherland * * 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 @@ -136,16 +137,32 @@ sqlite3_T_blob(BindingColumnData aData, //////////////////////////////////////////////////////////////////////////////// //// 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() { @@ -160,18 +177,75 @@ BindingParams::lock() } 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(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 BindingParams::bind(sqlite3_stmt *aStatement) { @@ -191,14 +265,26 @@ BindingParams::bind(sqlite3_stmt *aStatement) } } - // No error occurred, so return null! return nsnull; } -NS_IMPL_THREADSAFE_ISUPPORTS1( - BindingParams, - mozIStorageBindingParams -) +already_AddRefed +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 @@ -217,6 +303,18 @@ BindingParams::BindByName(const nsACString &aName, 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) @@ -305,6 +403,20 @@ BindingParams::BindByIndex(PRUint32 aIndex, 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) diff --git a/storage/src/mozStorageBindingParams.h b/storage/src/mozStorageBindingParams.h index fd175954131..eecf44905dc 100644 --- a/storage/src/mozStorageBindingParams.h +++ b/storage/src/mozStorageBindingParams.h @@ -22,6 +22,7 @@ * * Contributor(s): * Shawn Wilsher (Original Author) + * Andrew Sutherland * * 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 @@ -40,25 +41,27 @@ #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 @@ -68,33 +71,82 @@ public: /** * Unlocks the parameters and allows modification to it again. - */ - void unlock(); - - /** - * @returns the pointer to the owning BindingParamsArray. - */ - const BindingParamsArray *getOwner() const; - - /** - * Binds our stored data to the statement. * - * @param aStatement - * The statement to bind our data to. - * @returns nsnull on success, or a mozIStorageError object if an error - * occurred. + * @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. */ - already_AddRefed bind(sqlite3_stmt *aStatement); + void unlock(Statement *aOwningStatement); - BindingParams(BindingParamsArray *aOwningArray, + /** + * @returns the pointer to the owning BindingParamsArray. Used by a + * BindingParamsArray to verify that we belong to it when added. + */ + const mozIStorageBindingParamsArray *getOwner() const; + + BindingParams(mozIStorageBindingParamsArray *aOwningArray, Statement *aOwningStatement); + virtual ~BindingParams() {} + +protected: + BindingParams(mozIStorageBindingParamsArray *aOwningArray); + nsCOMArray mParameters; + bool mLocked; private: - nsRefPtr mOwningArray; + + /** + * 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 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. + */ Statement *mOwningStatement; - nsCOMArray mParameters; PRUint32 mParamCount; - bool mLocked; +}; + +/** + * 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 bind(sqlite3_stmt * aStatement); + + AsyncBindingParams(mozIStorageBindingParamsArray *aOwningArray); + virtual ~AsyncBindingParams() {} + +private: + nsInterfaceHashtable mNamedParameters; + + struct NamedParameterIterationClosureThunk + { + AsyncBindingParams *self; + sqlite3_stmt *statement; + nsCOMPtr err; + }; + + static PLDHashOperator iterateOverNamedParameters(const nsACString &aName, + nsIVariant *aValue, + void *voidClosureThunk); }; } // namespace storage diff --git a/storage/src/mozStorageBindingParamsArray.cpp b/storage/src/mozStorageBindingParamsArray.cpp index d59258f9adb..47519f1fa4d 100644 --- a/storage/src/mozStorageBindingParamsArray.cpp +++ b/storage/src/mozStorageBindingParamsArray.cpp @@ -39,6 +39,7 @@ #include "mozStorageBindingParamsArray.h" #include "mozStorageBindingParams.h" +#include "StorageBaseStatementInternal.h" namespace mozilla { namespace storage { @@ -46,7 +47,9 @@ namespace storage { //////////////////////////////////////////////////////////////////////////////// //// BindingParamsArray -BindingParamsArray::BindingParamsArray(Statement *aOwningStatement) +BindingParamsArray::BindingParamsArray( + StorageBaseStatementInternal *aOwningStatement +) : mOwningStatement(aOwningStatement) , mLocked(false) { @@ -63,7 +66,7 @@ BindingParamsArray::lock() mOwningStatement = nsnull; } -const Statement * +const StorageBaseStatementInternal * BindingParamsArray::getOwner() const { return mOwningStatement; @@ -82,9 +85,9 @@ BindingParamsArray::NewBindingParams(mozIStorageBindingParams **_params) { NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); - nsCOMPtr params = - new BindingParams(this, mOwningStatement); - NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); + nsCOMPtr params( + mOwningStatement->newBindingParams(this)); + NS_ENSURE_TRUE(params, NS_ERROR_UNEXPECTED); params.forget(_params); return NS_OK; diff --git a/storage/src/mozStorageBindingParamsArray.h b/storage/src/mozStorageBindingParamsArray.h index 0ce35ca93b5..e0198e47c1d 100644 --- a/storage/src/mozStorageBindingParamsArray.h +++ b/storage/src/mozStorageBindingParamsArray.h @@ -48,8 +48,7 @@ namespace mozilla { namespace storage { -class BindingParams; -class Statement; +class StorageBaseStatementInternal; class BindingParamsArray : public mozIStorageBindingParamsArray { @@ -57,7 +56,7 @@ public: NS_DECL_ISUPPORTS NS_DECL_MOZISTORAGEBINDINGPARAMSARRAY - BindingParamsArray(Statement *aOwningStatement); + BindingParamsArray(StorageBaseStatementInternal *aOwningStatement); typedef nsTArray_base::size_type size_type; @@ -70,7 +69,7 @@ public: /** * @return the pointer to the owning BindingParamsArray. */ - const Statement *getOwner() const; + const StorageBaseStatementInternal *getOwner() const; /** * @return the number of elemets the array contains. @@ -100,7 +99,7 @@ public: { return !(*this == aOther); } - BindingParams *operator*() + mozIStorageBindingParams *operator*() { NS_ASSERTION(mIndex < mArray->length(), "Dereferenceing an invalid value!"); @@ -132,8 +131,8 @@ public: return iterator(this, length()); } private: - nsRefPtr mOwningStatement; - nsTArray< nsRefPtr > mArray; + nsCOMPtr mOwningStatement; + nsTArray< nsCOMPtr > mArray; bool mLocked; friend class iterator; diff --git a/storage/src/mozStorageConnection.cpp b/storage/src/mozStorageConnection.cpp index 7f0cd8c719a..51d32869728 100644 --- a/storage/src/mozStorageConnection.cpp +++ b/storage/src/mozStorageConnection.cpp @@ -62,9 +62,11 @@ #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" @@ -296,10 +298,10 @@ private: 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) @@ -317,7 +319,7 @@ NS_IMPL_THREADSAFE_ISUPPORTS1( mozIStorageConnection ) -already_AddRefed +nsIEventTarget * Connection::getAsyncExecutionTarget() { MutexAutoLock lockedScope(sharedAsyncExecutionMutex); @@ -335,9 +337,7 @@ Connection::getAsyncExecutionTarget() } } - nsIEventTarget *eventTarget; - NS_ADDREF(eventTarget = mAsyncExecutionThread); - return eventTarget; + return mAsyncExecutionThread; } nsresult @@ -367,7 +367,7 @@ Connection::initialize(nsIFile *aDatabaseFile) } // Properly wrap the database handle's mutex. - mDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn)); + sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn)); #ifdef PR_LOGGING if (!gStorageLog) @@ -487,7 +487,7 @@ Connection::databaseElementExists(enum DatabaseElementType aElementType, bool Connection::findFunctionByInstance(nsISupports *aInstance) { - mDBMutex.assertCurrentThreadOwns(); + sharedDBMutex.assertCurrentThreadOwns(); FFEArguments args = { aInstance, false }; mFunctions.EnumerateRead(findFunctionEnumerator, &args); return args.found; @@ -503,7 +503,7 @@ Connection::sProgressHelper(void *aArg) int Connection::progressHandler() { - mDBMutex.assertCurrentThreadOwns(); + sharedDBMutex.assertCurrentThreadOwns(); if (mProgressHandler) { PRBool result; nsresult rv = mProgressHandler->OnProgress(this, &result); @@ -610,7 +610,7 @@ Connection::AsyncClose(mozIStorageCompletionCallback *aCallback) if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; - nsCOMPtr asyncThread(getAsyncExecutionTarget()); + nsIEventTarget *asyncThread = getAsyncExecutionTarget(); NS_ENSURE_TRUE(asyncThread, NS_ERROR_UNEXPECTED); nsresult rv = setClosedState(); @@ -732,6 +732,25 @@ Connection::CreateStatement(const nsACString &aSQLStatement, 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 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) { @@ -742,22 +761,23 @@ Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement) return convertResultCode(srv); } -nsresult -Connection::ExecuteAsync(mozIStorageStatement **aStatements, +NS_IMETHODIMP +Connection::ExecuteAsync(mozIStorageBaseStatement **aStatements, PRUint32 aNumStatements, mozIStorageStatementCallback *aCallback, mozIStoragePendingStatement **_handle) { nsTArray stmts(aNumStatements); for (PRUint32 i = 0; i < aNumStatements; i++) { - Statement *stmt = static_cast(aStatements[i]); + nsCOMPtr 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. @@ -787,7 +807,7 @@ Connection::GetTransactionInProgress(PRBool *_inProgress) { if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; - SQLiteMutexAutoLock lockedScope(mDBMutex); + SQLiteMutexAutoLock lockedScope(sharedDBMutex); *_inProgress = mTransactionInProgress; return NS_OK; } @@ -803,7 +823,7 @@ 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; @@ -830,7 +850,7 @@ 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")); @@ -844,7 +864,7 @@ 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")); @@ -878,7 +898,7 @@ Connection::CreateFunction(const nsACString &aFunctionName, // 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, @@ -906,7 +926,7 @@ Connection::CreateAggregateFunction(const nsACString &aFunctionName, 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 @@ -936,7 +956,7 @@ 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, @@ -963,7 +983,7 @@ Connection::SetProgressHandler(PRInt32 aGranularity, 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) { @@ -982,7 +1002,7 @@ 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; diff --git a/storage/src/mozStorageConnection.h b/storage/src/mozStorageConnection.h index f8a06d14625..cd82d98cd12 100644 --- a/storage/src/mozStorageConnection.h +++ b/storage/src/mozStorageConnection.h @@ -92,16 +92,24 @@ public: * * @returns an event target suitable for asynchronous statement execution. */ - already_AddRefed 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. @@ -174,11 +182,6 @@ private: */ 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. diff --git a/storage/src/mozStoragePrivateHelpers.cpp b/storage/src/mozStoragePrivateHelpers.cpp index 41728e28044..7507ea398ec 100644 --- a/storage/src/mozStoragePrivateHelpers.cpp +++ b/storage/src/mozStoragePrivateHelpers.cpp @@ -48,9 +48,11 @@ #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 { @@ -122,23 +124,16 @@ checkAndLogStatementPerformance(sqlite3_stmt *aStatement) 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); @@ -146,38 +141,33 @@ bindJSValue(JSContext *aCtx, reinterpret_cast(::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 { diff --git a/storage/src/mozStoragePrivateHelpers.h b/storage/src/mozStoragePrivateHelpers.h index 403776aa14e..53ab1b3683b 100644 --- a/storage/src/mozStoragePrivateHelpers.h +++ b/storage/src/mozStoragePrivateHelpers.h @@ -52,7 +52,8 @@ #include "nsAutoPtr.h" class mozIStorageCompletionCallback; -class mozIStorageStatement; +class mozIStorageBaseStatement; +class mozIStorageBindingParams; class nsIRunnable; namespace mozilla { @@ -88,20 +89,18 @@ nsresult convertResultCode(int aSQLiteResultCode); 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. @@ -110,8 +109,9 @@ bool bindJSValue(JSContext *aCtx, mozIStorageStatement *aStatement, int aIdx, * The callback to be notified. * @return an nsIRunnable that can be dispatched to the calling thread. */ -already_AddRefed -newCompletionEvent(mozIStorageCompletionCallback *aCallback); +already_AddRefed newCompletionEvent( + mozIStorageCompletionCallback *aCallback +); } // namespace storage } // namespace mozilla diff --git a/storage/src/mozStorageStatement.cpp b/storage/src/mozStorageStatement.cpp index 8ef7500e4e2..e7198317bb9 100644 --- a/storage/src/mozStorageStatement.cpp +++ b/storage/src/mozStorageStatement.cpp @@ -39,14 +39,15 @@ * * ***** END LICENSE BLOCK ***** */ +#include #include #include "nsError.h" #include "nsMemory.h" -#include "nsProxyRelease.h" #include "nsThreadUtils.h" #include "nsIClassInfoImpl.h" #include "nsIProgrammingLanguage.h" +#include "Variant.h" #include "mozIStorageError.h" @@ -67,55 +68,16 @@ extern PRLogModuleInfo* gStorageLog; 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 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 @@ -194,11 +156,10 @@ static StatementClassInfo sStatementClassInfo; //// Statement Statement::Statement() -: mDBConnection(nsnull) +: StorageBaseStatementInternal() , mDBStatement(NULL) , mColumnNames() , mExecuting(false) -, mCachedAsyncStatement(NULL) { } @@ -279,48 +240,7 @@ Statement::initialize(Connection *aDBConnection, 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; @@ -344,7 +264,7 @@ Statement::getParams() // 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 @@ -357,25 +277,94 @@ Statement::getParams() 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(&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 +Statement::newBindingParams(mozIStorageBindingParamsArray *aOwner) +{ + nsCOMPtr 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) { @@ -392,6 +381,12 @@ Statement::Clone(mozIStorageStatement **_statement) NS_IMETHODIMP Statement::Finalize() +{ + return internalFinalize(false); +} + +nsresult +Statement::internalFinalize(bool aDestructing) { if (!mDBStatement) return NS_OK; @@ -404,26 +399,13 @@ Statement::Finalize() 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 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 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 @@ -570,97 +552,6 @@ Statement::Reset() 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) { @@ -680,17 +571,6 @@ Statement::BindParameters(mozIStorageBindingParamsArray *aParameters) return NS_OK; } -NS_IMETHODIMP -Statement::NewBindingParamsArray(mozIStorageBindingParamsArray **_array) -{ - nsCOMPtr array = - new BindingParamsArray(this); - NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY); - - array.forget(_array); - return NS_OK; -} - NS_IMETHODIMP Statement::Execute() { @@ -718,8 +598,9 @@ Statement::ExecuteStep(PRBool *_moreResults) return NS_ERROR_UNEXPECTED; BindingParamsArray::iterator row = mParamsArray->begin(); - nsCOMPtr error; - error = (*row)->bind(mDBStatement); + nsCOMPtr bindingInternal = + do_QueryInterface(*row); + nsCOMPtr error = bindingInternal->bind(mDBStatement); if (error) { PRInt32 srv; (void)error->GetResult(&srv); @@ -767,14 +648,6 @@ Statement::ExecuteStep(PRBool *_moreResults) 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) { @@ -788,25 +661,6 @@ Statement::GetState(PRInt32 *_state) 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) @@ -821,7 +675,7 @@ Statement::GetColumnDecltype(PRUint32 aParamIndex, } //////////////////////////////////////////////////////////////////////////////// -//// mozIStorageValueArray +//// mozIStorageValueArray (now part of mozIStorageStatement too) NS_IMETHODIMP Statement::GetNumEntries(PRUint32 *_length) @@ -845,19 +699,19 @@ Statement::GetTypeOfIndex(PRUint32 aIndex, 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; @@ -924,7 +778,7 @@ Statement::GetUTF8String(PRUint32 aIndex, 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); @@ -947,7 +801,7 @@ Statement::GetString(PRUint32 aIndex, 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); @@ -1031,9 +885,17 @@ Statement::GetIsNull(PRUint32 aIndex, 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 diff --git a/storage/src/mozStorageStatement.h b/storage/src/mozStorageStatement.h index c3dfe7908b9..03358f9474a 100644 --- a/storage/src/mozStorageStatement.h +++ b/storage/src/mozStorageStatement.h @@ -48,6 +48,8 @@ #include "mozStorageBindingParamsArray.h" #include "mozStorageStatementData.h" #include "mozIStorageStatement.h" +#include "mozIStorageValueArray.h" +#include "StorageBaseStatementInternal.h" class nsIXPConnectJSObjectHolder; struct sqlite3_stmt; @@ -56,14 +58,18 @@ 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(); @@ -94,20 +100,9 @@ public: 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 mDBConnection; sqlite3_stmt *mDBStatement; PRUint32 mParamCount; PRUint32 mResultColumnCount; @@ -118,7 +113,7 @@ private: * @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 @@ -126,22 +121,6 @@ private: */ nsRefPtr 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. @@ -149,6 +128,16 @@ private: nsCOMPtr mStatementParamsHolder; nsCOMPtr 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; }; diff --git a/storage/src/mozStorageStatementData.h b/storage/src/mozStorageStatementData.h index 1ad80bd88b6..de3a8002d8b 100644 --- a/storage/src/mozStorageStatementData.h +++ b/storage/src/mozStorageStatementData.h @@ -46,6 +46,9 @@ #include "nsTArray.h" #include "mozStorageBindingParamsArray.h" +#include "mozIStorageBaseStatement.h" +#include "mozStorageConnection.h" +#include "StorageBaseStatementInternal.h" struct sqlite3_stmt; @@ -57,7 +60,7 @@ class StatementData public: StatementData(sqlite3_stmt *aStatement, already_AddRefed aParamsArray, - nsISupports *aStatementOwner) + StorageBaseStatementInternal *aStatementOwner) : mStatement(aStatement) , mParamsArray(aParamsArray) , mStatementOwner(aStatementOwner) @@ -73,24 +76,50 @@ public: { } - 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; + } } /** @@ -119,7 +148,7 @@ private: * We hold onto a reference of the statement's owner so it doesn't get * destroyed out from under us. */ - nsCOMPtr mStatementOwner; + nsCOMPtr mStatementOwner; }; } // namespace storage diff --git a/storage/src/mozStorageStatementJSHelper.cpp b/storage/src/mozStorageStatementJSHelper.cpp index c7a087fb089..d8b3948f648 100644 --- a/storage/src/mozStorageStatementJSHelper.cpp +++ b/storage/src/mozStorageStatementJSHelper.cpp @@ -74,15 +74,19 @@ stepFunc(JSContext *aCtx, return JS_FALSE; } - Statement *stmt = static_cast(wrapper->Native()); - #ifdef DEBUG { - nsCOMPtr isStatement(do_QueryInterface(stmt)); + nsCOMPtr isStatement( + do_QueryInterface(wrapper->Native()) + ); NS_ASSERTION(isStatement, "How is this not a statement?!"); } #endif + Statement *stmt = static_cast( + static_cast(wrapper->Native()) + ); + PRBool hasMore = PR_FALSE; rv = stmt->ExecuteStep(&hasMore); if (NS_SUCCEEDED(rv) && !hasMore) { @@ -208,15 +212,18 @@ StatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper, if (!JSVAL_IS_STRING(aId)) return NS_OK; - Statement *stmt = static_cast(aWrapper->Native()); - #ifdef DEBUG { - nsCOMPtr isStatement(do_QueryInterface(stmt)); + nsCOMPtr isStatement( + do_QueryInterface(aWrapper->Native())); NS_ASSERTION(isStatement, "How is this not a statement?!"); } #endif + Statement *stmt = static_cast( + static_cast(aWrapper->Native()) + ); + const char *propName = ::JS_GetStringBytes(JSVAL_TO_STRING(aId)); if (::strcmp(propName, "row") == 0) return getRow(stmt, aCtx, aScopeObj, _result); diff --git a/storage/src/mozStorageStatementParams.cpp b/storage/src/mozStorageStatementParams.cpp index 40ad30be780..d7b84e7e4bf 100644 --- a/storage/src/mozStorageStatementParams.cpp +++ b/storage/src/mozStorageStatementParams.cpp @@ -88,8 +88,10 @@ StatementParams::SetProperty(nsIXPConnectWrappedNative *aWrapper, 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 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); @@ -98,12 +100,10 @@ StatementParams::SetProperty(nsIXPConnectWrappedNative *aWrapper, ::JS_GetStringLength(str)); // check to see if there's a parameter with this name - PRUint32 index; - nsresult rv = mStatement->GetParameterIndex(name, &index); + nsCOMPtr 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; diff --git a/storage/src/mozStorageStatementRow.cpp b/storage/src/mozStorageStatementRow.cpp index e3db3a75029..892e56f54fd 100644 --- a/storage/src/mozStorageStatementRow.cpp +++ b/storage/src/mozStorageStatementRow.cpp @@ -107,7 +107,8 @@ StatementRow::GetProperty(nsIXPConnectWrappedNative *aWrapper, else if (type == mozIStorageValueArray::VALUE_TYPE_TEXT) { PRUint32 bytes; const jschar *sval = reinterpret_cast( - mStatement->AsSharedWString(idx, &bytes) + static_cast(mStatement)-> + AsSharedWString(idx, &bytes) ); JSString *str = ::JS_NewUCStringCopyN(aCtx, sval, bytes / 2); if (!str) { @@ -118,7 +119,8 @@ StatementRow::GetProperty(nsIXPConnectWrappedNative *aWrapper, } else if (type == mozIStorageValueArray::VALUE_TYPE_BLOB) { PRUint32 length; - const PRUint8 *blob = mStatement->AsSharedBlob(idx, &length); + const PRUint8 *blob = static_cast(mStatement)-> + AsSharedBlob(idx, &length); JSObject *obj = ::JS_NewArrayObject(aCtx, length, nsnull); if (!obj) { *_retval = PR_FALSE; diff --git a/storage/src/mozStorageStatementWrapper.cpp b/storage/src/mozStorageStatementWrapper.cpp index 9e4f4866693..680ba7140e5 100644 --- a/storage/src/mozStorageStatementWrapper.cpp +++ b/storage/src/mozStorageStatementWrapper.cpp @@ -203,7 +203,9 @@ StatementWrapper::Call(nsIXPConnectWrappedNative *aWrapper, // bind parameters for (int i = 0; i < (int)aArgc; i++) { - if (!bindJSValue(aCtx, mStatement, i, aArgv[i])) { + nsCOMPtr variant(convertJSValToVariant(aCtx, aArgv[i])); + if (!variant || + NS_FAILED(mStatement->BindByIndex(i, variant))) { *_retval = PR_FALSE; return NS_ERROR_INVALID_ARG; } diff --git a/storage/style.txt b/storage/style.txt index 320355690d9..d9595965be6 100644 --- a/storage/style.txt +++ b/storage/style.txt @@ -25,6 +25,16 @@ will be enforcing them, so please obey them! * 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. @@ -39,7 +49,93 @@ will be enforcing them, so please obey them! * 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; +} diff --git a/storage/test/Makefile.in b/storage/test/Makefile.in index c83d41efcf9..789977d28d4 100644 --- a/storage/test/Makefile.in +++ b/storage/test/Makefile.in @@ -53,6 +53,7 @@ CPP_UNIT_TESTS = \ test_statement_scoper.cpp \ test_mutex.cpp \ test_binding_params.cpp \ + test_true_async.cpp \ $(NULL) ifdef MOZ_DEBUG diff --git a/storage/test/test_true_async.cpp b/storage/test/test_true_async.cpp new file mode 100644 index 00000000000..dd684b1a787 --- /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 (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 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 spinner(new AsyncStatementSpinner()); + + nsCOMPtr 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 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 +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 stmt; + db->CreateAsyncStatement( + NS_LITERAL_CSTRING("SELECT 1"), + getter_AddRefs(stmt)); + blocking_async_execute(stmt); + stmt->Finalize(); + + nsCOMPtr threadMan = + do_GetService("@mozilla.org/thread-manager;1"); + nsCOMPtr asyncThread; + threadMan->GetThreadFromPRThread(last_non_watched_thread, + getter_AddRefs(asyncThread)); + return asyncThread.forget(); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// Tests + +void +test_TrueAsyncStatement() +{ + hook_sqlite_mutex(); + + nsCOMPtr db(getMemoryDatabase()); + + // Start watching for forbidden mutex usage. + watch_for_mutex_use_on_this_thread(); + + // - statement with nothing to bind + nsCOMPtr 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 paramsArray; + stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); + nsCOMPtr 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 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 db(getMemoryDatabase()); + + // -- wedge the thread + nsCOMPtr target(get_conn_async_thread(db)); + do_check_true(target); + nsRefPtr wedger (new ThreadWedger(target)); + + // -- create statements and cancel them + // - async + nsCOMPtr asyncStmt; + db->CreateAsyncStatement( + NS_LITERAL_CSTRING("CREATE TABLE asyncTable (id INTEGER PRIMARY KEY)"), + getter_AddRefs(asyncStmt) + ); + + nsRefPtr asyncSpin(new AsyncStatementSpinner()); + nsCOMPtr asyncPend; + (void)asyncStmt->ExecuteAsync(asyncSpin, getter_AddRefs(asyncPend)); + do_check_true(asyncPend); + asyncPend->Cancel(); + + // - sync + nsCOMPtr syncStmt; + db->CreateStatement( + NS_LITERAL_CSTRING("CREATE TABLE syncTable (id INTEGER PRIMARY KEY)"), + getter_AddRefs(syncStmt) + ); + + nsRefPtr syncSpin(new AsyncStatementSpinner()); + nsCOMPtr 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" diff --git a/storage/test/unit/head_storage.js b/storage/test/unit/head_storage.js index 122f1013875..881b5b654ec 100644 --- a/storage/test/unit/head_storage.js +++ b/storage/test/unit/head_storage.js @@ -75,6 +75,34 @@ function cleanup() 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); @@ -119,5 +147,117 @@ 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(); diff --git a/storage/test/unit/test_connection_executeAsync.js b/storage/test/unit/test_connection_executeAsync.js index dd96b13e05a..a80c892e60d 100644 --- a/storage/test/unit/test_connection_executeAsync.js +++ b/storage/test/unit/test_connection_executeAsync.js @@ -35,7 +35,10 @@ * * ***** 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"; @@ -63,7 +66,7 @@ function test_create_and_add() 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); @@ -131,7 +134,7 @@ function test_create_and_add() function test_transaction_created() { let stmts = []; - stmts[0] = getOpenedDatabase().createStatement( + stmts[0] = getOpenedDatabase().createAsyncStatement( "BEGIN" ); stmts[1] = getOpenedDatabase().createStatement( @@ -169,13 +172,18 @@ function test_multiple_bindings_on_statements() 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); diff --git a/storage/test/unit/test_statement_executeAsync.js b/storage/test/unit/test_statement_executeAsync.js index a52f64b44e0..df1762fe977 100644 --- a/storage/test/unit/test_statement_executeAsync.js +++ b/storage/test/unit/test_statement_executeAsync.js @@ -20,6 +20,7 @@ * * Contributor(s): * Shawn Wilsher (Original Author) + * Andrew Sutherland * * 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 @@ -35,19 +36,179 @@ * * ***** 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, " + @@ -56,43 +217,23 @@ function test_create_table() "blober BLOB" + ")" ); - - 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")); - - // 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(); - } - }); + execAsync(stmt); stmt.finalize(); + + // 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) + 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 (?, ?, ?, ?, ?)" ); @@ -102,68 +243,25 @@ function test_add_data() 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); - - // 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(); - } - }); + execAsync(stmt); 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 @@ -208,46 +306,18 @@ function test_get_data() 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 @@ -280,30 +350,14 @@ function test_tuple_out_of_bounds() 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); @@ -316,7 +370,7 @@ function test_no_listener_works_on_success() function test_no_listener_works_on_results() { - var stmt = getOpenedDatabase().createStatement( + var stmt = makeTestStatement( "SELECT ?" ); stmt.bindInt32Parameter(0, 1); @@ -330,7 +384,7 @@ function test_no_listener_works_on_results() 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(); @@ -342,7 +396,7 @@ function test_no_listener_works_on_error() function test_partial_listener_works() { - var stmt = getOpenedDatabase().createStatement( + var stmt = makeTestStatement( "DELETE FROM test WHERE id = ?" ); stmt.bindInt32Parameter(0, 0); @@ -367,130 +421,80 @@ function test_partial_listener_works() 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(); @@ -504,10 +508,94 @@ function test_finalized_statement_does_not_crash() 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 (?, ?, ?, ?, ?)" ); @@ -524,60 +612,17 @@ function test_bind_multiple_rows_by_index() } 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)" ); @@ -594,59 +639,20 @@ function test_bind_multiple_rows_by_name() } 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 (?)" ); @@ -655,38 +661,45 @@ function test_bind_out_of_bounds() 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)" ); @@ -695,122 +708,82 @@ function test_bind_no_such_name() 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)" ); @@ -821,24 +794,16 @@ function test_bind_params_already_locked() 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)" ); @@ -852,24 +817,16 @@ function test_bind_params_array_already_locked() 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)" ); @@ -882,24 +839,16 @@ function test_no_binding_params_from_locked_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)" ); @@ -910,28 +859,20 @@ function test_not_right_owning_array() 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)" ); @@ -943,24 +884,17 @@ function test_not_right_owning_statement() 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)" ); @@ -969,72 +903,61 @@ function test_bind_empty_array() // 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, @@ -1045,12 +968,19 @@ var tests = 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, @@ -1063,22 +993,53 @@ var tests = ]; 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() diff --git a/toolkit/components/places/src/nsFaviconService.cpp b/toolkit/components/places/src/nsFaviconService.cpp index e5b9d1b2e11..1eb4f6d8794 100644 --- a/toolkit/components/places/src/nsFaviconService.cpp +++ b/toolkit/components/places/src/nsFaviconService.cpp @@ -456,7 +456,7 @@ nsFaviconService::ExpireAllFavicons() // 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), diff --git a/toolkit/components/places/src/nsNavHistory.cpp b/toolkit/components/places/src/nsNavHistory.cpp index f815ac9c2d7..8bdeb8805e0 100644 --- a/toolkit/components/places/src/nsNavHistory.cpp +++ b/toolkit/components/places/src/nsNavHistory.cpp @@ -5853,7 +5853,7 @@ nsNavHistory::VacuumDatabase() getter_AddRefs(journalToDefault)); NS_ENSURE_SUCCESS(rv, rv); - mozIStorageStatement *stmts[] = { + mozIStorageBaseStatement *stmts[] = { journalToMemory, vacuum, journalToDefault @@ -5907,7 +5907,7 @@ nsNavHistory::DecayFrecency() getter_AddRefs(deleteAdaptive)); NS_ENSURE_SUCCESS(rv, rv); - mozIStorageStatement *stmts[] = { + mozIStorageBaseStatement *stmts[] = { decayFrecency, decayAdaptive, deleteAdaptive @@ -6385,13 +6385,11 @@ nsNavHistory::ResultsAsList(mozIStorageStatement* statement, nsCOMArray* aResults) { nsresult rv; - nsCOMPtr row = do_QueryInterface(statement, &rv); - NS_ENSURE_SUCCESS(rv, rv); PRBool hasMore = PR_FALSE; while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { nsRefPtr result; - rv = RowToResult(row, aOptions, getter_AddRefs(result)); + rv = RowToResult(statement, aOptions, getter_AddRefs(result)); NS_ENSURE_SUCCESS(rv, rv); aResults->AppendObject(result); } @@ -6778,7 +6776,7 @@ nsNavHistory::GetRedirectFor(const nsACString& aDestination, // or full visit. nsresult -nsNavHistory::RowToResult(mozIStorageValueArray* aRow, +nsNavHistory::RowToResult(mozIStorageStatement* aRow, nsNavHistoryQueryOptions* aOptions, nsNavHistoryResultNode** aResult) { diff --git a/toolkit/components/places/src/nsNavHistory.h b/toolkit/components/places/src/nsNavHistory.h index 0bbdedadf4c..8c60dcea007 100644 --- a/toolkit/components/places/src/nsNavHistory.h +++ b/toolkit/components/places/src/nsNavHistory.h @@ -255,7 +255,7 @@ public: // 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,