gecko-dev/storage/mozStorageHelper.h
Kit Cambridge e4711b8178 Bug 1435446 - Add a default transaction type for storage connections. r=mak
This patch adds a `mozIStorageConnection::defaultTransactionType`
attribute that controls the default transaction behavior for the
connection. As before, `mozStorageTransaction` can override the default
behavior for individual transactions.

MozReview-Commit-ID: IRSlMesETWN

--HG--
extra : rebase_source : fc63af108bb246bc096cb9ef7c13b41fabba5563
2018-02-28 22:44:40 -08:00

217 lines
7.2 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZSTORAGEHELPER_H
#define MOZSTORAGEHELPER_H
#include "nsAutoPtr.h"
#include "nsString.h"
#include "mozilla/DebugOnly.h"
#include "nsIConsoleService.h"
#include "nsIScriptError.h"
#include "mozIStorageAsyncConnection.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "mozIStoragePendingStatement.h"
#include "nsError.h"
#include "nsIXPConnect.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.
*
* A common use is to create an instance with aCommitOnComplete = false (rollback),
* then call Commit() on this object manually when your function completes
* successfully.
*
* @note nested transactions are not supported by Sqlite, so if a transaction
* is already in progress, this object does nothing. Note that in this case,
* you may not get the transaction type you asked for, and you won't be able
* to rollback.
*
* @param aConnection
* The connection to create the transaction on.
* @param aCommitOnComplete
* Controls whether the transaction is committed or rolled back when
* this object goes out of scope.
* @param aType [optional]
* The transaction type, as defined in mozIStorageConnection. Uses the
* default transaction behavior for the connection if unspecified.
* @param aAsyncCommit [optional]
* Whether commit should be executed asynchronously on the helper thread.
* This is a special option introduced as an interim solution to reduce
* main-thread fsyncs in Places. Can only be used on main-thread.
*
* WARNING: YOU SHOULD _NOT_ WRITE NEW MAIN-THREAD CODE USING THIS!
*
* Notice that async commit might cause synchronous statements to fail
* with SQLITE_BUSY. A possible mitigation strategy is to use
* PRAGMA busy_timeout, but notice that might cause main-thread jank.
* Finally, if the database is using WAL journaling mode, other
* connections won't see the changes done in async committed transactions
* until commit is complete.
*
* For all of the above reasons, this should only be used as an interim
* solution and avoided completely if possible.
*/
class mozStorageTransaction
{
public:
mozStorageTransaction(mozIStorageConnection* aConnection,
bool aCommitOnComplete,
int32_t aType = mozIStorageConnection::TRANSACTION_DEFAULT,
bool aAsyncCommit = false)
: mConnection(aConnection),
mHasTransaction(false),
mCommitOnComplete(aCommitOnComplete),
mCompleted(false),
mAsyncCommit(aAsyncCommit)
{
if (mConnection) {
nsAutoCString query("BEGIN");
int32_t type = aType;
if (type == mozIStorageConnection::TRANSACTION_DEFAULT) {
MOZ_ALWAYS_SUCCEEDS(mConnection->GetDefaultTransactionType(&type));
}
switch (type) {
case mozIStorageConnection::TRANSACTION_IMMEDIATE:
query.AppendLiteral(" IMMEDIATE");
break;
case mozIStorageConnection::TRANSACTION_EXCLUSIVE:
query.AppendLiteral(" EXCLUSIVE");
break;
case mozIStorageConnection::TRANSACTION_DEFERRED:
query.AppendLiteral(" DEFERRED");
break;
default:
MOZ_ASSERT(false, "Unknown transaction type");
}
// If a transaction is already in progress, this will fail, since Sqlite
// doesn't support nested transactions.
mHasTransaction = NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(query));
}
}
~mozStorageTransaction()
{
if (mConnection && mHasTransaction && !mCompleted) {
if (mCommitOnComplete) {
mozilla::DebugOnly<nsresult> rv = Commit();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"A transaction didn't commit correctly");
}
else {
mozilla::DebugOnly<nsresult> rv = Rollback();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"A transaction didn't rollback correctly");
}
}
}
/**
* Commits the transaction if one is in progress. If one is not in progress,
* this is a NOP since the actual owner of the transaction outside of our
* scope is in charge of finally committing or rolling back the transaction.
*/
nsresult Commit()
{
if (!mConnection || mCompleted || !mHasTransaction)
return NS_OK;
mCompleted = true;
// TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't handle
// it, thus the transaction might stay open until the next COMMIT.
nsresult rv;
if (mAsyncCommit) {
nsCOMPtr<mozIStoragePendingStatement> ps;
rv = mConnection->ExecuteSimpleSQLAsync(NS_LITERAL_CSTRING("COMMIT"),
nullptr, getter_AddRefs(ps));
}
else {
rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT"));
}
if (NS_SUCCEEDED(rv))
mHasTransaction = false;
return rv;
}
/**
* Rolls back the transaction if one is in progress. If one is not in progress,
* this is a NOP since the actual owner of the transaction outside of our
* scope is in charge of finally rolling back the transaction.
*/
nsresult Rollback()
{
if (!mConnection || mCompleted || !mHasTransaction)
return NS_OK;
mCompleted = true;
// TODO (bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return
// a busy error, so this handling can be removed.
nsresult rv = NS_OK;
do {
rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK"));
if (rv == NS_ERROR_STORAGE_BUSY)
(void)PR_Sleep(PR_INTERVAL_NO_WAIT);
} while (rv == NS_ERROR_STORAGE_BUSY);
if (NS_SUCCEEDED(rv))
mHasTransaction = false;
return rv;
}
protected:
nsCOMPtr<mozIStorageConnection> mConnection;
bool mHasTransaction;
bool mCommitOnComplete;
bool mCompleted;
bool mAsyncCommit;
};
/**
* This class wraps a statement so that it is guaraneed to be reset when
* this object goes out of scope.
*
* Note that this always just resets the statement. If the statement doesn't
* need resetting, the reset operation is inexpensive.
*/
class MOZ_STACK_CLASS mozStorageStatementScoper
{
public:
explicit mozStorageStatementScoper(mozIStorageStatement* aStatement)
: mStatement(aStatement)
{
}
~mozStorageStatementScoper()
{
if (mStatement)
mStatement->Reset();
}
/**
* Call this to make the statement not reset. You might do this if you know
* that the statement has been reset.
*/
void Abandon()
{
mStatement = nullptr;
}
protected:
nsCOMPtr<mozIStorageStatement> mStatement;
};
// Use this to make queries uniquely identifiable in telemetry
// statistics, especially PRAGMAs. We don't include __LINE__ so that
// queries are stable in the face of source code changes.
#define MOZ_STORAGE_UNIQUIFY_QUERY_STR "/* " __FILE__ " */ "
#endif /* MOZSTORAGEHELPER_H */