nsresult Service::initialize() { NS_TIME_FUNCTION; int rc; #ifdef MOZ_STORAGE_MEMORY rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods); if (rc != SQLITE_OK) return convertResultCode(rc); #endif // Explicitly initialize sqlite3. Although this is implicitly called by // various sqlite3 functions (and the sqlite3_open calls in our case), // the documentation suggests calling this directly. So we do. rc = ::sqlite3_initialize(); if (rc != SQLITE_OK) return convertResultCode(rc); mSqliteVFS = ConstructTelemetryVFS(); if (mSqliteVFS) { rc = sqlite3_vfs_register(mSqliteVFS, 1); if (rc != SQLITE_OK) return convertResultCode(rc); } else { NS_WARNING("Failed to register telemetry VFS"); } rc = ::sqlite3_quota_initialize("telemetry-vfs", 0); if (rc != SQLITE_OK) return convertResultCode(rc); // Set the default value for the toolkit.storage.synchronous pref. It will be // updated with the user preference on the main thread. sSynchronousPref = PREF_TS_SYNCHRONOUS_DEFAULT; // Run the things that need to run on the main thread there. nsCOMPtr<nsIRunnable> event = new ServiceMainThreadInitializer(this, this, &sXPConnect, &sSynchronousPref); if (event && ::NS_IsMainThread()) { (void)event->Run(); } else { (void)::NS_DispatchToMainThread(event); } return NS_OK; }
NS_IMETHODIMP Connection::CreateFunction(const nsACString &aFunctionName, PRInt32 aNumArguments, mozIStorageFunction *aFunction) { if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; // 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. SQLiteMutexAutoLock lockedScope(sharedDBMutex); NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, NULL), NS_ERROR_FAILURE); int srv = ::sqlite3_create_function(mDBConn, nsPromiseFlatCString(aFunctionName).get(), aNumArguments, SQLITE_ANY, aFunction, basicFunctionHelper, NULL, NULL); if (srv != SQLITE_OK) return convertResultCode(srv); FunctionInfo info = { aFunction, Connection::FunctionInfo::SIMPLE, aNumArguments }; NS_ENSURE_TRUE(mFunctions.Put(aFunctionName, info), NS_ERROR_OUT_OF_MEMORY); return NS_OK; }
NS_IMETHODIMP Connection::CreateAggregateFunction(const nsACString &aFunctionName, PRInt32 aNumArguments, mozIStorageAggregateFunction *aFunction) { if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; // Check to see if this function name is already defined. SQLiteMutexAutoLock lockedScope(sharedDBMutex); NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, NULL), NS_ERROR_FAILURE); // Because aggregate functions depend on state across calls, you cannot have // the same instance use the same name. We want to enumerate all functions // and make sure this instance is not already registered. NS_ENSURE_FALSE(findFunctionByInstance(aFunction), NS_ERROR_FAILURE); int srv = ::sqlite3_create_function(mDBConn, nsPromiseFlatCString(aFunctionName).get(), aNumArguments, SQLITE_ANY, aFunction, NULL, aggregateFunctionStepHelper, aggregateFunctionFinalHelper); if (srv != SQLITE_OK) return convertResultCode(srv); FunctionInfo info = { aFunction, Connection::FunctionInfo::AGGREGATE, aNumArguments }; NS_ENSURE_TRUE(mFunctions.Put(aFunctionName, info), NS_ERROR_OUT_OF_MEMORY); return NS_OK; }
nsresult Service::initialize() { MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread"); int rc = AutoSQLiteLifetime::getInitResult(); if (rc != SQLITE_OK) return convertResultCode(rc); mSqliteVFS = ConstructTelemetryVFS(); if (mSqliteVFS) { rc = sqlite3_vfs_register(mSqliteVFS, 0); if (rc != SQLITE_OK) return convertResultCode(rc); } else { NS_WARNING("Failed to register telemetry VFS"); } nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); NS_ENSURE_TRUE(os, NS_ERROR_FAILURE); for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) { nsresult rv = os->AddObserver(this, sObserverTopics[i], false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // We need to obtain the toolkit.storage.synchronous preferences on the main // thread because the preference service can only be accessed there. This // is cached in the service for all future Open[Unshared]Database calls. sSynchronousPref = Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT); // We need to obtain the toolkit.storage.pageSize preferences on the main // thread because the preference service can only be accessed there. This // is cached in the service for all future Open[Unshared]Database calls. sDefaultPageSize = Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT); mozilla::RegisterWeakMemoryReporter(this); mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount); return NS_OK; }
NS_IMETHODIMP Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement) { if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; int srv = ::sqlite3_exec(mDBConn, PromiseFlatCString(aSQLStatement).get(), NULL, NULL, NULL); return convertResultCode(srv); }
nsresult Connection::databaseElementExists(enum DatabaseElementType aElementType, const nsACString &aElementName, PRBool *_exists) { if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; nsCAutoString query("SELECT name FROM sqlite_master WHERE type = '"); switch (aElementType) { case INDEX: query.Append("index"); break; case TABLE: query.Append("table"); break; } query.Append("' AND name ='"); query.Append(aElementName); query.Append("'"); sqlite3_stmt *stmt; int srv = prepareStmt(mDBConn, query, &stmt); if (srv != SQLITE_OK) return convertResultCode(srv); srv = stepStmt(stmt); // we just care about the return value from step (void)::sqlite3_finalize(stmt); if (srv == SQLITE_ROW) { *_exists = PR_TRUE; return NS_OK; } if (srv == SQLITE_DONE) { *_exists = PR_FALSE; return NS_OK; } return convertResultCode(srv); }
NS_IMETHODIMP Service::UpdateQuotaInformationForFile(nsIFile *aFile) { NS_ENSURE_ARG_POINTER(aFile); nsCString path; nsresult rv = aFile->GetNativePath(path); NS_ENSURE_SUCCESS(rv, rv); int rc = ::sqlite3_quota_file(PromiseFlatCString(path).get()); NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc)); return NS_OK; }
nsresult Statement::getAsynchronousStatementData(StatementData &_data) { if (!mDBStatement) return NS_ERROR_UNEXPECTED; sqlite3_stmt *stmt; int rc = getAsyncStatement(&stmt); if (rc != SQLITE_OK) return convertResultCode(rc); _data = StatementData(stmt, bindingParamsArray(), this); return NS_OK; }
NS_IMETHODIMP Connection::CreateTable(const char *aTableName, const char *aTableSchema) { if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; char *buf = ::PR_smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema); if (!buf) return NS_ERROR_OUT_OF_MEMORY; int srv = ::sqlite3_exec(mDBConn, buf, NULL, NULL, NULL); ::PR_smprintf_free(buf); return convertResultCode(srv); }
nsresult Statement::internalFinalize(bool aDestructing) { if (!mDBStatement) return NS_OK; #ifdef PR_LOGGING PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Finalizing statement '%s'", ::sqlite3_sql(mDBStatement))); #endif int srv = ::sqlite3_finalize(mDBStatement); mDBStatement = nullptr; if (mAsyncStatement) { // If the destructor called us, there are no pending async statements (they // hold a reference to us) and we can/must just kill the statement directly. if (aDestructing) destructorAsyncFinalize(); else asyncFinalize(); } // We are considered dead at this point, so any wrappers for row or params // need to lose their reference to us. if (mStatementParamsHolder) { nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mStatementParamsHolder); nsCOMPtr<mozIStorageStatementParams> iParams = do_QueryWrappedNative(wrapper); StatementParams *params = static_cast<StatementParams *>(iParams.get()); params->mStatement = nullptr; mStatementParamsHolder = nullptr; } if (mStatementRowHolder) { nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mStatementRowHolder); nsCOMPtr<mozIStorageStatementRow> iRow = do_QueryWrappedNative(wrapper); StatementRow *row = static_cast<StatementRow *>(iRow.get()); row->mStatement = nullptr; mStatementRowHolder = nullptr; } return convertResultCode(srv); }
nsresult Connection::internalClose() { #ifdef DEBUG // Sanity checks to make sure we are in the proper state before calling this. NS_ASSERTION(mDBConn, "Database connection is already null!"); { // Make sure we have marked our async thread as shutting down. MutexAutoLock lockedScope(sharedAsyncExecutionMutex); NS_ASSERTION(mAsyncExecutionThreadShuttingDown, "Did not call setClosedState!"); } { // Ensure that we are being called on the thread we were opened with. PRBool onOpenedThread = PR_FALSE; (void)threadOpenedOn->IsOnCurrentThread(&onOpenedThread); NS_ASSERTION(onOpenedThread, "Not called on the thread the database was opened on!"); } #endif #ifdef PR_LOGGING nsCAutoString leafName(":memory"); if (mDatabaseFile) (void)mDatabaseFile->GetNativeLeafName(leafName); PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Closing connection to '%s'", leafName.get())); #endif #ifdef DEBUG // Notify about any non-finalized statements. sqlite3_stmt *stmt = NULL; while ((stmt = ::sqlite3_next_stmt(mDBConn, stmt))) { char *msg = ::PR_smprintf("SQL statement '%s' was not finalized", ::sqlite3_sql(stmt)); NS_WARNING(msg); ::PR_smprintf_free(msg); } #endif int srv = ::sqlite3_close(mDBConn); NS_ASSERTION(srv == SQLITE_OK, "sqlite3_close failed. There are probably outstanding statements that are listed above!"); mDBConn = NULL; return convertResultCode(srv); }
NS_IMETHODIMP Service::SetQuotaForFilenamePattern(const nsACString &aPattern, PRInt64 aSizeLimit, mozIStorageQuotaCallback *aCallback, nsISupports *aUserData) { NS_ENSURE_FALSE(aPattern.IsEmpty(), NS_ERROR_INVALID_ARG); nsAutoPtr<QuotaCallbackData> data; if (aSizeLimit && aCallback) { data = new QuotaCallbackData(aCallback, aUserData); } int rc = ::sqlite3_quota_set(PromiseFlatCString(aPattern).get(), aSizeLimit, QuotaCallbackData::Callback, data, QuotaCallbackData::Destroy); NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc)); data.forget(); return NS_OK; }
NS_IMETHODIMP Connection::RemoveFunction(const nsACString &aFunctionName) { if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; SQLiteMutexAutoLock lockedScope(sharedDBMutex); NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, NULL), NS_ERROR_FAILURE); int srv = ::sqlite3_create_function(mDBConn, nsPromiseFlatCString(aFunctionName).get(), 0, SQLITE_ANY, NULL, NULL, NULL, NULL); if (srv != SQLITE_OK) return convertResultCode(srv); mFunctions.Remove(aFunctionName); return NS_OK; }
NS_IMETHODIMP Statement::ExecuteStep(bool *_moreResults) { PROFILER_LABEL("storage", "Statement::ExecuteStep"); if (!mDBStatement) return NS_ERROR_NOT_INITIALIZED; // Bind any parameters first before executing. if (mParamsArray) { // If we have more than one row of parameters to bind, they shouldn't be // calling this method (and instead use executeAsync). if (mParamsArray->length() != 1) return NS_ERROR_UNEXPECTED; BindingParamsArray::iterator row = mParamsArray->begin(); nsCOMPtr<IStorageBindingParamsInternal> bindingInternal = do_QueryInterface(*row); nsCOMPtr<mozIStorageError> error = bindingInternal->bind(mDBStatement); if (error) { int32_t srv; (void)error->GetResult(&srv); return convertResultCode(srv); } // We have bound, so now we can clear our array. mParamsArray = nullptr; } int srv = mDBConnection->stepStatement(mDBStatement); #ifdef PR_LOGGING if (srv != SQLITE_ROW && srv != SQLITE_DONE) { nsAutoCString errStr; (void)mDBConnection->GetLastErrorString(errStr); PR_LOG(gStorageLog, PR_LOG_DEBUG, ("Statement::ExecuteStep error: %s", errStr.get())); } #endif // SQLITE_ROW and SQLITE_DONE are non-errors if (srv == SQLITE_ROW) { // we got a row back mExecuting = true; *_moreResults = true; return NS_OK; } else if (srv == SQLITE_DONE) { // statement is done (no row returned) mExecuting = false; *_moreResults = false; return NS_OK; } else if (srv == SQLITE_BUSY || srv == SQLITE_MISUSE) { mExecuting = false; } else if (mExecuting) { #ifdef PR_LOGGING PR_LOG(gStorageLog, PR_LOG_ERROR, ("SQLite error after mExecuting was true!")); #endif mExecuting = false; } return convertResultCode(srv); }
nsresult Statement::internalFinalize(bool aDestructing) { if (!mDBStatement) return NS_OK; int srv = SQLITE_OK; if (!mDBConnection->isClosed()) { // // The connection is still open. While statement finalization and // closing may, in some cases, take place in two distinct threads, // we have a guarantee that the connection will remain open until // this method terminates: // // a. The connection will be closed synchronously. In this case, // there is no race condition, as everything takes place on the // same thread. // // b. The connection is closed asynchronously and this code is // executed on the opener thread. In this case, asyncClose() has // not been called yet and will not be called before we return // from this function. // // c. The connection is closed asynchronously and this code is // executed on the async execution thread. In this case, // AsyncCloseConnection::Run() has not been called yet and will // not be called before we return from this function. // // In either case, the connection is still valid, hence closing // here is safe. // MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s' during garbage-collection", ::sqlite3_sql(mDBStatement))); srv = ::sqlite3_finalize(mDBStatement); } #ifdef DEBUG else { // // The database connection is either closed or closing. The sqlite // statement has either been finalized already by the connection // or is about to be finalized by the connection. // // Finalizing it here would be useless and segfaultish. // char *msg = ::PR_smprintf("SQL statement (%x) should have been finalized" " before garbage-collection. For more details on this statement, set" " NSPR_LOG_MESSAGES=mozStorage:5 .", mDBStatement); // // Note that we can't display the statement itself, as the data structure // is not valid anymore. However, the address shown here should help // developers correlate with the more complete debug message triggered // by AsyncClose(). // #if 0 // Deactivate the warning until we have fixed the exising culprit // (see bug 914070). NS_WARNING(msg); #endif // 0 MOZ_LOG(gStorageLog, LogLevel::Warning, (msg)); ::PR_smprintf_free(msg); } #endif mDBStatement = nullptr; if (mAsyncStatement) { // If the destructor called us, there are no pending async statements (they // hold a reference to us) and we can/must just kill the statement directly. if (aDestructing) destructorAsyncFinalize(); else asyncFinalize(); } // Release the holders, so they can release the reference to us. mStatementParamsHolder = nullptr; mStatementRowHolder = nullptr; return convertResultCode(srv); }
nsresult Connection::initialize(nsIFile *aDatabaseFile) { NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); int srv; nsresult rv; mDatabaseFile = aDatabaseFile; if (aDatabaseFile) { nsAutoString path; rv = aDatabaseFile->GetPath(path); NS_ENSURE_SUCCESS(rv, rv); srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags, NULL); } else { // in memory database requested, sqlite uses a magic file name srv = ::sqlite3_open_v2(":memory:", &mDBConn, mFlags, NULL); } if (srv != SQLITE_OK) { mDBConn = nsnull; return convertResultCode(srv); } // Properly wrap the database handle's mutex. sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn)); #ifdef PR_LOGGING if (!gStorageLog) gStorageLog = ::PR_NewLogModule("mozStorage"); ::sqlite3_trace(mDBConn, tracefunc, this); nsCAutoString leafName(":memory"); if (aDatabaseFile) (void)aDatabaseFile->GetNativeLeafName(leafName); PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Opening connection to '%s' (%p)", leafName.get(), this)); #endif // Switch db to preferred page size in case the user vacuums. sqlite3_stmt *stmt; srv = prepareStmt(mDBConn, NS_LITERAL_CSTRING("PRAGMA page_size = 32768"), &stmt); if (srv == SQLITE_OK) { (void)stepStmt(stmt); (void)::sqlite3_finalize(stmt); } // Register our built-in SQL functions. srv = registerFunctions(mDBConn); if (srv != SQLITE_OK) { ::sqlite3_close(mDBConn); mDBConn = nsnull; return convertResultCode(srv); } // Register our built-in SQL collating sequences. srv = registerCollations(mDBConn, mStorageService); if (srv != SQLITE_OK) { ::sqlite3_close(mDBConn); mDBConn = nsnull; return convertResultCode(srv); } // Execute a dummy statement to force the db open, and to verify if it is // valid or not. srv = prepareStmt(mDBConn, NS_LITERAL_CSTRING("SELECT * FROM sqlite_master"), &stmt); if (srv == SQLITE_OK) { srv = stepStmt(stmt); if (srv == SQLITE_DONE || srv == SQLITE_ROW) srv = SQLITE_OK; ::sqlite3_finalize(stmt); } if (srv != SQLITE_OK) { ::sqlite3_close(mDBConn); mDBConn = nsnull; return convertResultCode(srv); } // Set the synchronous PRAGMA, according to the pref nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID)); PRInt32 synchronous = 1; // Default to NORMAL if pref not set if (pref) (void)pref->GetIntPref(PREF_TS_SYNCHRONOUS, &synchronous); switch (synchronous) { case 2: (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING( "PRAGMA synchronous = FULL;")); break; case 0: (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING( "PRAGMA synchronous = OFF;")); break; case 1: default: (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING( "PRAGMA synchronous = NORMAL;")); break; } return NS_OK; }