1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include <stdio.h>
8
9 #include "nsError.h"
10 #include "nsThreadUtils.h"
11 #include "nsIFile.h"
12 #include "nsIFileURL.h"
13 #include "nsIXPConnect.h"
14 #include "mozilla/Telemetry.h"
15 #include "mozilla/Mutex.h"
16 #include "mozilla/CondVar.h"
17 #include "mozilla/Attributes.h"
18 #include "mozilla/ErrorNames.h"
19 #include "mozilla/Unused.h"
20 #include "mozilla/dom/quota/QuotaObject.h"
21 #include "mozilla/ScopeExit.h"
22
23 #include "mozIStorageAggregateFunction.h"
24 #include "mozIStorageCompletionCallback.h"
25 #include "mozIStorageFunction.h"
26
27 #include "mozStorageAsyncStatementExecution.h"
28 #include "mozStorageSQLFunctions.h"
29 #include "mozStorageConnection.h"
30 #include "mozStorageService.h"
31 #include "mozStorageStatement.h"
32 #include "mozStorageAsyncStatement.h"
33 #include "mozStorageArgValueArray.h"
34 #include "mozStoragePrivateHelpers.h"
35 #include "mozStorageStatementData.h"
36 #include "StorageBaseStatementInternal.h"
37 #include "SQLCollations.h"
38 #include "FileSystemModule.h"
39 #include "mozStorageHelper.h"
40 #include "GeckoProfiler.h"
41
42 #include "mozilla/Logging.h"
43 #include "mozilla/Printf.h"
44 #include "nsProxyRelease.h"
45 #include <algorithm>
46
47 #define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000 // 500 MiB
48
49 // Maximum size of the pages cache per connection.
50 #define MAX_CACHE_SIZE_KIBIBYTES 2048 // 2 MiB
51
52 mozilla::LazyLogModule gStorageLog("mozStorage");
53
54 // Checks that the protected code is running on the main-thread only if the
55 // connection was also opened on it.
56 #ifdef DEBUG
57 # define CHECK_MAINTHREAD_ABUSE() \
58 do { \
59 nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); \
60 NS_WARNING_ASSERTION( \
61 threadOpenedOn == mainThread || !NS_IsMainThread(), \
62 "Using Storage synchronous API on main-thread, but " \
63 "the connection was " \
64 "opened on another thread."); \
65 } while (0)
66 #else
67 # define CHECK_MAINTHREAD_ABUSE() \
68 do { /* Nothing */ \
69 } while (0)
70 #endif
71
72 namespace mozilla::storage {
73
74 using mozilla::dom::quota::QuotaObject;
75
76 const char* GetVFSName();
77
78 namespace {
79
nsresultToSQLiteResult(nsresult aXPCOMResultCode)80 int nsresultToSQLiteResult(nsresult aXPCOMResultCode) {
81 if (NS_SUCCEEDED(aXPCOMResultCode)) {
82 return SQLITE_OK;
83 }
84
85 switch (aXPCOMResultCode) {
86 case NS_ERROR_FILE_CORRUPTED:
87 return SQLITE_CORRUPT;
88 case NS_ERROR_FILE_ACCESS_DENIED:
89 return SQLITE_CANTOPEN;
90 case NS_ERROR_STORAGE_BUSY:
91 return SQLITE_BUSY;
92 case NS_ERROR_FILE_IS_LOCKED:
93 return SQLITE_LOCKED;
94 case NS_ERROR_FILE_READ_ONLY:
95 return SQLITE_READONLY;
96 case NS_ERROR_STORAGE_IOERR:
97 return SQLITE_IOERR;
98 case NS_ERROR_FILE_NO_DEVICE_SPACE:
99 return SQLITE_FULL;
100 case NS_ERROR_OUT_OF_MEMORY:
101 return SQLITE_NOMEM;
102 case NS_ERROR_UNEXPECTED:
103 return SQLITE_MISUSE;
104 case NS_ERROR_ABORT:
105 return SQLITE_ABORT;
106 case NS_ERROR_STORAGE_CONSTRAINT:
107 return SQLITE_CONSTRAINT;
108 default:
109 return SQLITE_ERROR;
110 }
111
112 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Must return in switch above!");
113 }
114
115 ////////////////////////////////////////////////////////////////////////////////
116 //// Variant Specialization Functions (variantToSQLiteT)
117
sqlite3_T_int(sqlite3_context * aCtx,int aValue)118 int sqlite3_T_int(sqlite3_context* aCtx, int aValue) {
119 ::sqlite3_result_int(aCtx, aValue);
120 return SQLITE_OK;
121 }
122
sqlite3_T_int64(sqlite3_context * aCtx,sqlite3_int64 aValue)123 int sqlite3_T_int64(sqlite3_context* aCtx, sqlite3_int64 aValue) {
124 ::sqlite3_result_int64(aCtx, aValue);
125 return SQLITE_OK;
126 }
127
sqlite3_T_double(sqlite3_context * aCtx,double aValue)128 int sqlite3_T_double(sqlite3_context* aCtx, double aValue) {
129 ::sqlite3_result_double(aCtx, aValue);
130 return SQLITE_OK;
131 }
132
sqlite3_T_text(sqlite3_context * aCtx,const nsCString & aValue)133 int sqlite3_T_text(sqlite3_context* aCtx, const nsCString& aValue) {
134 ::sqlite3_result_text(aCtx, aValue.get(), aValue.Length(), SQLITE_TRANSIENT);
135 return SQLITE_OK;
136 }
137
sqlite3_T_text16(sqlite3_context * aCtx,const nsString & aValue)138 int sqlite3_T_text16(sqlite3_context* aCtx, const nsString& aValue) {
139 ::sqlite3_result_text16(
140 aCtx, aValue.get(),
141 aValue.Length() * sizeof(char16_t), // Number of bytes.
142 SQLITE_TRANSIENT);
143 return SQLITE_OK;
144 }
145
sqlite3_T_null(sqlite3_context * aCtx)146 int sqlite3_T_null(sqlite3_context* aCtx) {
147 ::sqlite3_result_null(aCtx);
148 return SQLITE_OK;
149 }
150
sqlite3_T_blob(sqlite3_context * aCtx,const void * aData,int aSize)151 int sqlite3_T_blob(sqlite3_context* aCtx, const void* aData, int aSize) {
152 ::sqlite3_result_blob(aCtx, aData, aSize, free);
153 return SQLITE_OK;
154 }
155
156 #include "variantToSQLiteT_impl.h"
157
158 ////////////////////////////////////////////////////////////////////////////////
159 //// Modules
160
161 struct Module {
162 const char* name;
163 int (*registerFunc)(sqlite3*, const char*);
164 };
165
166 Module gModules[] = {{"filesystem", RegisterFileSystemModule}};
167
168 ////////////////////////////////////////////////////////////////////////////////
169 //// Local Functions
170
tracefunc(unsigned aReason,void * aClosure,void * aP,void * aX)171 int tracefunc(unsigned aReason, void* aClosure, void* aP, void* aX) {
172 switch (aReason) {
173 case SQLITE_TRACE_STMT: {
174 // aP is a pointer to the prepared statement.
175 sqlite3_stmt* stmt = static_cast<sqlite3_stmt*>(aP);
176 // aX is a pointer to a string containing the unexpanded SQL or a comment,
177 // starting with "--"" in case of a trigger.
178 char* expanded = static_cast<char*>(aX);
179 // Simulate what sqlite_trace was doing.
180 if (!::strncmp(expanded, "--", 2)) {
181 MOZ_LOG(gStorageLog, LogLevel::Debug,
182 ("TRACE_STMT on %p: '%s'", aClosure, expanded));
183 } else {
184 char* sql = ::sqlite3_expanded_sql(stmt);
185 MOZ_LOG(gStorageLog, LogLevel::Debug,
186 ("TRACE_STMT on %p: '%s'", aClosure, sql));
187 ::sqlite3_free(sql);
188 }
189 break;
190 }
191 case SQLITE_TRACE_PROFILE: {
192 // aX is pointer to a 64bit integer containing nanoseconds it took to
193 // execute the last command.
194 sqlite_int64 time = *(static_cast<sqlite_int64*>(aX)) / 1000000;
195 if (time > 0) {
196 MOZ_LOG(gStorageLog, LogLevel::Debug,
197 ("TRACE_TIME on %p: %lldms", aClosure, time));
198 }
199 break;
200 }
201 }
202 return 0;
203 }
204
basicFunctionHelper(sqlite3_context * aCtx,int aArgc,sqlite3_value ** aArgv)205 void basicFunctionHelper(sqlite3_context* aCtx, int aArgc,
206 sqlite3_value** aArgv) {
207 void* userData = ::sqlite3_user_data(aCtx);
208
209 mozIStorageFunction* func = static_cast<mozIStorageFunction*>(userData);
210
211 RefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv));
212 if (!arguments) return;
213
214 nsCOMPtr<nsIVariant> result;
215 nsresult rv = func->OnFunctionCall(arguments, getter_AddRefs(result));
216 if (NS_FAILED(rv)) {
217 nsAutoCString errorMessage;
218 GetErrorName(rv, errorMessage);
219 errorMessage.InsertLiteral("User function returned ", 0);
220 errorMessage.Append('!');
221
222 NS_WARNING(errorMessage.get());
223
224 ::sqlite3_result_error(aCtx, errorMessage.get(), -1);
225 ::sqlite3_result_error_code(aCtx, nsresultToSQLiteResult(rv));
226 return;
227 }
228 int retcode = variantToSQLiteT(aCtx, result);
229 if (retcode != SQLITE_OK) {
230 NS_WARNING("User function returned invalid data type!");
231 ::sqlite3_result_error(aCtx, "User function returned invalid data type",
232 -1);
233 }
234 }
235
aggregateFunctionStepHelper(sqlite3_context * aCtx,int aArgc,sqlite3_value ** aArgv)236 void aggregateFunctionStepHelper(sqlite3_context* aCtx, int aArgc,
237 sqlite3_value** aArgv) {
238 void* userData = ::sqlite3_user_data(aCtx);
239 mozIStorageAggregateFunction* func =
240 static_cast<mozIStorageAggregateFunction*>(userData);
241
242 RefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv));
243 if (!arguments) return;
244
245 if (NS_FAILED(func->OnStep(arguments)))
246 NS_WARNING("User aggregate step function returned error code!");
247 }
248
aggregateFunctionFinalHelper(sqlite3_context * aCtx)249 void aggregateFunctionFinalHelper(sqlite3_context* aCtx) {
250 void* userData = ::sqlite3_user_data(aCtx);
251 mozIStorageAggregateFunction* func =
252 static_cast<mozIStorageAggregateFunction*>(userData);
253
254 RefPtr<nsIVariant> result;
255 if (NS_FAILED(func->OnFinal(getter_AddRefs(result)))) {
256 NS_WARNING("User aggregate final function returned error code!");
257 ::sqlite3_result_error(
258 aCtx, "User aggregate final function returned error code", -1);
259 return;
260 }
261
262 if (variantToSQLiteT(aCtx, result) != SQLITE_OK) {
263 NS_WARNING("User aggregate final function returned invalid data type!");
264 ::sqlite3_result_error(
265 aCtx, "User aggregate final function returned invalid data type", -1);
266 }
267 }
268
269 /**
270 * This code is heavily based on the sample at:
271 * http://www.sqlite.org/unlock_notify.html
272 */
273 class UnlockNotification {
274 public:
UnlockNotification()275 UnlockNotification()
276 : mMutex("UnlockNotification mMutex"),
277 mCondVar(mMutex, "UnlockNotification condVar"),
278 mSignaled(false) {}
279
Wait()280 void Wait() {
281 MutexAutoLock lock(mMutex);
282 while (!mSignaled) {
283 (void)mCondVar.Wait();
284 }
285 }
286
Signal()287 void Signal() {
288 MutexAutoLock lock(mMutex);
289 mSignaled = true;
290 (void)mCondVar.Notify();
291 }
292
293 private:
294 Mutex mMutex;
295 CondVar mCondVar;
296 bool mSignaled;
297 };
298
UnlockNotifyCallback(void ** aArgs,int aArgsSize)299 void UnlockNotifyCallback(void** aArgs, int aArgsSize) {
300 for (int i = 0; i < aArgsSize; i++) {
301 UnlockNotification* notification =
302 static_cast<UnlockNotification*>(aArgs[i]);
303 notification->Signal();
304 }
305 }
306
WaitForUnlockNotify(sqlite3 * aDatabase)307 int WaitForUnlockNotify(sqlite3* aDatabase) {
308 UnlockNotification notification;
309 int srv =
310 ::sqlite3_unlock_notify(aDatabase, UnlockNotifyCallback, ¬ification);
311 MOZ_ASSERT(srv == SQLITE_LOCKED || srv == SQLITE_OK);
312 if (srv == SQLITE_OK) {
313 notification.Wait();
314 }
315
316 return srv;
317 }
318
319 ////////////////////////////////////////////////////////////////////////////////
320 //// Local Classes
321
322 class AsyncCloseConnection final : public Runnable {
323 public:
AsyncCloseConnection(Connection * aConnection,sqlite3 * aNativeConnection,nsIRunnable * aCallbackEvent)324 AsyncCloseConnection(Connection* aConnection, sqlite3* aNativeConnection,
325 nsIRunnable* aCallbackEvent)
326 : Runnable("storage::AsyncCloseConnection"),
327 mConnection(aConnection),
328 mNativeConnection(aNativeConnection),
329 mCallbackEvent(aCallbackEvent) {}
330
Run()331 NS_IMETHOD Run() override {
332 // This code is executed on the background thread
333 MOZ_ASSERT(NS_GetCurrentThread() != mConnection->threadOpenedOn);
334
335 nsCOMPtr<nsIRunnable> event =
336 NewRunnableMethod("storage::Connection::shutdownAsyncThread",
337 mConnection, &Connection::shutdownAsyncThread);
338 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event));
339
340 // Internal close.
341 (void)mConnection->internalClose(mNativeConnection);
342
343 // Callback
344 if (mCallbackEvent) {
345 nsCOMPtr<nsIThread> thread;
346 (void)NS_GetMainThread(getter_AddRefs(thread));
347 (void)thread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL);
348 }
349
350 return NS_OK;
351 }
352
~AsyncCloseConnection()353 ~AsyncCloseConnection() override {
354 NS_ReleaseOnMainThread("AsyncCloseConnection::mConnection",
355 mConnection.forget());
356 NS_ReleaseOnMainThread("AsyncCloseConnection::mCallbackEvent",
357 mCallbackEvent.forget());
358 }
359
360 private:
361 RefPtr<Connection> mConnection;
362 sqlite3* mNativeConnection;
363 nsCOMPtr<nsIRunnable> mCallbackEvent;
364 };
365
366 /**
367 * An event used to initialize the clone of a connection.
368 *
369 * Must be executed on the clone's async execution thread.
370 */
371 class AsyncInitializeClone final : public Runnable {
372 public:
373 /**
374 * @param aConnection The connection being cloned.
375 * @param aClone The clone.
376 * @param aReadOnly If |true|, the clone is read only.
377 * @param aCallback A callback to trigger once initialization
378 * is complete. This event will be called on
379 * aClone->threadOpenedOn.
380 */
AsyncInitializeClone(Connection * aConnection,Connection * aClone,const bool aReadOnly,mozIStorageCompletionCallback * aCallback)381 AsyncInitializeClone(Connection* aConnection, Connection* aClone,
382 const bool aReadOnly,
383 mozIStorageCompletionCallback* aCallback)
384 : Runnable("storage::AsyncInitializeClone"),
385 mConnection(aConnection),
386 mClone(aClone),
387 mReadOnly(aReadOnly),
388 mCallback(aCallback) {
389 MOZ_ASSERT(NS_IsMainThread());
390 }
391
Run()392 NS_IMETHOD Run() override {
393 MOZ_ASSERT(!NS_IsMainThread());
394 nsresult rv = mConnection->initializeClone(mClone, mReadOnly);
395 if (NS_FAILED(rv)) {
396 return Dispatch(rv, nullptr);
397 }
398 return Dispatch(NS_OK,
399 NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mClone));
400 }
401
402 private:
Dispatch(nsresult aResult,nsISupports * aValue)403 nsresult Dispatch(nsresult aResult, nsISupports* aValue) {
404 RefPtr<CallbackComplete> event =
405 new CallbackComplete(aResult, aValue, mCallback.forget());
406 return mClone->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
407 }
408
~AsyncInitializeClone()409 ~AsyncInitializeClone() override {
410 nsCOMPtr<nsIThread> thread;
411 DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
412 MOZ_ASSERT(NS_SUCCEEDED(rv));
413
414 // Handle ambiguous nsISupports inheritance.
415 NS_ProxyRelease("AsyncInitializeClone::mConnection", thread,
416 mConnection.forget());
417 NS_ProxyRelease("AsyncInitializeClone::mClone", thread, mClone.forget());
418
419 // Generally, the callback will be released by CallbackComplete.
420 // However, if for some reason Run() is not executed, we still
421 // need to ensure that it is released here.
422 NS_ProxyRelease("AsyncInitializeClone::mCallback", thread,
423 mCallback.forget());
424 }
425
426 RefPtr<Connection> mConnection;
427 RefPtr<Connection> mClone;
428 const bool mReadOnly;
429 nsCOMPtr<mozIStorageCompletionCallback> mCallback;
430 };
431
432 /**
433 * A listener for async connection closing.
434 */
435 class CloseListener final : public mozIStorageCompletionCallback {
436 public:
437 NS_DECL_ISUPPORTS
CloseListener()438 CloseListener() : mClosed(false) {}
439
Complete(nsresult,nsISupports *)440 NS_IMETHOD Complete(nsresult, nsISupports*) override {
441 mClosed = true;
442 return NS_OK;
443 }
444
445 bool mClosed;
446
447 private:
448 ~CloseListener() = default;
449 };
450
451 NS_IMPL_ISUPPORTS(CloseListener, mozIStorageCompletionCallback)
452
453 } // namespace
454
455 ////////////////////////////////////////////////////////////////////////////////
456 //// Connection
457
Connection(Service * aService,int aFlags,ConnectionOperation aSupportedOperations,bool aIgnoreLockingMode)458 Connection::Connection(Service* aService, int aFlags,
459 ConnectionOperation aSupportedOperations,
460 bool aIgnoreLockingMode)
461 : sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex"),
462 sharedDBMutex("Connection::sharedDBMutex"),
463 threadOpenedOn(do_GetCurrentThread()),
464 mDBConn(nullptr),
465 mAsyncExecutionThreadShuttingDown(false),
466 mConnectionClosed(false),
467 mDefaultTransactionType(mozIStorageConnection::TRANSACTION_DEFERRED),
468 mDestroying(false),
469 mProgressHandler(nullptr),
470 mFlags(aFlags),
471 mIgnoreLockingMode(aIgnoreLockingMode),
472 mStorageService(aService),
473 mSupportedOperations(aSupportedOperations) {
474 MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY,
475 "Can't ignore locking for a non-readonly connection!");
476 mStorageService->registerConnection(this);
477 }
478
~Connection()479 Connection::~Connection() {
480 // Failsafe Close() occurs in our custom Release method because of
481 // complications related to Close() potentially invoking AsyncClose() which
482 // will increment our refcount.
483 MOZ_ASSERT(!mAsyncExecutionThread,
484 "The async thread has not been shutdown properly!");
485 }
486
487 NS_IMPL_ADDREF(Connection)
488
NS_INTERFACE_MAP_BEGIN(Connection)489 NS_INTERFACE_MAP_BEGIN(Connection)
490 NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection)
491 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
492 NS_INTERFACE_MAP_ENTRY(mozIStorageConnection)
493 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageConnection)
494 NS_INTERFACE_MAP_END
495
496 // This is identical to what NS_IMPL_RELEASE provides, but with the
497 // extra |1 == count| case.
498 NS_IMETHODIMP_(MozExternalRefCountType) Connection::Release(void) {
499 MOZ_ASSERT(0 != mRefCnt, "dup release");
500 nsrefcnt count = --mRefCnt;
501 NS_LOG_RELEASE(this, count, "Connection");
502 if (1 == count) {
503 // If the refcount went to 1, the single reference must be from
504 // gService->mConnections (in class |Service|). And the code calling
505 // Release is either:
506 // - The "user" code that had created the connection, releasing on any
507 // thread.
508 // - One of Service's getConnections() callers had acquired a strong
509 // reference to the Connection that out-lived the last "user" reference,
510 // and now that just got dropped. Note that this reference could be
511 // getting dropped on the main thread or Connection->threadOpenedOn
512 // (because of the NewRunnableMethod used by minimizeMemory).
513 //
514 // Either way, we should now perform our failsafe Close() and unregister.
515 // However, we only want to do this once, and the reality is that our
516 // refcount could go back up above 1 and down again at any time if we are
517 // off the main thread and getConnections() gets called on the main thread,
518 // so we use an atomic here to do this exactly once.
519 if (mDestroying.compareExchange(false, true)) {
520 // Close the connection, dispatching to the opening thread if we're not
521 // on that thread already and that thread is still accepting runnables.
522 // We do this because it's possible we're on the main thread because of
523 // getConnections(), and we REALLY don't want to transfer I/O to the main
524 // thread if we can avoid it.
525 if (threadOpenedOn->IsOnCurrentThread()) {
526 // This could cause SpinningSynchronousClose() to be invoked and AddRef
527 // triggered for AsyncCloseConnection's strong ref if the conn was ever
528 // use for async purposes. (Main-thread only, though.)
529 Unused << synchronousClose();
530 } else {
531 nsCOMPtr<nsIRunnable> event =
532 NewRunnableMethod("storage::Connection::synchronousClose", this,
533 &Connection::synchronousClose);
534 if (NS_FAILED(
535 threadOpenedOn->Dispatch(event.forget(), NS_DISPATCH_NORMAL))) {
536 // The target thread was dead and so we've just leaked our runnable.
537 // This should not happen because our non-main-thread consumers should
538 // be explicitly closing their connections, not relying on us to close
539 // them for them. (It's okay to let a statement go out of scope for
540 // automatic cleanup, but not a Connection.)
541 MOZ_ASSERT(false,
542 "Leaked Connection::synchronousClose(), ownership fail.");
543 Unused << synchronousClose();
544 }
545 }
546
547 // This will drop its strong reference right here, right now.
548 mStorageService->unregisterConnection(this);
549 }
550 } else if (0 == count) {
551 mRefCnt = 1; /* stabilize */
552 #if 0 /* enable this to find non-threadsafe destructors: */
553 NS_ASSERT_OWNINGTHREAD(Connection);
554 #endif
555 delete (this);
556 return 0;
557 }
558 return count;
559 }
560
getSqliteRuntimeStatus(int32_t aStatusOption,int32_t * aMaxValue)561 int32_t Connection::getSqliteRuntimeStatus(int32_t aStatusOption,
562 int32_t* aMaxValue) {
563 MOZ_ASSERT(connectionReady(), "A connection must exist at this point");
564 int curr = 0, max = 0;
565 DebugOnly<int> rc =
566 ::sqlite3_db_status(mDBConn, aStatusOption, &curr, &max, 0);
567 MOZ_ASSERT(NS_SUCCEEDED(convertResultCode(rc)));
568 if (aMaxValue) *aMaxValue = max;
569 return curr;
570 }
571
getAsyncExecutionTarget()572 nsIEventTarget* Connection::getAsyncExecutionTarget() {
573 NS_ENSURE_TRUE(threadOpenedOn == NS_GetCurrentThread(), nullptr);
574
575 // Don't return the asynchronous thread if we are shutting down.
576 if (mAsyncExecutionThreadShuttingDown) {
577 return nullptr;
578 }
579
580 // Create the async thread if there's none yet.
581 if (!mAsyncExecutionThread) {
582 static nsThreadPoolNaming naming;
583 nsresult rv = NS_NewNamedThread(naming.GetNextThreadName("mozStorage"),
584 getter_AddRefs(mAsyncExecutionThread));
585 if (NS_FAILED(rv)) {
586 NS_WARNING("Failed to create async thread.");
587 return nullptr;
588 }
589 mAsyncExecutionThread->SetNameForWakeupTelemetry(
590 NS_LITERAL_CSTRING("mozStorage (all)"));
591 }
592
593 return mAsyncExecutionThread;
594 }
595
initialize()596 nsresult Connection::initialize() {
597 NS_ASSERTION(!connectionReady(),
598 "Initialize called on already opened database!");
599 MOZ_ASSERT(!mIgnoreLockingMode, "Can't ignore locking on an in-memory db.");
600 AUTO_PROFILER_LABEL("Connection::initialize", OTHER);
601
602 // in memory database requested, sqlite uses a magic file name
603 int srv = ::sqlite3_open_v2(":memory:", &mDBConn, mFlags, GetVFSName());
604 if (srv != SQLITE_OK) {
605 mDBConn = nullptr;
606 return convertResultCode(srv);
607 }
608
609 #ifdef MOZ_SQLITE_FTS3_TOKENIZER
610 srv =
611 ::sqlite3_db_config(mDBConn, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0);
612 MOZ_ASSERT(srv == SQLITE_OK,
613 "SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER should be enabled");
614 #endif
615
616 // Do not set mDatabaseFile or mFileURL here since this is a "memory"
617 // database.
618
619 nsresult rv = initializeInternal();
620 NS_ENSURE_SUCCESS(rv, rv);
621
622 return NS_OK;
623 }
624
initialize(nsIFile * aDatabaseFile)625 nsresult Connection::initialize(nsIFile* aDatabaseFile) {
626 NS_ASSERTION(aDatabaseFile, "Passed null file!");
627 NS_ASSERTION(!connectionReady(),
628 "Initialize called on already opened database!");
629 AUTO_PROFILER_LABEL("Connection::initialize", OTHER);
630
631 mDatabaseFile = aDatabaseFile;
632
633 nsAutoString path;
634 nsresult rv = aDatabaseFile->GetPath(path);
635 NS_ENSURE_SUCCESS(rv, rv);
636
637 #ifdef XP_WIN
638 static const char* sIgnoreLockingVFS = "win32-none";
639 #else
640 static const char* sIgnoreLockingVFS = "unix-none";
641 #endif
642 const char* vfs = mIgnoreLockingMode ? sIgnoreLockingVFS : GetVFSName();
643
644 int srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn,
645 mFlags, vfs);
646 if (srv != SQLITE_OK) {
647 mDBConn = nullptr;
648 return convertResultCode(srv);
649 }
650
651 #ifdef MOZ_SQLITE_FTS3_TOKENIZER
652 srv =
653 ::sqlite3_db_config(mDBConn, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0);
654 MOZ_ASSERT(srv == SQLITE_OK,
655 "SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER should be enabled");
656 #endif
657
658 // Do not set mFileURL here since this is database does not have an associated
659 // URL.
660 mDatabaseFile = aDatabaseFile;
661
662 rv = initializeInternal();
663 NS_ENSURE_SUCCESS(rv, rv);
664
665 return NS_OK;
666 }
667
initialize(nsIFileURL * aFileURL)668 nsresult Connection::initialize(nsIFileURL* aFileURL) {
669 NS_ASSERTION(aFileURL, "Passed null file URL!");
670 NS_ASSERTION(!connectionReady(),
671 "Initialize called on already opened database!");
672 AUTO_PROFILER_LABEL("Connection::initialize", OTHER);
673
674 nsCOMPtr<nsIFile> databaseFile;
675 nsresult rv = aFileURL->GetFile(getter_AddRefs(databaseFile));
676 NS_ENSURE_SUCCESS(rv, rv);
677
678 nsAutoCString spec;
679 rv = aFileURL->GetSpec(spec);
680 NS_ENSURE_SUCCESS(rv, rv);
681
682 int srv = ::sqlite3_open_v2(spec.get(), &mDBConn, mFlags, GetVFSName());
683 if (srv != SQLITE_OK) {
684 mDBConn = nullptr;
685 return convertResultCode(srv);
686 }
687
688 #ifdef MOZ_SQLITE_FTS3_TOKENIZER
689 srv =
690 ::sqlite3_db_config(mDBConn, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0);
691 MOZ_ASSERT(srv == SQLITE_OK,
692 "SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER should be enabled");
693 #endif
694
695 // Set both mDatabaseFile and mFileURL here.
696 mFileURL = aFileURL;
697 mDatabaseFile = databaseFile;
698
699 rv = initializeInternal();
700 NS_ENSURE_SUCCESS(rv, rv);
701
702 return NS_OK;
703 }
704
initializeInternal()705 nsresult Connection::initializeInternal() {
706 MOZ_ASSERT(mDBConn);
707
708 auto guard = MakeScopeExit([&]() { initializeFailed(); });
709
710 if (mFileURL) {
711 const char* dbPath = ::sqlite3_db_filename(mDBConn, "main");
712 MOZ_ASSERT(dbPath);
713
714 const char* telemetryFilename =
715 ::sqlite3_uri_parameter(dbPath, "telemetryFilename");
716 if (telemetryFilename) {
717 if (NS_WARN_IF(*telemetryFilename == '\0')) {
718 return NS_ERROR_INVALID_ARG;
719 }
720 mTelemetryFilename = telemetryFilename;
721 }
722 }
723
724 if (mTelemetryFilename.IsEmpty()) {
725 mTelemetryFilename = getFilename();
726 MOZ_ASSERT(!mTelemetryFilename.IsEmpty());
727 }
728
729 // Properly wrap the database handle's mutex.
730 sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn));
731
732 // SQLite tracing can slow down queries (especially long queries)
733 // significantly. Don't trace unless the user is actively monitoring SQLite.
734 if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) {
735 ::sqlite3_trace_v2(mDBConn, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE,
736 tracefunc, this);
737
738 MOZ_LOG(
739 gStorageLog, LogLevel::Debug,
740 ("Opening connection to '%s' (%p)", mTelemetryFilename.get(), this));
741 }
742
743 int64_t pageSize = Service::getDefaultPageSize();
744
745 // Set page_size to the preferred default value. This is effective only if
746 // the database has just been created, otherwise, if the database does not
747 // use WAL journal mode, a VACUUM operation will updated its page_size.
748 nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
749 "PRAGMA page_size = ");
750 pageSizeQuery.AppendInt(pageSize);
751 int srv = executeSql(mDBConn, pageSizeQuery.get());
752 if (srv != SQLITE_OK) {
753 return convertResultCode(srv);
754 }
755
756 // Setting the cache_size forces the database open, verifying if it is valid
757 // or corrupt. So this is executed regardless it being actually needed.
758 // The cache_size is calculated from the actual page_size, to save memory.
759 nsAutoCString cacheSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
760 "PRAGMA cache_size = ");
761 cacheSizeQuery.AppendInt(-MAX_CACHE_SIZE_KIBIBYTES);
762 srv = executeSql(mDBConn, cacheSizeQuery.get());
763 if (srv != SQLITE_OK) {
764 return convertResultCode(srv);
765 }
766
767 // Register our built-in SQL functions.
768 srv = registerFunctions(mDBConn);
769 if (srv != SQLITE_OK) {
770 return convertResultCode(srv);
771 }
772
773 // Register our built-in SQL collating sequences.
774 srv = registerCollations(mDBConn, mStorageService);
775 if (srv != SQLITE_OK) {
776 return convertResultCode(srv);
777 }
778
779 // Set the synchronous PRAGMA, according to the preference.
780 switch (Service::getSynchronousPref()) {
781 case 2:
782 (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = FULL;"));
783 break;
784 case 0:
785 (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
786 break;
787 case 1:
788 default:
789 (void)ExecuteSimpleSQL(
790 NS_LITERAL_CSTRING("PRAGMA synchronous = NORMAL;"));
791 break;
792 }
793
794 // Initialization succeeded, we can stop guarding for failures.
795 guard.release();
796 return NS_OK;
797 }
798
initializeOnAsyncThread(nsIFile * aStorageFile)799 nsresult Connection::initializeOnAsyncThread(nsIFile* aStorageFile) {
800 MOZ_ASSERT(threadOpenedOn != NS_GetCurrentThread());
801 nsresult rv = aStorageFile ? initialize(aStorageFile) : initialize();
802 if (NS_FAILED(rv)) {
803 // Shutdown the async thread, since initialization failed.
804 MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
805 mAsyncExecutionThreadShuttingDown = true;
806 nsCOMPtr<nsIRunnable> event =
807 NewRunnableMethod("Connection::shutdownAsyncThread", this,
808 &Connection::shutdownAsyncThread);
809 Unused << NS_DispatchToMainThread(event);
810 }
811 return rv;
812 }
813
initializeFailed()814 void Connection::initializeFailed() {
815 {
816 MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
817 mConnectionClosed = true;
818 }
819 MOZ_ALWAYS_TRUE(::sqlite3_close(mDBConn) == SQLITE_OK);
820 mDBConn = nullptr;
821 sharedDBMutex.destroy();
822 }
823
databaseElementExists(enum DatabaseElementType aElementType,const nsACString & aElementName,bool * _exists)824 nsresult Connection::databaseElementExists(
825 enum DatabaseElementType aElementType, const nsACString& aElementName,
826 bool* _exists) {
827 if (!connectionReady()) {
828 return NS_ERROR_NOT_AVAILABLE;
829 }
830 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
831 if (NS_FAILED(rv)) {
832 return rv;
833 }
834
835 // When constructing the query, make sure to SELECT the correct db's
836 // sqlite_master if the user is prefixing the element with a specific db. ex:
837 // sample.test
838 nsCString query("SELECT name FROM (SELECT * FROM ");
839 nsDependentCSubstring element;
840 int32_t ind = aElementName.FindChar('.');
841 if (ind == kNotFound) {
842 element.Assign(aElementName);
843 } else {
844 nsDependentCSubstring db(Substring(aElementName, 0, ind + 1));
845 element.Assign(Substring(aElementName, ind + 1, aElementName.Length()));
846 query.Append(db);
847 }
848 query.AppendLiteral(
849 "sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = "
850 "'");
851
852 switch (aElementType) {
853 case INDEX:
854 query.AppendLiteral("index");
855 break;
856 case TABLE:
857 query.AppendLiteral("table");
858 break;
859 }
860 query.AppendLiteral("' AND name ='");
861 query.Append(element);
862 query.Append('\'');
863
864 sqlite3_stmt* stmt;
865 int srv = prepareStatement(mDBConn, query, &stmt);
866 if (srv != SQLITE_OK) return convertResultCode(srv);
867
868 srv = stepStatement(mDBConn, stmt);
869 // we just care about the return value from step
870 (void)::sqlite3_finalize(stmt);
871
872 if (srv == SQLITE_ROW) {
873 *_exists = true;
874 return NS_OK;
875 }
876 if (srv == SQLITE_DONE) {
877 *_exists = false;
878 return NS_OK;
879 }
880
881 return convertResultCode(srv);
882 }
883
findFunctionByInstance(nsISupports * aInstance)884 bool Connection::findFunctionByInstance(nsISupports* aInstance) {
885 sharedDBMutex.assertCurrentThreadOwns();
886
887 for (auto iter = mFunctions.Iter(); !iter.Done(); iter.Next()) {
888 if (iter.UserData().function == aInstance) {
889 return true;
890 }
891 }
892 return false;
893 }
894
895 /* static */
sProgressHelper(void * aArg)896 int Connection::sProgressHelper(void* aArg) {
897 Connection* _this = static_cast<Connection*>(aArg);
898 return _this->progressHandler();
899 }
900
progressHandler()901 int Connection::progressHandler() {
902 sharedDBMutex.assertCurrentThreadOwns();
903 if (mProgressHandler) {
904 bool result;
905 nsresult rv = mProgressHandler->OnProgress(this, &result);
906 if (NS_FAILED(rv)) return 0; // Don't break request
907 return result ? 1 : 0;
908 }
909 return 0;
910 }
911
setClosedState()912 nsresult Connection::setClosedState() {
913 // Ensure that we are on the correct thread to close the database.
914 bool onOpenedThread;
915 nsresult rv = threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
916 NS_ENSURE_SUCCESS(rv, rv);
917 if (!onOpenedThread) {
918 NS_ERROR("Must close the database on the thread that you opened it with!");
919 return NS_ERROR_UNEXPECTED;
920 }
921
922 // Flag that we are shutting down the async thread, so that
923 // getAsyncExecutionTarget knows not to expose/create the async thread.
924 {
925 MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
926 NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED);
927 mAsyncExecutionThreadShuttingDown = true;
928
929 // Set the property to null before closing the connection, otherwise the
930 // other functions in the module may try to use the connection after it is
931 // closed.
932 mDBConn = nullptr;
933 }
934 return NS_OK;
935 }
936
operationSupported(ConnectionOperation aOperationType)937 bool Connection::operationSupported(ConnectionOperation aOperationType) {
938 if (aOperationType == ASYNCHRONOUS) {
939 // Async operations are supported for all connections, on any thread.
940 return true;
941 }
942 // Sync operations are supported for sync connections (on any thread), and
943 // async connections on a background thread.
944 MOZ_ASSERT(aOperationType == SYNCHRONOUS);
945 return mSupportedOperations == SYNCHRONOUS || !NS_IsMainThread();
946 }
947
ensureOperationSupported(ConnectionOperation aOperationType)948 nsresult Connection::ensureOperationSupported(
949 ConnectionOperation aOperationType) {
950 if (NS_WARN_IF(!operationSupported(aOperationType))) {
951 #ifdef DEBUG
952 if (NS_IsMainThread()) {
953 nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
954 Unused << xpc->DebugDumpJSStack(false, false, false);
955 }
956 #endif
957 MOZ_ASSERT(false,
958 "Don't use async connections synchronously on the main thread");
959 return NS_ERROR_NOT_AVAILABLE;
960 }
961 return NS_OK;
962 }
963
isConnectionReadyOnThisThread()964 bool Connection::isConnectionReadyOnThisThread() {
965 MOZ_ASSERT_IF(connectionReady(), !mConnectionClosed);
966 if (mAsyncExecutionThread && mAsyncExecutionThread->IsOnCurrentThread()) {
967 return true;
968 }
969 return connectionReady();
970 }
971
isClosing()972 bool Connection::isClosing() {
973 MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
974 return mAsyncExecutionThreadShuttingDown && !mConnectionClosed;
975 }
976
isClosed()977 bool Connection::isClosed() {
978 MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
979 return mConnectionClosed;
980 }
981
isClosed(MutexAutoLock & lock)982 bool Connection::isClosed(MutexAutoLock& lock) { return mConnectionClosed; }
983
isAsyncExecutionThreadAvailable()984 bool Connection::isAsyncExecutionThreadAvailable() {
985 MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
986 return mAsyncExecutionThread && !mAsyncExecutionThreadShuttingDown;
987 }
988
shutdownAsyncThread()989 void Connection::shutdownAsyncThread() {
990 MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
991 MOZ_ASSERT(mAsyncExecutionThread);
992 MOZ_ASSERT(mAsyncExecutionThreadShuttingDown);
993
994 MOZ_ALWAYS_SUCCEEDS(mAsyncExecutionThread->Shutdown());
995 mAsyncExecutionThread = nullptr;
996 }
997
internalClose(sqlite3 * aNativeConnection)998 nsresult Connection::internalClose(sqlite3* aNativeConnection) {
999 #ifdef DEBUG
1000 { // Make sure we have marked our async thread as shutting down.
1001 MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
1002 MOZ_ASSERT(mAsyncExecutionThreadShuttingDown,
1003 "Did not call setClosedState!");
1004 MOZ_ASSERT(!isClosed(lockedScope), "Unexpected closed state");
1005 }
1006 #endif // DEBUG
1007
1008 if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) {
1009 nsAutoCString leafName(":memory");
1010 if (mDatabaseFile) (void)mDatabaseFile->GetNativeLeafName(leafName);
1011 MOZ_LOG(gStorageLog, LogLevel::Debug,
1012 ("Closing connection to '%s'", leafName.get()));
1013 }
1014
1015 // At this stage, we may still have statements that need to be
1016 // finalized. Attempt to close the database connection. This will
1017 // always disconnect any virtual tables and cleanly finalize their
1018 // internal statements. Once this is done, closing may fail due to
1019 // unfinalized client statements, in which case we need to finalize
1020 // these statements and close again.
1021 {
1022 MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
1023 mConnectionClosed = true;
1024 }
1025
1026 // Nothing else needs to be done if we don't have a connection here.
1027 if (!aNativeConnection) return NS_OK;
1028
1029 int srv = ::sqlite3_close(aNativeConnection);
1030
1031 if (srv == SQLITE_BUSY) {
1032 {
1033 // Nothing else should change the connection or statements status until we
1034 // are done here.
1035 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1036 // We still have non-finalized statements. Finalize them.
1037 sqlite3_stmt* stmt = nullptr;
1038 while ((stmt = ::sqlite3_next_stmt(aNativeConnection, stmt))) {
1039 MOZ_LOG(gStorageLog, LogLevel::Debug,
1040 ("Auto-finalizing SQL statement '%s' (%p)", ::sqlite3_sql(stmt),
1041 stmt));
1042
1043 #ifdef DEBUG
1044 SmprintfPointer msg = ::mozilla::Smprintf(
1045 "SQL statement '%s' (%p) should have been finalized before closing "
1046 "the connection",
1047 ::sqlite3_sql(stmt), stmt);
1048 NS_WARNING(msg.get());
1049 #endif // DEBUG
1050
1051 srv = ::sqlite3_finalize(stmt);
1052
1053 #ifdef DEBUG
1054 if (srv != SQLITE_OK) {
1055 SmprintfPointer msg = ::mozilla::Smprintf(
1056 "Could not finalize SQL statement (%p)", stmt);
1057 NS_WARNING(msg.get());
1058 }
1059 #endif // DEBUG
1060
1061 // Ensure that the loop continues properly, whether closing has
1062 // succeeded or not.
1063 if (srv == SQLITE_OK) {
1064 stmt = nullptr;
1065 }
1066 }
1067 // Scope exiting will unlock the mutex before we invoke sqlite3_close()
1068 // again, since Sqlite will try to acquire it.
1069 }
1070
1071 // Now that all statements have been finalized, we
1072 // should be able to close.
1073 srv = ::sqlite3_close(aNativeConnection);
1074 MOZ_ASSERT(false,
1075 "Had to forcibly close the database connection because not all "
1076 "the statements have been finalized.");
1077 }
1078
1079 if (srv == SQLITE_OK) {
1080 sharedDBMutex.destroy();
1081 } else {
1082 MOZ_ASSERT(false,
1083 "sqlite3_close failed. There are probably outstanding "
1084 "statements that are listed above!");
1085 }
1086
1087 return convertResultCode(srv);
1088 }
1089
getFilename()1090 nsCString Connection::getFilename() {
1091 nsCString leafname(":memory:");
1092 if (mDatabaseFile) {
1093 (void)mDatabaseFile->GetNativeLeafName(leafname);
1094 }
1095 return leafname;
1096 }
1097
stepStatement(sqlite3 * aNativeConnection,sqlite3_stmt * aStatement)1098 int Connection::stepStatement(sqlite3* aNativeConnection,
1099 sqlite3_stmt* aStatement) {
1100 MOZ_ASSERT(aStatement);
1101
1102 AUTO_PROFILER_LABEL_DYNAMIC_CSTR("Connection::stepStatement", OTHER,
1103 ::sqlite3_sql(aStatement));
1104
1105 bool checkedMainThread = false;
1106 TimeStamp startTime = TimeStamp::Now();
1107
1108 // The connection may have been closed if the executing statement has been
1109 // created and cached after a call to asyncClose() but before the actual
1110 // sqlite3_close(). This usually happens when other tasks using cached
1111 // statements are asynchronously scheduled for execution and any of them ends
1112 // up after asyncClose. See bug 728653 for details.
1113 if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE;
1114
1115 (void)::sqlite3_extended_result_codes(aNativeConnection, 1);
1116
1117 int srv;
1118 while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) {
1119 if (!checkedMainThread) {
1120 checkedMainThread = true;
1121 if (::NS_IsMainThread()) {
1122 NS_WARNING("We won't allow blocking on the main thread!");
1123 break;
1124 }
1125 }
1126
1127 srv = WaitForUnlockNotify(aNativeConnection);
1128 if (srv != SQLITE_OK) {
1129 break;
1130 }
1131
1132 ::sqlite3_reset(aStatement);
1133 }
1134
1135 // Report very slow SQL statements to Telemetry
1136 TimeDuration duration = TimeStamp::Now() - startTime;
1137 const uint32_t threshold = NS_IsMainThread()
1138 ? Telemetry::kSlowSQLThresholdForMainThread
1139 : Telemetry::kSlowSQLThresholdForHelperThreads;
1140 if (duration.ToMilliseconds() >= threshold) {
1141 nsDependentCString statementString(::sqlite3_sql(aStatement));
1142 Telemetry::RecordSlowSQLStatement(statementString, mTelemetryFilename,
1143 duration.ToMilliseconds());
1144 }
1145
1146 (void)::sqlite3_extended_result_codes(aNativeConnection, 0);
1147 // Drop off the extended result bits of the result code.
1148 return srv & 0xFF;
1149 }
1150
prepareStatement(sqlite3 * aNativeConnection,const nsCString & aSQL,sqlite3_stmt ** _stmt)1151 int Connection::prepareStatement(sqlite3* aNativeConnection,
1152 const nsCString& aSQL, sqlite3_stmt** _stmt) {
1153 // We should not even try to prepare statements after the connection has
1154 // been closed.
1155 if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE;
1156
1157 bool checkedMainThread = false;
1158
1159 (void)::sqlite3_extended_result_codes(aNativeConnection, 1);
1160
1161 int srv;
1162 while ((srv = ::sqlite3_prepare_v2(aNativeConnection, aSQL.get(), -1, _stmt,
1163 nullptr)) == SQLITE_LOCKED_SHAREDCACHE) {
1164 if (!checkedMainThread) {
1165 checkedMainThread = true;
1166 if (::NS_IsMainThread()) {
1167 NS_WARNING("We won't allow blocking on the main thread!");
1168 break;
1169 }
1170 }
1171
1172 srv = WaitForUnlockNotify(aNativeConnection);
1173 if (srv != SQLITE_OK) {
1174 break;
1175 }
1176 }
1177
1178 if (srv != SQLITE_OK) {
1179 nsCString warnMsg;
1180 warnMsg.AppendLiteral("The SQL statement '");
1181 warnMsg.Append(aSQL);
1182 warnMsg.AppendLiteral("' could not be compiled due to an error: ");
1183 warnMsg.Append(::sqlite3_errmsg(aNativeConnection));
1184
1185 #ifdef DEBUG
1186 NS_WARNING(warnMsg.get());
1187 #endif
1188 MOZ_LOG(gStorageLog, LogLevel::Error, ("%s", warnMsg.get()));
1189 }
1190
1191 (void)::sqlite3_extended_result_codes(aNativeConnection, 0);
1192 // Drop off the extended result bits of the result code.
1193 int rc = srv & 0xFF;
1194 // sqlite will return OK on a comment only string and set _stmt to nullptr.
1195 // The callers of this function are used to only checking the return value,
1196 // so it is safer to return an error code.
1197 if (rc == SQLITE_OK && *_stmt == nullptr) {
1198 return SQLITE_MISUSE;
1199 }
1200
1201 return rc;
1202 }
1203
executeSql(sqlite3 * aNativeConnection,const char * aSqlString)1204 int Connection::executeSql(sqlite3* aNativeConnection, const char* aSqlString) {
1205 if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE;
1206
1207 AUTO_PROFILER_LABEL_DYNAMIC_CSTR("Connection::executeSql", OTHER, aSqlString);
1208
1209 TimeStamp startTime = TimeStamp::Now();
1210 int srv =
1211 ::sqlite3_exec(aNativeConnection, aSqlString, nullptr, nullptr, nullptr);
1212
1213 // Report very slow SQL statements to Telemetry
1214 TimeDuration duration = TimeStamp::Now() - startTime;
1215 const uint32_t threshold = NS_IsMainThread()
1216 ? Telemetry::kSlowSQLThresholdForMainThread
1217 : Telemetry::kSlowSQLThresholdForHelperThreads;
1218 if (duration.ToMilliseconds() >= threshold) {
1219 nsDependentCString statementString(aSqlString);
1220 Telemetry::RecordSlowSQLStatement(statementString, mTelemetryFilename,
1221 duration.ToMilliseconds());
1222 }
1223
1224 return srv;
1225 }
1226
1227 ////////////////////////////////////////////////////////////////////////////////
1228 //// nsIInterfaceRequestor
1229
1230 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** _result)1231 Connection::GetInterface(const nsIID& aIID, void** _result) {
1232 if (aIID.Equals(NS_GET_IID(nsIEventTarget))) {
1233 nsIEventTarget* background = getAsyncExecutionTarget();
1234 NS_IF_ADDREF(background);
1235 *_result = background;
1236 return NS_OK;
1237 }
1238 return NS_ERROR_NO_INTERFACE;
1239 }
1240
1241 ////////////////////////////////////////////////////////////////////////////////
1242 //// mozIStorageConnection
1243
1244 NS_IMETHODIMP
Close()1245 Connection::Close() {
1246 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1247 if (NS_FAILED(rv)) {
1248 return rv;
1249 }
1250 return synchronousClose();
1251 }
1252
synchronousClose()1253 nsresult Connection::synchronousClose() {
1254 if (!connectionReady()) {
1255 return NS_ERROR_NOT_INITIALIZED;
1256 }
1257
1258 #ifdef DEBUG
1259 // Since we're accessing mAsyncExecutionThread, we need to be on the opener
1260 // thread. We make this check outside of debug code below in setClosedState,
1261 // but this is here to be explicit.
1262 bool onOpenerThread = false;
1263 (void)threadOpenedOn->IsOnCurrentThread(&onOpenerThread);
1264 MOZ_ASSERT(onOpenerThread);
1265 #endif // DEBUG
1266
1267 // Make sure we have not executed any asynchronous statements.
1268 // If this fails, the mDBConn may be left open, resulting in a leak.
1269 // We'll try to finalize the pending statements and close the connection.
1270 if (isAsyncExecutionThreadAvailable()) {
1271 #ifdef DEBUG
1272 if (NS_IsMainThread()) {
1273 nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
1274 Unused << xpc->DebugDumpJSStack(false, false, false);
1275 }
1276 #endif
1277 MOZ_ASSERT(false,
1278 "Close() was invoked on a connection that executed asynchronous "
1279 "statements. "
1280 "Should have used asyncClose().");
1281 // Try to close the database regardless, to free up resources.
1282 Unused << SpinningSynchronousClose();
1283 return NS_ERROR_UNEXPECTED;
1284 }
1285
1286 // setClosedState nullifies our connection pointer, so we take a raw pointer
1287 // off it, to pass it through the close procedure.
1288 sqlite3* nativeConn = mDBConn;
1289 nsresult rv = setClosedState();
1290 NS_ENSURE_SUCCESS(rv, rv);
1291
1292 return internalClose(nativeConn);
1293 }
1294
1295 NS_IMETHODIMP
SpinningSynchronousClose()1296 Connection::SpinningSynchronousClose() {
1297 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1298 if (NS_FAILED(rv)) {
1299 return rv;
1300 }
1301 if (threadOpenedOn != NS_GetCurrentThread()) {
1302 return NS_ERROR_NOT_SAME_THREAD;
1303 }
1304
1305 // As currently implemented, we can't spin to wait for an existing AsyncClose.
1306 // Our only existing caller will never have called close; assert if misused
1307 // so that no new callers assume this works after an AsyncClose.
1308 MOZ_DIAGNOSTIC_ASSERT(connectionReady());
1309 if (!connectionReady()) {
1310 return NS_ERROR_UNEXPECTED;
1311 }
1312
1313 RefPtr<CloseListener> listener = new CloseListener();
1314 rv = AsyncClose(listener);
1315 NS_ENSURE_SUCCESS(rv, rv);
1316 MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return listener->mClosed; }));
1317 MOZ_ASSERT(isClosed(), "The connection should be closed at this point");
1318
1319 return rv;
1320 }
1321
1322 NS_IMETHODIMP
AsyncClose(mozIStorageCompletionCallback * aCallback)1323 Connection::AsyncClose(mozIStorageCompletionCallback* aCallback) {
1324 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
1325 // Check if AsyncClose or Close were already invoked.
1326 if (!connectionReady()) {
1327 return NS_ERROR_NOT_INITIALIZED;
1328 }
1329 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
1330 if (NS_FAILED(rv)) {
1331 return rv;
1332 }
1333
1334 // The two relevant factors at this point are whether we have a database
1335 // connection and whether we have an async execution thread. Here's what the
1336 // states mean and how we handle them:
1337 //
1338 // - (mDBConn && asyncThread): The expected case where we are either an
1339 // async connection or a sync connection that has been used asynchronously.
1340 // Either way the caller must call us and not Close(). Nothing surprising
1341 // about this. We'll dispatch AsyncCloseConnection to the already-existing
1342 // async thread.
1343 //
1344 // - (mDBConn && !asyncThread): A somewhat unusual case where the caller
1345 // opened the connection synchronously and was planning to use it
1346 // asynchronously, but never got around to using it asynchronously before
1347 // needing to shutdown. This has been observed to happen for the cookie
1348 // service in a case where Firefox shuts itself down almost immediately
1349 // after startup (for unknown reasons). In the Firefox shutdown case,
1350 // we may also fail to create a new async execution thread if one does not
1351 // already exist. (nsThreadManager will refuse to create new threads when
1352 // it has already been told to shutdown.) As such, we need to handle a
1353 // failure to create the async execution thread by falling back to
1354 // synchronous Close() and also dispatching the completion callback because
1355 // at least Places likes to spin a nested event loop that depends on the
1356 // callback being invoked.
1357 //
1358 // Note that we have considered not trying to spin up the async execution
1359 // thread in this case if it does not already exist, but the overhead of
1360 // thread startup (if successful) is significantly less expensive than the
1361 // worst-case potential I/O hit of synchronously closing a database when we
1362 // could close it asynchronously.
1363 //
1364 // - (!mDBConn && asyncThread): This happens in some but not all cases where
1365 // OpenAsyncDatabase encountered a problem opening the database. If it
1366 // happened in all cases AsyncInitDatabase would just shut down the thread
1367 // directly and we would avoid this case. But it doesn't, so for simplicity
1368 // and consistency AsyncCloseConnection knows how to handle this and we
1369 // act like this was the (mDBConn && asyncThread) case in this method.
1370 //
1371 // - (!mDBConn && !asyncThread): The database was never successfully opened or
1372 // Close() or AsyncClose() has already been called (at least) once. This is
1373 // undeniably a misuse case by the caller. We could optimize for this
1374 // case by adding an additional check of mAsyncExecutionThread without using
1375 // getAsyncExecutionTarget() to avoid wastefully creating a thread just to
1376 // shut it down. But this complicates the method for broken caller code
1377 // whereas we're still correct and safe without the special-case.
1378 nsIEventTarget* asyncThread = getAsyncExecutionTarget();
1379
1380 // Create our callback event if we were given a callback. This will
1381 // eventually be dispatched in all cases, even if we fall back to Close() and
1382 // the database wasn't open and we return an error. The rationale is that
1383 // no existing consumer checks our return value and several of them like to
1384 // spin nested event loops until the callback fires. Given that, it seems
1385 // preferable for us to dispatch the callback in all cases. (Except the
1386 // wrong thread misuse case we bailed on up above. But that's okay because
1387 // that is statically wrong whereas these edge cases are dynamic.)
1388 nsCOMPtr<nsIRunnable> completeEvent;
1389 if (aCallback) {
1390 completeEvent = newCompletionEvent(aCallback);
1391 }
1392
1393 if (!asyncThread) {
1394 // We were unable to create an async thread, so we need to fall back to
1395 // using normal Close(). Since there is no async thread, Close() will
1396 // not complain about that. (Close() may, however, complain if the
1397 // connection is closed, but that's okay.)
1398 if (completeEvent) {
1399 // Closing the database is more important than returning an error code
1400 // about a failure to dispatch, especially because all existing native
1401 // callers ignore our return value.
1402 Unused << NS_DispatchToMainThread(completeEvent.forget());
1403 }
1404 MOZ_ALWAYS_SUCCEEDS(synchronousClose());
1405 // Return a success inconditionally here, since Close() is unlikely to fail
1406 // and we want to reassure the consumer that its callback will be invoked.
1407 return NS_OK;
1408 }
1409
1410 // setClosedState nullifies our connection pointer, so we take a raw pointer
1411 // off it, to pass it through the close procedure.
1412 sqlite3* nativeConn = mDBConn;
1413 rv = setClosedState();
1414 NS_ENSURE_SUCCESS(rv, rv);
1415
1416 // Create and dispatch our close event to the background thread.
1417 nsCOMPtr<nsIRunnable> closeEvent =
1418 new AsyncCloseConnection(this, nativeConn, completeEvent);
1419 rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL);
1420 NS_ENSURE_SUCCESS(rv, rv);
1421
1422 return NS_OK;
1423 }
1424
1425 NS_IMETHODIMP
AsyncClone(bool aReadOnly,mozIStorageCompletionCallback * aCallback)1426 Connection::AsyncClone(bool aReadOnly,
1427 mozIStorageCompletionCallback* aCallback) {
1428 AUTO_PROFILER_LABEL("Connection::AsyncClone", OTHER);
1429
1430 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
1431 if (!connectionReady()) {
1432 return NS_ERROR_NOT_INITIALIZED;
1433 }
1434 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
1435 if (NS_FAILED(rv)) {
1436 return rv;
1437 }
1438 if (!mDatabaseFile) return NS_ERROR_UNEXPECTED;
1439
1440 int flags = mFlags;
1441 if (aReadOnly) {
1442 // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
1443 flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY;
1444 // Turn off SQLITE_OPEN_CREATE.
1445 flags = (~SQLITE_OPEN_CREATE & flags);
1446 }
1447
1448 // The cloned connection will still implement the synchronous API, but throw
1449 // if any synchronous methods are called on the main thread.
1450 RefPtr<Connection> clone =
1451 new Connection(mStorageService, flags, ASYNCHRONOUS);
1452
1453 RefPtr<AsyncInitializeClone> initEvent =
1454 new AsyncInitializeClone(this, clone, aReadOnly, aCallback);
1455 // Dispatch to our async thread, since the originating connection must remain
1456 // valid and open for the whole cloning process. This also ensures we are
1457 // properly serialized with a `close` operation, rather than race with it.
1458 nsCOMPtr<nsIEventTarget> target = getAsyncExecutionTarget();
1459 if (!target) {
1460 return NS_ERROR_UNEXPECTED;
1461 }
1462 return target->Dispatch(initEvent, NS_DISPATCH_NORMAL);
1463 }
1464
initializeClone(Connection * aClone,bool aReadOnly)1465 nsresult Connection::initializeClone(Connection* aClone, bool aReadOnly) {
1466 nsresult rv = mFileURL ? aClone->initialize(mFileURL)
1467 : aClone->initialize(mDatabaseFile);
1468 if (NS_FAILED(rv)) {
1469 return rv;
1470 }
1471
1472 auto guard = MakeScopeExit([&]() { aClone->initializeFailed(); });
1473
1474 rv = aClone->SetDefaultTransactionType(mDefaultTransactionType);
1475 NS_ENSURE_SUCCESS(rv, rv);
1476
1477 // Re-attach on-disk databases that were attached to the original connection.
1478 {
1479 nsCOMPtr<mozIStorageStatement> stmt;
1480 rv = CreateStatement(NS_LITERAL_CSTRING("PRAGMA database_list"),
1481 getter_AddRefs(stmt));
1482 MOZ_ASSERT(NS_SUCCEEDED(rv));
1483 bool hasResult = false;
1484 while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1485 nsAutoCString name;
1486 rv = stmt->GetUTF8String(1, name);
1487 if (NS_SUCCEEDED(rv) && !name.EqualsLiteral("main") &&
1488 !name.EqualsLiteral("temp")) {
1489 nsCString path;
1490 rv = stmt->GetUTF8String(2, path);
1491 if (NS_SUCCEEDED(rv) && !path.IsEmpty()) {
1492 nsCOMPtr<mozIStorageStatement> attachStmt;
1493 rv = aClone->CreateStatement(
1494 NS_LITERAL_CSTRING("ATTACH DATABASE :path AS ") + name,
1495 getter_AddRefs(attachStmt));
1496 MOZ_ASSERT(NS_SUCCEEDED(rv));
1497 rv = attachStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
1498 path);
1499 MOZ_ASSERT(NS_SUCCEEDED(rv));
1500 rv = attachStmt->Execute();
1501 MOZ_ASSERT(NS_SUCCEEDED(rv),
1502 "couldn't re-attach database to cloned connection");
1503 }
1504 }
1505 }
1506 }
1507
1508 // Copy over pragmas from the original connection.
1509 // LIMITATION WARNING! Many of these pragmas are actually scoped to the
1510 // schema ("main" and any other attached databases), and this implmentation
1511 // fails to propagate them. This is being addressed on trunk.
1512 static const char* pragmas[] = {
1513 "cache_size", "temp_store", "foreign_keys", "journal_size_limit",
1514 "synchronous", "wal_autocheckpoint", "busy_timeout"};
1515 for (auto& pragma : pragmas) {
1516 // Read-only connections just need cache_size and temp_store pragmas.
1517 if (aReadOnly && ::strcmp(pragma, "cache_size") != 0 &&
1518 ::strcmp(pragma, "temp_store") != 0) {
1519 continue;
1520 }
1521
1522 nsAutoCString pragmaQuery("PRAGMA ");
1523 pragmaQuery.Append(pragma);
1524 nsCOMPtr<mozIStorageStatement> stmt;
1525 rv = CreateStatement(pragmaQuery, getter_AddRefs(stmt));
1526 MOZ_ASSERT(NS_SUCCEEDED(rv));
1527 bool hasResult = false;
1528 if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1529 pragmaQuery.AppendLiteral(" = ");
1530 pragmaQuery.AppendInt(stmt->AsInt32(0));
1531 rv = aClone->ExecuteSimpleSQL(pragmaQuery);
1532 MOZ_ASSERT(NS_SUCCEEDED(rv));
1533 }
1534 }
1535
1536 // Copy over temporary tables, triggers, and views from the original
1537 // connections. Entities in `sqlite_temp_master` are only visible to the
1538 // connection that created them.
1539 if (!aReadOnly) {
1540 rv = aClone->ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN TRANSACTION"));
1541 NS_ENSURE_SUCCESS(rv, rv);
1542
1543 nsCOMPtr<mozIStorageStatement> stmt;
1544 rv =
1545 CreateStatement(NS_LITERAL_CSTRING("SELECT sql FROM sqlite_temp_master "
1546 "WHERE type IN ('table', 'view', "
1547 "'index', 'trigger')"),
1548 getter_AddRefs(stmt));
1549 // Propagate errors, because failing to copy triggers might cause schema
1550 // coherency issues when writing to the database from the cloned connection.
1551 NS_ENSURE_SUCCESS(rv, rv);
1552 bool hasResult = false;
1553 while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1554 nsAutoCString query;
1555 rv = stmt->GetUTF8String(0, query);
1556 NS_ENSURE_SUCCESS(rv, rv);
1557
1558 // The `CREATE` SQL statements in `sqlite_temp_master` omit the `TEMP`
1559 // keyword. We need to add it back, or we'll recreate temporary entities
1560 // as persistent ones. `sqlite_temp_master` also holds `CREATE INDEX`
1561 // statements, but those don't need `TEMP` keywords.
1562 if (StringBeginsWith(query, NS_LITERAL_CSTRING("CREATE TABLE ")) ||
1563 StringBeginsWith(query, NS_LITERAL_CSTRING("CREATE TRIGGER ")) ||
1564 StringBeginsWith(query, NS_LITERAL_CSTRING("CREATE VIEW "))) {
1565 query.Replace(0, 6, "CREATE TEMP");
1566 }
1567
1568 rv = aClone->ExecuteSimpleSQL(query);
1569 NS_ENSURE_SUCCESS(rv, rv);
1570 }
1571
1572 rv = aClone->ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT"));
1573 NS_ENSURE_SUCCESS(rv, rv);
1574 }
1575
1576 // Copy any functions that have been added to this connection.
1577 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1578 for (auto iter = mFunctions.Iter(); !iter.Done(); iter.Next()) {
1579 const nsACString& key = iter.Key();
1580 Connection::FunctionInfo data = iter.UserData();
1581
1582 MOZ_ASSERT(data.type == Connection::FunctionInfo::SIMPLE ||
1583 data.type == Connection::FunctionInfo::AGGREGATE,
1584 "Invalid function type!");
1585
1586 if (data.type == Connection::FunctionInfo::SIMPLE) {
1587 mozIStorageFunction* function =
1588 static_cast<mozIStorageFunction*>(data.function.get());
1589 rv = aClone->CreateFunction(key, data.numArgs, function);
1590 if (NS_FAILED(rv)) {
1591 NS_WARNING("Failed to copy function to cloned connection");
1592 }
1593
1594 } else {
1595 mozIStorageAggregateFunction* function =
1596 static_cast<mozIStorageAggregateFunction*>(data.function.get());
1597 rv = aClone->CreateAggregateFunction(key, data.numArgs, function);
1598 if (NS_FAILED(rv)) {
1599 NS_WARNING("Failed to copy aggregate function to cloned connection");
1600 }
1601 }
1602 }
1603
1604 guard.release();
1605 return NS_OK;
1606 }
1607
1608 NS_IMETHODIMP
Clone(bool aReadOnly,mozIStorageConnection ** _connection)1609 Connection::Clone(bool aReadOnly, mozIStorageConnection** _connection) {
1610 MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
1611
1612 AUTO_PROFILER_LABEL("Connection::Clone", OTHER);
1613
1614 if (!connectionReady()) {
1615 return NS_ERROR_NOT_INITIALIZED;
1616 }
1617 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1618 if (NS_FAILED(rv)) {
1619 return rv;
1620 }
1621 if (!mDatabaseFile) return NS_ERROR_UNEXPECTED;
1622
1623 int flags = mFlags;
1624 if (aReadOnly) {
1625 // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
1626 flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY;
1627 // Turn off SQLITE_OPEN_CREATE.
1628 flags = (~SQLITE_OPEN_CREATE & flags);
1629 }
1630
1631 RefPtr<Connection> clone =
1632 new Connection(mStorageService, flags, mSupportedOperations);
1633
1634 rv = initializeClone(clone, aReadOnly);
1635 if (NS_FAILED(rv)) {
1636 return rv;
1637 }
1638
1639 NS_IF_ADDREF(*_connection = clone);
1640 return NS_OK;
1641 }
1642
1643 NS_IMETHODIMP
Interrupt()1644 Connection::Interrupt() {
1645 MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
1646 if (!connectionReady()) {
1647 return NS_ERROR_NOT_INITIALIZED;
1648 }
1649 if (operationSupported(SYNCHRONOUS) || !(mFlags & SQLITE_OPEN_READONLY)) {
1650 // Interrupting a synchronous connection from the same thread doesn't make
1651 // sense, and read-write connections aren't safe to interrupt.
1652 return NS_ERROR_INVALID_ARG;
1653 }
1654 ::sqlite3_interrupt(mDBConn);
1655 return NS_OK;
1656 }
1657
1658 NS_IMETHODIMP
GetDefaultPageSize(int32_t * _defaultPageSize)1659 Connection::GetDefaultPageSize(int32_t* _defaultPageSize) {
1660 *_defaultPageSize = Service::getDefaultPageSize();
1661 return NS_OK;
1662 }
1663
1664 NS_IMETHODIMP
GetConnectionReady(bool * _ready)1665 Connection::GetConnectionReady(bool* _ready) {
1666 MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
1667 *_ready = connectionReady();
1668 return NS_OK;
1669 }
1670
1671 NS_IMETHODIMP
GetDatabaseFile(nsIFile ** _dbFile)1672 Connection::GetDatabaseFile(nsIFile** _dbFile) {
1673 if (!connectionReady()) {
1674 return NS_ERROR_NOT_INITIALIZED;
1675 }
1676 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
1677 if (NS_FAILED(rv)) {
1678 return rv;
1679 }
1680
1681 NS_IF_ADDREF(*_dbFile = mDatabaseFile);
1682
1683 return NS_OK;
1684 }
1685
1686 NS_IMETHODIMP
GetLastInsertRowID(int64_t * _id)1687 Connection::GetLastInsertRowID(int64_t* _id) {
1688 if (!connectionReady()) {
1689 return NS_ERROR_NOT_INITIALIZED;
1690 }
1691 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1692 if (NS_FAILED(rv)) {
1693 return rv;
1694 }
1695
1696 sqlite_int64 id = ::sqlite3_last_insert_rowid(mDBConn);
1697 *_id = id;
1698
1699 return NS_OK;
1700 }
1701
1702 NS_IMETHODIMP
GetAffectedRows(int32_t * _rows)1703 Connection::GetAffectedRows(int32_t* _rows) {
1704 if (!connectionReady()) {
1705 return NS_ERROR_NOT_INITIALIZED;
1706 }
1707 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1708 if (NS_FAILED(rv)) {
1709 return rv;
1710 }
1711
1712 *_rows = ::sqlite3_changes(mDBConn);
1713
1714 return NS_OK;
1715 }
1716
1717 NS_IMETHODIMP
GetLastError(int32_t * _error)1718 Connection::GetLastError(int32_t* _error) {
1719 if (!connectionReady()) {
1720 return NS_ERROR_NOT_INITIALIZED;
1721 }
1722 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1723 if (NS_FAILED(rv)) {
1724 return rv;
1725 }
1726
1727 *_error = ::sqlite3_errcode(mDBConn);
1728
1729 return NS_OK;
1730 }
1731
1732 NS_IMETHODIMP
GetLastErrorString(nsACString & _errorString)1733 Connection::GetLastErrorString(nsACString& _errorString) {
1734 if (!connectionReady()) {
1735 return NS_ERROR_NOT_INITIALIZED;
1736 }
1737 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1738 if (NS_FAILED(rv)) {
1739 return rv;
1740 }
1741
1742 const char* serr = ::sqlite3_errmsg(mDBConn);
1743 _errorString.Assign(serr);
1744
1745 return NS_OK;
1746 }
1747
1748 NS_IMETHODIMP
GetSchemaVersion(int32_t * _version)1749 Connection::GetSchemaVersion(int32_t* _version) {
1750 if (!connectionReady()) {
1751 return NS_ERROR_NOT_INITIALIZED;
1752 }
1753 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1754 if (NS_FAILED(rv)) {
1755 return rv;
1756 }
1757
1758 nsCOMPtr<mozIStorageStatement> stmt;
1759 (void)CreateStatement(NS_LITERAL_CSTRING("PRAGMA user_version"),
1760 getter_AddRefs(stmt));
1761 NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY);
1762
1763 *_version = 0;
1764 bool hasResult;
1765 if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult)
1766 *_version = stmt->AsInt32(0);
1767
1768 return NS_OK;
1769 }
1770
1771 NS_IMETHODIMP
SetSchemaVersion(int32_t aVersion)1772 Connection::SetSchemaVersion(int32_t aVersion) {
1773 if (!connectionReady()) {
1774 return NS_ERROR_NOT_INITIALIZED;
1775 }
1776 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1777 if (NS_FAILED(rv)) {
1778 return rv;
1779 }
1780
1781 nsAutoCString stmt(NS_LITERAL_CSTRING("PRAGMA user_version = "));
1782 stmt.AppendInt(aVersion);
1783
1784 return ExecuteSimpleSQL(stmt);
1785 }
1786
1787 NS_IMETHODIMP
CreateStatement(const nsACString & aSQLStatement,mozIStorageStatement ** _stmt)1788 Connection::CreateStatement(const nsACString& aSQLStatement,
1789 mozIStorageStatement** _stmt) {
1790 NS_ENSURE_ARG_POINTER(_stmt);
1791 if (!connectionReady()) {
1792 return NS_ERROR_NOT_INITIALIZED;
1793 }
1794 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1795 if (NS_FAILED(rv)) {
1796 return rv;
1797 }
1798
1799 RefPtr<Statement> statement(new Statement());
1800 NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
1801
1802 rv = statement->initialize(this, mDBConn, aSQLStatement);
1803 NS_ENSURE_SUCCESS(rv, rv);
1804
1805 Statement* rawPtr;
1806 statement.forget(&rawPtr);
1807 *_stmt = rawPtr;
1808 return NS_OK;
1809 }
1810
1811 NS_IMETHODIMP
CreateAsyncStatement(const nsACString & aSQLStatement,mozIStorageAsyncStatement ** _stmt)1812 Connection::CreateAsyncStatement(const nsACString& aSQLStatement,
1813 mozIStorageAsyncStatement** _stmt) {
1814 NS_ENSURE_ARG_POINTER(_stmt);
1815 if (!connectionReady()) {
1816 return NS_ERROR_NOT_INITIALIZED;
1817 }
1818 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
1819 if (NS_FAILED(rv)) {
1820 return rv;
1821 }
1822
1823 RefPtr<AsyncStatement> statement(new AsyncStatement());
1824 NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
1825
1826 rv = statement->initialize(this, mDBConn, aSQLStatement);
1827 NS_ENSURE_SUCCESS(rv, rv);
1828
1829 AsyncStatement* rawPtr;
1830 statement.forget(&rawPtr);
1831 *_stmt = rawPtr;
1832 return NS_OK;
1833 }
1834
1835 NS_IMETHODIMP
ExecuteSimpleSQL(const nsACString & aSQLStatement)1836 Connection::ExecuteSimpleSQL(const nsACString& aSQLStatement) {
1837 CHECK_MAINTHREAD_ABUSE();
1838 if (!connectionReady()) {
1839 return NS_ERROR_NOT_INITIALIZED;
1840 }
1841 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1842 if (NS_FAILED(rv)) {
1843 return rv;
1844 }
1845
1846 int srv = executeSql(mDBConn, PromiseFlatCString(aSQLStatement).get());
1847 return convertResultCode(srv);
1848 }
1849
1850 NS_IMETHODIMP
ExecuteAsync(const nsTArray<RefPtr<mozIStorageBaseStatement>> & aStatements,mozIStorageStatementCallback * aCallback,mozIStoragePendingStatement ** _handle)1851 Connection::ExecuteAsync(
1852 const nsTArray<RefPtr<mozIStorageBaseStatement>>& aStatements,
1853 mozIStorageStatementCallback* aCallback,
1854 mozIStoragePendingStatement** _handle) {
1855 nsTArray<StatementData> stmts(aStatements.Length());
1856 for (uint32_t i = 0; i < aStatements.Length(); i++) {
1857 nsCOMPtr<StorageBaseStatementInternal> stmt =
1858 do_QueryInterface(aStatements[i]);
1859 NS_ENSURE_STATE(stmt);
1860
1861 // Obtain our StatementData.
1862 StatementData data;
1863 nsresult rv = stmt->getAsynchronousStatementData(data);
1864 NS_ENSURE_SUCCESS(rv, rv);
1865
1866 NS_ASSERTION(stmt->getOwner() == this,
1867 "Statement must be from this database connection!");
1868
1869 // Now append it to our array.
1870 stmts.AppendElement(data);
1871 }
1872
1873 // Dispatch to the background
1874 return AsyncExecuteStatements::execute(stmts, this, mDBConn, aCallback,
1875 _handle);
1876 }
1877
1878 NS_IMETHODIMP
ExecuteSimpleSQLAsync(const nsACString & aSQLStatement,mozIStorageStatementCallback * aCallback,mozIStoragePendingStatement ** _handle)1879 Connection::ExecuteSimpleSQLAsync(const nsACString& aSQLStatement,
1880 mozIStorageStatementCallback* aCallback,
1881 mozIStoragePendingStatement** _handle) {
1882 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
1883
1884 nsCOMPtr<mozIStorageAsyncStatement> stmt;
1885 nsresult rv = CreateAsyncStatement(aSQLStatement, getter_AddRefs(stmt));
1886 if (NS_FAILED(rv)) {
1887 return rv;
1888 }
1889
1890 nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
1891 rv = stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
1892 if (NS_FAILED(rv)) {
1893 return rv;
1894 }
1895
1896 pendingStatement.forget(_handle);
1897 return rv;
1898 }
1899
1900 NS_IMETHODIMP
TableExists(const nsACString & aTableName,bool * _exists)1901 Connection::TableExists(const nsACString& aTableName, bool* _exists) {
1902 return databaseElementExists(TABLE, aTableName, _exists);
1903 }
1904
1905 NS_IMETHODIMP
IndexExists(const nsACString & aIndexName,bool * _exists)1906 Connection::IndexExists(const nsACString& aIndexName, bool* _exists) {
1907 return databaseElementExists(INDEX, aIndexName, _exists);
1908 }
1909
1910 NS_IMETHODIMP
GetTransactionInProgress(bool * _inProgress)1911 Connection::GetTransactionInProgress(bool* _inProgress) {
1912 if (!connectionReady()) {
1913 return NS_ERROR_NOT_INITIALIZED;
1914 }
1915 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
1916 if (NS_FAILED(rv)) {
1917 return rv;
1918 }
1919
1920 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1921 *_inProgress = transactionInProgress(lockedScope);
1922 return NS_OK;
1923 }
1924
1925 NS_IMETHODIMP
GetDefaultTransactionType(int32_t * _type)1926 Connection::GetDefaultTransactionType(int32_t* _type) {
1927 *_type = mDefaultTransactionType;
1928 return NS_OK;
1929 }
1930
1931 NS_IMETHODIMP
SetDefaultTransactionType(int32_t aType)1932 Connection::SetDefaultTransactionType(int32_t aType) {
1933 NS_ENSURE_ARG_RANGE(aType, TRANSACTION_DEFERRED, TRANSACTION_EXCLUSIVE);
1934 mDefaultTransactionType = aType;
1935 return NS_OK;
1936 }
1937
1938 NS_IMETHODIMP
GetVariableLimit(int32_t * _limit)1939 Connection::GetVariableLimit(int32_t* _limit) {
1940 if (!connectionReady()) {
1941 return NS_ERROR_NOT_INITIALIZED;
1942 }
1943 int limit = ::sqlite3_limit(mDBConn, SQLITE_LIMIT_VARIABLE_NUMBER, -1);
1944 if (limit < 0) {
1945 return NS_ERROR_UNEXPECTED;
1946 }
1947 *_limit = limit;
1948 return NS_OK;
1949 }
1950
1951 NS_IMETHODIMP
BeginTransaction()1952 Connection::BeginTransaction() {
1953 if (!connectionReady()) {
1954 return NS_ERROR_NOT_INITIALIZED;
1955 }
1956 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1957 if (NS_FAILED(rv)) {
1958 return rv;
1959 }
1960
1961 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1962 return beginTransactionInternal(lockedScope, mDBConn,
1963 mDefaultTransactionType);
1964 }
1965
beginTransactionInternal(const SQLiteMutexAutoLock & aProofOfLock,sqlite3 * aNativeConnection,int32_t aTransactionType)1966 nsresult Connection::beginTransactionInternal(
1967 const SQLiteMutexAutoLock& aProofOfLock, sqlite3* aNativeConnection,
1968 int32_t aTransactionType) {
1969 if (transactionInProgress(aProofOfLock)) {
1970 return NS_ERROR_FAILURE;
1971 }
1972 nsresult rv;
1973 switch (aTransactionType) {
1974 case TRANSACTION_DEFERRED:
1975 rv = convertResultCode(executeSql(aNativeConnection, "BEGIN DEFERRED"));
1976 break;
1977 case TRANSACTION_IMMEDIATE:
1978 rv = convertResultCode(executeSql(aNativeConnection, "BEGIN IMMEDIATE"));
1979 break;
1980 case TRANSACTION_EXCLUSIVE:
1981 rv = convertResultCode(executeSql(aNativeConnection, "BEGIN EXCLUSIVE"));
1982 break;
1983 default:
1984 return NS_ERROR_ILLEGAL_VALUE;
1985 }
1986 return rv;
1987 }
1988
1989 NS_IMETHODIMP
CommitTransaction()1990 Connection::CommitTransaction() {
1991 if (!connectionReady()) {
1992 return NS_ERROR_NOT_INITIALIZED;
1993 }
1994 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
1995 if (NS_FAILED(rv)) {
1996 return rv;
1997 }
1998
1999 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
2000 return commitTransactionInternal(lockedScope, mDBConn);
2001 }
2002
commitTransactionInternal(const SQLiteMutexAutoLock & aProofOfLock,sqlite3 * aNativeConnection)2003 nsresult Connection::commitTransactionInternal(
2004 const SQLiteMutexAutoLock& aProofOfLock, sqlite3* aNativeConnection) {
2005 if (!transactionInProgress(aProofOfLock)) {
2006 return NS_ERROR_UNEXPECTED;
2007 }
2008 nsresult rv =
2009 convertResultCode(executeSql(aNativeConnection, "COMMIT TRANSACTION"));
2010 return rv;
2011 }
2012
2013 NS_IMETHODIMP
RollbackTransaction()2014 Connection::RollbackTransaction() {
2015 if (!connectionReady()) {
2016 return NS_ERROR_NOT_INITIALIZED;
2017 }
2018 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
2019 if (NS_FAILED(rv)) {
2020 return rv;
2021 }
2022
2023 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
2024 return rollbackTransactionInternal(lockedScope, mDBConn);
2025 }
2026
rollbackTransactionInternal(const SQLiteMutexAutoLock & aProofOfLock,sqlite3 * aNativeConnection)2027 nsresult Connection::rollbackTransactionInternal(
2028 const SQLiteMutexAutoLock& aProofOfLock, sqlite3* aNativeConnection) {
2029 if (!transactionInProgress(aProofOfLock)) {
2030 return NS_ERROR_UNEXPECTED;
2031 }
2032
2033 nsresult rv =
2034 convertResultCode(executeSql(aNativeConnection, "ROLLBACK TRANSACTION"));
2035 return rv;
2036 }
2037
2038 NS_IMETHODIMP
CreateTable(const char * aTableName,const char * aTableSchema)2039 Connection::CreateTable(const char* aTableName, const char* aTableSchema) {
2040 if (!connectionReady()) {
2041 return NS_ERROR_NOT_INITIALIZED;
2042 }
2043 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
2044 if (NS_FAILED(rv)) {
2045 return rv;
2046 }
2047
2048 SmprintfPointer buf =
2049 ::mozilla::Smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema);
2050 if (!buf) return NS_ERROR_OUT_OF_MEMORY;
2051
2052 int srv = executeSql(mDBConn, buf.get());
2053
2054 return convertResultCode(srv);
2055 }
2056
2057 NS_IMETHODIMP
CreateFunction(const nsACString & aFunctionName,int32_t aNumArguments,mozIStorageFunction * aFunction)2058 Connection::CreateFunction(const nsACString& aFunctionName,
2059 int32_t aNumArguments,
2060 mozIStorageFunction* aFunction) {
2061 if (!connectionReady()) {
2062 return NS_ERROR_NOT_INITIALIZED;
2063 }
2064 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
2065 if (NS_FAILED(rv)) {
2066 return rv;
2067 }
2068
2069 // Check to see if this function is already defined. We only check the name
2070 // because a function can be defined with the same body but different names.
2071 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
2072 NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
2073
2074 int srv = ::sqlite3_create_function(
2075 mDBConn, nsPromiseFlatCString(aFunctionName).get(), aNumArguments,
2076 SQLITE_ANY, aFunction, basicFunctionHelper, nullptr, nullptr);
2077 if (srv != SQLITE_OK) return convertResultCode(srv);
2078
2079 FunctionInfo info = {aFunction, Connection::FunctionInfo::SIMPLE,
2080 aNumArguments};
2081 mFunctions.Put(aFunctionName, info);
2082
2083 return NS_OK;
2084 }
2085
2086 NS_IMETHODIMP
CreateAggregateFunction(const nsACString & aFunctionName,int32_t aNumArguments,mozIStorageAggregateFunction * aFunction)2087 Connection::CreateAggregateFunction(const nsACString& aFunctionName,
2088 int32_t aNumArguments,
2089 mozIStorageAggregateFunction* aFunction) {
2090 if (!connectionReady()) {
2091 return NS_ERROR_NOT_INITIALIZED;
2092 }
2093 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
2094 if (NS_FAILED(rv)) {
2095 return rv;
2096 }
2097
2098 // Check to see if this function name is already defined.
2099 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
2100 NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
2101
2102 // Because aggregate functions depend on state across calls, you cannot have
2103 // the same instance use the same name. We want to enumerate all functions
2104 // and make sure this instance is not already registered.
2105 NS_ENSURE_FALSE(findFunctionByInstance(aFunction), NS_ERROR_FAILURE);
2106
2107 int srv = ::sqlite3_create_function(
2108 mDBConn, nsPromiseFlatCString(aFunctionName).get(), aNumArguments,
2109 SQLITE_ANY, aFunction, nullptr, aggregateFunctionStepHelper,
2110 aggregateFunctionFinalHelper);
2111 if (srv != SQLITE_OK) return convertResultCode(srv);
2112
2113 FunctionInfo info = {aFunction, Connection::FunctionInfo::AGGREGATE,
2114 aNumArguments};
2115 mFunctions.Put(aFunctionName, info);
2116
2117 return NS_OK;
2118 }
2119
2120 NS_IMETHODIMP
RemoveFunction(const nsACString & aFunctionName)2121 Connection::RemoveFunction(const nsACString& aFunctionName) {
2122 if (!connectionReady()) {
2123 return NS_ERROR_NOT_INITIALIZED;
2124 }
2125 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
2126 if (NS_FAILED(rv)) {
2127 return rv;
2128 }
2129
2130 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
2131 NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
2132
2133 int srv = ::sqlite3_create_function(
2134 mDBConn, nsPromiseFlatCString(aFunctionName).get(), 0, SQLITE_ANY,
2135 nullptr, nullptr, nullptr, nullptr);
2136 if (srv != SQLITE_OK) return convertResultCode(srv);
2137
2138 mFunctions.Remove(aFunctionName);
2139
2140 return NS_OK;
2141 }
2142
2143 NS_IMETHODIMP
SetProgressHandler(int32_t aGranularity,mozIStorageProgressHandler * aHandler,mozIStorageProgressHandler ** _oldHandler)2144 Connection::SetProgressHandler(int32_t aGranularity,
2145 mozIStorageProgressHandler* aHandler,
2146 mozIStorageProgressHandler** _oldHandler) {
2147 if (!connectionReady()) {
2148 return NS_ERROR_NOT_INITIALIZED;
2149 }
2150 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
2151 if (NS_FAILED(rv)) {
2152 return rv;
2153 }
2154
2155 // Return previous one
2156 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
2157 NS_IF_ADDREF(*_oldHandler = mProgressHandler);
2158
2159 if (!aHandler || aGranularity <= 0) {
2160 aHandler = nullptr;
2161 aGranularity = 0;
2162 }
2163 mProgressHandler = aHandler;
2164 ::sqlite3_progress_handler(mDBConn, aGranularity, sProgressHelper, this);
2165
2166 return NS_OK;
2167 }
2168
2169 NS_IMETHODIMP
RemoveProgressHandler(mozIStorageProgressHandler ** _oldHandler)2170 Connection::RemoveProgressHandler(mozIStorageProgressHandler** _oldHandler) {
2171 if (!connectionReady()) {
2172 return NS_ERROR_NOT_INITIALIZED;
2173 }
2174 nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
2175 if (NS_FAILED(rv)) {
2176 return rv;
2177 }
2178
2179 // Return previous one
2180 SQLiteMutexAutoLock lockedScope(sharedDBMutex);
2181 NS_IF_ADDREF(*_oldHandler = mProgressHandler);
2182
2183 mProgressHandler = nullptr;
2184 ::sqlite3_progress_handler(mDBConn, 0, nullptr, nullptr);
2185
2186 return NS_OK;
2187 }
2188
2189 NS_IMETHODIMP
SetGrowthIncrement(int32_t aChunkSize,const nsACString & aDatabaseName)2190 Connection::SetGrowthIncrement(int32_t aChunkSize,
2191 const nsACString& aDatabaseName) {
2192 if (!connectionReady()) {
2193 return NS_ERROR_NOT_INITIALIZED;
2194 }
2195 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
2196 if (NS_FAILED(rv)) {
2197 return rv;
2198 }
2199
2200 // Bug 597215: Disk space is extremely limited on Android
2201 // so don't preallocate space. This is also not effective
2202 // on log structured file systems used by Android devices
2203 #if !defined(ANDROID) && !defined(MOZ_PLATFORM_MAEMO)
2204 // Don't preallocate if less than 500MiB is available.
2205 int64_t bytesAvailable;
2206 rv = mDatabaseFile->GetDiskSpaceAvailable(&bytesAvailable);
2207 NS_ENSURE_SUCCESS(rv, rv);
2208 if (bytesAvailable < MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH) {
2209 return NS_ERROR_FILE_TOO_BIG;
2210 }
2211
2212 (void)::sqlite3_file_control(mDBConn,
2213 aDatabaseName.Length()
2214 ? nsPromiseFlatCString(aDatabaseName).get()
2215 : nullptr,
2216 SQLITE_FCNTL_CHUNK_SIZE, &aChunkSize);
2217 #endif
2218 return NS_OK;
2219 }
2220
2221 NS_IMETHODIMP
EnableModule(const nsACString & aModuleName)2222 Connection::EnableModule(const nsACString& aModuleName) {
2223 if (!connectionReady()) {
2224 return NS_ERROR_NOT_INITIALIZED;
2225 }
2226 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
2227 if (NS_FAILED(rv)) {
2228 return rv;
2229 }
2230
2231 for (auto& gModule : gModules) {
2232 struct Module* m = &gModule;
2233 if (aModuleName.Equals(m->name)) {
2234 int srv = m->registerFunc(mDBConn, m->name);
2235 if (srv != SQLITE_OK) return convertResultCode(srv);
2236
2237 return NS_OK;
2238 }
2239 }
2240
2241 return NS_ERROR_FAILURE;
2242 }
2243
2244 // Implemented in TelemetryVFS.cpp
2245 already_AddRefed<QuotaObject> GetQuotaObjectForFile(sqlite3_file* pFile);
2246
2247 NS_IMETHODIMP
GetQuotaObjects(QuotaObject ** aDatabaseQuotaObject,QuotaObject ** aJournalQuotaObject)2248 Connection::GetQuotaObjects(QuotaObject** aDatabaseQuotaObject,
2249 QuotaObject** aJournalQuotaObject) {
2250 MOZ_ASSERT(aDatabaseQuotaObject);
2251 MOZ_ASSERT(aJournalQuotaObject);
2252
2253 if (!connectionReady()) {
2254 return NS_ERROR_NOT_INITIALIZED;
2255 }
2256 nsresult rv = ensureOperationSupported(SYNCHRONOUS);
2257 if (NS_FAILED(rv)) {
2258 return rv;
2259 }
2260
2261 sqlite3_file* file;
2262 int srv = ::sqlite3_file_control(mDBConn, nullptr, SQLITE_FCNTL_FILE_POINTER,
2263 &file);
2264 if (srv != SQLITE_OK) {
2265 return convertResultCode(srv);
2266 }
2267
2268 RefPtr<QuotaObject> databaseQuotaObject = GetQuotaObjectForFile(file);
2269 if (NS_WARN_IF(!databaseQuotaObject)) {
2270 return NS_ERROR_FAILURE;
2271 }
2272
2273 srv = ::sqlite3_file_control(mDBConn, nullptr, SQLITE_FCNTL_JOURNAL_POINTER,
2274 &file);
2275 if (srv != SQLITE_OK) {
2276 return convertResultCode(srv);
2277 }
2278
2279 RefPtr<QuotaObject> journalQuotaObject = GetQuotaObjectForFile(file);
2280 if (NS_WARN_IF(!journalQuotaObject)) {
2281 return NS_ERROR_FAILURE;
2282 }
2283
2284 databaseQuotaObject.forget(aDatabaseQuotaObject);
2285 journalQuotaObject.forget(aJournalQuotaObject);
2286 return NS_OK;
2287 }
2288
2289 } // namespace mozilla::storage
2290