michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsAutoPtr.h" michael@0: michael@0: #include "sqlite3.h" michael@0: michael@0: #include "mozIStorageStatementCallback.h" michael@0: #include "mozStorageBindingParams.h" michael@0: #include "mozStorageHelper.h" michael@0: #include "mozStorageResultSet.h" michael@0: #include "mozStorageRow.h" michael@0: #include "mozStorageConnection.h" michael@0: #include "mozStorageError.h" michael@0: #include "mozStoragePrivateHelpers.h" michael@0: #include "mozStorageStatementData.h" michael@0: #include "mozStorageAsyncStatementExecution.h" michael@0: michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: namespace mozilla { michael@0: namespace storage { michael@0: michael@0: /** michael@0: * The following constants help batch rows into result sets. michael@0: * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that michael@0: * takes less than 200 milliseconds is considered to feel instantaneous to end michael@0: * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of michael@0: * dispatches to calling thread, while also providing reasonably-sized sets of michael@0: * data for consumers. Both of these constants are used because we assume that michael@0: * consumers are trying to avoid blocking their execution thread for long michael@0: * periods of time, and dispatching many small events to the calling thread will michael@0: * end up blocking it. michael@0: */ michael@0: #define MAX_MILLISECONDS_BETWEEN_RESULTS 75 michael@0: #define MAX_ROWS_PER_RESULT 15 michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Local Classes michael@0: michael@0: namespace { michael@0: michael@0: typedef AsyncExecuteStatements::ExecutionState ExecutionState; michael@0: typedef AsyncExecuteStatements::StatementDataArray StatementDataArray; michael@0: michael@0: /** michael@0: * Notifies a callback with a result set. michael@0: */ michael@0: class CallbackResultNotifier : public nsRunnable michael@0: { michael@0: public: michael@0: CallbackResultNotifier(mozIStorageStatementCallback *aCallback, michael@0: mozIStorageResultSet *aResults, michael@0: AsyncExecuteStatements *aEventStatus) : michael@0: mCallback(aCallback) michael@0: , mResults(aResults) michael@0: , mEventStatus(aEventStatus) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: NS_ASSERTION(mCallback, "Trying to notify about results without a callback!"); michael@0: michael@0: if (mEventStatus->shouldNotify()) { michael@0: // Hold a strong reference to the callback while notifying it, so that if michael@0: // it spins the event loop, the callback won't be released and freed out michael@0: // from under us. michael@0: nsCOMPtr callback = michael@0: do_QueryInterface(mCallback); michael@0: michael@0: (void)mCallback->HandleResult(mResults); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: mozIStorageStatementCallback *mCallback; michael@0: nsCOMPtr mResults; michael@0: nsRefPtr mEventStatus; michael@0: }; michael@0: michael@0: /** michael@0: * Notifies the calling thread that an error has occurred. michael@0: */ michael@0: class ErrorNotifier : public nsRunnable michael@0: { michael@0: public: michael@0: ErrorNotifier(mozIStorageStatementCallback *aCallback, michael@0: mozIStorageError *aErrorObj, michael@0: AsyncExecuteStatements *aEventStatus) : michael@0: mCallback(aCallback) michael@0: , mErrorObj(aErrorObj) michael@0: , mEventStatus(aEventStatus) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mEventStatus->shouldNotify() && mCallback) { michael@0: // Hold a strong reference to the callback while notifying it, so that if michael@0: // it spins the event loop, the callback won't be released and freed out michael@0: // from under us. michael@0: nsCOMPtr callback = michael@0: do_QueryInterface(mCallback); michael@0: michael@0: (void)mCallback->HandleError(mErrorObj); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: mozIStorageStatementCallback *mCallback; michael@0: nsCOMPtr mErrorObj; michael@0: nsRefPtr mEventStatus; michael@0: }; michael@0: michael@0: /** michael@0: * Notifies the calling thread that the statement has finished executing. Takes michael@0: * ownership of the StatementData so it is released on the proper thread. michael@0: */ michael@0: class CompletionNotifier : public nsRunnable michael@0: { michael@0: public: michael@0: /** michael@0: * This takes ownership of the callback and the StatementData. They are michael@0: * released on the thread this is dispatched to (which should always be the michael@0: * calling thread). michael@0: */ michael@0: CompletionNotifier(mozIStorageStatementCallback *aCallback, michael@0: ExecutionState aReason) michael@0: : mCallback(aCallback) michael@0: , mReason(aReason) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mCallback) { michael@0: (void)mCallback->HandleCompletion(mReason); michael@0: NS_RELEASE(mCallback); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: mozIStorageStatementCallback *mCallback; michael@0: ExecutionState mReason; michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// AsyncExecuteStatements michael@0: michael@0: /* static */ michael@0: nsresult michael@0: AsyncExecuteStatements::execute(StatementDataArray &aStatements, michael@0: Connection *aConnection, michael@0: sqlite3 *aNativeConnection, michael@0: mozIStorageStatementCallback *aCallback, michael@0: mozIStoragePendingStatement **_stmt) michael@0: { michael@0: // Create our event to run in the background michael@0: nsRefPtr event = michael@0: new AsyncExecuteStatements(aStatements, aConnection, aNativeConnection, michael@0: aCallback); michael@0: NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: // Dispatch it to the background michael@0: nsIEventTarget *target = aConnection->getAsyncExecutionTarget(); michael@0: michael@0: // If we don't have a valid target, this is a bug somewhere else. In the past, michael@0: // this assert found cases where a Run method would schedule a new statement michael@0: // without checking if asyncClose had been called. The caller must prevent michael@0: // that from happening or, if the work is not critical, just avoid creating michael@0: // the new statement during shutdown. See bug 718449 for an example. michael@0: MOZ_ASSERT(target); michael@0: if (!target) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Return it as the pending statement object and track it. michael@0: NS_ADDREF(*_stmt = event); michael@0: return NS_OK; michael@0: } michael@0: michael@0: AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements, michael@0: Connection *aConnection, michael@0: sqlite3 *aNativeConnection, michael@0: mozIStorageStatementCallback *aCallback) michael@0: : mConnection(aConnection) michael@0: , mNativeConnection(aNativeConnection) michael@0: , mHasTransaction(false) michael@0: , mCallback(aCallback) michael@0: , mCallingThread(::do_GetCurrentThread()) michael@0: , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS)) michael@0: , mIntervalStart(TimeStamp::Now()) michael@0: , mState(PENDING) michael@0: , mCancelRequested(false) michael@0: , mMutex(aConnection->sharedAsyncExecutionMutex) michael@0: , mDBMutex(aConnection->sharedDBMutex) michael@0: , mRequestStartDate(TimeStamp::Now()) michael@0: { michael@0: (void)mStatements.SwapElements(aStatements); michael@0: NS_ASSERTION(mStatements.Length(), "We weren't given any statements!"); michael@0: NS_IF_ADDREF(mCallback); michael@0: } michael@0: michael@0: AsyncExecuteStatements::~AsyncExecuteStatements() michael@0: { michael@0: MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point"); michael@0: } michael@0: michael@0: bool michael@0: AsyncExecuteStatements::shouldNotify() michael@0: { michael@0: #ifdef DEBUG michael@0: mMutex.AssertNotCurrentThreadOwns(); michael@0: michael@0: bool onCallingThread = false; michael@0: (void)mCallingThread->IsOnCurrentThread(&onCallingThread); michael@0: NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!"); michael@0: #endif michael@0: michael@0: // We do not need to acquire mMutex here because it can only ever be written michael@0: // to on the calling thread, and the only thread that can call us is the michael@0: // calling thread, so we know that our access is serialized. michael@0: return !mCancelRequested; michael@0: } michael@0: michael@0: bool michael@0: AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData, michael@0: bool aLastStatement) michael@0: { michael@0: mMutex.AssertNotCurrentThreadOwns(); michael@0: michael@0: sqlite3_stmt *aStatement = nullptr; michael@0: // This cannot fail; we are only called if it's available. michael@0: (void)aData.getSqliteStatement(&aStatement); michael@0: NS_ASSERTION(aStatement, "You broke the code; do not call here like that!"); michael@0: BindingParamsArray *paramsArray(aData); michael@0: michael@0: // Iterate through all of our parameters, bind them, and execute. michael@0: bool continueProcessing = true; michael@0: BindingParamsArray::iterator itr = paramsArray->begin(); michael@0: BindingParamsArray::iterator end = paramsArray->end(); michael@0: while (itr != end && continueProcessing) { michael@0: // Bind the data to our statement. michael@0: nsCOMPtr bindingInternal = michael@0: do_QueryInterface(*itr); michael@0: nsCOMPtr error = bindingInternal->bind(aStatement); michael@0: if (error) { michael@0: // Set our error state. michael@0: mState = ERROR; michael@0: michael@0: // And notify. michael@0: (void)notifyError(error); michael@0: return false; michael@0: } michael@0: michael@0: // Advance our iterator, execute, and then process the statement. michael@0: itr++; michael@0: bool lastStatement = aLastStatement && itr == end; michael@0: continueProcessing = executeAndProcessStatement(aStatement, lastStatement); michael@0: michael@0: // Always reset our statement. michael@0: (void)::sqlite3_reset(aStatement); michael@0: } michael@0: michael@0: return continueProcessing; michael@0: } michael@0: michael@0: bool michael@0: AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement, michael@0: bool aLastStatement) michael@0: { michael@0: mMutex.AssertNotCurrentThreadOwns(); michael@0: michael@0: // Execute our statement michael@0: bool hasResults; michael@0: do { michael@0: hasResults = executeStatement(aStatement); michael@0: michael@0: // If we had an error, bail. michael@0: if (mState == ERROR) michael@0: return false; michael@0: michael@0: // If we have been canceled, there is no point in going on... michael@0: { michael@0: MutexAutoLock lockedScope(mMutex); michael@0: if (mCancelRequested) { michael@0: mState = CANCELED; michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Build our result set and notify if we got anything back and have a michael@0: // callback to notify. michael@0: if (mCallback && hasResults && michael@0: NS_FAILED(buildAndNotifyResults(aStatement))) { michael@0: // We had an error notifying, so we notify on error and stop processing. michael@0: mState = ERROR; michael@0: michael@0: // Notify, and stop processing statements. michael@0: (void)notifyError(mozIStorageError::ERROR, michael@0: "An error occurred while notifying about results"); michael@0: michael@0: return false; michael@0: } michael@0: } while (hasResults); michael@0: michael@0: #ifdef DEBUG michael@0: // Check to make sure that this statement was smart about what it did. michael@0: checkAndLogStatementPerformance(aStatement); michael@0: #endif michael@0: michael@0: // If we are done, we need to set our state accordingly while we still hold michael@0: // our mutex. We would have already returned if we were canceled or had michael@0: // an error at this point. michael@0: if (aLastStatement) michael@0: mState = COMPLETED; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement) michael@0: { michael@0: mMutex.AssertNotCurrentThreadOwns(); michael@0: Telemetry::AutoTimer finallySendExecutionDuration(mRequestStartDate); michael@0: while (true) { michael@0: // lock the sqlite mutex so sqlite3_errmsg cannot change michael@0: SQLiteMutexAutoLock lockedScope(mDBMutex); michael@0: michael@0: int rc = mConnection->stepStatement(mNativeConnection, aStatement); michael@0: // Stop if we have no more results. michael@0: if (rc == SQLITE_DONE) michael@0: { michael@0: Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true); michael@0: return false; michael@0: } michael@0: michael@0: // If we got results, we can return now. michael@0: if (rc == SQLITE_ROW) michael@0: { michael@0: Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, true); michael@0: return true; michael@0: } michael@0: michael@0: // Some errors are not fatal, and we can handle them and continue. michael@0: if (rc == SQLITE_BUSY) { michael@0: // Don't hold the lock while we call outside our module. michael@0: SQLiteMutexAutoUnlock unlockedScope(mDBMutex); michael@0: michael@0: // Yield, and try again michael@0: (void)::PR_Sleep(PR_INTERVAL_NO_WAIT); michael@0: continue; michael@0: } michael@0: michael@0: // Set an error state. michael@0: mState = ERROR; michael@0: Telemetry::Accumulate(Telemetry::MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, false); michael@0: michael@0: // Construct the error message before giving up the mutex (which we cannot michael@0: // hold during the call to notifyError). michael@0: nsCOMPtr errorObj( michael@0: new Error(rc, ::sqlite3_errmsg(mNativeConnection)) michael@0: ); michael@0: // We cannot hold the DB mutex while calling notifyError. michael@0: SQLiteMutexAutoUnlock unlockedScope(mDBMutex); michael@0: (void)notifyError(errorObj); michael@0: michael@0: // Finally, indicate that we should stop processing. michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement) michael@0: { michael@0: NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!"); michael@0: mMutex.AssertNotCurrentThreadOwns(); michael@0: michael@0: // Build result object if we need it. michael@0: if (!mResultSet) michael@0: mResultSet = new ResultSet(); michael@0: NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsRefPtr row(new Row()); michael@0: NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsresult rv = row->initialize(aStatement); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mResultSet->add(row); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If we have hit our maximum number of allowed results, or if we have hit michael@0: // the maximum amount of time we want to wait for results, notify the michael@0: // calling thread about it. michael@0: TimeStamp now = TimeStamp::Now(); michael@0: TimeDuration delta = now - mIntervalStart; michael@0: if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) { michael@0: // Notify the caller michael@0: rv = notifyResults(); michael@0: if (NS_FAILED(rv)) michael@0: return NS_OK; // we'll try again with the next result michael@0: michael@0: // Reset our start time michael@0: mIntervalStart = now; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: AsyncExecuteStatements::notifyComplete() michael@0: { michael@0: mMutex.AssertNotCurrentThreadOwns(); michael@0: NS_ASSERTION(mState != PENDING, michael@0: "Still in a pending state when calling Complete!"); michael@0: michael@0: // Reset our statements before we try to commit or rollback. If we are michael@0: // canceling and have statements that think they have pending work, the michael@0: // rollback will fail. michael@0: for (uint32_t i = 0; i < mStatements.Length(); i++) michael@0: mStatements[i].reset(); michael@0: michael@0: // Release references to the statement data as soon as possible. If this michael@0: // is the last reference, statements will be finalized immediately on the michael@0: // async thread, hence avoiding several bounces between threads and possible michael@0: // race conditions with AsyncClose(). michael@0: mStatements.Clear(); michael@0: michael@0: // Handle our transaction, if we have one michael@0: if (mHasTransaction) { michael@0: if (mState == COMPLETED) { michael@0: nsresult rv = mConnection->commitTransactionInternal(mNativeConnection); michael@0: if (NS_FAILED(rv)) { michael@0: mState = ERROR; michael@0: (void)notifyError(mozIStorageError::ERROR, michael@0: "Transaction failed to commit"); michael@0: } michael@0: } michael@0: else { michael@0: NS_WARN_IF(NS_FAILED(mConnection->rollbackTransactionInternal(mNativeConnection))); michael@0: } michael@0: mHasTransaction = false; michael@0: } michael@0: michael@0: // Always generate a completion notification; it is what guarantees that our michael@0: // destruction does not happen here on the async thread. michael@0: nsRefPtr completionEvent = michael@0: new CompletionNotifier(mCallback, mState); michael@0: michael@0: // We no longer own mCallback (the CompletionNotifier takes ownership). michael@0: mCallback = nullptr; michael@0: michael@0: (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: AsyncExecuteStatements::notifyError(int32_t aErrorCode, michael@0: const char *aMessage) michael@0: { michael@0: mMutex.AssertNotCurrentThreadOwns(); michael@0: mDBMutex.assertNotCurrentThreadOwns(); michael@0: michael@0: if (!mCallback) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr errorObj(new Error(aErrorCode, aMessage)); michael@0: NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: return notifyError(errorObj); michael@0: } michael@0: michael@0: nsresult michael@0: AsyncExecuteStatements::notifyError(mozIStorageError *aError) michael@0: { michael@0: mMutex.AssertNotCurrentThreadOwns(); michael@0: mDBMutex.assertNotCurrentThreadOwns(); michael@0: michael@0: if (!mCallback) michael@0: return NS_OK; michael@0: michael@0: nsRefPtr notifier = michael@0: new ErrorNotifier(mCallback, aError, this); michael@0: NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: nsresult michael@0: AsyncExecuteStatements::notifyResults() michael@0: { michael@0: mMutex.AssertNotCurrentThreadOwns(); michael@0: NS_ASSERTION(mCallback, "notifyResults called without a callback!"); michael@0: michael@0: nsRefPtr notifier = michael@0: new CallbackResultNotifier(mCallback, mResultSet, this); michael@0: NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mResultSet = nullptr; // we no longer own it on success michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS( michael@0: AsyncExecuteStatements, michael@0: nsIRunnable, michael@0: mozIStoragePendingStatement michael@0: ) michael@0: michael@0: bool michael@0: AsyncExecuteStatements::statementsNeedTransaction() michael@0: { michael@0: // If there is more than one write statement, run in a transaction. michael@0: // Additionally, if we have only one statement but it needs a transaction, due michael@0: // to multiple BindingParams, we will wrap it in one. michael@0: for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) { michael@0: transactionsCount += mStatements[i].needsTransaction(); michael@0: if (transactionsCount > 1) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStoragePendingStatement michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncExecuteStatements::Cancel() michael@0: { michael@0: #ifdef DEBUG michael@0: bool onCallingThread = false; michael@0: (void)mCallingThread->IsOnCurrentThread(&onCallingThread); michael@0: NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!"); michael@0: #endif michael@0: michael@0: // If we have already canceled, we have an error, but always indicate that michael@0: // we are trying to cancel. michael@0: NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED); michael@0: michael@0: { michael@0: MutexAutoLock lockedScope(mMutex); michael@0: michael@0: // We need to indicate that we want to try and cancel now. michael@0: mCancelRequested = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// nsIRunnable michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncExecuteStatements::Run() michael@0: { michael@0: MOZ_ASSERT(!mConnection->isClosed()); michael@0: michael@0: // Do not run if we have been canceled. michael@0: { michael@0: MutexAutoLock lockedScope(mMutex); michael@0: if (mCancelRequested) michael@0: mState = CANCELED; michael@0: } michael@0: if (mState == CANCELED) michael@0: return notifyComplete(); michael@0: michael@0: if (statementsNeedTransaction()) { michael@0: Connection* rawConnection = static_cast(mConnection.get()); michael@0: if (NS_SUCCEEDED(mConnection->beginTransactionInternal(mNativeConnection, michael@0: mozIStorageConnection::TRANSACTION_IMMEDIATE))) { michael@0: mHasTransaction = true; michael@0: } michael@0: #ifdef DEBUG michael@0: else { michael@0: NS_WARNING("Unable to create a transaction for async execution."); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // Execute each statement, giving the callback results if it returns any. michael@0: for (uint32_t i = 0; i < mStatements.Length(); i++) { michael@0: bool finished = (i == (mStatements.Length() - 1)); michael@0: michael@0: sqlite3_stmt *stmt; michael@0: { // lock the sqlite mutex so sqlite3_errmsg cannot change michael@0: SQLiteMutexAutoLock lockedScope(mDBMutex); michael@0: michael@0: int rc = mStatements[i].getSqliteStatement(&stmt); michael@0: if (rc != SQLITE_OK) { michael@0: // Set our error state. michael@0: mState = ERROR; michael@0: michael@0: // Build the error object; can't call notifyError with the lock held michael@0: nsCOMPtr errorObj( michael@0: new Error(rc, ::sqlite3_errmsg(mNativeConnection)) michael@0: ); michael@0: { michael@0: // We cannot hold the DB mutex and call notifyError. michael@0: SQLiteMutexAutoUnlock unlockedScope(mDBMutex); michael@0: (void)notifyError(errorObj); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // If we have parameters to bind, bind them, execute, and process. michael@0: if (mStatements[i].hasParametersToBeBound()) { michael@0: if (!bindExecuteAndProcessStatement(mStatements[i], finished)) michael@0: break; michael@0: } michael@0: // Otherwise, just execute and process the statement. michael@0: else if (!executeAndProcessStatement(stmt, finished)) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // If we still have results that we haven't notified about, take care of michael@0: // them now. michael@0: if (mResultSet) michael@0: (void)notifyResults(); michael@0: michael@0: // Notify about completion michael@0: return notifyComplete(); michael@0: } michael@0: michael@0: } // namespace storage michael@0: } // namespace mozilla