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 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Oracle Corporation code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Oracle Corporation
20 : * Portions created by the Initial Developer are Copyright (C) 2004
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
25 : * Brett Wilson <brettw@gmail.com>
26 : * Shawn Wilsher <me@shawnwilsher.com>
27 : * Lev Serebryakov <lev@serebryakov.spb.ru>
28 : * Drew Willcoxon <adw@mozilla.com>
29 : *
30 : * Alternatively, the contents of this file may be used under the terms of
31 : * either the GNU General Public License Version 2 or later (the "GPL"), or
32 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
33 : * in which case the provisions of the GPL or the LGPL are applicable instead
34 : * of those above. If you wish to allow use of your version of this file only
35 : * under the terms of either the GPL or the LGPL, and not to allow others to
36 : * use your version of this file under the terms of the MPL, indicate your
37 : * decision by deleting the provisions above and replace them with the notice
38 : * and other provisions required by the GPL or the LGPL. If you do not delete
39 : * the provisions above, a recipient may use your version of this file under
40 : * the terms of any one of the MPL, the GPL or the LGPL.
41 : *
42 : * ***** END LICENSE BLOCK ***** */
43 :
44 : #include <stdio.h>
45 :
46 : #include "nsError.h"
47 : #include "nsIMutableArray.h"
48 : #include "nsAutoPtr.h"
49 : #include "nsIMemoryReporter.h"
50 : #include "nsThreadUtils.h"
51 : #include "nsILocalFile.h"
52 : #include "mozilla/Telemetry.h"
53 : #include "mozilla/Mutex.h"
54 : #include "mozilla/CondVar.h"
55 :
56 : #include "mozIStorageAggregateFunction.h"
57 : #include "mozIStorageCompletionCallback.h"
58 : #include "mozIStorageFunction.h"
59 :
60 : #include "mozStorageAsyncStatementExecution.h"
61 : #include "mozStorageSQLFunctions.h"
62 : #include "mozStorageConnection.h"
63 : #include "mozStorageService.h"
64 : #include "mozStorageStatement.h"
65 : #include "mozStorageAsyncStatement.h"
66 : #include "mozStorageArgValueArray.h"
67 : #include "mozStoragePrivateHelpers.h"
68 : #include "mozStorageStatementData.h"
69 : #include "StorageBaseStatementInternal.h"
70 : #include "SQLCollations.h"
71 : #include "FileSystemModule.h"
72 : #include "mozStorageHelper.h"
73 : #include "sampler.h"
74 :
75 : #include "prlog.h"
76 : #include "prprf.h"
77 :
78 : #define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000 // 500 MiB
79 :
80 : // Maximum size of the pages cache per connection. If the default cache_size
81 : // value evaluates to a larger size, it will be reduced to save memory.
82 : #define MAX_CACHE_SIZE_BYTES 4194304 // 4 MiB
83 :
84 : // Default maximum number of pages to allow in the connection pages cache.
85 : #define DEFAULT_CACHE_SIZE_PAGES 2000
86 :
87 : #ifdef PR_LOGGING
88 : PRLogModuleInfo* gStorageLog = nsnull;
89 : #endif
90 :
91 : namespace mozilla {
92 : namespace storage {
93 :
94 : namespace {
95 :
96 : ////////////////////////////////////////////////////////////////////////////////
97 : //// Variant Specialization Functions (variantToSQLiteT)
98 :
99 : int
100 11 : sqlite3_T_int(sqlite3_context *aCtx,
101 : int aValue)
102 : {
103 11 : ::sqlite3_result_int(aCtx, aValue);
104 11 : return SQLITE_OK;
105 : }
106 :
107 : int
108 6976 : sqlite3_T_int64(sqlite3_context *aCtx,
109 : sqlite3_int64 aValue)
110 : {
111 6976 : ::sqlite3_result_int64(aCtx, aValue);
112 6976 : return SQLITE_OK;
113 : }
114 :
115 : int
116 0 : sqlite3_T_double(sqlite3_context *aCtx,
117 : double aValue)
118 : {
119 0 : ::sqlite3_result_double(aCtx, aValue);
120 0 : return SQLITE_OK;
121 : }
122 :
123 : int
124 5124 : sqlite3_T_text(sqlite3_context *aCtx,
125 : const nsCString &aValue)
126 : {
127 : ::sqlite3_result_text(aCtx,
128 : aValue.get(),
129 5124 : aValue.Length(),
130 5124 : SQLITE_TRANSIENT);
131 5124 : return SQLITE_OK;
132 : }
133 :
134 : int
135 33201 : sqlite3_T_text16(sqlite3_context *aCtx,
136 : const nsString &aValue)
137 : {
138 : ::sqlite3_result_text16(aCtx,
139 33201 : aValue.get(),
140 33201 : aValue.Length() * 2, // Number of bytes.
141 66402 : SQLITE_TRANSIENT);
142 33201 : return SQLITE_OK;
143 : }
144 :
145 : int
146 5 : sqlite3_T_null(sqlite3_context *aCtx)
147 : {
148 5 : ::sqlite3_result_null(aCtx);
149 5 : return SQLITE_OK;
150 : }
151 :
152 : int
153 0 : sqlite3_T_blob(sqlite3_context *aCtx,
154 : const void *aData,
155 : int aSize)
156 : {
157 0 : ::sqlite3_result_blob(aCtx, aData, aSize, NS_Free);
158 0 : return SQLITE_OK;
159 : }
160 :
161 : #include "variantToSQLiteT_impl.h"
162 :
163 : ////////////////////////////////////////////////////////////////////////////////
164 : //// Modules
165 :
166 : struct Module
167 : {
168 : const char* name;
169 : int (*registerFunc)(sqlite3*, const char*);
170 : };
171 :
172 : Module gModules[] = {
173 : { "filesystem", RegisterFileSystemModule }
174 : };
175 :
176 : ////////////////////////////////////////////////////////////////////////////////
177 : //// Local Functions
178 :
179 : #ifdef PR_LOGGING
180 204609 : void tracefunc (void *aClosure, const char *aStmt)
181 : {
182 204609 : PR_LOG(gStorageLog, PR_LOG_DEBUG, ("sqlite3_trace on %p for '%s'", aClosure,
183 : aStmt));
184 204609 : }
185 : #endif
186 :
187 : struct FFEArguments
188 : {
189 : nsISupports *target;
190 : bool found;
191 : };
192 : PLDHashOperator
193 1 : findFunctionEnumerator(const nsACString &aKey,
194 : Connection::FunctionInfo aData,
195 : void *aUserArg)
196 : {
197 1 : FFEArguments *args = static_cast<FFEArguments *>(aUserArg);
198 1 : if (aData.function == args->target) {
199 1 : args->found = true;
200 1 : return PL_DHASH_STOP;
201 : }
202 0 : return PL_DHASH_NEXT;
203 : }
204 :
205 : PLDHashOperator
206 166 : copyFunctionEnumerator(const nsACString &aKey,
207 : Connection::FunctionInfo aData,
208 : void *aUserArg)
209 : {
210 166 : NS_PRECONDITION(aData.type == Connection::FunctionInfo::SIMPLE ||
211 : aData.type == Connection::FunctionInfo::AGGREGATE,
212 : "Invalid function type!");
213 :
214 166 : Connection *connection = static_cast<Connection *>(aUserArg);
215 166 : if (aData.type == Connection::FunctionInfo::SIMPLE) {
216 : mozIStorageFunction *function =
217 158 : static_cast<mozIStorageFunction *>(aData.function.get());
218 158 : (void)connection->CreateFunction(aKey, aData.numArgs, function);
219 : }
220 : else {
221 : mozIStorageAggregateFunction *function =
222 8 : static_cast<mozIStorageAggregateFunction *>(aData.function.get());
223 8 : (void)connection->CreateAggregateFunction(aKey, aData.numArgs, function);
224 : }
225 :
226 166 : return PL_DHASH_NEXT;
227 : }
228 :
229 : void
230 45311 : basicFunctionHelper(sqlite3_context *aCtx,
231 : int aArgc,
232 : sqlite3_value **aArgv)
233 : {
234 45311 : void *userData = ::sqlite3_user_data(aCtx);
235 :
236 45311 : mozIStorageFunction *func = static_cast<mozIStorageFunction *>(userData);
237 :
238 90622 : nsRefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv));
239 45311 : if (!arguments)
240 : return;
241 :
242 90622 : nsCOMPtr<nsIVariant> result;
243 45311 : if (NS_FAILED(func->OnFunctionCall(arguments, getter_AddRefs(result)))) {
244 0 : NS_WARNING("User function returned error code!");
245 : ::sqlite3_result_error(aCtx,
246 : "User function returned error code",
247 0 : -1);
248 : return;
249 : }
250 45311 : if (variantToSQLiteT(aCtx, result) != SQLITE_OK) {
251 0 : NS_WARNING("User function returned invalid data type!");
252 : ::sqlite3_result_error(aCtx,
253 : "User function returned invalid data type",
254 0 : -1);
255 : }
256 : }
257 :
258 : void
259 16 : aggregateFunctionStepHelper(sqlite3_context *aCtx,
260 : int aArgc,
261 : sqlite3_value **aArgv)
262 : {
263 16 : void *userData = ::sqlite3_user_data(aCtx);
264 : mozIStorageAggregateFunction *func =
265 16 : static_cast<mozIStorageAggregateFunction *>(userData);
266 :
267 32 : nsRefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv));
268 16 : if (!arguments)
269 : return;
270 :
271 16 : if (NS_FAILED(func->OnStep(arguments)))
272 0 : NS_WARNING("User aggregate step function returned error code!");
273 : }
274 :
275 : void
276 6 : aggregateFunctionFinalHelper(sqlite3_context *aCtx)
277 : {
278 6 : void *userData = ::sqlite3_user_data(aCtx);
279 : mozIStorageAggregateFunction *func =
280 6 : static_cast<mozIStorageAggregateFunction *>(userData);
281 :
282 12 : nsRefPtr<nsIVariant> result;
283 6 : if (NS_FAILED(func->OnFinal(getter_AddRefs(result)))) {
284 0 : NS_WARNING("User aggregate final function returned error code!");
285 : ::sqlite3_result_error(aCtx,
286 : "User aggregate final function returned error code",
287 0 : -1);
288 : return;
289 : }
290 :
291 6 : if (variantToSQLiteT(aCtx, result) != SQLITE_OK) {
292 0 : NS_WARNING("User aggregate final function returned invalid data type!");
293 : ::sqlite3_result_error(aCtx,
294 : "User aggregate final function returned invalid data type",
295 0 : -1);
296 : }
297 : }
298 :
299 : /**
300 : * This code is heavily based on the sample at:
301 : * http://www.sqlite.org/unlock_notify.html
302 : */
303 : class UnlockNotification
304 297 : {
305 : public:
306 297 : UnlockNotification()
307 : : mMutex("UnlockNotification mMutex")
308 : , mCondVar(mMutex, "UnlockNotification condVar")
309 297 : , mSignaled(false)
310 : {
311 297 : }
312 :
313 297 : void Wait()
314 : {
315 594 : MutexAutoLock lock(mMutex);
316 867 : while (!mSignaled) {
317 273 : (void)mCondVar.Wait();
318 : }
319 297 : }
320 :
321 297 : void Signal()
322 : {
323 594 : MutexAutoLock lock(mMutex);
324 297 : mSignaled = true;
325 297 : (void)mCondVar.Notify();
326 297 : }
327 :
328 : private:
329 : Mutex mMutex;
330 : CondVar mCondVar;
331 : bool mSignaled;
332 : };
333 :
334 : void
335 297 : UnlockNotifyCallback(void **aArgs,
336 : int aArgsSize)
337 : {
338 594 : for (int i = 0; i < aArgsSize; i++) {
339 : UnlockNotification *notification =
340 297 : static_cast<UnlockNotification *>(aArgs[i]);
341 297 : notification->Signal();
342 : }
343 297 : }
344 :
345 : int
346 297 : WaitForUnlockNotify(sqlite3* aDatabase)
347 : {
348 594 : UnlockNotification notification;
349 : int srv = ::sqlite3_unlock_notify(aDatabase, UnlockNotifyCallback,
350 297 : ¬ification);
351 297 : MOZ_ASSERT(srv == SQLITE_LOCKED || srv == SQLITE_OK);
352 297 : if (srv == SQLITE_OK) {
353 297 : notification.Wait();
354 : }
355 :
356 297 : return srv;
357 : }
358 :
359 : } // anonymous namespace
360 :
361 : ////////////////////////////////////////////////////////////////////////////////
362 : //// Local Classes
363 :
364 : namespace {
365 :
366 : class AsyncCloseConnection : public nsRunnable
367 6616 : {
368 : public:
369 1654 : AsyncCloseConnection(Connection *aConnection,
370 : nsIEventTarget *aCallingThread,
371 : nsIRunnable *aCallbackEvent)
372 : : mConnection(aConnection)
373 : , mCallingThread(aCallingThread)
374 1654 : , mCallbackEvent(aCallbackEvent)
375 : {
376 1654 : }
377 :
378 3308 : NS_METHOD Run()
379 : {
380 : // This event is first dispatched to the background thread to ensure that
381 : // all pending asynchronous events are completed, and then back to the
382 : // calling thread to actually close and notify.
383 3308 : bool onCallingThread = false;
384 3308 : (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
385 3308 : if (!onCallingThread) {
386 1654 : (void)mCallingThread->Dispatch(this, NS_DISPATCH_NORMAL);
387 1654 : return NS_OK;
388 : }
389 :
390 1654 : (void)mConnection->internalClose();
391 1654 : if (mCallbackEvent)
392 1599 : (void)mCallingThread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL);
393 :
394 : // Because we have no guarantee that the invocation of this method on the
395 : // asynchronous thread has fully completed (including the Release of the
396 : // reference to this object held by that event loop), we need to explicitly
397 : // null out our pointers here. It is possible this object will be destroyed
398 : // on the asynchronous thread and if the references are still alive we will
399 : // release them on that thread. We definitely do not want that for
400 : // mConnection and it's nice to avoid for mCallbackEvent too. We do not
401 : // null out mCallingThread because it is conceivable the async thread might
402 : // still be 'in' the object.
403 1654 : mConnection = nsnull;
404 1654 : mCallbackEvent = nsnull;
405 :
406 1654 : return NS_OK;
407 : }
408 : private:
409 : nsRefPtr<Connection> mConnection;
410 : nsCOMPtr<nsIEventTarget> mCallingThread;
411 : nsCOMPtr<nsIRunnable> mCallbackEvent;
412 : };
413 :
414 : } // anonymous namespace
415 :
416 : ////////////////////////////////////////////////////////////////////////////////
417 : //// Memory Reporting
418 :
419 : class StorageMemoryReporter : public nsIMemoryReporter
420 0 : {
421 : public:
422 : NS_DECL_ISUPPORTS
423 :
424 : enum ReporterType {
425 : Cache_Used,
426 : Schema_Used,
427 : Stmt_Used
428 : };
429 :
430 : StorageMemoryReporter(sqlite3 *aDBConn,
431 : const nsCString &aFileName,
432 : ReporterType aType)
433 : : mDBConn(aDBConn)
434 : , mFileName(aFileName)
435 : , mType(aType)
436 : , mHasLeaked(false)
437 : {
438 : }
439 :
440 0 : NS_IMETHOD GetProcess(nsACString &process)
441 : {
442 0 : process.Truncate();
443 0 : return NS_OK;
444 : }
445 :
446 0 : NS_IMETHOD GetPath(nsACString &path)
447 : {
448 0 : path.AssignLiteral("explicit/storage/sqlite/");
449 0 : path.Append(mFileName);
450 0 : if (mHasLeaked) {
451 0 : path.AppendLiteral("-LEAKED");
452 : }
453 :
454 0 : if (mType == Cache_Used) {
455 0 : path.AppendLiteral("/cache-used");
456 : }
457 0 : else if (mType == Schema_Used) {
458 0 : path.AppendLiteral("/schema-used");
459 : }
460 0 : else if (mType == Stmt_Used) {
461 0 : path.AppendLiteral("/stmt-used");
462 : }
463 0 : return NS_OK;
464 : }
465 :
466 0 : NS_IMETHOD GetKind(PRInt32 *kind)
467 : {
468 0 : *kind = KIND_HEAP;
469 0 : return NS_OK;
470 : }
471 :
472 0 : NS_IMETHOD GetUnits(PRInt32 *units)
473 : {
474 0 : *units = UNITS_BYTES;
475 0 : return NS_OK;
476 : }
477 :
478 0 : NS_IMETHOD GetAmount(PRInt64 *amount)
479 : {
480 0 : int type = 0;
481 0 : if (mType == Cache_Used) {
482 0 : type = SQLITE_DBSTATUS_CACHE_USED;
483 : }
484 0 : else if (mType == Schema_Used) {
485 0 : type = SQLITE_DBSTATUS_SCHEMA_USED;
486 : }
487 0 : else if (mType == Stmt_Used) {
488 0 : type = SQLITE_DBSTATUS_STMT_USED;
489 : }
490 :
491 0 : int cur=0, max=0;
492 0 : int rc = ::sqlite3_db_status(mDBConn, type, &cur, &max, 0);
493 0 : *amount = cur;
494 0 : return convertResultCode(rc);
495 : }
496 :
497 0 : NS_IMETHOD GetDescription(nsACString &desc)
498 : {
499 0 : if (mType == Cache_Used) {
500 : desc.AssignLiteral("Memory (approximate) used by all pager caches used "
501 0 : "by connections to this database.");
502 : }
503 0 : else if (mType == Schema_Used) {
504 : desc.AssignLiteral("Memory (approximate) used to store the schema "
505 : "for all databases associated with connections to "
506 0 : "this database.");
507 : }
508 0 : else if (mType == Stmt_Used) {
509 : desc.AssignLiteral("Memory (approximate) used by all prepared statements "
510 0 : "used by connections to this database.");
511 : }
512 0 : return NS_OK;
513 : }
514 :
515 : // We call this when we know we've leaked a connection.
516 : void markAsLeaked()
517 : {
518 : mHasLeaked = true;
519 : }
520 :
521 : private:
522 : sqlite3 *mDBConn;
523 : nsCString mFileName;
524 : ReporterType mType;
525 : bool mHasLeaked;
526 : };
527 0 : NS_IMPL_THREADSAFE_ISUPPORTS1(
528 : StorageMemoryReporter
529 : , nsIMemoryReporter
530 : )
531 :
532 : ////////////////////////////////////////////////////////////////////////////////
533 : //// Connection
534 :
535 3281 : Connection::Connection(Service *aService,
536 : int aFlags)
537 : : sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex")
538 : , sharedDBMutex("Connection::sharedDBMutex")
539 : , threadOpenedOn(do_GetCurrentThread())
540 : , mDBConn(nsnull)
541 : , mAsyncExecutionThreadShuttingDown(false)
542 : , mTransactionInProgress(false)
543 : , mProgressHandler(nsnull)
544 : , mFlags(aFlags)
545 3281 : , mStorageService(aService)
546 : {
547 3281 : mFunctions.Init();
548 3281 : mStorageService->registerConnection(this);
549 3281 : }
550 :
551 6548 : Connection::~Connection()
552 : {
553 3274 : (void)Close();
554 3274 : }
555 :
556 154483 : NS_IMPL_THREADSAFE_ADDREF(Connection)
557 43586 : NS_IMPL_THREADSAFE_QUERY_INTERFACE2(
558 : Connection,
559 : mozIStorageConnection,
560 : nsIInterfaceRequestor
561 : )
562 :
563 : // This is identical to what NS_IMPL_THREADSAFE_RELEASE provides, but with the
564 : // extra |1 == count| case.
565 154455 : NS_IMETHODIMP_(nsrefcnt) Connection::Release(void)
566 : {
567 154455 : NS_PRECONDITION(0 != mRefCnt, "dup release");
568 154455 : nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt);
569 154455 : NS_LOG_RELEASE(this, count, "Connection");
570 154455 : if (1 == count) {
571 : // If the refcount is 1, the single reference must be from
572 : // gService->mConnections (in class |Service|). Which means we can
573 : // unregister it safely.
574 3274 : mStorageService->unregisterConnection(this);
575 151181 : } else if (0 == count) {
576 3274 : mRefCnt = 1; /* stabilize */
577 : /* enable this to find non-threadsafe destructors: */
578 : /* NS_ASSERT_OWNINGTHREAD(Connection); */
579 3274 : delete (this);
580 3274 : return 0;
581 : }
582 151181 : return count;
583 : }
584 :
585 : nsIEventTarget *
586 109151 : Connection::getAsyncExecutionTarget()
587 : {
588 218302 : MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
589 :
590 : // If we are shutting down the asynchronous thread, don't hand out any more
591 : // references to the thread.
592 109151 : if (mAsyncExecutionThreadShuttingDown)
593 267 : return nsnull;
594 :
595 108884 : if (!mAsyncExecutionThread) {
596 1654 : nsresult rv = ::NS_NewThread(getter_AddRefs(mAsyncExecutionThread));
597 1654 : if (NS_FAILED(rv)) {
598 0 : NS_WARNING("Failed to create async thread.");
599 0 : return nsnull;
600 : }
601 : }
602 :
603 108884 : return mAsyncExecutionThread;
604 : }
605 :
606 : nsresult
607 3281 : Connection::initialize(nsIFile *aDatabaseFile,
608 : const char* aVFSName)
609 : {
610 3281 : NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
611 6562 : SAMPLE_LABEL("storage", "Connection::initialize");
612 :
613 : int srv;
614 : nsresult rv;
615 :
616 3281 : mDatabaseFile = aDatabaseFile;
617 :
618 3281 : if (aDatabaseFile) {
619 6464 : nsAutoString path;
620 3232 : rv = aDatabaseFile->GetPath(path);
621 3232 : NS_ENSURE_SUCCESS(rv, rv);
622 :
623 6464 : srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags,
624 3232 : aVFSName);
625 : }
626 : else {
627 : // in memory database requested, sqlite uses a magic file name
628 49 : srv = ::sqlite3_open_v2(":memory:", &mDBConn, mFlags, aVFSName);
629 : }
630 3281 : if (srv != SQLITE_OK) {
631 4 : mDBConn = nsnull;
632 4 : return convertResultCode(srv);
633 : }
634 :
635 : // Properly wrap the database handle's mutex.
636 3277 : sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn));
637 :
638 : #ifdef PR_LOGGING
639 3277 : if (!gStorageLog)
640 697 : gStorageLog = ::PR_NewLogModule("mozStorage");
641 :
642 3277 : ::sqlite3_trace(mDBConn, tracefunc, this);
643 :
644 6554 : nsCAutoString leafName(":memory");
645 3277 : if (aDatabaseFile)
646 3228 : (void)aDatabaseFile->GetNativeLeafName(leafName);
647 3277 : PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Opening connection to '%s' (%p)",
648 : leafName.get(), this));
649 : #endif
650 :
651 : // Set page_size to the preferred default value. This is effective only if
652 : // the database has just been created, otherwise, if the database does not
653 : // use WAL journal mode, a VACUUM operation will updated its page_size.
654 3277 : PRInt64 pageSize = DEFAULT_PAGE_SIZE;
655 : nsCAutoString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
656 6554 : "PRAGMA page_size = ");
657 3277 : pageSizeQuery.AppendInt(pageSize);
658 3277 : rv = ExecuteSimpleSQL(pageSizeQuery);
659 3277 : NS_ENSURE_SUCCESS(rv, rv);
660 :
661 : // Get the current page_size, since it may differ from the specified value.
662 : sqlite3_stmt *stmt;
663 6554 : NS_NAMED_LITERAL_CSTRING(pragma_page_size,
664 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size");
665 3277 : srv = prepareStatement(pragma_page_size, &stmt);
666 3277 : if (srv == SQLITE_OK) {
667 3277 : if (SQLITE_ROW == stepStatement(stmt)) {
668 3277 : pageSize = ::sqlite3_column_int64(stmt, 0);
669 : }
670 3277 : (void)::sqlite3_finalize(stmt);
671 : }
672 :
673 : // Setting the cache_size forces the database open, verifying if it is valid
674 : // or corrupt. So this is executed regardless it being actually needed.
675 : // The cache_size is calculated from the actual page_size, to save memory.
676 : nsCAutoString cacheSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
677 6554 : "PRAGMA cache_size = ");
678 : cacheSizeQuery.AppendInt(NS_MIN(DEFAULT_CACHE_SIZE_PAGES,
679 3277 : PRInt32(MAX_CACHE_SIZE_BYTES / pageSize)));
680 3277 : srv = ::sqlite3_exec(mDBConn, cacheSizeQuery.get(), NULL, NULL, NULL);
681 3277 : if (srv != SQLITE_OK) {
682 10 : ::sqlite3_close(mDBConn);
683 10 : mDBConn = nsnull;
684 10 : return convertResultCode(srv);
685 : }
686 :
687 : // Register our built-in SQL functions.
688 3267 : srv = registerFunctions(mDBConn);
689 3267 : if (srv != SQLITE_OK) {
690 0 : ::sqlite3_close(mDBConn);
691 0 : mDBConn = nsnull;
692 0 : return convertResultCode(srv);
693 : }
694 :
695 : // Register our built-in SQL collating sequences.
696 3267 : srv = registerCollations(mDBConn, mStorageService);
697 3267 : if (srv != SQLITE_OK) {
698 0 : ::sqlite3_close(mDBConn);
699 0 : mDBConn = nsnull;
700 0 : return convertResultCode(srv);
701 : }
702 :
703 : // Set the synchronous PRAGMA, according to the preference.
704 3267 : switch (Service::getSynchronousPref()) {
705 : case 2:
706 0 : (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
707 0 : "PRAGMA synchronous = FULL;"));
708 0 : break;
709 : case 0:
710 0 : (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
711 0 : "PRAGMA synchronous = OFF;"));
712 0 : break;
713 : case 1:
714 : default:
715 3267 : (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
716 3267 : "PRAGMA synchronous = NORMAL;"));
717 3267 : break;
718 : }
719 :
720 3267 : return NS_OK;
721 : }
722 :
723 : nsresult
724 850 : Connection::databaseElementExists(enum DatabaseElementType aElementType,
725 : const nsACString &aElementName,
726 : bool *_exists)
727 : {
728 850 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
729 :
730 1700 : nsCAutoString query("SELECT name FROM sqlite_master WHERE type = '");
731 850 : switch (aElementType) {
732 : case INDEX:
733 32 : query.Append("index");
734 32 : break;
735 : case TABLE:
736 818 : query.Append("table");
737 818 : break;
738 : }
739 850 : query.Append("' AND name ='");
740 850 : query.Append(aElementName);
741 850 : query.Append("'");
742 :
743 : sqlite3_stmt *stmt;
744 850 : int srv = prepareStatement(query, &stmt);
745 850 : if (srv != SQLITE_OK)
746 0 : return convertResultCode(srv);
747 :
748 850 : srv = stepStatement(stmt);
749 : // we just care about the return value from step
750 850 : (void)::sqlite3_finalize(stmt);
751 :
752 850 : if (srv == SQLITE_ROW) {
753 122 : *_exists = true;
754 122 : return NS_OK;
755 : }
756 728 : if (srv == SQLITE_DONE) {
757 728 : *_exists = false;
758 728 : return NS_OK;
759 : }
760 :
761 0 : return convertResultCode(srv);
762 : }
763 :
764 : bool
765 19 : Connection::findFunctionByInstance(nsISupports *aInstance)
766 : {
767 19 : sharedDBMutex.assertCurrentThreadOwns();
768 19 : FFEArguments args = { aInstance, false };
769 19 : (void)mFunctions.EnumerateRead(findFunctionEnumerator, &args);
770 19 : return args.found;
771 : }
772 :
773 : /* static */ int
774 1084 : Connection::sProgressHelper(void *aArg)
775 : {
776 1084 : Connection *_this = static_cast<Connection *>(aArg);
777 1084 : return _this->progressHandler();
778 : }
779 :
780 : int
781 1084 : Connection::progressHandler()
782 : {
783 1084 : sharedDBMutex.assertCurrentThreadOwns();
784 1084 : if (mProgressHandler) {
785 : bool result;
786 1084 : nsresult rv = mProgressHandler->OnProgress(this, &result);
787 1084 : if (NS_FAILED(rv)) return 0; // Don't break request
788 1084 : return result ? 1 : 0;
789 : }
790 0 : return 0;
791 : }
792 :
793 : nsresult
794 3267 : Connection::setClosedState()
795 : {
796 : // Ensure that we are on the correct thread to close the database.
797 : bool onOpenedThread;
798 3267 : nsresult rv = threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
799 3267 : NS_ENSURE_SUCCESS(rv, rv);
800 3267 : if (!onOpenedThread) {
801 0 : NS_ERROR("Must close the database on the thread that you opened it with!");
802 0 : return NS_ERROR_UNEXPECTED;
803 : }
804 :
805 : // Flag that we are shutting down the async thread, so that
806 : // getAsyncExecutionTarget knows not to expose/create the async thread.
807 : {
808 6534 : MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
809 3267 : NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED);
810 6534 : mAsyncExecutionThreadShuttingDown = true;
811 : }
812 :
813 3267 : return NS_OK;
814 : }
815 :
816 : nsresult
817 3267 : Connection::internalClose()
818 : {
819 : #ifdef DEBUG
820 : // Sanity checks to make sure we are in the proper state before calling this.
821 3267 : NS_ASSERTION(mDBConn, "Database connection is already null!");
822 :
823 : { // Make sure we have marked our async thread as shutting down.
824 6534 : MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
825 3267 : NS_ASSERTION(mAsyncExecutionThreadShuttingDown,
826 : "Did not call setClosedState!");
827 : }
828 :
829 : { // Ensure that we are being called on the thread we were opened with.
830 3267 : bool onOpenedThread = false;
831 3267 : (void)threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
832 3267 : NS_ASSERTION(onOpenedThread,
833 : "Not called on the thread the database was opened on!");
834 : }
835 : #endif
836 :
837 : #ifdef PR_LOGGING
838 6534 : nsCAutoString leafName(":memory");
839 3267 : if (mDatabaseFile)
840 3218 : (void)mDatabaseFile->GetNativeLeafName(leafName);
841 3267 : PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Closing connection to '%s'",
842 : leafName.get()));
843 : #endif
844 :
845 : #ifdef DEBUG
846 : // Notify about any non-finalized statements.
847 3267 : sqlite3_stmt *stmt = NULL;
848 6540 : while ((stmt = ::sqlite3_next_stmt(mDBConn, stmt))) {
849 : char *msg = ::PR_smprintf("SQL statement '%s' was not finalized",
850 6 : ::sqlite3_sql(stmt));
851 6 : NS_WARNING(msg);
852 6 : ::PR_smprintf_free(msg);
853 : }
854 : #endif
855 :
856 3267 : int srv = ::sqlite3_close(mDBConn);
857 3267 : NS_ASSERTION(srv == SQLITE_OK,
858 : "sqlite3_close failed. There are probably outstanding statements that are listed above!");
859 :
860 3267 : mDBConn = NULL;
861 3267 : return convertResultCode(srv);
862 : }
863 :
864 : nsCString
865 22 : Connection::getFilename()
866 : {
867 22 : nsCString leafname(":memory:");
868 22 : if (mDatabaseFile) {
869 22 : (void)mDatabaseFile->GetNativeLeafName(leafname);
870 : }
871 : return leafname;
872 : }
873 :
874 : int
875 220552 : Connection::stepStatement(sqlite3_stmt *aStatement)
876 : {
877 220552 : bool checkedMainThread = false;
878 220552 : TimeStamp startTime = TimeStamp::Now();
879 :
880 : // mDBConn may be null if the executing statement has been created and cached
881 : // after a call to asyncClose() but before the connection has been nullified
882 : // by internalClose(). In such a case closing the connection fails due to
883 : // the existence of prepared statements, but mDBConn is set to null
884 : // regardless. This usually happens when other tasks using cached statements
885 : // are asynchronously scheduled for execution and any of them ends up after
886 : // asyncClose. See bug 728653 for details.
887 220553 : if (!mDBConn)
888 0 : return SQLITE_MISUSE;
889 :
890 220553 : (void)::sqlite3_extended_result_codes(mDBConn, 1);
891 :
892 : int srv;
893 441403 : while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) {
894 298 : if (!checkedMainThread) {
895 298 : checkedMainThread = true;
896 298 : if (::NS_IsMainThread()) {
897 1 : NS_WARNING("We won't allow blocking on the main thread!");
898 1 : break;
899 : }
900 : }
901 :
902 297 : srv = WaitForUnlockNotify(mDBConn);
903 297 : if (srv != SQLITE_OK) {
904 0 : break;
905 : }
906 :
907 297 : ::sqlite3_reset(aStatement);
908 : }
909 :
910 : // Report very slow SQL statements to Telemetry
911 220552 : TimeDuration duration = TimeStamp::Now() - startTime;
912 220553 : if (duration.ToMilliseconds() >= Telemetry::kSlowStatementThreshold) {
913 26 : nsDependentCString statementString(::sqlite3_sql(aStatement));
914 13 : Telemetry::RecordSlowSQLStatement(statementString, getFilename(),
915 13 : duration.ToMilliseconds());
916 : }
917 :
918 220553 : (void)::sqlite3_extended_result_codes(mDBConn, 0);
919 : // Drop off the extended result bits of the result code.
920 220553 : return srv & 0xFF;
921 : }
922 :
923 : int
924 50895 : Connection::prepareStatement(const nsCString &aSQL,
925 : sqlite3_stmt **_stmt)
926 : {
927 50895 : bool checkedMainThread = false;
928 :
929 50895 : (void)::sqlite3_extended_result_codes(mDBConn, 1);
930 :
931 : int srv;
932 50895 : while((srv = ::sqlite3_prepare_v2(mDBConn, aSQL.get(), -1, _stmt, NULL)) ==
933 : SQLITE_LOCKED_SHAREDCACHE) {
934 0 : if (!checkedMainThread) {
935 0 : checkedMainThread = true;
936 0 : if (::NS_IsMainThread()) {
937 0 : NS_WARNING("We won't allow blocking on the main thread!");
938 0 : break;
939 : }
940 : }
941 :
942 0 : srv = WaitForUnlockNotify(mDBConn);
943 0 : if (srv != SQLITE_OK) {
944 0 : break;
945 : }
946 : }
947 :
948 50895 : if (srv != SQLITE_OK) {
949 114 : nsCString warnMsg;
950 57 : warnMsg.AppendLiteral("The SQL statement '");
951 57 : warnMsg.Append(aSQL);
952 57 : warnMsg.AppendLiteral("' could not be compiled due to an error: ");
953 57 : warnMsg.Append(::sqlite3_errmsg(mDBConn));
954 :
955 : #ifdef DEBUG
956 57 : NS_WARNING(warnMsg.get());
957 : #endif
958 : #ifdef PR_LOGGING
959 57 : PR_LOG(gStorageLog, PR_LOG_ERROR, ("%s", warnMsg.get()));
960 : #endif
961 : }
962 :
963 50895 : (void)::sqlite3_extended_result_codes(mDBConn, 0);
964 : // Drop off the extended result bits of the result code.
965 50895 : return srv & 0xFF;
966 : }
967 :
968 : ////////////////////////////////////////////////////////////////////////////////
969 : //// nsIInterfaceRequestor
970 :
971 : NS_IMETHODIMP
972 622 : Connection::GetInterface(const nsIID &aIID,
973 : void **_result)
974 : {
975 622 : if (aIID.Equals(NS_GET_IID(nsIEventTarget))) {
976 622 : nsIEventTarget *background = getAsyncExecutionTarget();
977 622 : NS_IF_ADDREF(background);
978 622 : *_result = background;
979 622 : return NS_OK;
980 : }
981 0 : return NS_ERROR_NO_INTERFACE;
982 : }
983 :
984 : ////////////////////////////////////////////////////////////////////////////////
985 : //// mozIStorageConnection
986 :
987 : NS_IMETHODIMP
988 4549 : Connection::Close()
989 : {
990 4549 : if (!mDBConn)
991 2935 : return NS_ERROR_NOT_INITIALIZED;
992 :
993 : { // Make sure we have not executed any asynchronous statements.
994 : // If this fails, the mDBConn will be left open, resulting in a leak.
995 : // Ideally we'd schedule some code to destroy the mDBConn once all its
996 : // async statements have finished executing; see bug 704030.
997 3228 : MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
998 1614 : bool asyncCloseWasCalled = !mAsyncExecutionThread;
999 1614 : NS_ENSURE_TRUE(asyncCloseWasCalled, NS_ERROR_UNEXPECTED);
1000 : }
1001 :
1002 1613 : nsresult rv = setClosedState();
1003 1613 : NS_ENSURE_SUCCESS(rv, rv);
1004 :
1005 1613 : return internalClose();
1006 : }
1007 :
1008 : NS_IMETHODIMP
1009 1661 : Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
1010 : {
1011 1661 : if (!mDBConn)
1012 6 : return NS_ERROR_NOT_INITIALIZED;
1013 :
1014 1655 : nsIEventTarget *asyncThread = getAsyncExecutionTarget();
1015 1655 : NS_ENSURE_TRUE(asyncThread, NS_ERROR_UNEXPECTED);
1016 :
1017 1654 : nsresult rv = setClosedState();
1018 1654 : NS_ENSURE_SUCCESS(rv, rv);
1019 :
1020 : // Create our callback event if we were given a callback.
1021 3308 : nsCOMPtr<nsIRunnable> completeEvent;
1022 1654 : if (aCallback) {
1023 1599 : completeEvent = newCompletionEvent(aCallback);
1024 1599 : NS_ENSURE_TRUE(completeEvent, NS_ERROR_OUT_OF_MEMORY);
1025 : }
1026 :
1027 : // Create and dispatch our close event to the background thread.
1028 : nsCOMPtr<nsIRunnable> closeEvent =
1029 4962 : new AsyncCloseConnection(this, NS_GetCurrentThread(), completeEvent);
1030 1654 : NS_ENSURE_TRUE(closeEvent, NS_ERROR_OUT_OF_MEMORY);
1031 :
1032 1654 : rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL);
1033 1654 : NS_ENSURE_SUCCESS(rv, rv);
1034 :
1035 1654 : return NS_OK;
1036 : }
1037 :
1038 : NS_IMETHODIMP
1039 52 : Connection::Clone(bool aReadOnly,
1040 : mozIStorageConnection **_connection)
1041 : {
1042 104 : SAMPLE_LABEL("storage", "Connection::Clone");
1043 52 : if (!mDBConn)
1044 2 : return NS_ERROR_NOT_INITIALIZED;
1045 50 : if (!mDatabaseFile)
1046 0 : return NS_ERROR_UNEXPECTED;
1047 :
1048 50 : int flags = mFlags;
1049 50 : if (aReadOnly) {
1050 : // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
1051 40 : flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY;
1052 : // Turn off SQLITE_OPEN_CREATE.
1053 40 : flags = (~SQLITE_OPEN_CREATE & flags);
1054 : }
1055 150 : nsRefPtr<Connection> clone = new Connection(mStorageService, flags);
1056 50 : NS_ENSURE_TRUE(clone, NS_ERROR_OUT_OF_MEMORY);
1057 :
1058 50 : nsresult rv = clone->initialize(mDatabaseFile);
1059 50 : NS_ENSURE_SUCCESS(rv, rv);
1060 :
1061 : // Copy over pragmas from the original connection.
1062 : static const char * pragmas[] = {
1063 : "cache_size",
1064 : "temp_store",
1065 : "foreign_keys",
1066 : "journal_size_limit",
1067 : "synchronous",
1068 : "wal_autocheckpoint",
1069 : };
1070 350 : for (PRUint32 i = 0; i < ArrayLength(pragmas); ++i) {
1071 : // Read-only connections just need cache_size and temp_store pragmas.
1072 500 : if (aReadOnly && ::strcmp(pragmas[i], "cache_size") != 0 &&
1073 200 : ::strcmp(pragmas[i], "temp_store") != 0) {
1074 160 : continue;
1075 : }
1076 :
1077 280 : nsCAutoString pragmaQuery("PRAGMA ");
1078 140 : pragmaQuery.Append(pragmas[i]);
1079 280 : nsCOMPtr<mozIStorageStatement> stmt;
1080 140 : rv = CreateStatement(pragmaQuery, getter_AddRefs(stmt));
1081 140 : MOZ_ASSERT(NS_SUCCEEDED(rv));
1082 140 : bool hasResult = false;
1083 140 : if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1084 140 : pragmaQuery.AppendLiteral(" = ");
1085 140 : pragmaQuery.AppendInt(stmt->AsInt32(0));
1086 140 : rv = clone->ExecuteSimpleSQL(pragmaQuery);
1087 140 : MOZ_ASSERT(NS_SUCCEEDED(rv));
1088 : }
1089 : }
1090 :
1091 : // Copy any functions that have been added to this connection.
1092 50 : (void)mFunctions.EnumerateRead(copyFunctionEnumerator, clone);
1093 :
1094 50 : NS_ADDREF(*_connection = clone);
1095 50 : return NS_OK;
1096 : }
1097 :
1098 : NS_IMETHODIMP
1099 1107 : Connection::GetConnectionReady(bool *_ready)
1100 : {
1101 1107 : *_ready = (mDBConn != nsnull);
1102 1107 : return NS_OK;
1103 : }
1104 :
1105 : NS_IMETHODIMP
1106 1162 : Connection::GetDatabaseFile(nsIFile **_dbFile)
1107 : {
1108 1162 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1109 :
1110 1162 : NS_IF_ADDREF(*_dbFile = mDatabaseFile);
1111 :
1112 1162 : return NS_OK;
1113 : }
1114 :
1115 : NS_IMETHODIMP
1116 5717 : Connection::GetLastInsertRowID(PRInt64 *_id)
1117 : {
1118 5717 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1119 :
1120 5717 : sqlite_int64 id = ::sqlite3_last_insert_rowid(mDBConn);
1121 5717 : *_id = id;
1122 :
1123 5717 : return NS_OK;
1124 : }
1125 :
1126 : NS_IMETHODIMP
1127 0 : Connection::GetAffectedRows(PRInt32 *_rows)
1128 : {
1129 0 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1130 :
1131 0 : *_rows = ::sqlite3_changes(mDBConn);
1132 :
1133 0 : return NS_OK;
1134 : }
1135 :
1136 : NS_IMETHODIMP
1137 90 : Connection::GetLastError(PRInt32 *_error)
1138 : {
1139 90 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1140 :
1141 90 : *_error = ::sqlite3_errcode(mDBConn);
1142 :
1143 90 : return NS_OK;
1144 : }
1145 :
1146 : NS_IMETHODIMP
1147 52 : Connection::GetLastErrorString(nsACString &_errorString)
1148 : {
1149 52 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1150 :
1151 52 : const char *serr = ::sqlite3_errmsg(mDBConn);
1152 52 : _errorString.Assign(serr);
1153 :
1154 52 : return NS_OK;
1155 : }
1156 :
1157 : NS_IMETHODIMP
1158 1798 : Connection::GetSchemaVersion(PRInt32 *_version)
1159 : {
1160 1798 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1161 :
1162 3596 : nsCOMPtr<mozIStorageStatement> stmt;
1163 1798 : (void)CreateStatement(NS_LITERAL_CSTRING("PRAGMA user_version"),
1164 3596 : getter_AddRefs(stmt));
1165 1798 : NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY);
1166 :
1167 1798 : *_version = 0;
1168 : bool hasResult;
1169 1798 : if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult)
1170 1798 : *_version = stmt->AsInt32(0);
1171 :
1172 1798 : return NS_OK;
1173 : }
1174 :
1175 : NS_IMETHODIMP
1176 1317 : Connection::SetSchemaVersion(PRInt32 aVersion)
1177 : {
1178 1317 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1179 :
1180 2634 : nsCAutoString stmt(NS_LITERAL_CSTRING("PRAGMA user_version = "));
1181 1317 : stmt.AppendInt(aVersion);
1182 :
1183 1317 : return ExecuteSimpleSQL(stmt);
1184 : }
1185 :
1186 : NS_IMETHODIMP
1187 36811 : Connection::CreateStatement(const nsACString &aSQLStatement,
1188 : mozIStorageStatement **_stmt)
1189 : {
1190 36811 : NS_ENSURE_ARG_POINTER(_stmt);
1191 36811 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1192 :
1193 73622 : nsRefPtr<Statement> statement(new Statement());
1194 36811 : NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
1195 :
1196 36811 : nsresult rv = statement->initialize(this, aSQLStatement);
1197 36811 : NS_ENSURE_SUCCESS(rv, rv);
1198 :
1199 : Statement *rawPtr;
1200 36762 : statement.forget(&rawPtr);
1201 36762 : *_stmt = rawPtr;
1202 36762 : return NS_OK;
1203 : }
1204 :
1205 : NS_IMETHODIMP
1206 5918 : Connection::CreateAsyncStatement(const nsACString &aSQLStatement,
1207 : mozIStorageAsyncStatement **_stmt)
1208 : {
1209 5918 : NS_ENSURE_ARG_POINTER(_stmt);
1210 5918 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1211 :
1212 11836 : nsRefPtr<AsyncStatement> statement(new AsyncStatement());
1213 5918 : NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
1214 :
1215 5918 : nsresult rv = statement->initialize(this, aSQLStatement);
1216 5918 : NS_ENSURE_SUCCESS(rv, rv);
1217 :
1218 : AsyncStatement *rawPtr;
1219 5918 : statement.forget(&rawPtr);
1220 5918 : *_stmt = rawPtr;
1221 5918 : return NS_OK;
1222 : }
1223 :
1224 : NS_IMETHODIMP
1225 44242 : Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement)
1226 : {
1227 44242 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1228 :
1229 44242 : int srv = ::sqlite3_exec(mDBConn, PromiseFlatCString(aSQLStatement).get(),
1230 44242 : NULL, NULL, NULL);
1231 44242 : return convertResultCode(srv);
1232 : }
1233 :
1234 : NS_IMETHODIMP
1235 5534 : Connection::ExecuteAsync(mozIStorageBaseStatement **aStatements,
1236 : PRUint32 aNumStatements,
1237 : mozIStorageStatementCallback *aCallback,
1238 : mozIStoragePendingStatement **_handle)
1239 : {
1240 11068 : nsTArray<StatementData> stmts(aNumStatements);
1241 22316 : for (PRUint32 i = 0; i < aNumStatements; i++) {
1242 : nsCOMPtr<StorageBaseStatementInternal> stmt =
1243 33564 : do_QueryInterface(aStatements[i]);
1244 :
1245 : // Obtain our StatementData.
1246 33564 : StatementData data;
1247 16782 : nsresult rv = stmt->getAsynchronousStatementData(data);
1248 16782 : NS_ENSURE_SUCCESS(rv, rv);
1249 :
1250 16782 : NS_ASSERTION(stmt->getOwner() == this,
1251 : "Statement must be from this database connection!");
1252 :
1253 : // Now append it to our array.
1254 16782 : NS_ENSURE_TRUE(stmts.AppendElement(data), NS_ERROR_OUT_OF_MEMORY);
1255 : }
1256 :
1257 : // Dispatch to the background
1258 5534 : return AsyncExecuteStatements::execute(stmts, this, aCallback, _handle);
1259 : }
1260 :
1261 : NS_IMETHODIMP
1262 818 : Connection::TableExists(const nsACString &aTableName,
1263 : bool *_exists)
1264 : {
1265 818 : return databaseElementExists(TABLE, aTableName, _exists);
1266 : }
1267 :
1268 : NS_IMETHODIMP
1269 32 : Connection::IndexExists(const nsACString &aIndexName,
1270 : bool* _exists)
1271 : {
1272 32 : return databaseElementExists(INDEX, aIndexName, _exists);
1273 : }
1274 :
1275 : NS_IMETHODIMP
1276 646 : Connection::GetTransactionInProgress(bool *_inProgress)
1277 : {
1278 646 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1279 :
1280 1292 : SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1281 646 : *_inProgress = mTransactionInProgress;
1282 646 : return NS_OK;
1283 : }
1284 :
1285 : NS_IMETHODIMP
1286 439 : Connection::BeginTransaction()
1287 : {
1288 439 : return BeginTransactionAs(mozIStorageConnection::TRANSACTION_DEFERRED);
1289 : }
1290 :
1291 : NS_IMETHODIMP
1292 12841 : Connection::BeginTransactionAs(PRInt32 aTransactionType)
1293 : {
1294 12841 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1295 :
1296 25682 : SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1297 12841 : if (mTransactionInProgress)
1298 5244 : return NS_ERROR_FAILURE;
1299 : nsresult rv;
1300 7597 : switch(aTransactionType) {
1301 : case TRANSACTION_DEFERRED:
1302 4620 : rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN DEFERRED"));
1303 4620 : break;
1304 : case TRANSACTION_IMMEDIATE:
1305 2977 : rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN IMMEDIATE"));
1306 2977 : break;
1307 : case TRANSACTION_EXCLUSIVE:
1308 0 : rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN EXCLUSIVE"));
1309 0 : break;
1310 : default:
1311 0 : return NS_ERROR_ILLEGAL_VALUE;
1312 : }
1313 7597 : if (NS_SUCCEEDED(rv))
1314 7596 : mTransactionInProgress = true;
1315 7597 : return rv;
1316 : }
1317 :
1318 : NS_IMETHODIMP
1319 7560 : Connection::CommitTransaction()
1320 : {
1321 7560 : if (!mDBConn)
1322 0 : return NS_ERROR_NOT_INITIALIZED;
1323 :
1324 15120 : SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1325 7560 : if (!mTransactionInProgress)
1326 1 : return NS_ERROR_UNEXPECTED;
1327 :
1328 7559 : nsresult rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT TRANSACTION"));
1329 7559 : if (NS_SUCCEEDED(rv))
1330 7559 : mTransactionInProgress = false;
1331 7559 : return rv;
1332 : }
1333 :
1334 : NS_IMETHODIMP
1335 36 : Connection::RollbackTransaction()
1336 : {
1337 36 : if (!mDBConn)
1338 0 : return NS_ERROR_NOT_INITIALIZED;
1339 :
1340 72 : SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1341 36 : if (!mTransactionInProgress)
1342 1 : return NS_ERROR_UNEXPECTED;
1343 :
1344 35 : nsresult rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK TRANSACTION"));
1345 35 : if (NS_SUCCEEDED(rv))
1346 35 : mTransactionInProgress = false;
1347 35 : return rv;
1348 : }
1349 :
1350 : NS_IMETHODIMP
1351 1617 : Connection::CreateTable(const char *aTableName,
1352 : const char *aTableSchema)
1353 : {
1354 1617 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1355 :
1356 1617 : char *buf = ::PR_smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema);
1357 1617 : if (!buf)
1358 0 : return NS_ERROR_OUT_OF_MEMORY;
1359 :
1360 1617 : int srv = ::sqlite3_exec(mDBConn, buf, NULL, NULL, NULL);
1361 1617 : ::PR_smprintf_free(buf);
1362 :
1363 1617 : return convertResultCode(srv);
1364 : }
1365 :
1366 : NS_IMETHODIMP
1367 2043 : Connection::CreateFunction(const nsACString &aFunctionName,
1368 : PRInt32 aNumArguments,
1369 : mozIStorageFunction *aFunction)
1370 : {
1371 2043 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1372 :
1373 : // Check to see if this function is already defined. We only check the name
1374 : // because a function can be defined with the same body but different names.
1375 4086 : SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1376 2043 : NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, NULL), NS_ERROR_FAILURE);
1377 :
1378 : int srv = ::sqlite3_create_function(mDBConn,
1379 2042 : nsPromiseFlatCString(aFunctionName).get(),
1380 : aNumArguments,
1381 : SQLITE_ANY,
1382 : aFunction,
1383 : basicFunctionHelper,
1384 : NULL,
1385 2042 : NULL);
1386 2042 : if (srv != SQLITE_OK)
1387 0 : return convertResultCode(srv);
1388 :
1389 : FunctionInfo info = { aFunction,
1390 : Connection::FunctionInfo::SIMPLE,
1391 4084 : aNumArguments };
1392 2042 : NS_ENSURE_TRUE(mFunctions.Put(aFunctionName, info),
1393 : NS_ERROR_OUT_OF_MEMORY);
1394 :
1395 2042 : return NS_OK;
1396 : }
1397 :
1398 : NS_IMETHODIMP
1399 20 : Connection::CreateAggregateFunction(const nsACString &aFunctionName,
1400 : PRInt32 aNumArguments,
1401 : mozIStorageAggregateFunction *aFunction)
1402 : {
1403 20 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1404 :
1405 : // Check to see if this function name is already defined.
1406 40 : SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1407 20 : NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, NULL), NS_ERROR_FAILURE);
1408 :
1409 : // Because aggregate functions depend on state across calls, you cannot have
1410 : // the same instance use the same name. We want to enumerate all functions
1411 : // and make sure this instance is not already registered.
1412 19 : NS_ENSURE_FALSE(findFunctionByInstance(aFunction), NS_ERROR_FAILURE);
1413 :
1414 : int srv = ::sqlite3_create_function(mDBConn,
1415 18 : nsPromiseFlatCString(aFunctionName).get(),
1416 : aNumArguments,
1417 : SQLITE_ANY,
1418 : aFunction,
1419 : NULL,
1420 : aggregateFunctionStepHelper,
1421 18 : aggregateFunctionFinalHelper);
1422 18 : if (srv != SQLITE_OK)
1423 0 : return convertResultCode(srv);
1424 :
1425 : FunctionInfo info = { aFunction,
1426 : Connection::FunctionInfo::AGGREGATE,
1427 36 : aNumArguments };
1428 18 : NS_ENSURE_TRUE(mFunctions.Put(aFunctionName, info),
1429 : NS_ERROR_OUT_OF_MEMORY);
1430 :
1431 18 : return NS_OK;
1432 : }
1433 :
1434 : NS_IMETHODIMP
1435 369 : Connection::RemoveFunction(const nsACString &aFunctionName)
1436 : {
1437 369 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1438 :
1439 738 : SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1440 369 : NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, NULL), NS_ERROR_FAILURE);
1441 :
1442 : int srv = ::sqlite3_create_function(mDBConn,
1443 369 : nsPromiseFlatCString(aFunctionName).get(),
1444 : 0,
1445 : SQLITE_ANY,
1446 : NULL,
1447 : NULL,
1448 : NULL,
1449 369 : NULL);
1450 369 : if (srv != SQLITE_OK)
1451 0 : return convertResultCode(srv);
1452 :
1453 369 : mFunctions.Remove(aFunctionName);
1454 :
1455 369 : return NS_OK;
1456 : }
1457 :
1458 : NS_IMETHODIMP
1459 4674 : Connection::SetProgressHandler(PRInt32 aGranularity,
1460 : mozIStorageProgressHandler *aHandler,
1461 : mozIStorageProgressHandler **_oldHandler)
1462 : {
1463 4674 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1464 :
1465 : // Return previous one
1466 9348 : SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1467 4674 : NS_IF_ADDREF(*_oldHandler = mProgressHandler);
1468 :
1469 4674 : if (!aHandler || aGranularity <= 0) {
1470 0 : aHandler = nsnull;
1471 0 : aGranularity = 0;
1472 : }
1473 4674 : mProgressHandler = aHandler;
1474 4674 : ::sqlite3_progress_handler(mDBConn, aGranularity, sProgressHelper, this);
1475 :
1476 4674 : return NS_OK;
1477 : }
1478 :
1479 : NS_IMETHODIMP
1480 4672 : Connection::RemoveProgressHandler(mozIStorageProgressHandler **_oldHandler)
1481 : {
1482 4672 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1483 :
1484 : // Return previous one
1485 9344 : SQLiteMutexAutoLock lockedScope(sharedDBMutex);
1486 4672 : NS_IF_ADDREF(*_oldHandler = mProgressHandler);
1487 :
1488 4672 : mProgressHandler = nsnull;
1489 4672 : ::sqlite3_progress_handler(mDBConn, 0, NULL, NULL);
1490 :
1491 4672 : return NS_OK;
1492 : }
1493 :
1494 : NS_IMETHODIMP
1495 659 : Connection::SetGrowthIncrement(PRInt32 aChunkSize, const nsACString &aDatabaseName)
1496 : {
1497 : // Bug 597215: Disk space is extremely limited on Android
1498 : // so don't preallocate space. This is also not effective
1499 : // on log structured file systems used by Android devices
1500 : #if !defined(ANDROID) && !defined(MOZ_PLATFORM_MAEMO)
1501 : // Don't preallocate if less than 500MiB is available.
1502 1318 : nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(mDatabaseFile);
1503 659 : NS_ENSURE_STATE(localFile);
1504 : PRInt64 bytesAvailable;
1505 659 : nsresult rv = localFile->GetDiskSpaceAvailable(&bytesAvailable);
1506 659 : NS_ENSURE_SUCCESS(rv, rv);
1507 659 : if (bytesAvailable < MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH) {
1508 0 : return NS_ERROR_FILE_TOO_BIG;
1509 : }
1510 :
1511 : (void)::sqlite3_file_control(mDBConn,
1512 1318 : aDatabaseName.Length() ? nsPromiseFlatCString(aDatabaseName).get() : NULL,
1513 : SQLITE_FCNTL_CHUNK_SIZE,
1514 659 : &aChunkSize);
1515 : #endif
1516 659 : return NS_OK;
1517 : }
1518 :
1519 : NS_IMETHODIMP
1520 76 : Connection::EnableModule(const nsACString& aModuleName)
1521 : {
1522 76 : if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
1523 :
1524 76 : for (size_t i = 0; i < ArrayLength(gModules); i++) {
1525 76 : struct Module* m = &gModules[i];
1526 76 : if (aModuleName.Equals(m->name)) {
1527 76 : int srv = m->registerFunc(mDBConn, m->name);
1528 76 : if (srv != SQLITE_OK)
1529 0 : return convertResultCode(srv);
1530 :
1531 76 : return NS_OK;
1532 : }
1533 : }
1534 :
1535 0 : return NS_ERROR_FAILURE;
1536 : }
1537 :
1538 : } // namespace storage
1539 : } // namespace mozilla
|