forked from mirrors/gecko-dev
Bug 1482608 - Add basic Rust bindings for mozStorage. r=nika,asuth,mak
This commit wraps just enough of the mozStorage API to support the bookmarks mirror. It's not complete: for example, there's no way to open, clone, or close a connection, because the mirror handles that from JS. The wrapper also omits shutdown blocking and retrying on `SQLITE_BUSY`. This commit also changes the behavior of sync and async mozStorage connections. Async (`mozIStorageAsyncConnection`) methods may be called from any thread on any connection. Sync (`mozIStorageConnection`) methods may be called from any thread on a sync connection, and from background threads on an async connection. All connections now QI to `mozIStorageConnection`, but attempting to call a sync method on an async connection from the main thread throws. Finally, this commit exposes an `OpenedConnection::unsafeRawConnection` getter in Sqlite.jsm, for JS code to access the underlying connection. Differential Revision: https://phabricator.services.mozilla.com/D20073 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
bc72d9813e
commit
3e894ac30e
15 changed files with 625 additions and 78 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -1216,6 +1216,7 @@ dependencies = [
|
|||
"profiler_helper 0.1.0",
|
||||
"rsdparsa_capi 0.1.0",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"storage 0.1.0",
|
||||
"u2fhid 0.2.3",
|
||||
"webrender_bindings 0.1.0",
|
||||
"xpcom 0.1.0",
|
||||
|
|
@ -2563,6 +2564,17 @@ name = "stable_deref_trait"
|
|||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nserror 0.1.0",
|
||||
"nsstring 0.1.0",
|
||||
"storage_variant 0.1.0",
|
||||
"xpcom 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "storage_variant"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ interface mozIStorageAsyncConnection : nsISupports {
|
|||
*
|
||||
* @note If your connection is already read-only, you will get a read-only
|
||||
* clone.
|
||||
* @note The resulting connection will NOT implement mozIStorageConnection,
|
||||
* it will only implement mozIStorageAsyncConnection.
|
||||
* @note The resulting connection will implement `mozIStorageConnection`, but
|
||||
* all synchronous methods will throw if called from the main thread.
|
||||
* @note Due to a bug in SQLite, if you use the shared cache
|
||||
* (see mozIStorageService), you end up with the same privileges as the
|
||||
* first connection opened regardless of what is specified in aReadOnly.
|
||||
|
|
|
|||
|
|
@ -149,6 +149,16 @@ interface mozIStorageStatement : mozIStorageBaseStatement {
|
|||
*/
|
||||
long getTypeOfIndex(in unsigned long aIndex);
|
||||
|
||||
/**
|
||||
* Retrieve the contents of a column from the current result row as a
|
||||
* variant.
|
||||
*
|
||||
* @param aIndex
|
||||
* 0-based colummn index.
|
||||
* @return A variant with the type of the column value.
|
||||
*/
|
||||
nsIVariant getVariant(in unsigned long aIndex);
|
||||
|
||||
/**
|
||||
* Retrieve the contents of a column from the current result row as an
|
||||
* integer.
|
||||
|
|
|
|||
|
|
@ -457,7 +457,8 @@ NS_IMPL_ISUPPORTS(CloseListener, mozIStorageCompletionCallback)
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Connection
|
||||
|
||||
Connection::Connection(Service *aService, int aFlags, bool aAsyncOnly,
|
||||
Connection::Connection(Service *aService, int aFlags,
|
||||
ConnectionOperation aSupportedOperations,
|
||||
bool aIgnoreLockingMode)
|
||||
: sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex"),
|
||||
sharedDBMutex("Connection::sharedDBMutex"),
|
||||
|
|
@ -472,7 +473,7 @@ Connection::Connection(Service *aService, int aFlags, bool aAsyncOnly,
|
|||
mFlags(aFlags),
|
||||
mIgnoreLockingMode(aIgnoreLockingMode),
|
||||
mStorageService(aService),
|
||||
mAsyncOnly(aAsyncOnly) {
|
||||
mSupportedOperations(aSupportedOperations) {
|
||||
MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY,
|
||||
"Can't ignore locking for a non-readonly connection!");
|
||||
mStorageService->registerConnection(this);
|
||||
|
|
@ -491,7 +492,7 @@ NS_IMPL_ADDREF(Connection)
|
|||
NS_INTERFACE_MAP_BEGIN(Connection)
|
||||
NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
|
||||
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(mozIStorageConnection, !mAsyncOnly)
|
||||
NS_INTERFACE_MAP_ENTRY(mozIStorageConnection)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageConnection)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
|
|
@ -528,10 +529,11 @@ NS_IMETHODIMP_(MozExternalRefCountType) Connection::Release(void) {
|
|||
// This could cause SpinningSynchronousClose() to be invoked and AddRef
|
||||
// triggered for AsyncCloseConnection's strong ref if the conn was ever
|
||||
// use for async purposes. (Main-thread only, though.)
|
||||
Unused << Close();
|
||||
Unused << synchronousClose();
|
||||
} else {
|
||||
nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
|
||||
"storage::Connection::Close", this, &Connection::Close);
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NewRunnableMethod("storage::Connection::synchronousClose", this,
|
||||
&Connection::synchronousClose);
|
||||
if (NS_FAILED(
|
||||
threadOpenedOn->Dispatch(event.forget(), NS_DISPATCH_NORMAL))) {
|
||||
// The target thread was dead and so we've just leaked our runnable.
|
||||
|
|
@ -539,8 +541,9 @@ NS_IMETHODIMP_(MozExternalRefCountType) Connection::Release(void) {
|
|||
// be explicitly closing their connections, not relying on us to close
|
||||
// them for them. (It's okay to let a statement go out of scope for
|
||||
// automatic cleanup, but not a Connection.)
|
||||
MOZ_ASSERT(false, "Leaked Connection::Close(), ownership fail.");
|
||||
Unused << Close();
|
||||
MOZ_ASSERT(false,
|
||||
"Leaked Connection::synchronousClose(), ownership fail.");
|
||||
Unused << synchronousClose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -823,7 +826,10 @@ void Connection::initializeFailed() {
|
|||
nsresult Connection::databaseElementExists(
|
||||
enum DatabaseElementType aElementType, const nsACString &aElementName,
|
||||
bool *_exists) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// When constructing the query, make sure to SELECT the correct db's
|
||||
// sqlite_master if the user is prefixing the element with a specific db. ex:
|
||||
|
|
@ -927,14 +933,25 @@ nsresult Connection::setClosedState() {
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
bool Connection::connectionReady() { return mDBConn != nullptr; }
|
||||
nsresult Connection::connectionReady(ConnectionOperation aOperation) {
|
||||
if (NS_WARN_IF(aOperation == SYNCHRONOUS &&
|
||||
mSupportedOperations == ASYNCHRONOUS && NS_IsMainThread())) {
|
||||
MOZ_ASSERT(false,
|
||||
"Don't use async connections synchronously on the main thread");
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
if (!mDBConn) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool Connection::isConnectionReadyOnThisThread() {
|
||||
MOZ_ASSERT_IF(mDBConn, !mConnectionClosed);
|
||||
if (mAsyncExecutionThread && mAsyncExecutionThread->IsOnCurrentThread()) {
|
||||
return true;
|
||||
}
|
||||
return connectionReady();
|
||||
return mDBConn != nullptr;
|
||||
}
|
||||
|
||||
bool Connection::isClosing() {
|
||||
|
|
@ -1211,7 +1228,17 @@ Connection::GetInterface(const nsIID &aIID, void **_result) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::Close() {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
return synchronousClose();
|
||||
}
|
||||
|
||||
nsresult Connection::synchronousClose() {
|
||||
if (!mDBConn) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// Since we're accessing mAsyncExecutionThread, we need to be on the opener
|
||||
|
|
@ -1259,13 +1286,14 @@ Connection::SpinningSynchronousClose() {
|
|||
// As currently implemented, we can't spin to wait for an existing AsyncClose.
|
||||
// Our only existing caller will never have called close; assert if misused
|
||||
// so that no new callers assume this works after an AsyncClose.
|
||||
MOZ_DIAGNOSTIC_ASSERT(connectionReady());
|
||||
if (!connectionReady()) {
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
RefPtr<CloseListener> listener = new CloseListener();
|
||||
nsresult rv = AsyncClose(listener);
|
||||
rv = AsyncClose(listener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return listener->mClosed; }));
|
||||
MOZ_ASSERT(isClosed(), "The connection should be closed at this point");
|
||||
|
|
@ -1277,8 +1305,9 @@ NS_IMETHODIMP
|
|||
Connection::AsyncClose(mozIStorageCompletionCallback *aCallback) {
|
||||
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
|
||||
// Check if AsyncClose or Close were already invoked.
|
||||
if (!mDBConn) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// The two relevant factors at this point are whether we have a database
|
||||
|
|
@ -1351,7 +1380,7 @@ Connection::AsyncClose(mozIStorageCompletionCallback *aCallback) {
|
|||
// callers ignore our return value.
|
||||
Unused << NS_DispatchToMainThread(completeEvent.forget());
|
||||
}
|
||||
MOZ_ALWAYS_SUCCEEDS(Close());
|
||||
MOZ_ALWAYS_SUCCEEDS(synchronousClose());
|
||||
// Return a success inconditionally here, since Close() is unlikely to fail
|
||||
// and we want to reassure the consumer that its callback will be invoked.
|
||||
return NS_OK;
|
||||
|
|
@ -1360,7 +1389,7 @@ Connection::AsyncClose(mozIStorageCompletionCallback *aCallback) {
|
|||
// setClosedState nullifies our connection pointer, so we take a raw pointer
|
||||
// off it, to pass it through the close procedure.
|
||||
sqlite3 *nativeConn = mDBConn;
|
||||
nsresult rv = setClosedState();
|
||||
rv = setClosedState();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Create and dispatch our close event to the background thread.
|
||||
|
|
@ -1378,7 +1407,10 @@ Connection::AsyncClone(bool aReadOnly,
|
|||
AUTO_PROFILER_LABEL("Connection::AsyncClone", OTHER);
|
||||
|
||||
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (!mDatabaseFile) return NS_ERROR_UNEXPECTED;
|
||||
|
||||
int flags = mFlags;
|
||||
|
|
@ -1389,8 +1421,10 @@ Connection::AsyncClone(bool aReadOnly,
|
|||
flags = (~SQLITE_OPEN_CREATE & flags);
|
||||
}
|
||||
|
||||
// Force the cloned connection to only implement the async connection API.
|
||||
RefPtr<Connection> clone = new Connection(mStorageService, flags, true);
|
||||
// The cloned connection will still implement the synchronous API, but throw
|
||||
// if any synchronous methods are called on the main thread.
|
||||
RefPtr<Connection> clone =
|
||||
new Connection(mStorageService, flags, ASYNCHRONOUS);
|
||||
|
||||
RefPtr<AsyncInitializeClone> initEvent =
|
||||
new AsyncInitializeClone(this, clone, aReadOnly, aCallback);
|
||||
|
|
@ -1553,7 +1587,10 @@ Connection::Clone(bool aReadOnly, mozIStorageConnection **_connection) {
|
|||
|
||||
AUTO_PROFILER_LABEL("Connection::Clone", OTHER);
|
||||
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (!mDatabaseFile) return NS_ERROR_UNEXPECTED;
|
||||
|
||||
int flags = mFlags;
|
||||
|
|
@ -1564,9 +1601,10 @@ Connection::Clone(bool aReadOnly, mozIStorageConnection **_connection) {
|
|||
flags = (~SQLITE_OPEN_CREATE & flags);
|
||||
}
|
||||
|
||||
RefPtr<Connection> clone = new Connection(mStorageService, flags, mAsyncOnly);
|
||||
RefPtr<Connection> clone =
|
||||
new Connection(mStorageService, flags, mSupportedOperations);
|
||||
|
||||
nsresult rv = initializeClone(clone, aReadOnly);
|
||||
rv = initializeClone(clone, aReadOnly);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
|
@ -1581,7 +1619,7 @@ Connection::Interrupt() {
|
|||
if (!mDBConn) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!mAsyncOnly || !(mFlags & SQLITE_OPEN_READONLY)) {
|
||||
if (mSupportedOperations == SYNCHRONOUS || !(mFlags & SQLITE_OPEN_READONLY)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
::sqlite3_interrupt(mDBConn);
|
||||
|
|
@ -1597,13 +1635,16 @@ Connection::GetDefaultPageSize(int32_t *_defaultPageSize) {
|
|||
NS_IMETHODIMP
|
||||
Connection::GetConnectionReady(bool *_ready) {
|
||||
MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
|
||||
*_ready = connectionReady();
|
||||
*_ready = !!mDBConn;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Connection::GetDatabaseFile(nsIFile **_dbFile) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IF_ADDREF(*_dbFile = mDatabaseFile);
|
||||
|
||||
|
|
@ -1612,7 +1653,10 @@ Connection::GetDatabaseFile(nsIFile **_dbFile) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::GetLastInsertRowID(int64_t *_id) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
sqlite_int64 id = ::sqlite3_last_insert_rowid(mDBConn);
|
||||
*_id = id;
|
||||
|
|
@ -1622,7 +1666,10 @@ Connection::GetLastInsertRowID(int64_t *_id) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::GetAffectedRows(int32_t *_rows) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
*_rows = ::sqlite3_changes(mDBConn);
|
||||
|
||||
|
|
@ -1631,7 +1678,10 @@ Connection::GetAffectedRows(int32_t *_rows) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::GetLastError(int32_t *_error) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
*_error = ::sqlite3_errcode(mDBConn);
|
||||
|
||||
|
|
@ -1640,7 +1690,10 @@ Connection::GetLastError(int32_t *_error) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::GetLastErrorString(nsACString &_errorString) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
const char *serr = ::sqlite3_errmsg(mDBConn);
|
||||
_errorString.Assign(serr);
|
||||
|
|
@ -1650,7 +1703,10 @@ Connection::GetLastErrorString(nsACString &_errorString) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::GetSchemaVersion(int32_t *_version) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<mozIStorageStatement> stmt;
|
||||
(void)CreateStatement(NS_LITERAL_CSTRING("PRAGMA user_version"),
|
||||
|
|
@ -1667,7 +1723,10 @@ Connection::GetSchemaVersion(int32_t *_version) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::SetSchemaVersion(int32_t aVersion) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoCString stmt(NS_LITERAL_CSTRING("PRAGMA user_version = "));
|
||||
stmt.AppendInt(aVersion);
|
||||
|
|
@ -1679,12 +1738,15 @@ NS_IMETHODIMP
|
|||
Connection::CreateStatement(const nsACString &aSQLStatement,
|
||||
mozIStorageStatement **_stmt) {
|
||||
NS_ENSURE_ARG_POINTER(_stmt);
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
RefPtr<Statement> statement(new Statement());
|
||||
NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
nsresult rv = statement->initialize(this, mDBConn, aSQLStatement);
|
||||
rv = statement->initialize(this, mDBConn, aSQLStatement);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
Statement *rawPtr;
|
||||
|
|
@ -1697,12 +1759,15 @@ NS_IMETHODIMP
|
|||
Connection::CreateAsyncStatement(const nsACString &aSQLStatement,
|
||||
mozIStorageAsyncStatement **_stmt) {
|
||||
NS_ENSURE_ARG_POINTER(_stmt);
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
RefPtr<AsyncStatement> statement(new AsyncStatement());
|
||||
NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
nsresult rv = statement->initialize(this, mDBConn, aSQLStatement);
|
||||
rv = statement->initialize(this, mDBConn, aSQLStatement);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
AsyncStatement *rawPtr;
|
||||
|
|
@ -1714,7 +1779,10 @@ Connection::CreateAsyncStatement(const nsACString &aSQLStatement,
|
|||
NS_IMETHODIMP
|
||||
Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement) {
|
||||
CHECK_MAINTHREAD_ABUSE();
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
int srv = executeSql(mDBConn, PromiseFlatCString(aSQLStatement).get());
|
||||
return convertResultCode(srv);
|
||||
|
|
@ -1781,7 +1849,10 @@ Connection::IndexExists(const nsACString &aIndexName, bool *_exists) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::GetTransactionInProgress(bool *_inProgress) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
SQLiteMutexAutoLock lockedScope(sharedDBMutex);
|
||||
*_inProgress = mTransactionInProgress;
|
||||
|
|
@ -1803,7 +1874,10 @@ Connection::SetDefaultTransactionType(int32_t aType) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::BeginTransaction() {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return beginTransactionInternal(mDBConn, mDefaultTransactionType);
|
||||
}
|
||||
|
|
@ -1832,7 +1906,10 @@ nsresult Connection::beginTransactionInternal(sqlite3 *aNativeConnection,
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::CommitTransaction() {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return commitTransactionInternal(mDBConn);
|
||||
}
|
||||
|
|
@ -1848,7 +1925,10 @@ nsresult Connection::commitTransactionInternal(sqlite3 *aNativeConnection) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::RollbackTransaction() {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return rollbackTransactionInternal(mDBConn);
|
||||
}
|
||||
|
|
@ -1865,7 +1945,10 @@ nsresult Connection::rollbackTransactionInternal(sqlite3 *aNativeConnection) {
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::CreateTable(const char *aTableName, const char *aTableSchema) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
SmprintfPointer buf =
|
||||
::mozilla::Smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema);
|
||||
|
|
@ -1880,7 +1963,10 @@ NS_IMETHODIMP
|
|||
Connection::CreateFunction(const nsACString &aFunctionName,
|
||||
int32_t aNumArguments,
|
||||
mozIStorageFunction *aFunction) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Check to see if this function is already defined. We only check the name
|
||||
// because a function can be defined with the same body but different names.
|
||||
|
|
@ -1903,7 +1989,10 @@ NS_IMETHODIMP
|
|||
Connection::CreateAggregateFunction(const nsACString &aFunctionName,
|
||||
int32_t aNumArguments,
|
||||
mozIStorageAggregateFunction *aFunction) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Check to see if this function name is already defined.
|
||||
SQLiteMutexAutoLock lockedScope(sharedDBMutex);
|
||||
|
|
@ -1929,7 +2018,10 @@ Connection::CreateAggregateFunction(const nsACString &aFunctionName,
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::RemoveFunction(const nsACString &aFunctionName) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
SQLiteMutexAutoLock lockedScope(sharedDBMutex);
|
||||
NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
|
||||
|
|
@ -1948,7 +2040,10 @@ NS_IMETHODIMP
|
|||
Connection::SetProgressHandler(int32_t aGranularity,
|
||||
mozIStorageProgressHandler *aHandler,
|
||||
mozIStorageProgressHandler **_oldHandler) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Return previous one
|
||||
SQLiteMutexAutoLock lockedScope(sharedDBMutex);
|
||||
|
|
@ -1981,13 +2076,17 @@ Connection::RemoveProgressHandler(mozIStorageProgressHandler **_oldHandler) {
|
|||
NS_IMETHODIMP
|
||||
Connection::SetGrowthIncrement(int32_t aChunkSize,
|
||||
const nsACString &aDatabaseName) {
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
// Bug 597215: Disk space is extremely limited on Android
|
||||
// so don't preallocate space. This is also not effective
|
||||
// on log structured file systems used by Android devices
|
||||
#if !defined(ANDROID) && !defined(MOZ_PLATFORM_MAEMO)
|
||||
// Don't preallocate if less than 500MiB is available.
|
||||
int64_t bytesAvailable;
|
||||
nsresult rv = mDatabaseFile->GetDiskSpaceAvailable(&bytesAvailable);
|
||||
rv = mDatabaseFile->GetDiskSpaceAvailable(&bytesAvailable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (bytesAvailable < MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH) {
|
||||
return NS_ERROR_FILE_TOO_BIG;
|
||||
|
|
@ -2004,7 +2103,10 @@ Connection::SetGrowthIncrement(int32_t aChunkSize,
|
|||
|
||||
NS_IMETHODIMP
|
||||
Connection::EnableModule(const nsACString &aModuleName) {
|
||||
if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
for (auto &gModule : gModules) {
|
||||
struct Module *m = &gModule;
|
||||
|
|
@ -2028,8 +2130,9 @@ Connection::GetQuotaObjects(QuotaObject **aDatabaseQuotaObject,
|
|||
MOZ_ASSERT(aDatabaseQuotaObject);
|
||||
MOZ_ASSERT(aJournalQuotaObject);
|
||||
|
||||
if (!mDBConn) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
nsresult rv = connectionReady(SYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
sqlite3_file *file;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,15 @@ class Connection final : public mozIStorageConnection,
|
|||
NS_DECL_MOZISTORAGECONNECTION
|
||||
NS_DECL_NSIINTERFACEREQUESTOR
|
||||
|
||||
/**
|
||||
* Indicates if a database operation is synchronous or asynchronous.
|
||||
*
|
||||
* - Async operations may be called from any thread for all connections.
|
||||
* - Sync operations may be called from any thread for sync connections, and
|
||||
* from background threads for async connections.
|
||||
*/
|
||||
enum ConnectionOperation { ASYNCHRONOUS, SYNCHRONOUS };
|
||||
|
||||
/**
|
||||
* Structure used to describe user functions on the database connection.
|
||||
*/
|
||||
|
|
@ -61,11 +70,11 @@ class Connection final : public mozIStorageConnection,
|
|||
* connection.
|
||||
* @param aFlags
|
||||
* The flags to pass to sqlite3_open_v2.
|
||||
* @param aAsyncOnly
|
||||
* If |true|, the Connection only implements asynchronous interface:
|
||||
* - |mozIStorageAsyncConnection|;
|
||||
* If |false|, the result also implements synchronous interface:
|
||||
* - |mozIStorageConnection|.
|
||||
* @param aSupportedOperations
|
||||
* The operation types supported on this connection. All connections
|
||||
* implement both the async (`mozIStorageAsyncConnection`) and sync
|
||||
* (`mozIStorageConnection`) interfaces, but async connections may not
|
||||
* call sync operations from the main thread.
|
||||
* @param aIgnoreLockingMode
|
||||
* If |true|, ignore locks in force on the file. Only usable with
|
||||
* read-only connections. Defaults to false.
|
||||
|
|
@ -74,7 +83,8 @@ class Connection final : public mozIStorageConnection,
|
|||
* corrupt) or produce wrong results without any indication that has
|
||||
* happened.
|
||||
*/
|
||||
Connection(Service *aService, int aFlags, bool aAsyncOnly,
|
||||
Connection(Service *aService, int aFlags,
|
||||
ConnectionOperation aSupportedOperations,
|
||||
bool aIgnoreLockingMode = false);
|
||||
|
||||
/**
|
||||
|
|
@ -225,7 +235,17 @@ class Connection final : public mozIStorageConnection,
|
|||
nsresult commitTransactionInternal(sqlite3 *aNativeConnection);
|
||||
nsresult rollbackTransactionInternal(sqlite3 *aNativeConnection);
|
||||
|
||||
bool connectionReady();
|
||||
/**
|
||||
* Indicates if this database connection is ready and supports the given
|
||||
* operation.
|
||||
*
|
||||
* @param aOperationType
|
||||
* The operation type, sync or async.
|
||||
* @throws NS_ERROR_NOT_AVAILABLE if the operation isn't supported on this
|
||||
* connection.
|
||||
* @throws NS_ERROR_NOT_INITIALIZED if the connection isn't set up.
|
||||
*/
|
||||
nsresult connectionReady(ConnectionOperation aOperationType);
|
||||
|
||||
/**
|
||||
* Thread-aware version of connectionReady, results per caller's thread are:
|
||||
|
|
@ -418,10 +438,11 @@ class Connection final : public mozIStorageConnection,
|
|||
RefPtr<Service> mStorageService;
|
||||
|
||||
/**
|
||||
* If |false|, this instance supports synchronous operations
|
||||
* and it can be cast to |mozIStorageConnection|.
|
||||
* Indicates which operations are supported on this connection.
|
||||
*/
|
||||
const bool mAsyncOnly;
|
||||
const ConnectionOperation mSupportedOperations;
|
||||
|
||||
nsresult synchronousClose();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -119,7 +119,8 @@ Service::CollectReports(nsIHandleReportCallback *aHandleReport,
|
|||
// main-thread, like the DOM Cache and IndexedDB, and as such we must be
|
||||
// sure that we have a connection.
|
||||
MutexAutoLock lockedAsyncScope(conn->sharedAsyncExecutionMutex);
|
||||
if (!conn->connectionReady()) {
|
||||
nsresult rv = conn->connectionReady(Connection::ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +304,10 @@ void Service::minimizeMemory() {
|
|||
RefPtr<Connection> conn = connections[i];
|
||||
// For non-main-thread owning/opening threads, we may be racing against them
|
||||
// closing their connection or their thread. That's okay, see below.
|
||||
if (!conn->connectionReady()) continue;
|
||||
nsresult rv = conn->connectionReady(Connection::ASYNCHRONOUS);
|
||||
if (NS_FAILED(rv)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
|
||||
nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
|
||||
|
|
@ -453,7 +457,8 @@ Service::OpenSpecialDatabase(const char *aStorageKey,
|
|||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
RefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
|
||||
RefPtr<Connection> msc =
|
||||
new Connection(this, SQLITE_OPEN_READWRITE, Connection::SYNCHRONOUS);
|
||||
|
||||
rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
@ -604,7 +609,8 @@ Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
|
|||
}
|
||||
|
||||
// Create connection on this thread, but initialize it on its helper thread.
|
||||
RefPtr<Connection> msc = new Connection(this, flags, true, ignoreLockingMode);
|
||||
RefPtr<Connection> msc =
|
||||
new Connection(this, flags, Connection::ASYNCHRONOUS, ignoreLockingMode);
|
||||
nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
|
||||
MOZ_ASSERT(target,
|
||||
"Cannot initialize a connection that has been closed already");
|
||||
|
|
@ -623,7 +629,7 @@ Service::OpenDatabase(nsIFile *aDatabaseFile,
|
|||
// reasons.
|
||||
int flags =
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_CREATE;
|
||||
RefPtr<Connection> msc = new Connection(this, flags, false);
|
||||
RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
|
||||
|
||||
nsresult rv = msc->initialize(aDatabaseFile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
@ -641,7 +647,7 @@ Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
|
|||
// reasons.
|
||||
int flags =
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_CREATE;
|
||||
RefPtr<Connection> msc = new Connection(this, flags, false);
|
||||
RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
|
||||
|
||||
nsresult rv = msc->initialize(aDatabaseFile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
@ -659,7 +665,7 @@ Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL,
|
|||
// reasons.
|
||||
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
|
||||
SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
|
||||
RefPtr<Connection> msc = new Connection(this, flags, false);
|
||||
RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
|
||||
|
||||
nsresult rv = msc->initialize(aFileURL);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
|
|||
|
|
@ -675,6 +675,52 @@ Statement::GetString(uint32_t aIndex, nsAString &_value) {
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Statement::GetVariant(uint32_t aIndex, nsIVariant **_value) {
|
||||
if (!mDBStatement) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
ENSURE_INDEX_VALUE(aIndex, mResultColumnCount);
|
||||
|
||||
if (!mExecuting) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIVariant> variant;
|
||||
int type = ::sqlite3_column_type(mDBStatement, aIndex);
|
||||
switch (type) {
|
||||
case SQLITE_INTEGER:
|
||||
variant =
|
||||
new IntegerVariant(::sqlite3_column_int64(mDBStatement, aIndex));
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
variant = new FloatVariant(::sqlite3_column_double(mDBStatement, aIndex));
|
||||
break;
|
||||
case SQLITE_TEXT: {
|
||||
const char16_t *value = static_cast<const char16_t *>(
|
||||
::sqlite3_column_text16(mDBStatement, aIndex));
|
||||
nsDependentString str(value,
|
||||
::sqlite3_column_bytes16(mDBStatement, aIndex) / 2);
|
||||
variant = new TextVariant(str);
|
||||
break;
|
||||
}
|
||||
case SQLITE_NULL:
|
||||
variant = new NullVariant();
|
||||
break;
|
||||
case SQLITE_BLOB: {
|
||||
int size = ::sqlite3_column_bytes(mDBStatement, aIndex);
|
||||
const void *data = ::sqlite3_column_blob(mDBStatement, aIndex);
|
||||
variant = new BlobVariant(std::pair<const void *, int>(data, size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED);
|
||||
|
||||
variant.forget(_value);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Statement::GetBlob(uint32_t aIndex, uint32_t *_size, uint8_t **_blob) {
|
||||
if (!mDBStatement) return NS_ERROR_NOT_INITIALIZED;
|
||||
|
|
|
|||
13
storage/rust/Cargo.toml
Normal file
13
storage/rust/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "storage"
|
||||
description = "Rust bindings for mozStorage."
|
||||
version = "0.1.0"
|
||||
authors = ["Lina Cambridge <lina@yakshaving.ninja>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
nserror = { path = "../../xpcom/rust/nserror" }
|
||||
nsstring = { path = "../../xpcom/rust/nsstring" }
|
||||
storage_variant = { path = "../variant" }
|
||||
xpcom = { path = "../../xpcom/rust/xpcom" }
|
||||
281
storage/rust/src/lib.rs
Normal file
281
storage/rust/src/lib.rs
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
/* 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/. */
|
||||
|
||||
//! A Rust wrapper for mozStorage.
|
||||
//!
|
||||
//! mozStorage wraps the SQLite C API with support for XPCOM data structures,
|
||||
//! asynchronous statement execution, cleanup on shutdown, and connection
|
||||
//! cloning that propagates attached databases, pragmas, functions, and
|
||||
//! temporary entities. It also collects timing and memory usage stats for
|
||||
//! telemetry, and supports detailed statement logging. Additionally, mozStorage
|
||||
//! makes it possible to use the same connection handle from JS and native
|
||||
//! (C++ and Rust) code.
|
||||
//!
|
||||
//! Most mozStorage objects, like connections, statements, result rows,
|
||||
//! and variants, are thread-safe. Each connection manages a background
|
||||
//! thread that can be used to execute statements asynchronously, without
|
||||
//! blocking the main thread.
|
||||
//!
|
||||
//! This crate provides a thin wrapper to make mozStorage easier to use
|
||||
//! from Rust. It only wraps the synchronous API, so you can either manage
|
||||
//! the entire connection from a background thread, or use the `moz_task`
|
||||
//! crate to dispatch tasks to the connection's async thread. Executing
|
||||
//! synchronous statements on the main thread is not supported, and will
|
||||
//! assert in debug builds.
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::{ops::Deref, result};
|
||||
|
||||
use nserror::{nsresult, NS_ERROR_NO_INTERFACE};
|
||||
use nsstring::nsCString;
|
||||
use storage_variant::VariantType;
|
||||
use xpcom::{
|
||||
getter_addrefs,
|
||||
interfaces::{
|
||||
mozIStorageAsyncConnection, mozIStorageConnection, mozIStorageStatement, nsIEventTarget,
|
||||
nsIThread,
|
||||
},
|
||||
RefPtr, XpCom,
|
||||
};
|
||||
|
||||
pub type Result<T> = result::Result<T, nsresult>;
|
||||
|
||||
/// `Conn` wraps a `mozIStorageConnection`.
|
||||
#[derive(Clone)]
|
||||
pub struct Conn {
|
||||
handle: RefPtr<mozIStorageConnection>,
|
||||
}
|
||||
|
||||
// This is safe as long as our `mozIStorageConnection` is an instance of
|
||||
// `mozilla::storage::Connection`, which is atomically reference counted.
|
||||
unsafe impl Send for Conn {}
|
||||
unsafe impl Sync for Conn {}
|
||||
|
||||
impl Conn {
|
||||
/// Wraps a `mozIStorageConnection` in a `Conn`.
|
||||
#[inline]
|
||||
pub fn wrap(connection: RefPtr<mozIStorageConnection>) -> Conn {
|
||||
Conn { handle: connection }
|
||||
}
|
||||
|
||||
/// Returns the wrapped `mozIStorageConnection`.
|
||||
#[inline]
|
||||
pub fn connection(&self) -> &mozIStorageConnection {
|
||||
&self.handle
|
||||
}
|
||||
|
||||
/// Returns the async thread for this connection. This can be used
|
||||
/// with `moz_task` to run synchronous statements on the storage
|
||||
/// thread, without blocking the main thread.
|
||||
pub fn thread(&self) -> Result<RefPtr<nsIThread>> {
|
||||
let target = self.handle.get_interface::<nsIEventTarget>();
|
||||
target
|
||||
.and_then(|t| t.query_interface::<nsIThread>())
|
||||
.ok_or(NS_ERROR_NO_INTERFACE)
|
||||
}
|
||||
|
||||
/// Prepares a SQL statement. `query` should only contain one SQL statement.
|
||||
/// If `query` contains multiple statements, only the first will be prepared,
|
||||
/// and the rest will be ignored.
|
||||
pub fn prepare<Q: AsRef<str>>(&self, query: Q) -> Result<Statement> {
|
||||
let statement = getter_addrefs(|p| unsafe {
|
||||
self.handle
|
||||
.CreateStatement(&*nsCString::from(query.as_ref()), p)
|
||||
})?;
|
||||
Ok(Statement { handle: statement })
|
||||
}
|
||||
|
||||
/// Executes a SQL statement. `query` may contain one or more
|
||||
/// semicolon-separated SQL statements.
|
||||
pub fn exec<Q: AsRef<str>>(&self, query: Q) -> Result<()> {
|
||||
unsafe {
|
||||
self.handle
|
||||
.ExecuteSimpleSQL(&*nsCString::from(query.as_ref()))
|
||||
}
|
||||
.to_result()
|
||||
}
|
||||
|
||||
/// Opens a transaction with the default transaction behavior for this
|
||||
/// connection. The transaction should be committed when done. Uncommitted
|
||||
/// `Transaction`s will automatically roll back when they go out of scope.
|
||||
pub fn transaction(&mut self) -> Result<Transaction> {
|
||||
let behavior = self.get_default_transaction_behavior();
|
||||
Transaction::new(self, behavior)
|
||||
}
|
||||
|
||||
/// Opens a transaction with the requested behavior.
|
||||
pub fn transaction_with_behavior(
|
||||
&mut self,
|
||||
behavior: TransactionBehavior,
|
||||
) -> Result<Transaction> {
|
||||
Transaction::new(self, behavior)
|
||||
}
|
||||
|
||||
fn get_default_transaction_behavior(&self) -> TransactionBehavior {
|
||||
let mut typ = 0i32;
|
||||
let rv = unsafe { self.handle.GetDefaultTransactionType(&mut typ) };
|
||||
if rv.failed() {
|
||||
return TransactionBehavior::Deferred;
|
||||
}
|
||||
match typ as i64 {
|
||||
mozIStorageAsyncConnection::TRANSACTION_IMMEDIATE => TransactionBehavior::Immediate,
|
||||
mozIStorageAsyncConnection::TRANSACTION_EXCLUSIVE => TransactionBehavior::Exclusive,
|
||||
_ => TransactionBehavior::Deferred,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TransactionBehavior {
|
||||
Deferred,
|
||||
Immediate,
|
||||
Exclusive,
|
||||
}
|
||||
|
||||
pub struct Transaction<'c> {
|
||||
conn: &'c mut Conn,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl<'c> Transaction<'c> {
|
||||
/// Opens a transaction on `conn` with the given `behavior`.
|
||||
fn new(conn: &'c mut Conn, behavior: TransactionBehavior) -> Result<Transaction<'c>> {
|
||||
conn.exec(match behavior {
|
||||
TransactionBehavior::Deferred => "BEGIN DEFERRED",
|
||||
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
||||
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
|
||||
})?;
|
||||
Ok(Transaction { conn, active: true })
|
||||
}
|
||||
|
||||
/// Commits the transaction.
|
||||
pub fn commit(mut self) -> Result<()> {
|
||||
if self.active {
|
||||
self.conn.exec("COMMIT")?;
|
||||
self.active = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rolls the transaction back.
|
||||
pub fn rollback(mut self) -> Result<()> {
|
||||
self.abort()
|
||||
}
|
||||
|
||||
fn abort(&mut self) -> Result<()> {
|
||||
if self.active {
|
||||
self.conn.exec("ROLLBACK")?;
|
||||
self.active = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> Deref for Transaction<'c> {
|
||||
type Target = Conn;
|
||||
|
||||
fn deref(&self) -> &Conn {
|
||||
self.conn
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> Drop for Transaction<'c> {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.abort();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Statement {
|
||||
handle: RefPtr<mozIStorageStatement>,
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
/// Binds a parameter at the given `index` to the prepared statement.
|
||||
/// `value` is any type that can be converted into a `Variant`.
|
||||
pub fn bind_by_index<V: VariantType>(&mut self, index: u32, value: V) -> Result<()> {
|
||||
let variant = value.into_variant();
|
||||
unsafe { self.handle.BindByIndex(index as u32, variant.coerce()) }.to_result()
|
||||
}
|
||||
|
||||
/// Binds a parameter with the given `name` to the prepared statement.
|
||||
pub fn bind_by_name<N: AsRef<str>, V: VariantType>(&mut self, name: N, value: V) -> Result<()> {
|
||||
let variant = value.into_variant();
|
||||
unsafe {
|
||||
self.handle
|
||||
.BindByName(&*nsCString::from(name.as_ref()), variant.coerce())
|
||||
}
|
||||
.to_result()
|
||||
}
|
||||
|
||||
/// Executes the statement and returns the next row of data.
|
||||
pub fn step<'a>(&'a mut self) -> Result<Option<Step<'a>>> {
|
||||
let mut has_more = false;
|
||||
unsafe { self.handle.ExecuteStep(&mut has_more) }.to_result()?;
|
||||
Ok(if has_more { Some(Step(self)) } else { None })
|
||||
}
|
||||
|
||||
/// Executes the statement once, discards any data, and resets the
|
||||
/// statement.
|
||||
pub fn execute(&mut self) -> Result<()> {
|
||||
unsafe { self.handle.Execute() }.to_result()
|
||||
}
|
||||
|
||||
/// Resets the prepared statement so that it's ready to be executed
|
||||
/// again, and clears any bound parameters.
|
||||
pub fn reset(&mut self) -> Result<()> {
|
||||
unsafe { self.handle.Reset() }.to_result()
|
||||
}
|
||||
|
||||
fn get_column_index<N: AsRef<str>>(&self, name: N) -> Result<u32> {
|
||||
let mut index = 0u32;
|
||||
unsafe {
|
||||
self.handle
|
||||
.GetColumnIndex(&*nsCString::from(name.as_ref()), &mut index)
|
||||
}
|
||||
.to_result()
|
||||
.map(|_| index)
|
||||
}
|
||||
|
||||
fn get_variant<T: VariantType>(&self, index: u32) -> Result<T> {
|
||||
let variant = getter_addrefs(|p| unsafe { self.handle.GetVariant(index, p) })?;
|
||||
T::from_variant(variant.coerce())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Statement {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.handle.Finalize() };
|
||||
}
|
||||
}
|
||||
|
||||
/// A step is the next row in the result set for a statement.
|
||||
pub struct Step<'a>(&'a mut Statement);
|
||||
|
||||
impl<'a> Step<'a> {
|
||||
/// Returns the value of the column at `index` for the current row.
|
||||
pub fn get_by_index<'s, T: VariantType>(&'s self, index: u32) -> Result<T> {
|
||||
self.0.get_variant(index)
|
||||
}
|
||||
|
||||
/// A convenience wrapper that returns the default value for the column
|
||||
/// at `index` if `NULL`.
|
||||
pub fn get_by_index_or_default<'s, T: VariantType + Default>(&'s self, index: u32) -> T {
|
||||
self.get_by_index(index).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns the value of the column specified by `name` for the current row.
|
||||
pub fn get_by_name<'s, N: AsRef<str>, T: VariantType>(&'s self, name: N) -> Result<T> {
|
||||
let index = self.0.get_column_index(name)?;
|
||||
self.0.get_variant(index)
|
||||
}
|
||||
|
||||
/// Returns the default value for the column with the given `name`, or the
|
||||
/// default if the column is `NULL`.
|
||||
pub fn get_by_name_or_default<'s, N: AsRef<str>, T: VariantType + Default>(
|
||||
&'s self,
|
||||
name: N,
|
||||
) -> T {
|
||||
self.get_by_name(name).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
|
@ -314,7 +314,7 @@ async function standardAsyncTest(promisedDB, name, shouldInit = false) {
|
|||
|
||||
let adb = await promisedDB;
|
||||
Assert.ok(adb instanceof Ci.mozIStorageAsyncConnection);
|
||||
Assert.equal(false, adb instanceof Ci.mozIStorageConnection);
|
||||
Assert.ok(adb instanceof Ci.mozIStorageConnection);
|
||||
|
||||
if (shouldInit) {
|
||||
let stmt = adb.createAsyncStatement("CREATE TABLE test(name TEXT)");
|
||||
|
|
@ -440,7 +440,7 @@ add_task(async function test_clone_trivial_async() {
|
|||
info("AsyncClone connection");
|
||||
let clone = await asyncClone(db, true);
|
||||
Assert.ok(clone instanceof Ci.mozIStorageAsyncConnection);
|
||||
Assert.equal(false, clone instanceof Ci.mozIStorageConnection);
|
||||
Assert.ok(clone instanceof Ci.mozIStorageConnection);
|
||||
info("Close connection");
|
||||
await asyncClose(db);
|
||||
info("Close clone");
|
||||
|
|
@ -452,7 +452,7 @@ add_task(async function test_clone_no_optional_param_async() {
|
|||
info("Testing async cloning");
|
||||
let adb1 = await openAsyncDatabase(getTestDB(), null);
|
||||
Assert.ok(adb1 instanceof Ci.mozIStorageAsyncConnection);
|
||||
Assert.equal(false, adb1 instanceof Ci.mozIStorageConnection);
|
||||
Assert.ok(adb1 instanceof Ci.mozIStorageConnection);
|
||||
|
||||
info("Cloning database");
|
||||
|
||||
|
|
@ -460,7 +460,7 @@ add_task(async function test_clone_no_optional_param_async() {
|
|||
info("Testing that the cloned db is a mozIStorageAsyncConnection " +
|
||||
"and not a mozIStorageConnection");
|
||||
Assert.ok(adb2 instanceof Ci.mozIStorageAsyncConnection);
|
||||
Assert.equal(false, adb2 instanceof Ci.mozIStorageConnection);
|
||||
Assert.ok(adb2 instanceof Ci.mozIStorageConnection);
|
||||
|
||||
info("Inserting data into source db");
|
||||
let stmt = adb1.
|
||||
|
|
|
|||
|
|
@ -142,6 +142,21 @@ impl VariantType for () {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> VariantType for Option<T> where T: VariantType {
|
||||
fn into_variant(self) -> RefPtr<nsIVariant> {
|
||||
match self {
|
||||
Some(v) => v.into_variant(),
|
||||
None => ().into_variant(),
|
||||
}
|
||||
}
|
||||
fn from_variant(variant: &nsIVariant) -> Result<Self, nsresult> {
|
||||
match variant.get_data_type() {
|
||||
DATA_TYPE_EMPTY => Ok(None),
|
||||
_ => Ok(Some(VariantType::from_variant(variant)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variant!(bool, NS_NewStorageBooleanVariant, GetAsBool);
|
||||
variant!(i32, NS_NewStorageIntegerVariant, GetAsInt32);
|
||||
variant!(i64, NS_NewStorageIntegerVariant, GetAsInt64);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ jsrust_shared = { path = "../../../../js/src/rust/shared", optional = true }
|
|||
arrayvec = "0.4"
|
||||
cert_storage = { path = "../../../../security/manager/ssl/cert_storage" }
|
||||
bitsdownload = { path = "../../../components/bitsdownload", optional = true }
|
||||
storage = { path = "../../../../storage/rust" }
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.2"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ extern crate rsdparsa_capi;
|
|||
extern crate jsrust_shared;
|
||||
#[cfg(feature = "bitsdownload")]
|
||||
extern crate bitsdownload;
|
||||
extern crate storage;
|
||||
|
||||
extern crate arrayvec;
|
||||
|
||||
|
|
|
|||
|
|
@ -1042,7 +1042,7 @@ function cloneStorageConnection(options) {
|
|||
}
|
||||
|
||||
if (isClosed) {
|
||||
throw new Error("Sqlite.jsm has been shutdown. Cannot clone connection to: " + source.database.path);
|
||||
throw new Error("Sqlite.jsm has been shutdown. Cannot clone connection to: " + source.databaseFile.path);
|
||||
}
|
||||
|
||||
let openedOptions = {};
|
||||
|
|
@ -1107,7 +1107,7 @@ function wrapStorageConnection(options) {
|
|||
}
|
||||
|
||||
if (isClosed) {
|
||||
throw new Error("Sqlite.jsm has been shutdown. Cannot wrap connection to: " + connection.database.path);
|
||||
throw new Error("Sqlite.jsm has been shutdown. Cannot wrap connection to: " + connection.databaseFile.path);
|
||||
}
|
||||
|
||||
let identifier = getIdentifierByFileName(connection.databaseFile.leafName);
|
||||
|
|
@ -1213,6 +1213,28 @@ OpenedConnection.prototype = Object.freeze({
|
|||
TRANSACTION_IMMEDIATE: "IMMEDIATE",
|
||||
TRANSACTION_EXCLUSIVE: "EXCLUSIVE",
|
||||
|
||||
/**
|
||||
* Returns a handle to the underlying `mozIStorageAsyncConnection`. This is
|
||||
* ⚠️ **extremely unsafe** ⚠️ because `Sqlite.jsm` continues to manage the
|
||||
* connection's lifecycle, including transactions and shutdown blockers.
|
||||
* Misusing the raw connection can easily lead to data loss, memory leaks,
|
||||
* and errors.
|
||||
*
|
||||
* Consumers of the raw connection **must not** close or re-wrap it,
|
||||
* and should not run statements concurrently with `Sqlite.jsm`.
|
||||
*
|
||||
* It's _much_ safer to open a `mozIStorage{Async}Connection` yourself,
|
||||
* and access it from JavaScript via `Sqlite.wrapStorageConnection`.
|
||||
* `unsafeRawConnection` is an escape hatch for cases where you can't
|
||||
* do that.
|
||||
*
|
||||
* Please do _not_ add new uses of `unsafeRawConnection` without review
|
||||
* from a storage peer.
|
||||
*/
|
||||
get unsafeRawConnection() {
|
||||
return this._connectionData._dbConn;
|
||||
},
|
||||
|
||||
/**
|
||||
* The integer schema version of the database.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use {
|
|||
RefPtr,
|
||||
GetterAddrefs
|
||||
};
|
||||
use interfaces::nsISupports;
|
||||
use interfaces::{nsIInterfaceRequestor, nsISupports};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
|
|
@ -44,4 +44,20 @@ pub unsafe trait XpCom : RefCounted {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a `GetInterface` call on this object, returning `None` if the
|
||||
/// object doesn't implement `nsIInterfaceRequestor`, or can't access the
|
||||
/// interface `T`.
|
||||
fn get_interface<T: XpCom>(&self) -> Option<RefPtr<T>> {
|
||||
let ireq = self.query_interface::<nsIInterfaceRequestor>()?;
|
||||
|
||||
let mut ga = GetterAddrefs::<T>::new();
|
||||
unsafe {
|
||||
if ireq.GetInterface(&T::IID, ga.void_ptr()).succeeded() {
|
||||
ga.refptr()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue