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