michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "nsError.h" michael@0: #include "nsIMutableArray.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsIMemoryReporter.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIFileURL.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/CondVar.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #include "mozIStorageAggregateFunction.h" michael@0: #include "mozIStorageCompletionCallback.h" michael@0: #include "mozIStorageFunction.h" michael@0: michael@0: #include "mozStorageAsyncStatementExecution.h" michael@0: #include "mozStorageSQLFunctions.h" michael@0: #include "mozStorageConnection.h" michael@0: #include "mozStorageService.h" michael@0: #include "mozStorageStatement.h" michael@0: #include "mozStorageAsyncStatement.h" michael@0: #include "mozStorageArgValueArray.h" michael@0: #include "mozStoragePrivateHelpers.h" michael@0: #include "mozStorageStatementData.h" michael@0: #include "StorageBaseStatementInternal.h" michael@0: #include "SQLCollations.h" michael@0: #include "FileSystemModule.h" michael@0: #include "mozStorageHelper.h" michael@0: #include "GeckoProfiler.h" michael@0: michael@0: #include "prlog.h" michael@0: #include "prprf.h" michael@0: #include "nsProxyRelease.h" michael@0: #include michael@0: michael@0: #define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000 // 500 MiB michael@0: michael@0: // Maximum size of the pages cache per connection. michael@0: #define MAX_CACHE_SIZE_KIBIBYTES 2048 // 2 MiB michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gStorageLog = nullptr; michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: namespace storage { michael@0: michael@0: namespace { michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Variant Specialization Functions (variantToSQLiteT) michael@0: michael@0: int michael@0: sqlite3_T_int(sqlite3_context *aCtx, michael@0: int aValue) michael@0: { michael@0: ::sqlite3_result_int(aCtx, aValue); michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int michael@0: sqlite3_T_int64(sqlite3_context *aCtx, michael@0: sqlite3_int64 aValue) michael@0: { michael@0: ::sqlite3_result_int64(aCtx, aValue); michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int michael@0: sqlite3_T_double(sqlite3_context *aCtx, michael@0: double aValue) michael@0: { michael@0: ::sqlite3_result_double(aCtx, aValue); michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int michael@0: sqlite3_T_text(sqlite3_context *aCtx, michael@0: const nsCString &aValue) michael@0: { michael@0: ::sqlite3_result_text(aCtx, michael@0: aValue.get(), michael@0: aValue.Length(), michael@0: SQLITE_TRANSIENT); michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int michael@0: sqlite3_T_text16(sqlite3_context *aCtx, michael@0: const nsString &aValue) michael@0: { michael@0: ::sqlite3_result_text16(aCtx, michael@0: aValue.get(), michael@0: aValue.Length() * 2, // Number of bytes. michael@0: SQLITE_TRANSIENT); michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int michael@0: sqlite3_T_null(sqlite3_context *aCtx) michael@0: { michael@0: ::sqlite3_result_null(aCtx); michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int michael@0: sqlite3_T_blob(sqlite3_context *aCtx, michael@0: const void *aData, michael@0: int aSize) michael@0: { michael@0: ::sqlite3_result_blob(aCtx, aData, aSize, NS_Free); michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: #include "variantToSQLiteT_impl.h" michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Modules michael@0: michael@0: struct Module michael@0: { michael@0: const char* name; michael@0: int (*registerFunc)(sqlite3*, const char*); michael@0: }; michael@0: michael@0: Module gModules[] = { michael@0: { "filesystem", RegisterFileSystemModule } michael@0: }; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Local Functions michael@0: michael@0: #ifdef PR_LOGGING michael@0: void tracefunc (void *aClosure, const char *aStmt) michael@0: { michael@0: PR_LOG(gStorageLog, PR_LOG_DEBUG, ("sqlite3_trace on %p for '%s'", aClosure, michael@0: aStmt)); michael@0: } michael@0: #endif michael@0: michael@0: struct FFEArguments michael@0: { michael@0: nsISupports *target; michael@0: bool found; michael@0: }; michael@0: PLDHashOperator michael@0: findFunctionEnumerator(const nsACString &aKey, michael@0: Connection::FunctionInfo aData, michael@0: void *aUserArg) michael@0: { michael@0: FFEArguments *args = static_cast(aUserArg); michael@0: if (aData.function == args->target) { michael@0: args->found = true; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: copyFunctionEnumerator(const nsACString &aKey, michael@0: Connection::FunctionInfo aData, michael@0: void *aUserArg) michael@0: { michael@0: NS_PRECONDITION(aData.type == Connection::FunctionInfo::SIMPLE || michael@0: aData.type == Connection::FunctionInfo::AGGREGATE, michael@0: "Invalid function type!"); michael@0: michael@0: Connection *connection = static_cast(aUserArg); michael@0: if (aData.type == Connection::FunctionInfo::SIMPLE) { michael@0: mozIStorageFunction *function = michael@0: static_cast(aData.function.get()); michael@0: (void)connection->CreateFunction(aKey, aData.numArgs, function); michael@0: } michael@0: else { michael@0: mozIStorageAggregateFunction *function = michael@0: static_cast(aData.function.get()); michael@0: (void)connection->CreateAggregateFunction(aKey, aData.numArgs, function); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: basicFunctionHelper(sqlite3_context *aCtx, michael@0: int aArgc, michael@0: sqlite3_value **aArgv) michael@0: { michael@0: void *userData = ::sqlite3_user_data(aCtx); michael@0: michael@0: mozIStorageFunction *func = static_cast(userData); michael@0: michael@0: nsRefPtr arguments(new ArgValueArray(aArgc, aArgv)); michael@0: if (!arguments) michael@0: return; michael@0: michael@0: nsCOMPtr result; michael@0: if (NS_FAILED(func->OnFunctionCall(arguments, getter_AddRefs(result)))) { michael@0: NS_WARNING("User function returned error code!"); michael@0: ::sqlite3_result_error(aCtx, michael@0: "User function returned error code", michael@0: -1); michael@0: return; michael@0: } michael@0: int retcode = variantToSQLiteT(aCtx, result); michael@0: if (retcode == SQLITE_IGNORE) { michael@0: ::sqlite3_result_int(aCtx, SQLITE_IGNORE); michael@0: } else if (retcode != SQLITE_OK) { michael@0: NS_WARNING("User function returned invalid data type!"); michael@0: ::sqlite3_result_error(aCtx, michael@0: "User function returned invalid data type", michael@0: -1); michael@0: } michael@0: } michael@0: michael@0: void michael@0: aggregateFunctionStepHelper(sqlite3_context *aCtx, michael@0: int aArgc, michael@0: sqlite3_value **aArgv) michael@0: { michael@0: void *userData = ::sqlite3_user_data(aCtx); michael@0: mozIStorageAggregateFunction *func = michael@0: static_cast(userData); michael@0: michael@0: nsRefPtr arguments(new ArgValueArray(aArgc, aArgv)); michael@0: if (!arguments) michael@0: return; michael@0: michael@0: if (NS_FAILED(func->OnStep(arguments))) michael@0: NS_WARNING("User aggregate step function returned error code!"); michael@0: } michael@0: michael@0: void michael@0: aggregateFunctionFinalHelper(sqlite3_context *aCtx) michael@0: { michael@0: void *userData = ::sqlite3_user_data(aCtx); michael@0: mozIStorageAggregateFunction *func = michael@0: static_cast(userData); michael@0: michael@0: nsRefPtr result; michael@0: if (NS_FAILED(func->OnFinal(getter_AddRefs(result)))) { michael@0: NS_WARNING("User aggregate final function returned error code!"); michael@0: ::sqlite3_result_error(aCtx, michael@0: "User aggregate final function returned error code", michael@0: -1); michael@0: return; michael@0: } michael@0: michael@0: if (variantToSQLiteT(aCtx, result) != SQLITE_OK) { michael@0: NS_WARNING("User aggregate final function returned invalid data type!"); michael@0: ::sqlite3_result_error(aCtx, michael@0: "User aggregate final function returned invalid data type", michael@0: -1); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This code is heavily based on the sample at: michael@0: * http://www.sqlite.org/unlock_notify.html michael@0: */ michael@0: class UnlockNotification michael@0: { michael@0: public: michael@0: UnlockNotification() michael@0: : mMutex("UnlockNotification mMutex") michael@0: , mCondVar(mMutex, "UnlockNotification condVar") michael@0: , mSignaled(false) michael@0: { michael@0: } michael@0: michael@0: void Wait() michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: while (!mSignaled) { michael@0: (void)mCondVar.Wait(); michael@0: } michael@0: } michael@0: michael@0: void Signal() michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: mSignaled = true; michael@0: (void)mCondVar.Notify(); michael@0: } michael@0: michael@0: private: michael@0: Mutex mMutex; michael@0: CondVar mCondVar; michael@0: bool mSignaled; michael@0: }; michael@0: michael@0: void michael@0: UnlockNotifyCallback(void **aArgs, michael@0: int aArgsSize) michael@0: { michael@0: for (int i = 0; i < aArgsSize; i++) { michael@0: UnlockNotification *notification = michael@0: static_cast(aArgs[i]); michael@0: notification->Signal(); michael@0: } michael@0: } michael@0: michael@0: int michael@0: WaitForUnlockNotify(sqlite3* aDatabase) michael@0: { michael@0: UnlockNotification notification; michael@0: int srv = ::sqlite3_unlock_notify(aDatabase, UnlockNotifyCallback, michael@0: ¬ification); michael@0: MOZ_ASSERT(srv == SQLITE_LOCKED || srv == SQLITE_OK); michael@0: if (srv == SQLITE_OK) { michael@0: notification.Wait(); michael@0: } michael@0: michael@0: return srv; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Local Classes michael@0: michael@0: namespace { michael@0: michael@0: class AsyncCloseConnection MOZ_FINAL: public nsRunnable michael@0: { michael@0: public: michael@0: AsyncCloseConnection(Connection *aConnection, michael@0: sqlite3 *aNativeConnection, michael@0: nsIRunnable *aCallbackEvent, michael@0: already_AddRefed aAsyncExecutionThread) michael@0: : mConnection(aConnection) michael@0: , mNativeConnection(aNativeConnection) michael@0: , mCallbackEvent(aCallbackEvent) michael@0: , mAsyncExecutionThread(aAsyncExecutionThread) michael@0: { michael@0: } michael@0: michael@0: NS_METHOD Run() michael@0: { michael@0: #ifdef DEBUG michael@0: // This code is executed on the background thread michael@0: bool onAsyncThread = false; michael@0: (void)mAsyncExecutionThread->IsOnCurrentThread(&onAsyncThread); michael@0: MOZ_ASSERT(onAsyncThread); michael@0: #endif // DEBUG michael@0: michael@0: // Internal close. michael@0: (void)mConnection->internalClose(mNativeConnection); michael@0: michael@0: // Callback michael@0: if (mCallbackEvent) { michael@0: nsCOMPtr thread; michael@0: (void)NS_GetMainThread(getter_AddRefs(thread)); michael@0: (void)thread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: ~AsyncCloseConnection() { michael@0: nsCOMPtr thread; michael@0: (void)NS_GetMainThread(getter_AddRefs(thread)); michael@0: // Handle ambiguous nsISupports inheritance. michael@0: Connection *rawConnection = nullptr; michael@0: mConnection.swap(rawConnection); michael@0: (void)NS_ProxyRelease(thread, michael@0: NS_ISUPPORTS_CAST(mozIStorageConnection *, michael@0: rawConnection)); michael@0: (void)NS_ProxyRelease(thread, mCallbackEvent); michael@0: } michael@0: private: michael@0: nsRefPtr mConnection; michael@0: sqlite3 *mNativeConnection; michael@0: nsCOMPtr mCallbackEvent; michael@0: nsCOMPtr mAsyncExecutionThread; michael@0: }; michael@0: michael@0: /** michael@0: * An event used to initialize the clone of a connection. michael@0: * michael@0: * Must be executed on the clone's async execution thread. michael@0: */ michael@0: class AsyncInitializeClone MOZ_FINAL: public nsRunnable michael@0: { michael@0: public: michael@0: /** michael@0: * @param aConnection The connection being cloned. michael@0: * @param aClone The clone. michael@0: * @param aReadOnly If |true|, the clone is read only. michael@0: * @param aCallback A callback to trigger once initialization michael@0: * is complete. This event will be called on michael@0: * aClone->threadOpenedOn. michael@0: */ michael@0: AsyncInitializeClone(Connection* aConnection, michael@0: Connection* aClone, michael@0: const bool aReadOnly, michael@0: mozIStorageCompletionCallback* aCallback) michael@0: : mConnection(aConnection) michael@0: , mClone(aClone) michael@0: , mReadOnly(aReadOnly) michael@0: , mCallback(aCallback) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: } michael@0: michael@0: NS_IMETHOD Run() { michael@0: MOZ_ASSERT (NS_GetCurrentThread() == mClone->getAsyncExecutionTarget()); michael@0: michael@0: nsresult rv = mConnection->initializeClone(mClone, mReadOnly); michael@0: if (NS_FAILED(rv)) { michael@0: return Dispatch(rv, nullptr); michael@0: } michael@0: return Dispatch(NS_OK, michael@0: NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mClone)); michael@0: } michael@0: michael@0: private: michael@0: nsresult Dispatch(nsresult aResult, nsISupports* aValue) { michael@0: nsRefPtr event = new CallbackComplete(aResult, michael@0: aValue, michael@0: mCallback.forget()); michael@0: return mClone->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: ~AsyncInitializeClone() { michael@0: nsCOMPtr thread; michael@0: DebugOnly rv = NS_GetMainThread(getter_AddRefs(thread)); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: // Handle ambiguous nsISupports inheritance. michael@0: Connection *rawConnection = nullptr; michael@0: mConnection.swap(rawConnection); michael@0: (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *, michael@0: rawConnection)); michael@0: michael@0: Connection *rawClone = nullptr; michael@0: mClone.swap(rawClone); michael@0: (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *, michael@0: rawClone)); michael@0: michael@0: // Generally, the callback will be released by CallbackComplete. michael@0: // However, if for some reason Run() is not executed, we still michael@0: // need to ensure that it is released here. michael@0: mozIStorageCompletionCallback *rawCallback = nullptr; michael@0: mCallback.swap(rawCallback); michael@0: (void)NS_ProxyRelease(thread, rawCallback); michael@0: } michael@0: michael@0: nsRefPtr mConnection; michael@0: nsRefPtr mClone; michael@0: const bool mReadOnly; michael@0: nsCOMPtr mCallback; michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Connection michael@0: michael@0: Connection::Connection(Service *aService, michael@0: int aFlags, michael@0: bool aAsyncOnly) michael@0: : sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex") michael@0: , sharedDBMutex("Connection::sharedDBMutex") michael@0: , threadOpenedOn(do_GetCurrentThread()) michael@0: , mDBConn(nullptr) michael@0: , mAsyncExecutionThreadShuttingDown(false) michael@0: , mConnectionClosed(false) michael@0: , mTransactionInProgress(false) michael@0: , mProgressHandler(nullptr) michael@0: , mFlags(aFlags) michael@0: , mStorageService(aService) michael@0: , mAsyncOnly(aAsyncOnly) michael@0: { michael@0: mStorageService->registerConnection(this); michael@0: } michael@0: michael@0: Connection::~Connection() michael@0: { michael@0: (void)Close(); michael@0: michael@0: MOZ_ASSERT(!mAsyncExecutionThread, michael@0: "AsyncClose has not been invoked on this connection!"); michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(Connection) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(Connection) michael@0: NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) michael@0: NS_INTERFACE_MAP_ENTRY_CONDITIONAL(mozIStorageConnection, !mAsyncOnly) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageConnection) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: // This is identical to what NS_IMPL_RELEASE provides, but with the michael@0: // extra |1 == count| case. michael@0: NS_IMETHODIMP_(MozExternalRefCountType) Connection::Release(void) michael@0: { michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: nsrefcnt count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "Connection"); michael@0: if (1 == count) { michael@0: // If the refcount is 1, the single reference must be from michael@0: // gService->mConnections (in class |Service|). Which means we can michael@0: // unregister it safely. michael@0: mStorageService->unregisterConnection(this); michael@0: } else if (0 == count) { michael@0: mRefCnt = 1; /* stabilize */ michael@0: #if 0 /* enable this to find non-threadsafe destructors: */ michael@0: NS_ASSERT_OWNINGTHREAD(Connection); michael@0: #endif michael@0: delete (this); michael@0: return 0; michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: int32_t michael@0: Connection::getSqliteRuntimeStatus(int32_t aStatusOption, int32_t* aMaxValue) michael@0: { michael@0: MOZ_ASSERT(mDBConn, "A connection must exist at this point"); michael@0: int curr = 0, max = 0; michael@0: DebugOnly rc = ::sqlite3_db_status(mDBConn, aStatusOption, &curr, &max, 0); michael@0: MOZ_ASSERT(NS_SUCCEEDED(convertResultCode(rc))); michael@0: if (aMaxValue) michael@0: *aMaxValue = max; michael@0: return curr; michael@0: } michael@0: michael@0: nsIEventTarget * michael@0: Connection::getAsyncExecutionTarget() michael@0: { michael@0: MutexAutoLock lockedScope(sharedAsyncExecutionMutex); michael@0: michael@0: // If we are shutting down the asynchronous thread, don't hand out any more michael@0: // references to the thread. michael@0: if (mAsyncExecutionThreadShuttingDown) michael@0: return nullptr; michael@0: michael@0: if (!mAsyncExecutionThread) { michael@0: nsresult rv = ::NS_NewThread(getter_AddRefs(mAsyncExecutionThread)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to create async thread."); michael@0: return nullptr; michael@0: } michael@0: static nsThreadPoolNaming naming; michael@0: naming.SetThreadPoolName(NS_LITERAL_CSTRING("mozStorage"), michael@0: mAsyncExecutionThread); michael@0: } michael@0: michael@0: return mAsyncExecutionThread; michael@0: } michael@0: michael@0: nsresult michael@0: Connection::initialize() michael@0: { michael@0: NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); michael@0: PROFILER_LABEL("storage", "Connection::initialize"); michael@0: michael@0: // in memory database requested, sqlite uses a magic file name michael@0: int srv = ::sqlite3_open_v2(":memory:", &mDBConn, mFlags, nullptr); michael@0: if (srv != SQLITE_OK) { michael@0: mDBConn = nullptr; michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: return initializeInternal(nullptr); michael@0: } michael@0: michael@0: nsresult michael@0: Connection::initialize(nsIFile *aDatabaseFile) michael@0: { michael@0: NS_ASSERTION (aDatabaseFile, "Passed null file!"); michael@0: NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); michael@0: PROFILER_LABEL("storage", "Connection::initialize"); michael@0: michael@0: mDatabaseFile = aDatabaseFile; michael@0: michael@0: nsAutoString path; michael@0: nsresult rv = aDatabaseFile->GetPath(path); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, michael@0: mFlags, nullptr); michael@0: if (srv != SQLITE_OK) { michael@0: mDBConn = nullptr; michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: rv = initializeInternal(aDatabaseFile); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mDatabaseFile = aDatabaseFile; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Connection::initialize(nsIFileURL *aFileURL) michael@0: { michael@0: NS_ASSERTION (aFileURL, "Passed null file URL!"); michael@0: NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); michael@0: PROFILER_LABEL("storage", "Connection::initialize"); michael@0: michael@0: nsCOMPtr databaseFile; michael@0: nsresult rv = aFileURL->GetFile(getter_AddRefs(databaseFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString spec; michael@0: rv = aFileURL->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int srv = ::sqlite3_open_v2(spec.get(), &mDBConn, mFlags, nullptr); michael@0: if (srv != SQLITE_OK) { michael@0: mDBConn = nullptr; michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: rv = initializeInternal(databaseFile); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mFileURL = aFileURL; michael@0: mDatabaseFile = databaseFile; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: Connection::initializeInternal(nsIFile* aDatabaseFile) michael@0: { michael@0: // Properly wrap the database handle's mutex. michael@0: sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn)); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (!gStorageLog) michael@0: gStorageLog = ::PR_NewLogModule("mozStorage"); michael@0: michael@0: ::sqlite3_trace(mDBConn, tracefunc, this); michael@0: michael@0: nsAutoCString leafName(":memory"); michael@0: if (aDatabaseFile) michael@0: (void)aDatabaseFile->GetNativeLeafName(leafName); michael@0: PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Opening connection to '%s' (%p)", michael@0: leafName.get(), this)); michael@0: #endif michael@0: michael@0: int64_t pageSize = Service::getDefaultPageSize(); michael@0: michael@0: // Set page_size to the preferred default value. This is effective only if michael@0: // the database has just been created, otherwise, if the database does not michael@0: // use WAL journal mode, a VACUUM operation will updated its page_size. michael@0: nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR michael@0: "PRAGMA page_size = "); michael@0: pageSizeQuery.AppendInt(pageSize); michael@0: nsresult rv = ExecuteSimpleSQL(pageSizeQuery); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Setting the cache_size forces the database open, verifying if it is valid michael@0: // or corrupt. So this is executed regardless it being actually needed. michael@0: // The cache_size is calculated from the actual page_size, to save memory. michael@0: nsAutoCString cacheSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR michael@0: "PRAGMA cache_size = "); michael@0: cacheSizeQuery.AppendInt(-MAX_CACHE_SIZE_KIBIBYTES); michael@0: int srv = executeSql(mDBConn, cacheSizeQuery.get()); michael@0: if (srv != SQLITE_OK) { michael@0: ::sqlite3_close(mDBConn); michael@0: mDBConn = nullptr; michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: // Register our built-in SQL functions. michael@0: srv = registerFunctions(mDBConn); michael@0: if (srv != SQLITE_OK) { michael@0: ::sqlite3_close(mDBConn); michael@0: mDBConn = nullptr; michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: // Register our built-in SQL collating sequences. michael@0: srv = registerCollations(mDBConn, mStorageService); michael@0: if (srv != SQLITE_OK) { michael@0: ::sqlite3_close(mDBConn); michael@0: mDBConn = nullptr; michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: // Set the synchronous PRAGMA, according to the preference. michael@0: switch (Service::getSynchronousPref()) { michael@0: case 2: michael@0: (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "PRAGMA synchronous = FULL;")); michael@0: break; michael@0: case 0: michael@0: (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "PRAGMA synchronous = OFF;")); michael@0: break; michael@0: case 1: michael@0: default: michael@0: (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "PRAGMA synchronous = NORMAL;")); michael@0: break; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Connection::databaseElementExists(enum DatabaseElementType aElementType, michael@0: const nsACString &aElementName, michael@0: bool *_exists) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // When constructing the query, make sure to SELECT the correct db's sqlite_master michael@0: // if the user is prefixing the element with a specific db. ex: sample.test michael@0: nsCString query("SELECT name FROM (SELECT * FROM "); michael@0: nsDependentCSubstring element; michael@0: int32_t ind = aElementName.FindChar('.'); michael@0: if (ind == kNotFound) { michael@0: element.Assign(aElementName); michael@0: } michael@0: else { michael@0: nsDependentCSubstring db(Substring(aElementName, 0, ind + 1)); michael@0: element.Assign(Substring(aElementName, ind + 1, aElementName.Length())); michael@0: query.Append(db); michael@0: } michael@0: query.Append("sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = '"); michael@0: michael@0: switch (aElementType) { michael@0: case INDEX: michael@0: query.Append("index"); michael@0: break; michael@0: case TABLE: michael@0: query.Append("table"); michael@0: break; michael@0: } michael@0: query.Append("' AND name ='"); michael@0: query.Append(element); michael@0: query.Append("'"); michael@0: michael@0: sqlite3_stmt *stmt; michael@0: int srv = prepareStatement(mDBConn, query, &stmt); michael@0: if (srv != SQLITE_OK) michael@0: return convertResultCode(srv); michael@0: michael@0: srv = stepStatement(mDBConn, stmt); michael@0: // we just care about the return value from step michael@0: (void)::sqlite3_finalize(stmt); michael@0: michael@0: if (srv == SQLITE_ROW) { michael@0: *_exists = true; michael@0: return NS_OK; michael@0: } michael@0: if (srv == SQLITE_DONE) { michael@0: *_exists = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: bool michael@0: Connection::findFunctionByInstance(nsISupports *aInstance) michael@0: { michael@0: sharedDBMutex.assertCurrentThreadOwns(); michael@0: FFEArguments args = { aInstance, false }; michael@0: (void)mFunctions.EnumerateRead(findFunctionEnumerator, &args); michael@0: return args.found; michael@0: } michael@0: michael@0: /* static */ int michael@0: Connection::sProgressHelper(void *aArg) michael@0: { michael@0: Connection *_this = static_cast(aArg); michael@0: return _this->progressHandler(); michael@0: } michael@0: michael@0: int michael@0: Connection::progressHandler() michael@0: { michael@0: sharedDBMutex.assertCurrentThreadOwns(); michael@0: if (mProgressHandler) { michael@0: bool result; michael@0: nsresult rv = mProgressHandler->OnProgress(this, &result); michael@0: if (NS_FAILED(rv)) return 0; // Don't break request michael@0: return result ? 1 : 0; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: nsresult michael@0: Connection::setClosedState() michael@0: { michael@0: // Ensure that we are on the correct thread to close the database. michael@0: bool onOpenedThread; michael@0: nsresult rv = threadOpenedOn->IsOnCurrentThread(&onOpenedThread); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!onOpenedThread) { michael@0: NS_ERROR("Must close the database on the thread that you opened it with!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Flag that we are shutting down the async thread, so that michael@0: // getAsyncExecutionTarget knows not to expose/create the async thread. michael@0: { michael@0: MutexAutoLock lockedScope(sharedAsyncExecutionMutex); michael@0: NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED); michael@0: mAsyncExecutionThreadShuttingDown = true; michael@0: } michael@0: michael@0: // Set the property to null before closing the connection, otherwise the other michael@0: // functions in the module may try to use the connection after it is closed. michael@0: mDBConn = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: Connection::connectionReady() michael@0: { michael@0: return mDBConn != nullptr; michael@0: } michael@0: michael@0: bool michael@0: Connection::isClosing() michael@0: { michael@0: bool shuttingDown = false; michael@0: { michael@0: MutexAutoLock lockedScope(sharedAsyncExecutionMutex); michael@0: shuttingDown = mAsyncExecutionThreadShuttingDown; michael@0: } michael@0: return shuttingDown && !isClosed(); michael@0: } michael@0: michael@0: bool michael@0: Connection::isClosed() michael@0: { michael@0: MutexAutoLock lockedScope(sharedAsyncExecutionMutex); michael@0: return mConnectionClosed; michael@0: } michael@0: michael@0: nsresult michael@0: Connection::internalClose(sqlite3 *aNativeConnection) michael@0: { michael@0: // Sanity checks to make sure we are in the proper state before calling this. michael@0: MOZ_ASSERT(aNativeConnection, "Database connection is invalid!"); michael@0: MOZ_ASSERT(!isClosed()); michael@0: michael@0: #ifdef DEBUG michael@0: { // Make sure we have marked our async thread as shutting down. michael@0: MutexAutoLock lockedScope(sharedAsyncExecutionMutex); michael@0: NS_ASSERTION(mAsyncExecutionThreadShuttingDown, michael@0: "Did not call setClosedState!"); michael@0: } michael@0: #endif // DEBUG michael@0: michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString leafName(":memory"); michael@0: if (mDatabaseFile) michael@0: (void)mDatabaseFile->GetNativeLeafName(leafName); michael@0: PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Closing connection to '%s'", michael@0: leafName.get())); michael@0: #endif michael@0: michael@0: // At this stage, we may still have statements that need to be michael@0: // finalized. Attempt to close the database connection. This will michael@0: // always disconnect any virtual tables and cleanly finalize their michael@0: // internal statements. Once this is done, closing may fail due to michael@0: // unfinalized client statements, in which case we need to finalize michael@0: // these statements and close again. michael@0: { michael@0: MutexAutoLock lockedScope(sharedAsyncExecutionMutex); michael@0: mConnectionClosed = true; michael@0: } michael@0: int srv = sqlite3_close(aNativeConnection); michael@0: michael@0: if (srv == SQLITE_BUSY) { michael@0: // We still have non-finalized statements. Finalize them. michael@0: michael@0: sqlite3_stmt *stmt = nullptr; michael@0: while ((stmt = ::sqlite3_next_stmt(aNativeConnection, stmt))) { michael@0: PR_LOG(gStorageLog, PR_LOG_NOTICE, michael@0: ("Auto-finalizing SQL statement '%s' (%x)", michael@0: ::sqlite3_sql(stmt), michael@0: stmt)); michael@0: michael@0: #ifdef DEBUG michael@0: char *msg = ::PR_smprintf("SQL statement '%s' (%x) should have been finalized before closing the connection", michael@0: ::sqlite3_sql(stmt), michael@0: stmt); michael@0: NS_WARNING(msg); michael@0: ::PR_smprintf_free(msg); michael@0: #endif // DEBUG michael@0: michael@0: srv = ::sqlite3_finalize(stmt); michael@0: michael@0: #ifdef DEBUG michael@0: if (srv != SQLITE_OK) { michael@0: char *msg = ::PR_smprintf("Could not finalize SQL statement '%s' (%x)", michael@0: ::sqlite3_sql(stmt), michael@0: stmt); michael@0: NS_WARNING(msg); michael@0: ::PR_smprintf_free(msg); michael@0: } michael@0: #endif // DEBUG michael@0: michael@0: // Ensure that the loop continues properly, whether closing has succeeded michael@0: // or not. michael@0: if (srv == SQLITE_OK) { michael@0: stmt = nullptr; michael@0: } michael@0: } michael@0: michael@0: // Now that all statements have been finalized, we michael@0: // should be able to close. michael@0: srv = ::sqlite3_close(aNativeConnection); michael@0: michael@0: } michael@0: michael@0: if (srv != SQLITE_OK) { michael@0: MOZ_ASSERT(srv == SQLITE_OK, michael@0: "sqlite3_close failed. There are probably outstanding statements that are listed above!"); michael@0: } michael@0: michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: nsCString michael@0: Connection::getFilename() michael@0: { michael@0: nsCString leafname(":memory:"); michael@0: if (mDatabaseFile) { michael@0: (void)mDatabaseFile->GetNativeLeafName(leafname); michael@0: } michael@0: return leafname; michael@0: } michael@0: michael@0: int michael@0: Connection::stepStatement(sqlite3 *aNativeConnection, sqlite3_stmt *aStatement) michael@0: { michael@0: MOZ_ASSERT(aStatement); michael@0: bool checkedMainThread = false; michael@0: TimeStamp startTime = TimeStamp::Now(); michael@0: michael@0: // The connection may have been closed if the executing statement has been michael@0: // created and cached after a call to asyncClose() but before the actual michael@0: // sqlite3_close(). This usually happens when other tasks using cached michael@0: // statements are asynchronously scheduled for execution and any of them ends michael@0: // up after asyncClose. See bug 728653 for details. michael@0: if (isClosed()) michael@0: return SQLITE_MISUSE; michael@0: michael@0: (void)::sqlite3_extended_result_codes(aNativeConnection, 1); michael@0: michael@0: int srv; michael@0: while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) { michael@0: if (!checkedMainThread) { michael@0: checkedMainThread = true; michael@0: if (::NS_IsMainThread()) { michael@0: NS_WARNING("We won't allow blocking on the main thread!"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: srv = WaitForUnlockNotify(aNativeConnection); michael@0: if (srv != SQLITE_OK) { michael@0: break; michael@0: } michael@0: michael@0: ::sqlite3_reset(aStatement); michael@0: } michael@0: michael@0: // Report very slow SQL statements to Telemetry michael@0: TimeDuration duration = TimeStamp::Now() - startTime; michael@0: const uint32_t threshold = michael@0: NS_IsMainThread() ? Telemetry::kSlowSQLThresholdForMainThread michael@0: : Telemetry::kSlowSQLThresholdForHelperThreads; michael@0: if (duration.ToMilliseconds() >= threshold) { michael@0: nsDependentCString statementString(::sqlite3_sql(aStatement)); michael@0: Telemetry::RecordSlowSQLStatement(statementString, getFilename(), michael@0: duration.ToMilliseconds()); michael@0: } michael@0: michael@0: (void)::sqlite3_extended_result_codes(aNativeConnection, 0); michael@0: // Drop off the extended result bits of the result code. michael@0: return srv & 0xFF; michael@0: } michael@0: michael@0: int michael@0: Connection::prepareStatement(sqlite3 *aNativeConnection, const nsCString &aSQL, michael@0: sqlite3_stmt **_stmt) michael@0: { michael@0: // We should not even try to prepare statements after the connection has michael@0: // been closed. michael@0: if (isClosed()) michael@0: return SQLITE_MISUSE; michael@0: michael@0: bool checkedMainThread = false; michael@0: michael@0: (void)::sqlite3_extended_result_codes(aNativeConnection, 1); michael@0: michael@0: int srv; michael@0: while((srv = ::sqlite3_prepare_v2(aNativeConnection, michael@0: aSQL.get(), michael@0: -1, michael@0: _stmt, michael@0: nullptr)) == SQLITE_LOCKED_SHAREDCACHE) { michael@0: if (!checkedMainThread) { michael@0: checkedMainThread = true; michael@0: if (::NS_IsMainThread()) { michael@0: NS_WARNING("We won't allow blocking on the main thread!"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: srv = WaitForUnlockNotify(aNativeConnection); michael@0: if (srv != SQLITE_OK) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (srv != SQLITE_OK) { michael@0: nsCString warnMsg; michael@0: warnMsg.AppendLiteral("The SQL statement '"); michael@0: warnMsg.Append(aSQL); michael@0: warnMsg.AppendLiteral("' could not be compiled due to an error: "); michael@0: warnMsg.Append(::sqlite3_errmsg(aNativeConnection)); michael@0: michael@0: #ifdef DEBUG michael@0: NS_WARNING(warnMsg.get()); michael@0: #endif michael@0: PR_LOG(gStorageLog, PR_LOG_ERROR, ("%s", warnMsg.get())); michael@0: } michael@0: michael@0: (void)::sqlite3_extended_result_codes(aNativeConnection, 0); michael@0: // Drop off the extended result bits of the result code. michael@0: int rc = srv & 0xFF; michael@0: // sqlite will return OK on a comment only string and set _stmt to nullptr. michael@0: // The callers of this function are used to only checking the return value, michael@0: // so it is safer to return an error code. michael@0: if (rc == SQLITE_OK && *_stmt == nullptr) { michael@0: return SQLITE_MISUSE; michael@0: } michael@0: michael@0: return rc; michael@0: } michael@0: michael@0: michael@0: int michael@0: Connection::executeSql(sqlite3 *aNativeConnection, const char *aSqlString) michael@0: { michael@0: if (isClosed()) michael@0: return SQLITE_MISUSE; michael@0: michael@0: TimeStamp startTime = TimeStamp::Now(); michael@0: int srv = ::sqlite3_exec(aNativeConnection, aSqlString, nullptr, nullptr, michael@0: nullptr); michael@0: michael@0: // Report very slow SQL statements to Telemetry michael@0: TimeDuration duration = TimeStamp::Now() - startTime; michael@0: const uint32_t threshold = michael@0: NS_IsMainThread() ? Telemetry::kSlowSQLThresholdForMainThread michael@0: : Telemetry::kSlowSQLThresholdForHelperThreads; michael@0: if (duration.ToMilliseconds() >= threshold) { michael@0: nsDependentCString statementString(aSqlString); michael@0: Telemetry::RecordSlowSQLStatement(statementString, getFilename(), michael@0: duration.ToMilliseconds()); michael@0: } michael@0: michael@0: return srv; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// nsIInterfaceRequestor michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetInterface(const nsIID &aIID, michael@0: void **_result) michael@0: { michael@0: if (aIID.Equals(NS_GET_IID(nsIEventTarget))) { michael@0: nsIEventTarget *background = getAsyncExecutionTarget(); michael@0: NS_IF_ADDREF(background); michael@0: *_result = background; michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageConnection michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::Close() michael@0: { michael@0: if (!mDBConn) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: { // Make sure we have not executed any asynchronous statements. michael@0: // If this fails, the mDBConn will be left open, resulting in a leak. michael@0: // Ideally we'd schedule some code to destroy the mDBConn once all its michael@0: // async statements have finished executing; see bug 704030. michael@0: MutexAutoLock lockedScope(sharedAsyncExecutionMutex); michael@0: bool asyncCloseWasCalled = !mAsyncExecutionThread; michael@0: NS_ENSURE_TRUE(asyncCloseWasCalled, NS_ERROR_UNEXPECTED); michael@0: } michael@0: michael@0: // setClosedState nullifies our connection pointer, so we take a raw pointer michael@0: // off it, to pass it through the close procedure. michael@0: sqlite3 *nativeConn = mDBConn; michael@0: nsresult rv = setClosedState(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return internalClose(nativeConn); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::AsyncClose(mozIStorageCompletionCallback *aCallback) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: } michael@0: if (!mDBConn) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: nsIEventTarget *asyncThread = getAsyncExecutionTarget(); michael@0: NS_ENSURE_TRUE(asyncThread, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // setClosedState nullifies our connection pointer, so we take a raw pointer michael@0: // off it, to pass it through the close procedure. michael@0: sqlite3 *nativeConn = mDBConn; michael@0: nsresult rv = setClosedState(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Create our callback event if we were given a callback. michael@0: nsCOMPtr completeEvent; michael@0: if (aCallback) { michael@0: completeEvent = newCompletionEvent(aCallback); michael@0: } michael@0: michael@0: // Create and dispatch our close event to the background thread. michael@0: nsCOMPtr closeEvent; michael@0: { michael@0: // We need to lock because we're modifying mAsyncExecutionThread michael@0: MutexAutoLock lockedScope(sharedAsyncExecutionMutex); michael@0: closeEvent = new AsyncCloseConnection(this, michael@0: nativeConn, michael@0: completeEvent, michael@0: mAsyncExecutionThread.forget()); michael@0: } michael@0: michael@0: rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::AsyncClone(bool aReadOnly, michael@0: mozIStorageCompletionCallback *aCallback) michael@0: { michael@0: PROFILER_LABEL("storage", "Connection::Clone"); michael@0: if (!NS_IsMainThread()) { michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: } michael@0: if (!mDBConn) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: if (!mDatabaseFile) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: int flags = mFlags; michael@0: if (aReadOnly) { michael@0: // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY. michael@0: flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY; michael@0: // Turn off SQLITE_OPEN_CREATE. michael@0: flags = (~SQLITE_OPEN_CREATE & flags); michael@0: } michael@0: michael@0: nsRefPtr clone = new Connection(mStorageService, flags, michael@0: mAsyncOnly); michael@0: michael@0: nsRefPtr initEvent = michael@0: new AsyncInitializeClone(this, clone, aReadOnly, aCallback); michael@0: nsCOMPtr target = clone->getAsyncExecutionTarget(); michael@0: if (!target) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: return target->Dispatch(initEvent, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: nsresult michael@0: Connection::initializeClone(Connection* aClone, bool aReadOnly) michael@0: { michael@0: nsresult rv = mFileURL ? aClone->initialize(mFileURL) michael@0: : aClone->initialize(mDatabaseFile); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // Copy over pragmas from the original connection. michael@0: static const char * pragmas[] = { michael@0: "cache_size", michael@0: "temp_store", michael@0: "foreign_keys", michael@0: "journal_size_limit", michael@0: "synchronous", michael@0: "wal_autocheckpoint", michael@0: }; michael@0: for (uint32_t i = 0; i < ArrayLength(pragmas); ++i) { michael@0: // Read-only connections just need cache_size and temp_store pragmas. michael@0: if (aReadOnly && ::strcmp(pragmas[i], "cache_size") != 0 && michael@0: ::strcmp(pragmas[i], "temp_store") != 0) { michael@0: continue; michael@0: } michael@0: michael@0: nsAutoCString pragmaQuery("PRAGMA "); michael@0: pragmaQuery.Append(pragmas[i]); michael@0: nsCOMPtr stmt; michael@0: rv = CreateStatement(pragmaQuery, getter_AddRefs(stmt)); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: bool hasResult = false; michael@0: if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { michael@0: pragmaQuery.AppendLiteral(" = "); michael@0: pragmaQuery.AppendInt(stmt->AsInt32(0)); michael@0: rv = aClone->ExecuteSimpleSQL(pragmaQuery); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } michael@0: } michael@0: michael@0: // Copy any functions that have been added to this connection. michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: (void)mFunctions.EnumerateRead(copyFunctionEnumerator, aClone); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::Clone(bool aReadOnly, michael@0: mozIStorageConnection **_connection) michael@0: { michael@0: MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread()); michael@0: michael@0: PROFILER_LABEL("storage", "Connection::Clone"); michael@0: if (!mDBConn) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: if (!mDatabaseFile) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: int flags = mFlags; michael@0: if (aReadOnly) { michael@0: // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY. michael@0: flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY; michael@0: // Turn off SQLITE_OPEN_CREATE. michael@0: flags = (~SQLITE_OPEN_CREATE & flags); michael@0: } michael@0: michael@0: nsRefPtr clone = new Connection(mStorageService, flags, michael@0: mAsyncOnly); michael@0: michael@0: nsresult rv = initializeClone(clone, aReadOnly); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: NS_IF_ADDREF(*_connection = clone); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetDefaultPageSize(int32_t *_defaultPageSize) michael@0: { michael@0: *_defaultPageSize = Service::getDefaultPageSize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetConnectionReady(bool *_ready) michael@0: { michael@0: *_ready = connectionReady(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetDatabaseFile(nsIFile **_dbFile) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: NS_IF_ADDREF(*_dbFile = mDatabaseFile); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetLastInsertRowID(int64_t *_id) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: sqlite_int64 id = ::sqlite3_last_insert_rowid(mDBConn); michael@0: *_id = id; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetAffectedRows(int32_t *_rows) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: *_rows = ::sqlite3_changes(mDBConn); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetLastError(int32_t *_error) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: *_error = ::sqlite3_errcode(mDBConn); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetLastErrorString(nsACString &_errorString) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: const char *serr = ::sqlite3_errmsg(mDBConn); michael@0: _errorString.Assign(serr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetSchemaVersion(int32_t *_version) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: nsCOMPtr stmt; michael@0: (void)CreateStatement(NS_LITERAL_CSTRING("PRAGMA user_version"), michael@0: getter_AddRefs(stmt)); michael@0: NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: *_version = 0; michael@0: bool hasResult; michael@0: if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) michael@0: *_version = stmt->AsInt32(0); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::SetSchemaVersion(int32_t aVersion) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: nsAutoCString stmt(NS_LITERAL_CSTRING("PRAGMA user_version = ")); michael@0: stmt.AppendInt(aVersion); michael@0: michael@0: return ExecuteSimpleSQL(stmt); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::CreateStatement(const nsACString &aSQLStatement, michael@0: mozIStorageStatement **_stmt) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(_stmt); michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: nsRefPtr statement(new Statement()); michael@0: NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsresult rv = statement->initialize(this, mDBConn, aSQLStatement); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: Statement *rawPtr; michael@0: statement.forget(&rawPtr); michael@0: *_stmt = rawPtr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::CreateAsyncStatement(const nsACString &aSQLStatement, michael@0: mozIStorageAsyncStatement **_stmt) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(_stmt); michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: nsRefPtr statement(new AsyncStatement()); michael@0: NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsresult rv = statement->initialize(this, mDBConn, aSQLStatement); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: AsyncStatement *rawPtr; michael@0: statement.forget(&rawPtr); michael@0: *_stmt = rawPtr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: int srv = executeSql(mDBConn, PromiseFlatCString(aSQLStatement).get()); michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::ExecuteAsync(mozIStorageBaseStatement **aStatements, michael@0: uint32_t aNumStatements, michael@0: mozIStorageStatementCallback *aCallback, michael@0: mozIStoragePendingStatement **_handle) michael@0: { michael@0: nsTArray stmts(aNumStatements); michael@0: for (uint32_t i = 0; i < aNumStatements; i++) { michael@0: nsCOMPtr stmt = michael@0: do_QueryInterface(aStatements[i]); michael@0: michael@0: // Obtain our StatementData. michael@0: StatementData data; michael@0: nsresult rv = stmt->getAsynchronousStatementData(data); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(stmt->getOwner() == this, michael@0: "Statement must be from this database connection!"); michael@0: michael@0: // Now append it to our array. michael@0: NS_ENSURE_TRUE(stmts.AppendElement(data), NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: michael@0: // Dispatch to the background michael@0: return AsyncExecuteStatements::execute(stmts, this, mDBConn, aCallback, michael@0: _handle); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::ExecuteSimpleSQLAsync(const nsACString &aSQLStatement, michael@0: mozIStorageStatementCallback *aCallback, michael@0: mozIStoragePendingStatement **_handle) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: } michael@0: michael@0: nsCOMPtr stmt; michael@0: nsresult rv = CreateAsyncStatement(aSQLStatement, getter_AddRefs(stmt)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr pendingStatement; michael@0: rv = stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: NS_ADDREF(*_handle = pendingStatement); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::TableExists(const nsACString &aTableName, michael@0: bool *_exists) michael@0: { michael@0: return databaseElementExists(TABLE, aTableName, _exists); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::IndexExists(const nsACString &aIndexName, michael@0: bool* _exists) michael@0: { michael@0: return databaseElementExists(INDEX, aIndexName, _exists); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::GetTransactionInProgress(bool *_inProgress) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: *_inProgress = mTransactionInProgress; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::BeginTransaction() michael@0: { michael@0: return BeginTransactionAs(mozIStorageConnection::TRANSACTION_DEFERRED); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::BeginTransactionAs(int32_t aTransactionType) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: return beginTransactionInternal(mDBConn, aTransactionType); michael@0: } michael@0: michael@0: nsresult michael@0: Connection::beginTransactionInternal(sqlite3 *aNativeConnection, michael@0: int32_t aTransactionType) michael@0: { michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: if (mTransactionInProgress) michael@0: return NS_ERROR_FAILURE; michael@0: nsresult rv; michael@0: switch(aTransactionType) { michael@0: case TRANSACTION_DEFERRED: michael@0: rv = convertResultCode(executeSql(aNativeConnection, "BEGIN DEFERRED")); michael@0: break; michael@0: case TRANSACTION_IMMEDIATE: michael@0: rv = convertResultCode(executeSql(aNativeConnection, "BEGIN IMMEDIATE")); michael@0: break; michael@0: case TRANSACTION_EXCLUSIVE: michael@0: rv = convertResultCode(executeSql(aNativeConnection, "BEGIN EXCLUSIVE")); michael@0: break; michael@0: default: michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: if (NS_SUCCEEDED(rv)) michael@0: mTransactionInProgress = true; michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::CommitTransaction() michael@0: { michael@0: if (!mDBConn) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: return commitTransactionInternal(mDBConn); michael@0: } michael@0: michael@0: nsresult michael@0: Connection::commitTransactionInternal(sqlite3 *aNativeConnection) michael@0: { michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: if (!mTransactionInProgress) michael@0: return NS_ERROR_UNEXPECTED; michael@0: nsresult rv = michael@0: convertResultCode(executeSql(aNativeConnection, "COMMIT TRANSACTION")); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mTransactionInProgress = false; michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::RollbackTransaction() michael@0: { michael@0: if (!mDBConn) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: return rollbackTransactionInternal(mDBConn); michael@0: } michael@0: michael@0: nsresult michael@0: Connection::rollbackTransactionInternal(sqlite3 *aNativeConnection) michael@0: { michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: if (!mTransactionInProgress) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsresult rv = michael@0: convertResultCode(executeSql(aNativeConnection, "ROLLBACK TRANSACTION")); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mTransactionInProgress = false; michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::CreateTable(const char *aTableName, michael@0: const char *aTableSchema) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: char *buf = ::PR_smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema); michael@0: if (!buf) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: int srv = executeSql(mDBConn, buf); michael@0: ::PR_smprintf_free(buf); michael@0: michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::CreateFunction(const nsACString &aFunctionName, michael@0: int32_t aNumArguments, michael@0: mozIStorageFunction *aFunction) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // Check to see if this function is already defined. We only check the name michael@0: // because a function can be defined with the same body but different names. michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE); michael@0: michael@0: int srv = ::sqlite3_create_function(mDBConn, michael@0: nsPromiseFlatCString(aFunctionName).get(), michael@0: aNumArguments, michael@0: SQLITE_ANY, michael@0: aFunction, michael@0: basicFunctionHelper, michael@0: nullptr, michael@0: nullptr); michael@0: if (srv != SQLITE_OK) michael@0: return convertResultCode(srv); michael@0: michael@0: FunctionInfo info = { aFunction, michael@0: Connection::FunctionInfo::SIMPLE, michael@0: aNumArguments }; michael@0: mFunctions.Put(aFunctionName, info); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::CreateAggregateFunction(const nsACString &aFunctionName, michael@0: int32_t aNumArguments, michael@0: mozIStorageAggregateFunction *aFunction) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // Check to see if this function name is already defined. michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE); michael@0: michael@0: // Because aggregate functions depend on state across calls, you cannot have michael@0: // the same instance use the same name. We want to enumerate all functions michael@0: // and make sure this instance is not already registered. michael@0: NS_ENSURE_FALSE(findFunctionByInstance(aFunction), NS_ERROR_FAILURE); michael@0: michael@0: int srv = ::sqlite3_create_function(mDBConn, michael@0: nsPromiseFlatCString(aFunctionName).get(), michael@0: aNumArguments, michael@0: SQLITE_ANY, michael@0: aFunction, michael@0: nullptr, michael@0: aggregateFunctionStepHelper, michael@0: aggregateFunctionFinalHelper); michael@0: if (srv != SQLITE_OK) michael@0: return convertResultCode(srv); michael@0: michael@0: FunctionInfo info = { aFunction, michael@0: Connection::FunctionInfo::AGGREGATE, michael@0: aNumArguments }; michael@0: mFunctions.Put(aFunctionName, info); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::RemoveFunction(const nsACString &aFunctionName) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE); michael@0: michael@0: int srv = ::sqlite3_create_function(mDBConn, michael@0: nsPromiseFlatCString(aFunctionName).get(), michael@0: 0, michael@0: SQLITE_ANY, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr); michael@0: if (srv != SQLITE_OK) michael@0: return convertResultCode(srv); michael@0: michael@0: mFunctions.Remove(aFunctionName); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::SetProgressHandler(int32_t aGranularity, michael@0: mozIStorageProgressHandler *aHandler, michael@0: mozIStorageProgressHandler **_oldHandler) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // Return previous one michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: NS_IF_ADDREF(*_oldHandler = mProgressHandler); michael@0: michael@0: if (!aHandler || aGranularity <= 0) { michael@0: aHandler = nullptr; michael@0: aGranularity = 0; michael@0: } michael@0: mProgressHandler = aHandler; michael@0: ::sqlite3_progress_handler(mDBConn, aGranularity, sProgressHelper, this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::RemoveProgressHandler(mozIStorageProgressHandler **_oldHandler) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // Return previous one michael@0: SQLiteMutexAutoLock lockedScope(sharedDBMutex); michael@0: NS_IF_ADDREF(*_oldHandler = mProgressHandler); michael@0: michael@0: mProgressHandler = nullptr; michael@0: ::sqlite3_progress_handler(mDBConn, 0, nullptr, nullptr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::SetGrowthIncrement(int32_t aChunkSize, const nsACString &aDatabaseName) michael@0: { michael@0: // Bug 597215: Disk space is extremely limited on Android michael@0: // so don't preallocate space. This is also not effective michael@0: // on log structured file systems used by Android devices michael@0: #if !defined(ANDROID) && !defined(MOZ_PLATFORM_MAEMO) michael@0: // Don't preallocate if less than 500MiB is available. michael@0: int64_t bytesAvailable; michael@0: nsresult rv = mDatabaseFile->GetDiskSpaceAvailable(&bytesAvailable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (bytesAvailable < MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH) { michael@0: return NS_ERROR_FILE_TOO_BIG; michael@0: } michael@0: michael@0: (void)::sqlite3_file_control(mDBConn, michael@0: aDatabaseName.Length() ? nsPromiseFlatCString(aDatabaseName).get() michael@0: : nullptr, michael@0: SQLITE_FCNTL_CHUNK_SIZE, michael@0: &aChunkSize); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Connection::EnableModule(const nsACString& aModuleName) michael@0: { michael@0: if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: for (size_t i = 0; i < ArrayLength(gModules); i++) { michael@0: struct Module* m = &gModules[i]; michael@0: if (aModuleName.Equals(m->name)) { michael@0: int srv = m->registerFunc(mDBConn, m->name); michael@0: if (srv != SQLITE_OK) michael@0: return convertResultCode(srv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: } // namespace storage michael@0: } // namespace mozilla