michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ 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 "base/basictypes.h" michael@0: michael@0: #include "IDBTransaction.h" michael@0: michael@0: #include "nsIAppShell.h" michael@0: #include "nsIScriptContext.h" michael@0: michael@0: #include "mozilla/dom/quota/QuotaManager.h" michael@0: #include "mozilla/storage.h" michael@0: #include "nsDOMClassInfoID.h" michael@0: #include "mozilla/dom/DOMStringList.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsWidgetsCID.h" michael@0: michael@0: #include "AsyncConnectionHelper.h" michael@0: #include "DatabaseInfo.h" michael@0: #include "IDBCursor.h" michael@0: #include "IDBEvents.h" michael@0: #include "IDBFactory.h" michael@0: #include "IDBObjectStore.h" michael@0: #include "IndexedDatabaseManager.h" michael@0: #include "ProfilerHelpers.h" michael@0: #include "ReportInternalError.h" michael@0: #include "TransactionThreadPool.h" michael@0: michael@0: #include "ipc/IndexedDBChild.h" michael@0: michael@0: #define SAVEPOINT_NAME "savepoint" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: USING_INDEXEDDB_NAMESPACE michael@0: using mozilla::dom::quota::QuotaManager; michael@0: using mozilla::ErrorResult; michael@0: michael@0: namespace { michael@0: michael@0: NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); michael@0: michael@0: #ifdef MOZ_ENABLE_PROFILER_SPS michael@0: uint64_t gNextTransactionSerialNumber = 1; michael@0: #endif michael@0: michael@0: PLDHashOperator michael@0: DoomCachedStatements(const nsACString& aQuery, michael@0: nsCOMPtr& aStatement, michael@0: void* aUserArg) michael@0: { michael@0: CommitHelper* helper = static_cast(aUserArg); michael@0: helper->AddDoomedObject(aStatement); michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: // This runnable doesn't actually do anything beyond "prime the pump" and get michael@0: // transactions in the right order on the transaction thread pool. michael@0: class StartTransactionRunnable : public nsIRunnable michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: // NOP michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: // Could really use those NS_REFCOUNTING_HAHA_YEAH_RIGHT macros here. michael@0: NS_IMETHODIMP_(MozExternalRefCountType) StartTransactionRunnable::AddRef() michael@0: { michael@0: return 2; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) StartTransactionRunnable::Release() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(StartTransactionRunnable, nsIRunnable) michael@0: michael@0: } // anonymous namespace michael@0: michael@0: michael@0: // static michael@0: already_AddRefed michael@0: IDBTransaction::CreateInternal(IDBDatabase* aDatabase, michael@0: const Sequence& aObjectStoreNames, michael@0: Mode aMode, michael@0: bool aDispatchDelayed, michael@0: bool aIsVersionChangeTransactionChild) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess() || !aDispatchDelayed, michael@0: "No support for delayed-dispatch transactions in child " michael@0: "process!"); michael@0: NS_ASSERTION(!aIsVersionChangeTransactionChild || michael@0: (!IndexedDatabaseManager::IsMainProcess() && michael@0: aMode == IDBTransaction::VERSION_CHANGE), michael@0: "Busted logic!"); michael@0: michael@0: nsRefPtr transaction = new IDBTransaction(aDatabase); michael@0: michael@0: transaction->SetScriptOwner(aDatabase->GetScriptOwner()); michael@0: transaction->mDatabase = aDatabase; michael@0: transaction->mMode = aMode; michael@0: transaction->mDatabaseInfo = aDatabase->Info(); michael@0: transaction->mObjectStoreNames.AppendElements(aObjectStoreNames); michael@0: transaction->mObjectStoreNames.Sort(); michael@0: michael@0: IndexedDBTransactionChild* actor = nullptr; michael@0: michael@0: if (IndexedDatabaseManager::IsMainProcess()) { michael@0: if (aMode != IDBTransaction::VERSION_CHANGE) { michael@0: TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate(); michael@0: NS_ENSURE_TRUE(pool, nullptr); michael@0: michael@0: static StartTransactionRunnable sStartTransactionRunnable; michael@0: pool->Dispatch(transaction, &sStartTransactionRunnable, false, nullptr); michael@0: } michael@0: } michael@0: else if (!aIsVersionChangeTransactionChild) { michael@0: IndexedDBDatabaseChild* dbActor = aDatabase->GetActorChild(); michael@0: NS_ASSERTION(dbActor, "Must have an actor here!"); michael@0: michael@0: ipc::NormalTransactionParams params; michael@0: params.names().AppendElements(aObjectStoreNames); michael@0: params.mode() = aMode; michael@0: michael@0: actor = new IndexedDBTransactionChild(); michael@0: michael@0: dbActor->SendPIndexedDBTransactionConstructor(actor, params); michael@0: } michael@0: michael@0: if (!aDispatchDelayed) { michael@0: nsCOMPtr appShell = do_GetService(kAppShellCID); michael@0: NS_ENSURE_TRUE(appShell, nullptr); michael@0: michael@0: nsresult rv = appShell->RunBeforeNextEvent(transaction); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: transaction->mCreating = true; michael@0: } michael@0: michael@0: if (actor) { michael@0: NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: actor->SetTransaction(transaction); michael@0: } michael@0: michael@0: return transaction.forget(); michael@0: } michael@0: michael@0: IDBTransaction::IDBTransaction(IDBDatabase* aDatabase) michael@0: : IDBWrapperCache(aDatabase), michael@0: mReadyState(IDBTransaction::INITIAL), michael@0: mMode(IDBTransaction::READ_ONLY), michael@0: mPendingRequests(0), michael@0: mSavepointCount(0), michael@0: mActorChild(nullptr), michael@0: mActorParent(nullptr), michael@0: mAbortCode(NS_OK), michael@0: #ifdef MOZ_ENABLE_PROFILER_SPS michael@0: mSerialNumber(gNextTransactionSerialNumber++), michael@0: #endif michael@0: mCreating(false) michael@0: #ifdef DEBUG michael@0: , mFiredCompleteOrAbort(false) michael@0: #endif michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: } michael@0: michael@0: IDBTransaction::~IDBTransaction() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!mPendingRequests, "Should have no pending requests here!"); michael@0: NS_ASSERTION(!mSavepointCount, "Should have released them all!"); michael@0: NS_ASSERTION(!mConnection, "Should have called CommitOrRollback!"); michael@0: NS_ASSERTION(!mCreating, "Should have been cleared already!"); michael@0: NS_ASSERTION(mFiredCompleteOrAbort, "Should have fired event!"); michael@0: michael@0: NS_ASSERTION(!mActorParent, "Actor parent owns us, how can we be dying?!"); michael@0: if (mActorChild) { michael@0: NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: mActorChild->Send__delete__(mActorChild); michael@0: NS_ASSERTION(!mActorChild, "Should have cleared in Send__delete__!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: IDBTransaction::OnNewRequest() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: if (!mPendingRequests) { michael@0: NS_ASSERTION(mReadyState == IDBTransaction::INITIAL, michael@0: "Reusing a transaction!"); michael@0: mReadyState = IDBTransaction::LOADING; michael@0: } michael@0: ++mPendingRequests; michael@0: } michael@0: michael@0: void michael@0: IDBTransaction::OnRequestFinished() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(mPendingRequests, "Mismatched calls!"); michael@0: --mPendingRequests; michael@0: if (!mPendingRequests) { michael@0: NS_ASSERTION(NS_FAILED(mAbortCode) || mReadyState == IDBTransaction::LOADING, michael@0: "Bad state!"); michael@0: mReadyState = IDBTransaction::COMMITTING; michael@0: CommitOrRollback(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: IDBTransaction::OnRequestDisconnected() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(mPendingRequests, "Mismatched calls!"); michael@0: --mPendingRequests; michael@0: } michael@0: michael@0: void michael@0: IDBTransaction::RemoveObjectStore(const nsAString& aName) michael@0: { michael@0: NS_ASSERTION(mMode == IDBTransaction::VERSION_CHANGE, michael@0: "Only remove object stores on VERSION_CHANGE transactions"); michael@0: michael@0: mDatabaseInfo->RemoveObjectStore(aName); michael@0: michael@0: for (uint32_t i = 0; i < mCreatedObjectStores.Length(); i++) { michael@0: if (mCreatedObjectStores[i]->Name() == aName) { michael@0: nsRefPtr objectStore = mCreatedObjectStores[i]; michael@0: mCreatedObjectStores.RemoveElementAt(i); michael@0: mDeletedObjectStores.AppendElement(objectStore); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: IDBTransaction::SetTransactionListener(IDBTransactionListener* aListener) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!mListener, "Shouldn't already have a listener!"); michael@0: mListener = aListener; michael@0: } michael@0: michael@0: nsresult michael@0: IDBTransaction::CommitOrRollback() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (!IndexedDatabaseManager::IsMainProcess()) { michael@0: if (mActorChild) { michael@0: mActorChild->SendAllRequestsFinished(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr helper = michael@0: new CommitHelper(this, mListener, mCreatedObjectStores); michael@0: michael@0: TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate(); michael@0: NS_ENSURE_STATE(pool); michael@0: michael@0: mCachedStatements.Enumerate(DoomCachedStatements, helper); michael@0: NS_ASSERTION(!mCachedStatements.Count(), "Statements left!"); michael@0: michael@0: nsresult rv = pool->Dispatch(this, helper, true, helper); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: IDBTransaction::StartSavepoint() michael@0: { michael@0: NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!"); michael@0: NS_PRECONDITION(mConnection, "No connection!"); michael@0: michael@0: nsCOMPtr stmt = GetCachedStatement(NS_LITERAL_CSTRING( michael@0: "SAVEPOINT " SAVEPOINT_NAME michael@0: )); michael@0: NS_ENSURE_TRUE(stmt, false); michael@0: michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: nsresult rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (IsWriteAllowed()) { michael@0: mUpdateFileRefcountFunction->StartSavepoint(); michael@0: } michael@0: michael@0: ++mSavepointCount; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: IDBTransaction::ReleaseSavepoint() michael@0: { michael@0: NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!"); michael@0: NS_PRECONDITION(mConnection, "No connection!"); michael@0: michael@0: NS_ASSERTION(mSavepointCount, "Mismatch!"); michael@0: michael@0: nsCOMPtr stmt = GetCachedStatement(NS_LITERAL_CSTRING( michael@0: "RELEASE SAVEPOINT " SAVEPOINT_NAME michael@0: )); michael@0: NS_ENSURE_TRUE(stmt, NS_OK); michael@0: michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: nsresult rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, NS_OK); michael@0: michael@0: if (IsWriteAllowed()) { michael@0: mUpdateFileRefcountFunction->ReleaseSavepoint(); michael@0: } michael@0: michael@0: --mSavepointCount; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: IDBTransaction::RollbackSavepoint() michael@0: { michael@0: NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!"); michael@0: NS_PRECONDITION(mConnection, "No connection!"); michael@0: michael@0: NS_ASSERTION(mSavepointCount == 1, "Mismatch!"); michael@0: mSavepointCount = 0; michael@0: michael@0: nsCOMPtr stmt = GetCachedStatement(NS_LITERAL_CSTRING( michael@0: "ROLLBACK TO SAVEPOINT " SAVEPOINT_NAME michael@0: )); michael@0: NS_ENSURE_TRUE_VOID(stmt); michael@0: michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: nsresult rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: if (IsWriteAllowed()) { michael@0: mUpdateFileRefcountFunction->RollbackSavepoint(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: IDBTransaction::GetOrCreateConnection(mozIStorageConnection** aResult) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "IDBTransaction::GetOrCreateConnection"); michael@0: michael@0: if (mDatabase->IsInvalidated()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (!mConnection) { michael@0: nsCOMPtr connection = michael@0: IDBFactory::GetConnection(mDatabase->FilePath(), mDatabase->Type(), michael@0: mDatabase->Group(), mDatabase->Origin()); michael@0: NS_ENSURE_TRUE(connection, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsRefPtr function; michael@0: nsCString beginTransaction; michael@0: if (mMode != IDBTransaction::READ_ONLY) { michael@0: function = new UpdateRefcountFunction(Database()->Manager()); michael@0: NS_ENSURE_TRUE(function, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: rv = connection->CreateFunction( michael@0: NS_LITERAL_CSTRING("update_refcount"), 2, function); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: beginTransaction.AssignLiteral("BEGIN IMMEDIATE TRANSACTION;"); michael@0: } michael@0: else { michael@0: beginTransaction.AssignLiteral("BEGIN TRANSACTION;"); michael@0: } michael@0: michael@0: nsCOMPtr stmt; michael@0: rv = connection->CreateStatement(beginTransaction, getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: function.swap(mUpdateFileRefcountFunction); michael@0: connection.swap(mConnection); michael@0: } michael@0: michael@0: nsCOMPtr result(mConnection); michael@0: result.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBTransaction::GetCachedStatement(const nsACString& aQuery) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!aQuery.IsEmpty(), "Empty sql statement!"); michael@0: NS_ASSERTION(mConnection, "No connection!"); michael@0: michael@0: nsCOMPtr stmt; michael@0: michael@0: if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) { michael@0: nsresult rv = mConnection->CreateStatement(aQuery, getter_AddRefs(stmt)); michael@0: #ifdef DEBUG michael@0: if (NS_FAILED(rv)) { michael@0: nsCString error; michael@0: error.AppendLiteral("The statement `"); michael@0: error.Append(aQuery); michael@0: error.AppendLiteral("` failed to compile with the error message `"); michael@0: nsCString msg; michael@0: (void)mConnection->GetLastErrorString(msg); michael@0: error.Append(msg); michael@0: error.AppendLiteral("`."); michael@0: NS_ERROR(error.get()); michael@0: } michael@0: #endif michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: mCachedStatements.Put(aQuery, stmt); michael@0: } michael@0: michael@0: return stmt.forget(); michael@0: } michael@0: michael@0: bool michael@0: IDBTransaction::IsOpen() const michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // If we haven't started anything then we're open. michael@0: if (mReadyState == IDBTransaction::INITIAL) { michael@0: return true; michael@0: } michael@0: michael@0: // If we've already started then we need to check to see if we still have the michael@0: // mCreating flag set. If we do (i.e. we haven't returned to the event loop michael@0: // from the time we were created) then we are open. Otherwise check the michael@0: // currently running transaction to see if it's the same. We only allow other michael@0: // requests to be made if this transaction is currently running. michael@0: if (mReadyState == IDBTransaction::LOADING) { michael@0: if (mCreating) { michael@0: return true; michael@0: } michael@0: michael@0: if (AsyncConnectionHelper::GetCurrentTransaction() == this) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBTransaction::GetOrCreateObjectStore(const nsAString& aName, michael@0: ObjectStoreInfo* aObjectStoreInfo, michael@0: bool aCreating) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aObjectStoreInfo, "Null pointer!"); michael@0: NS_ASSERTION(!aCreating || GetMode() == IDBTransaction::VERSION_CHANGE, michael@0: "How else can we create here?!"); michael@0: michael@0: nsRefPtr retval; michael@0: michael@0: for (uint32_t index = 0; index < mCreatedObjectStores.Length(); index++) { michael@0: nsRefPtr& objectStore = mCreatedObjectStores[index]; michael@0: if (objectStore->Name() == aName) { michael@0: retval = objectStore; michael@0: return retval.forget(); michael@0: } michael@0: } michael@0: michael@0: retval = IDBObjectStore::Create(this, aObjectStoreInfo, mDatabaseInfo->id, michael@0: aCreating); michael@0: michael@0: mCreatedObjectStores.AppendElement(retval); michael@0: michael@0: return retval.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBTransaction::GetFileInfo(nsIDOMBlob* aBlob) michael@0: { michael@0: nsRefPtr fileInfo; michael@0: mCreatedFileInfos.Get(aBlob, getter_AddRefs(fileInfo)); michael@0: return fileInfo.forget(); michael@0: } michael@0: michael@0: void michael@0: IDBTransaction::AddFileInfo(nsIDOMBlob* aBlob, FileInfo* aFileInfo) michael@0: { michael@0: mCreatedFileInfos.Put(aBlob, aFileInfo); michael@0: } michael@0: michael@0: void michael@0: IDBTransaction::ClearCreatedFileInfos() michael@0: { michael@0: mCreatedFileInfos.Clear(); michael@0: } michael@0: michael@0: nsresult michael@0: IDBTransaction::AbortInternal(nsresult aAbortCode, michael@0: already_AddRefed aError) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsRefPtr error = aError; michael@0: michael@0: if (IsFinished()) { michael@0: return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; michael@0: } michael@0: michael@0: if (mActorChild) { michael@0: NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: mActorChild->SendAbort(aAbortCode); michael@0: } michael@0: michael@0: bool needToCommitOrRollback = mReadyState == IDBTransaction::INITIAL; michael@0: michael@0: mAbortCode = aAbortCode; michael@0: mReadyState = IDBTransaction::DONE; michael@0: mError = error.forget(); michael@0: michael@0: if (GetMode() == IDBTransaction::VERSION_CHANGE) { michael@0: // If a version change transaction is aborted, we must revert the world michael@0: // back to its previous state. michael@0: mDatabase->RevertToPreviousState(); michael@0: michael@0: DatabaseInfo* dbInfo = mDatabase->Info(); michael@0: michael@0: for (uint32_t i = 0; i < mCreatedObjectStores.Length(); i++) { michael@0: nsRefPtr& objectStore = mCreatedObjectStores[i]; michael@0: ObjectStoreInfo* info = dbInfo->GetObjectStore(objectStore->Name()); michael@0: michael@0: if (!info) { michael@0: info = new ObjectStoreInfo(*objectStore->Info()); michael@0: info->indexes.Clear(); michael@0: } michael@0: michael@0: objectStore->SetInfo(info); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mDeletedObjectStores.Length(); i++) { michael@0: nsRefPtr& objectStore = mDeletedObjectStores[i]; michael@0: ObjectStoreInfo* info = dbInfo->GetObjectStore(objectStore->Name()); michael@0: michael@0: if (!info) { michael@0: info = new ObjectStoreInfo(*objectStore->Info()); michael@0: info->indexes.Clear(); michael@0: } michael@0: michael@0: objectStore->SetInfo(info); michael@0: } michael@0: michael@0: // and then the db must be closed michael@0: mDatabase->Close(); michael@0: } michael@0: michael@0: // Fire the abort event if there are no outstanding requests. Otherwise the michael@0: // abort event will be fired when all outstanding requests finish. michael@0: if (needToCommitOrRollback) { michael@0: return CommitOrRollback(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: IDBTransaction::Abort(IDBRequest* aRequest) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aRequest, "This is undesirable."); michael@0: michael@0: ErrorResult rv; michael@0: nsRefPtr error = aRequest->GetError(rv); michael@0: michael@0: return AbortInternal(aRequest->GetErrorCode(), error.forget()); michael@0: } michael@0: michael@0: nsresult michael@0: IDBTransaction::Abort(nsresult aErrorCode) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsRefPtr error = new DOMError(GetOwner(), aErrorCode); michael@0: return AbortInternal(aErrorCode, error.forget()); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction, michael@0: IDBWrapperCache) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatabase) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCreatedObjectStores) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedObjectStores) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction, IDBWrapperCache) michael@0: // Don't unlink mDatabase! michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mError) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mCreatedObjectStores) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedObjectStores) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBTransaction) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRunnable) michael@0: NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(IDBTransaction, IDBWrapperCache) michael@0: NS_IMPL_RELEASE_INHERITED(IDBTransaction, IDBWrapperCache) michael@0: michael@0: JSObject* michael@0: IDBTransaction::WrapObject(JSContext* aCx) michael@0: { michael@0: return IDBTransactionBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: mozilla::dom::IDBTransactionMode michael@0: IDBTransaction::GetMode(ErrorResult& aRv) const michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: switch (mMode) { michael@0: case READ_ONLY: michael@0: return mozilla::dom::IDBTransactionMode::Readonly; michael@0: michael@0: case READ_WRITE: michael@0: return mozilla::dom::IDBTransactionMode::Readwrite; michael@0: michael@0: case VERSION_CHANGE: michael@0: return mozilla::dom::IDBTransactionMode::Versionchange; michael@0: michael@0: case MODE_INVALID: michael@0: default: michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return mozilla::dom::IDBTransactionMode::Readonly; michael@0: } michael@0: } michael@0: michael@0: DOMError* michael@0: IDBTransaction::GetError(ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (IsOpen()) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: return mError; michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBTransaction::GetObjectStoreNames(ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsRefPtr list(new DOMStringList()); michael@0: michael@0: if (mMode == IDBTransaction::VERSION_CHANGE) { michael@0: mDatabaseInfo->GetObjectStoreNames(list->StringArray()); michael@0: } michael@0: else { michael@0: list->StringArray() = mObjectStoreNames; michael@0: } michael@0: michael@0: return list.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBTransaction::ObjectStore(const nsAString& aName, ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (IsFinished()) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: ObjectStoreInfo* info = nullptr; michael@0: michael@0: if (mMode == IDBTransaction::VERSION_CHANGE || michael@0: mObjectStoreNames.Contains(aName)) { michael@0: info = mDatabaseInfo->GetObjectStore(aName); michael@0: } michael@0: michael@0: if (!info) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr objectStore = michael@0: GetOrCreateObjectStore(aName, info, false); michael@0: if (!objectStore) { michael@0: IDB_WARNING("Failed to get or create object store!"); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: return objectStore.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: IDBTransaction::PreHandleEvent(EventChainPreVisitor& aVisitor) michael@0: { michael@0: aVisitor.mCanHandle = true; michael@0: aVisitor.mParentTarget = mDatabase; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: IDBTransaction::Run() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // We're back at the event loop, no longer newborn. michael@0: mCreating = false; michael@0: michael@0: // Maybe set the readyState to DONE if there were no requests generated. michael@0: if (mReadyState == IDBTransaction::INITIAL) { michael@0: mReadyState = IDBTransaction::DONE; michael@0: michael@0: if (NS_FAILED(CommitOrRollback())) { michael@0: NS_WARNING("Failed to commit!"); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: CommitHelper::CommitHelper( michael@0: IDBTransaction* aTransaction, michael@0: IDBTransactionListener* aListener, michael@0: const nsTArray >& aUpdatedObjectStores) michael@0: : mTransaction(aTransaction), michael@0: mListener(aListener), michael@0: mAbortCode(aTransaction->mAbortCode) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: mConnection.swap(aTransaction->mConnection); michael@0: mUpdateFileRefcountFunction.swap(aTransaction->mUpdateFileRefcountFunction); michael@0: michael@0: for (uint32_t i = 0; i < aUpdatedObjectStores.Length(); i++) { michael@0: ObjectStoreInfo* info = aUpdatedObjectStores[i]->Info(); michael@0: if (info->comittedAutoIncrementId != info->nextAutoIncrementId) { michael@0: mAutoIncrementObjectStores.AppendElement(aUpdatedObjectStores[i]); michael@0: } michael@0: } michael@0: } michael@0: michael@0: CommitHelper::CommitHelper(IDBTransaction* aTransaction, michael@0: nsresult aAbortCode) michael@0: : mTransaction(aTransaction), michael@0: mAbortCode(aAbortCode) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: } michael@0: michael@0: CommitHelper::~CommitHelper() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(CommitHelper, nsIRunnable) michael@0: michael@0: NS_IMETHODIMP michael@0: CommitHelper::Run() michael@0: { michael@0: if (NS_IsMainThread()) { michael@0: PROFILER_MAIN_THREAD_LABEL("IndexedDB", "CommitHelper::Run"); michael@0: michael@0: NS_ASSERTION(mDoomedObjects.IsEmpty(), "Didn't release doomed objects!"); michael@0: michael@0: mTransaction->mReadyState = IDBTransaction::DONE; michael@0: michael@0: // Release file infos on the main thread, so they will eventually get michael@0: // destroyed on correct thread. michael@0: mTransaction->ClearCreatedFileInfos(); michael@0: if (mUpdateFileRefcountFunction) { michael@0: mUpdateFileRefcountFunction->ClearFileInfoEntries(); michael@0: mUpdateFileRefcountFunction = nullptr; michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: if (NS_FAILED(mAbortCode)) { michael@0: if (mTransaction->GetMode() == IDBTransaction::VERSION_CHANGE) { michael@0: // This will make the database take a snapshot of it's DatabaseInfo michael@0: mTransaction->Database()->Close(); michael@0: // Then remove the info from the hash as it contains invalid data. michael@0: DatabaseInfo::Remove(mTransaction->Database()->Id()); michael@0: } michael@0: michael@0: event = CreateGenericEvent(mTransaction, michael@0: NS_LITERAL_STRING(ABORT_EVT_STR), michael@0: eDoesBubble, eNotCancelable); michael@0: michael@0: // The transaction may already have an error object (e.g. if one of the michael@0: // requests failed). If it doesn't, and it wasn't aborted michael@0: // programmatically, create one now. michael@0: if (!mTransaction->mError && michael@0: mAbortCode != NS_ERROR_DOM_INDEXEDDB_ABORT_ERR) { michael@0: mTransaction->mError = new DOMError(mTransaction->GetOwner(), mAbortCode); michael@0: } michael@0: } michael@0: else { michael@0: event = CreateGenericEvent(mTransaction, michael@0: NS_LITERAL_STRING(COMPLETE_EVT_STR), michael@0: eDoesNotBubble, eNotCancelable); michael@0: } michael@0: IDB_ENSURE_TRUE(event, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: if (mListener) { michael@0: mListener->NotifyTransactionPreComplete(mTransaction); michael@0: } michael@0: michael@0: IDB_PROFILER_MARK("IndexedDB Transaction %llu: Complete (rv = %lu)", michael@0: "IDBTransaction[%llu] MT Complete", michael@0: mTransaction->GetSerialNumber(), mAbortCode); michael@0: michael@0: bool dummy; michael@0: if (NS_FAILED(mTransaction->DispatchEvent(event, &dummy))) { michael@0: NS_WARNING("Dispatch failed!"); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: mTransaction->mFiredCompleteOrAbort = true; michael@0: #endif michael@0: michael@0: if (mListener) { michael@0: mListener->NotifyTransactionPostComplete(mTransaction); michael@0: } michael@0: michael@0: mTransaction = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: PROFILER_LABEL("IndexedDB", "CommitHelper::Run"); michael@0: michael@0: IDBDatabase* database = mTransaction->Database(); michael@0: if (database->IsInvalidated()) { michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: mAbortCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: michael@0: if (mConnection) { michael@0: QuotaManager::SetCurrentWindow(database->GetOwner()); michael@0: michael@0: if (NS_SUCCEEDED(mAbortCode) && mUpdateFileRefcountFunction && michael@0: NS_FAILED(mUpdateFileRefcountFunction->WillCommit(mConnection))) { michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: mAbortCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(mAbortCode) && NS_FAILED(WriteAutoIncrementCounts())) { michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: mAbortCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(mAbortCode)) { michael@0: NS_NAMED_LITERAL_CSTRING(release, "COMMIT TRANSACTION"); michael@0: nsresult rv = mConnection->ExecuteSimpleSQL(release); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (mUpdateFileRefcountFunction) { michael@0: mUpdateFileRefcountFunction->DidCommit(); michael@0: } michael@0: CommitAutoIncrementCounts(); michael@0: } michael@0: else if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) { michael@0: // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE, michael@0: // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR. michael@0: mAbortCode = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; michael@0: } michael@0: else { michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: mAbortCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: } michael@0: michael@0: if (NS_FAILED(mAbortCode)) { michael@0: if (mUpdateFileRefcountFunction) { michael@0: mUpdateFileRefcountFunction->DidAbort(); michael@0: } michael@0: RevertAutoIncrementCounts(); michael@0: NS_NAMED_LITERAL_CSTRING(rollback, "ROLLBACK TRANSACTION"); michael@0: if (NS_FAILED(mConnection->ExecuteSimpleSQL(rollback))) { michael@0: NS_WARNING("Failed to rollback transaction!"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mDoomedObjects.Clear(); michael@0: michael@0: if (mConnection) { michael@0: if (mUpdateFileRefcountFunction) { michael@0: nsresult rv = mConnection->RemoveFunction( michael@0: NS_LITERAL_CSTRING("update_refcount")); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to remove function!"); michael@0: } michael@0: } michael@0: michael@0: mConnection->Close(); michael@0: mConnection = nullptr; michael@0: michael@0: QuotaManager::SetCurrentWindow(nullptr); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CommitHelper::WriteAutoIncrementCounts() michael@0: { michael@0: nsCOMPtr stmt; michael@0: nsresult rv; michael@0: for (uint32_t i = 0; i < mAutoIncrementObjectStores.Length(); i++) { michael@0: ObjectStoreInfo* info = mAutoIncrementObjectStores[i]->Info(); michael@0: if (!stmt) { michael@0: rv = mConnection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE object_store SET auto_increment = :ai " michael@0: "WHERE id = :osid;"), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: stmt->Reset(); michael@0: } michael@0: michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), info->id); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("ai"), michael@0: info->nextAutoIncrementId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CommitHelper::CommitAutoIncrementCounts() michael@0: { michael@0: for (uint32_t i = 0; i < mAutoIncrementObjectStores.Length(); i++) { michael@0: ObjectStoreInfo* info = mAutoIncrementObjectStores[i]->Info(); michael@0: info->comittedAutoIncrementId = info->nextAutoIncrementId; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CommitHelper::RevertAutoIncrementCounts() michael@0: { michael@0: for (uint32_t i = 0; i < mAutoIncrementObjectStores.Length(); i++) { michael@0: ObjectStoreInfo* info = mAutoIncrementObjectStores[i]->Info(); michael@0: info->nextAutoIncrementId = info->comittedAutoIncrementId; michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(UpdateRefcountFunction, mozIStorageFunction) michael@0: michael@0: NS_IMETHODIMP michael@0: UpdateRefcountFunction::OnFunctionCall(mozIStorageValueArray* aValues, michael@0: nsIVariant** _retval) michael@0: { michael@0: *_retval = nullptr; michael@0: michael@0: uint32_t numEntries; michael@0: nsresult rv = aValues->GetNumEntries(&numEntries); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ASSERTION(numEntries == 2, "unexpected number of arguments"); michael@0: michael@0: #ifdef DEBUG michael@0: int32_t type1 = mozIStorageValueArray::VALUE_TYPE_NULL; michael@0: aValues->GetTypeOfIndex(0, &type1); michael@0: michael@0: int32_t type2 = mozIStorageValueArray::VALUE_TYPE_NULL; michael@0: aValues->GetTypeOfIndex(1, &type2); michael@0: michael@0: NS_ASSERTION(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL && michael@0: type2 == mozIStorageValueArray::VALUE_TYPE_NULL), michael@0: "Shouldn't be called!"); michael@0: #endif michael@0: michael@0: rv = ProcessValue(aValues, 0, eDecrement); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ProcessValue(aValues, 1, eIncrement); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpdateRefcountFunction::WillCommit(mozIStorageConnection* aConnection) michael@0: { michael@0: DatabaseUpdateFunction function(aConnection, this); michael@0: michael@0: mFileInfoEntries.EnumerateRead(DatabaseUpdateCallback, &function); michael@0: michael@0: nsresult rv = function.ErrorCode(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = CreateJournals(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: UpdateRefcountFunction::DidCommit() michael@0: { michael@0: mFileInfoEntries.EnumerateRead(FileInfoUpdateCallback, nullptr); michael@0: michael@0: nsresult rv = RemoveJournals(mJournalsToRemoveAfterCommit); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: } michael@0: michael@0: void michael@0: UpdateRefcountFunction::DidAbort() michael@0: { michael@0: nsresult rv = RemoveJournals(mJournalsToRemoveAfterAbort); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: } michael@0: michael@0: nsresult michael@0: UpdateRefcountFunction::ProcessValue(mozIStorageValueArray* aValues, michael@0: int32_t aIndex, michael@0: UpdateType aUpdateType) michael@0: { michael@0: int32_t type; michael@0: aValues->GetTypeOfIndex(aIndex, &type); michael@0: if (type == mozIStorageValueArray::VALUE_TYPE_NULL) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsString ids; michael@0: aValues->GetString(aIndex, ids); michael@0: michael@0: nsTArray fileIds; michael@0: nsresult rv = IDBObjectStore::ConvertFileIdsToArray(ids, fileIds); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (uint32_t i = 0; i < fileIds.Length(); i++) { michael@0: int64_t id = fileIds.ElementAt(i); michael@0: michael@0: FileInfoEntry* entry; michael@0: if (!mFileInfoEntries.Get(id, &entry)) { michael@0: nsRefPtr fileInfo = mFileManager->GetFileInfo(id); michael@0: NS_ASSERTION(fileInfo, "Shouldn't be null!"); michael@0: michael@0: nsAutoPtr newEntry(new FileInfoEntry(fileInfo)); michael@0: mFileInfoEntries.Put(id, newEntry); michael@0: entry = newEntry.forget(); michael@0: } michael@0: michael@0: if (mInSavepoint) { michael@0: mSavepointEntriesIndex.Put(id, entry); michael@0: } michael@0: michael@0: switch (aUpdateType) { michael@0: case eIncrement: michael@0: entry->mDelta++; michael@0: if (mInSavepoint) { michael@0: entry->mSavepointDelta++; michael@0: } michael@0: break; michael@0: case eDecrement: michael@0: entry->mDelta--; michael@0: if (mInSavepoint) { michael@0: entry->mSavepointDelta--; michael@0: } michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("Unknown update type!"); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpdateRefcountFunction::CreateJournals() michael@0: { michael@0: nsCOMPtr journalDirectory = mFileManager->GetJournalDirectory(); michael@0: NS_ENSURE_TRUE(journalDirectory, NS_ERROR_FAILURE); michael@0: michael@0: for (uint32_t i = 0; i < mJournalsToCreateBeforeCommit.Length(); i++) { michael@0: int64_t id = mJournalsToCreateBeforeCommit[i]; michael@0: michael@0: nsCOMPtr file = michael@0: mFileManager->GetFileForId(journalDirectory, id); michael@0: NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mJournalsToRemoveAfterAbort.AppendElement(id); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpdateRefcountFunction::RemoveJournals(const nsTArray& aJournals) michael@0: { michael@0: nsCOMPtr journalDirectory = mFileManager->GetJournalDirectory(); michael@0: NS_ENSURE_TRUE(journalDirectory, NS_ERROR_FAILURE); michael@0: michael@0: for (uint32_t index = 0; index < aJournals.Length(); index++) { michael@0: nsCOMPtr file = michael@0: mFileManager->GetFileForId(journalDirectory, aJournals[index]); michael@0: NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); michael@0: michael@0: if (NS_FAILED(file->Remove(false))) { michael@0: NS_WARNING("Failed to removed journal!"); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: UpdateRefcountFunction::DatabaseUpdateCallback(const uint64_t& aKey, michael@0: FileInfoEntry* aValue, michael@0: void* aUserArg) michael@0: { michael@0: if (!aValue->mDelta) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: DatabaseUpdateFunction* function = michael@0: static_cast(aUserArg); michael@0: michael@0: if (!function->Update(aKey, aValue->mDelta)) { michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: UpdateRefcountFunction::FileInfoUpdateCallback(const uint64_t& aKey, michael@0: FileInfoEntry* aValue, michael@0: void* aUserArg) michael@0: { michael@0: if (aValue->mDelta) { michael@0: aValue->mFileInfo->UpdateDBRefs(aValue->mDelta); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: UpdateRefcountFunction::RollbackSavepointCallback(const uint64_t& aKey, michael@0: FileInfoEntry* aValue, michael@0: void* aUserArg) michael@0: { michael@0: aValue->mDelta -= aValue->mSavepointDelta; michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: bool michael@0: UpdateRefcountFunction::DatabaseUpdateFunction::Update(int64_t aId, michael@0: int32_t aDelta) michael@0: { michael@0: nsresult rv = UpdateInternal(aId, aDelta); michael@0: if (NS_FAILED(rv)) { michael@0: mErrorCode = rv; michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: UpdateRefcountFunction::DatabaseUpdateFunction::UpdateInternal(int64_t aId, michael@0: int32_t aDelta) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (!mUpdateStatement) { michael@0: rv = mConnection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE file SET refcount = refcount + :delta WHERE id = :id" michael@0: ), getter_AddRefs(mUpdateStatement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mozStorageStatementScoper updateScoper(mUpdateStatement); michael@0: michael@0: rv = mUpdateStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mUpdateStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mUpdateStatement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t rows; michael@0: rv = mConnection->GetAffectedRows(&rows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (rows > 0) { michael@0: if (!mSelectStatement) { michael@0: rv = mConnection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT id FROM file where id = :id" michael@0: ), getter_AddRefs(mSelectStatement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mozStorageStatementScoper selectScoper(mSelectStatement); michael@0: michael@0: rv = mSelectStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasResult; michael@0: rv = mSelectStatement->ExecuteStep(&hasResult); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!hasResult) { michael@0: // Don't have to create the journal here, we can create all at once, michael@0: // just before commit michael@0: mFunction->mJournalsToCreateBeforeCommit.AppendElement(aId); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mInsertStatement) { michael@0: rv = mConnection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "INSERT INTO file (id, refcount) VALUES(:id, :delta)" michael@0: ), getter_AddRefs(mInsertStatement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mozStorageStatementScoper insertScoper(mInsertStatement); michael@0: michael@0: rv = mInsertStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mInsertStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mInsertStatement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mFunction->mJournalsToRemoveAfterCommit.AppendElement(aId); michael@0: michael@0: return NS_OK; michael@0: }