Bug 458811 - Allow for multiple statements to be executed at in a transaction asynchronously
This changeset allows consumers to execute a series of statements, in order,
asynchronously in a transaction.
r=dcamp
sr=sicking
--- a/storage/public/mozIStorageConnection.idl
+++ b/storage/public/mozIStorageConnection.idl
@@ -40,33 +40,35 @@
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface mozIStorageAggregateFunction;
interface mozIStorageFunction;
interface mozIStorageProgressHandler;
interface mozIStorageStatement;
+interface mozIStorageStatementCallback;
+interface mozIStoragePendingStatement;
interface nsIFile;
/**
* mozIStorageConnection represents a database connection attached to
* a specific file or to the in-memory data storage. It is the
* primary interface for interacting with a database, including
* creating prepared statements, executing SQL, and examining database
* errors.
*
* @threadsafe
*/
-[scriptable, uuid(623f9ddb-434b-4d39-bc2d-1c617da241d0)]
+[scriptable, uuid(ac3c486c-69a1-4cbe-8f25-2ad20880eab3)]
interface mozIStorageConnection : nsISupports {
/*
* Initialization and status
*/
-
+
/**
* Closes a database connection. C++ callers should simply set the database
* variable to NULL.
*/
void close();
/**
* Indicates if the connection is open and ready to use. This will be false
@@ -122,16 +124,41 @@ interface mozIStorageConnection : nsISup
/**
* Execute a SQL expression, expecting no arguments.
*
* @param aSQLStatement The SQL statement to execute
*/
void executeSimpleSQL(in AUTF8String aSQLStatement);
/**
+ * Execute an array of queries created with this connection asynchronously
+ * using any currently bound parameters. The statements are ran wrapped in a
+ * transaction. These statements 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 aStatements
+ * The array of statements to execute asynchronously, in the order they
+ * are given in the array.
+ * @param aNumStatements
+ * The number of statements in aStatements.
+ * @param aCallback [optional]
+ * The callback object that will be notified of progress, errors, and
+ * completion.
+ * @returns an object that can be used to cancel the statements execution.
+ */
+ mozIStoragePendingStatement executeAsync(
+ [array, size_is(aNumStatements)] in mozIStorageStatement aStatements,
+ in unsigned long aNumStatements,
+ [optional] in mozIStorageStatementCallback aCallback
+ );
+
+ /**
* Check if the given table exists.
*
* @param aTableName The table to check
* @returns TRUE if table exists, FALSE otherwise.
*/
boolean tableExists(in AUTF8String aTableName);
/**
--- a/storage/public/mozStorageHelper.h
+++ b/storage/public/mozStorageHelper.h
@@ -35,16 +35,17 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef _MOZSTORAGEHELPER_H_
#define _MOZSTORAGEHELPER_H_
#include "mozIStorageConnection.h"
+#include "mozIStorageStatement.h"
/**
* This class wraps a transaction inside a given C++ scope, guaranteeing that
* the transaction will be completed even if you have an exception or
* return early.
*
* aCommitOnComplete controls whether the transaction is committed or rolled
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -49,16 +49,17 @@
#include "nsIFile.h"
#include "nsIVariant.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "mozIStorageAggregateFunction.h"
#include "mozIStorageFunction.h"
+#include "mozStorageEvents.h"
#include "mozStorageUnicodeFunctions.h"
#include "mozStorageConnection.h"
#include "mozStorageService.h"
#include "mozStorageStatement.h"
#include "mozStorageValueArray.h"
#include "mozStorage.h"
#include "prlog.h"
@@ -353,16 +354,70 @@ mozStorageConnection::ExecuteSimpleSQL(c
if (srv != SQLITE_OK) {
HandleSqliteError(nsPromiseFlatCString(aSQLStatement).get());
return ConvertResultCode(srv);
}
return NS_OK;
}
+nsresult
+mozStorageConnection::ExecuteAsync(mozIStorageStatement ** aStatements,
+ PRUint32 aNumStatements,
+ mozIStorageStatementCallback *aCallback,
+ mozIStoragePendingStatement **_stmt)
+{
+ int rc = SQLITE_OK;
+ nsTArray<sqlite3_stmt *> stmts(aNumStatements);
+ for (PRUint32 i = 0; i < aNumStatements && rc == SQLITE_OK; i++) {
+ sqlite3_stmt *old_stmt = aStatements[i]->GetNativeStatementPointer();
+ NS_ASSERTION(sqlite3_db_handle(old_stmt) == mDBConn,
+ "Statement must be from this database connection!");
+
+ // Clone this statement. We only need a sqlite3_stmt object, so we can
+ // avoid all the extra work that making a new mozStorageStatement would
+ // normally involve and use the SQLite API.
+ sqlite3_stmt *new_stmt;
+ rc = sqlite3_prepare_v2(mDBConn, sqlite3_sql(old_stmt), -1, &new_stmt,
+ NULL);
+ if (rc != SQLITE_OK)
+ break;
+
+ // Transfer the bindings
+ rc = sqlite3_transfer_bindings(old_stmt, new_stmt);
+ if (rc != SQLITE_OK)
+ break;
+
+ if (!stmts.AppendElement(new_stmt)) {
+ rc = SQLITE_NOMEM;
+ break;
+ }
+ }
+
+ // Dispatch to the background
+ nsresult rv = NS_OK;
+ if (rc == SQLITE_OK)
+ rv = NS_executeAsync(stmts, this, aCallback, _stmt);
+
+ // We had a failure, so we need to clean up...
+ if (rc != SQLITE_OK || NS_FAILED(rv)) {
+ for (PRUint32 i = 0; i < stmts.Length(); i++)
+ (void)sqlite3_finalize(stmts[i]);
+
+ if (rc != SQLITE_OK)
+ rv = ConvertResultCode(rc);
+ }
+
+ // Always reset all the statements
+ for (PRUint32 i = 0; i < aNumStatements; i++)
+ (void)aStatements[i]->Reset();
+
+ return rv;
+}
+
NS_IMETHODIMP
mozStorageConnection::TableExists(const nsACString& aSQLStatement, PRBool *_retval)
{
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
nsCString query("SELECT name FROM sqlite_master WHERE type = 'table' AND name ='");
query.Append(aSQLStatement);
query.AppendLiteral("'");
--- a/storage/src/mozStorageEvents.cpp
+++ b/storage/src/mozStorageEvents.cpp
@@ -41,16 +41,17 @@
#include "nsAutoPtr.h"
#include "nsAutoLock.h"
#include "nsCOMArray.h"
#include "sqlite3.h"
#include "mozIStorageStatementCallback.h"
#include "mozIStoragePendingStatement.h"
+#include "mozStorageHelper.h"
#include "mozStorageResultSet.h"
#include "mozStorageRow.h"
#include "mozStorageBackground.h"
#include "mozStorageError.h"
#include "mozStorageEvents.h"
////////////////////////////////////////////////////////////////////////////////
//// Asynchronous Statement Execution
@@ -267,25 +268,29 @@ class AsyncExecute : public nsIRunnable
, public iCompletionNotifier
{
public:
NS_DECL_ISUPPORTS
/**
* This takes ownership of both the statement and the callback.
*/
- AsyncExecute(sqlite3_stmt *aStatement,
+ AsyncExecute(nsTArray<sqlite3_stmt *> &aStatements,
+ mozIStorageConnection *aConnection,
mozIStorageStatementCallback *aCallback) :
- mStatement(aStatement)
+ mConnection(aConnection)
+ , mTransactionManager(nsnull)
, mCallback(aCallback)
, mCallingThread(do_GetCurrentThread())
, mState(PENDING)
, mStateMutex(nsAutoLock::NewLock("AsyncExecute::mStateMutex"))
, mPendingEventsMutex(nsAutoLock::NewLock("AsyncExecute::mPendingEventsMutex"))
{
+ (void)mStatements.SwapElements(aStatements);
+ NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
}
nsresult initialize()
{
NS_ENSURE_TRUE(mStateMutex, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_TRUE(mPendingEventsMutex, NS_ERROR_OUT_OF_MEMORY);
NS_IF_ADDREF(mCallback);
return NS_OK;
@@ -295,104 +300,116 @@ public:
{
// do not run if we have been canceled
{
nsAutoLock mutex(mStateMutex);
if (mState == CANCELED)
return Complete();
}
- // Execute the statement, giving the callback results
- // XXX better chunking of results?
+ // If there is more than one statement, run it in a transaction. We assume
+ // that we have been given write statements since getting a batch of read
+ // statements doesn't make a whole lot of sense.
+ if (mStatements.Length() > 1) {
+ // We don't error if this failed because it's not terrible if it does.
+ mTransactionManager = new mozStorageTransaction(mConnection, PR_FALSE,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+ }
+
+ // Execute each statement, giving the callback results if it returns any.
nsresult rv = NS_OK;
- while (PR_TRUE) {
- int rc = sqlite3_step(mStatement);
- // Break out if we have no more results
- if (rc == SQLITE_DONE)
- break;
+ for (PRUint32 i = 0; i < mStatements.Length(); i++) {
+ while (PR_TRUE) {
+ int rc = sqlite3_step(mStatements[i]);
+ // Break out if we have no more results
+ if (rc == SQLITE_DONE)
+ break;
+
+ // Some errors are not fatal, and we can handle them and continue.
+ if (rc != SQLITE_OK && rc != SQLITE_ROW) {
+ if (rc == SQLITE_BUSY) {
+ // Yield, and try again
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ continue;
+ }
- // Some errors are not fatal, and we can handle them and continue.
- if (rc != SQLITE_OK && rc != SQLITE_ROW) {
- if (rc == SQLITE_BUSY) {
- // Yield, and try again
- PR_Sleep(PR_INTERVAL_NO_WAIT);
- continue;
+ // Set error state
+ {
+ nsAutoLock mutex(mStateMutex);
+ mState = ERROR;
+ }
+
+ // Notify
+ sqlite3 *db = sqlite3_db_handle(mStatements[i]);
+ iCancelable *cancelable = ErrorNotifier::Dispatch(
+ mCallingThread, mCallback, this, rc, sqlite3_errmsg(db)
+ );
+ if (cancelable) {
+ nsAutoLock mutex(mPendingEventsMutex);
+ (void)mPendingEvents.AppendObject(cancelable);
+ }
+
+ // And complete
+ return Complete();
}
- // Set error state
+ // Check to see if we have been canceled
{
nsAutoLock mutex(mStateMutex);
- mState = ERROR;
+ if (mState == CANCELED)
+ return Complete();
+ }
+
+ // If we do not have a callback, there's no point in executing this
+ // statement anymore.
+ if (!mCallback)
+ break;
+
+ // Build result object
+ // XXX bug 454740 chunk these results better
+ nsRefPtr<mozStorageResultSet> results(new mozStorageResultSet());
+ if (!results) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+
+ nsRefPtr<mozStorageRow> row(new mozStorageRow());
+ if (!row) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
}
- // Notify
- sqlite3 *db = sqlite3_db_handle(mStatement);
- iCancelable *cancelable = ErrorNotifier::Dispatch(
- mCallingThread, mCallback, this, rc, sqlite3_errmsg(db)
- );
- if (cancelable) {
- nsAutoLock mutex(mPendingEventsMutex);
- (void)mPendingEvents.AppendObject(cancelable);
+ rv = row->initialize(mStatements[i]);
+ if (NS_FAILED(rv)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
}
- // And complete
- return Complete();
- }
+ rv = results->add(row);
+ if (NS_FAILED(rv))
+ break;
- // Check to see if we have been canceled
- {
- nsAutoLock mutex(mStateMutex);
- if (mState == CANCELED)
- return Complete();
+ // Notify caller
+ nsRefPtr<CallbackResultNotifier> notifier =
+ new CallbackResultNotifier(mCallback, results, this);
+ if (!notifier) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+
+ nsresult status = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
+ if (NS_SUCCEEDED(status)) {
+ nsAutoLock mutex(mPendingEventsMutex);
+ (void)mPendingEvents.AppendObject(notifier);
+ }
}
- // If we do not have a callback, but are getting results, we should stop
- // now since all this work isn't going to accomplish anything
- if (!mCallback) {
- nsAutoLock mutex(mStateMutex);
- mState = COMPLETED;
- return Complete();
- }
-
- // Build result object
- nsRefPtr<mozStorageResultSet> results(new mozStorageResultSet());
- if (!results) {
- rv = NS_ERROR_OUT_OF_MEMORY;
- break;
- }
-
- nsRefPtr<mozStorageRow> row(new mozStorageRow());
- if (!row) {
- rv = NS_ERROR_OUT_OF_MEMORY;
- break;
- }
-
- rv = row->initialize(mStatement);
- if (NS_FAILED(rv)) {
- rv = NS_ERROR_OUT_OF_MEMORY;
- break;
- }
-
- rv = results->add(row);
+ // If we have an error, we need to break out now.
if (NS_FAILED(rv))
break;
-
- // Notify caller
- nsRefPtr<CallbackResultNotifier> notifier =
- new CallbackResultNotifier(mCallback, results, this);
- if (!notifier) {
- rv = NS_ERROR_OUT_OF_MEMORY;
- break;
- }
-
- nsresult status = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL);
- if (NS_SUCCEEDED(status)) {
- nsAutoLock mutex(mPendingEventsMutex);
- (void)mPendingEvents.AppendObject(notifier);
- }
}
// We have broken out of the loop because of an error or because we are
// completed. Handle accordingly.
if (NS_FAILED(rv)) {
// This is a fatal error :(
// Update state
@@ -469,19 +486,43 @@ private:
* Notifies callback about completion, and does any necessary cleanup.
* @note: When calling this function, mStateMutex must be held.
*/
nsresult Complete()
{
NS_ASSERTION(mState != PENDING,
"Still in a pending state when calling Complete!");
- // Reset the statement
- (void)sqlite3_finalize(mStatement);
- mStatement = NULL;
+ // Handle our transaction, if we have one
+ if (mTransactionManager) {
+ if (mState == COMPLETED) {
+ nsresult rv = mTransactionManager->Commit();
+ if (NS_FAILED(rv)) {
+ iCancelable *cancelable = ErrorNotifier::Dispatch(
+ mCallingThread, mCallback, this, mozIStorageError::ERROR,
+ "Transaction failed to commit"
+ );
+ if (cancelable) {
+ nsAutoLock mutex(mPendingEventsMutex);
+ (void)mPendingEvents.AppendObject(cancelable);
+ }
+ }
+ }
+ else {
+ (void)mTransactionManager->Rollback();
+ }
+ delete mTransactionManager;
+ mTransactionManager = nsnull;
+ }
+
+ // Finalize our statements
+ for (PRUint32 i = 0; i < mStatements.Length(); i++) {
+ (void)sqlite3_finalize(mStatements[i]);
+ mStatements[i] = NULL;
+ }
// Notify about completion iff we have a callback.
if (mCallback) {
nsRefPtr<CompletionNotifier> completionEvent =
new CompletionNotifier(mCallback, mState, this);
nsresult rv = mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL);
if (NS_SUCCEEDED(rv)) {
nsAutoLock mutex(mPendingEventsMutex);
@@ -490,17 +531,19 @@ private:
// We no longer own mCallback (the CompletionNotifier takes ownership).
mCallback = nsnull;
}
return NS_OK;
}
- sqlite3_stmt *mStatement;
+ nsTArray<sqlite3_stmt *> mStatements;
+ mozIStorageConnection *mConnection;
+ mozStorageTransaction *mTransactionManager;
mozIStorageStatementCallback *mCallback;
nsCOMPtr<nsIThread> mCallingThread;
/**
* Indicates the state the object is currently in.
*/
ExecutionState mState;
@@ -522,22 +565,23 @@ private:
};
NS_IMPL_THREADSAFE_ISUPPORTS2(
AsyncExecute,
nsIRunnable,
mozIStoragePendingStatement
)
nsresult
-NS_executeAsync(sqlite3_stmt *aStatement,
+NS_executeAsync(nsTArray<sqlite3_stmt *> &aStatements,
+ mozIStorageConnection *aConnection,
mozIStorageStatementCallback *aCallback,
mozIStoragePendingStatement **_stmt)
{
// Create our event to run in the background
- nsRefPtr<AsyncExecute> event(new AsyncExecute(aStatement, aCallback));
+ nsRefPtr<AsyncExecute> event(new AsyncExecute(aStatements, aConnection, aCallback));
NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = event->initialize();
NS_ENSURE_SUCCESS(rv, rv);
// Dispatch it to the background
nsIEventTarget *target = mozStorageBackground::getService()->target();
rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
--- a/storage/src/mozStorageEvents.h
+++ b/storage/src/mozStorageEvents.h
@@ -36,31 +36,35 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef _mozStorageEvents_h_
#define _mozStorageEvents_h_
#include "nscore.h"
+#include "nsTArray.h"
#include "mozStorageBackground.h"
struct sqlite3_stmt;
class mozIStorageStatementCallback;
class mozIStoragePendingStatement;
/**
* Executes a statement in the background, and passes results back to the
* caller.
*
- * @param aStatement
- * The SQLite statement to execute in the background.
+ * @param aStatements
+ * The SQLite statements to execute in the background.
+ * @param aConnection
+ * The connection that created the statements to execute.
* @param aCallback
* The callback that is notified of results, completion, and errors.
* @param _stmt
- * The handle to control the execution of the statement.
+ * The handle to control the execution of the statements.
*/
nsresult NS_executeAsync(
- sqlite3_stmt *aStatement,
+ nsTArray<sqlite3_stmt *> &aStatements,
+ mozIStorageConnection *aConnection,
mozIStorageStatementCallback *aCallback,
mozIStoragePendingStatement **_stmt
);
#endif // _mozStorageEvents_h_
--- a/storage/src/mozStorageStatement.cpp
+++ b/storage/src/mozStorageStatement.cpp
@@ -573,39 +573,18 @@ mozStorageStatement::ExecuteStep(PRBool
return ConvertResultCode(srv);
}
/* nsICancelable executeAsync([optional] in storageIStatementCallback aCallback); */
nsresult
mozStorageStatement::ExecuteAsync(mozIStorageStatementCallback *aCallback,
mozIStoragePendingStatement **_stmt)
{
- // Clone this statement. We only need a sqlite3_stmt object, so we can
- // avoid all the extra work that making a new mozStorageStatement would
- // normally involve and use the SQLite API.
- sqlite3 *db = mDBConnection->GetNativeConnection();
- sqlite3_stmt *stmt;
- int rc = sqlite3_prepare_v2(db, sqlite3_sql(mDBStatement), -1, &stmt, NULL);
- if (rc != SQLITE_OK)
- return ConvertResultCode(rc);
-
- // Transfer the bindings
- rc = sqlite3_transfer_bindings(mDBStatement, stmt);
- if (rc != SQLITE_OK)
- return ConvertResultCode(rc);
-
- // Dispatch to the background.
- nsresult rv = NS_executeAsync(stmt, aCallback, _stmt);
- NS_ENSURE_SUCCESS(rv, rv);
-
- // Reset this statement.
- rv = Reset();
- NS_ENSURE_SUCCESS(rv, rv);
-
- return NS_OK;
+ mozIStorageStatement * stmts[1] = {this};
+ return mDBConnection->ExecuteAsync(stmts, 1, aCallback, _stmt);
}
/* [noscript,notxpcom] sqlite3stmtptr getNativeStatementPointer(); */
sqlite3_stmt*
mozStorageStatement::GetNativeStatementPointer()
{
return mDBStatement;
}
new file mode 100644
--- /dev/null
+++ b/storage/test/unit/test_connection_executeAsync.js
@@ -0,0 +1,180 @@
+/* ***** 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
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This file tests the functionality of mozIStorageConnection::executeAsync
+
+const INTEGER = 1;
+const TEXT = "this is test text";
+const REAL = 3.23;
+const BLOB = [1, 2];
+
+function test_create_and_add()
+{
+ dump("test_create_and_add()\n");
+
+ getOpenedDatabase().executeSimpleSQL(
+ "CREATE TABLE test (" +
+ "id INTEGER PRIMARY KEY, " +
+ "string TEXT, " +
+ "number REAL, " +
+ "nuller NULL, " +
+ "blober BLOB" +
+ ")"
+ );
+
+ let stmts = [];
+ stmts[0] = getOpenedDatabase().createStatement(
+ "INSERT INTO test (id, string, number, nuller, blober) VALUES (?, ?, ?, ?, ?)"
+ );
+ stmts[0].bindInt32Parameter(0, INTEGER);
+ stmts[0].bindStringParameter(1, TEXT);
+ stmts[0].bindDoubleParameter(2, REAL);
+ stmts[0].bindNullParameter(3);
+ stmts[0].bindBlobParameter(4, BLOB, BLOB.length);
+ stmts[1] = getOpenedDatabase().createStatement(
+ "INSERT INTO test (string, number, nuller, blober) VALUES (?, ?, ?, ?)"
+ );
+ stmts[1].bindStringParameter(0, TEXT);
+ stmts[1].bindDoubleParameter(1, REAL);
+ stmts[1].bindNullParameter(2);
+ stmts[1].bindBlobParameter(3, BLOB, BLOB.length);
+
+ do_test_pending();
+ getOpenedDatabase().executeAsync(stmts, stmts.length, {
+ handleResult: function(aResultSet)
+ {
+ dump("handleResult("+aResultSet+")\n");
+ do_throw("unexpected results obtained!");
+ },
+ handleError: function(aError)
+ {
+ dump("handleError("+aError.result+")\n");
+ do_throw("unexpected error!");
+ },
+ handleCompletion: function(aReason)
+ {
+ dump("handleCompletion("+aReason+")\n");
+ do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
+
+ // Check that the result is in the table
+ let 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));
+ let count = { value: 0 };
+ let blob = { value: null };
+ stmt.getBlob(3, count, blob);
+ do_check_eq(BLOB.length, count.value);
+ for (let i = 0; i < BLOB.length; i++)
+ do_check_eq(BLOB[i], blob.value[i]);
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ // Make sure we have two rows in the table
+ stmt = getOpenedDatabase().createStatement(
+ "SELECT COUNT(1) FROM test"
+ );
+ try {
+ do_check_true(stmt.executeStep());
+ do_check_eq(2, stmt.getInt32(0));
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ do_test_finished();
+ }
+ });
+ stmts[0].finalize();
+ stmts[1].finalize();
+}
+
+function test_transaction_created()
+{
+ dump("test_transaction_created()\n");
+
+ let stmts = [];
+ stmts[0] = getOpenedDatabase().createStatement(
+ "BEGIN"
+ );
+ stmts[1] = getOpenedDatabase().createStatement(
+ "SELECT * FROM test"
+ );
+
+ do_test_pending()
+ getOpenedDatabase().executeAsync(stmts, stmts.length, {
+ handleResult: function(aResultSet)
+ {
+ dump("handleResults("+aResultSet+")\n");
+ do_throw("unexpected results obtained!");
+ },
+ handleError: function(aError)
+ {
+ dump("handleError("+aError.result+")\n");
+ },
+ handleCompletion: function(aReason)
+ {
+ dump("handleCompletion("+aReason+")\n");
+ do_check_eq(Ci.mozIStorageStatementCallback.REASON_ERROR, aReason);
+ do_test_finished();
+ }
+ });
+ stmts[0].finalize();
+ stmts[1].finalize();
+}
+
+
+let tests =
+[
+ test_create_and_add,
+ test_transaction_created,
+];
+
+function run_test()
+{
+ cleanup();
+
+ for (let i = 0; i < tests.length; i++)
+ tests[i]();
+}