1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 et sw=2 tw=80: */
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 Indexed Database.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * The Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2010
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Ben Turner <bent.mozilla@gmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "IDBTransaction.h"
41 :
42 : #include "nsIScriptContext.h"
43 :
44 : #include "mozilla/storage.h"
45 : #include "nsContentUtils.h"
46 : #include "nsDOMClassInfoID.h"
47 : #include "nsDOMLists.h"
48 : #include "nsEventDispatcher.h"
49 : #include "nsPIDOMWindow.h"
50 : #include "nsProxyRelease.h"
51 : #include "nsThreadUtils.h"
52 :
53 : #include "AsyncConnectionHelper.h"
54 : #include "DatabaseInfo.h"
55 : #include "IDBCursor.h"
56 : #include "IDBEvents.h"
57 : #include "IDBFactory.h"
58 : #include "IDBObjectStore.h"
59 : #include "IndexedDatabaseManager.h"
60 : #include "TransactionThreadPool.h"
61 :
62 : #define SAVEPOINT_NAME "savepoint"
63 :
64 : USING_INDEXEDDB_NAMESPACE
65 :
66 : namespace {
67 :
68 : PLDHashOperator
69 2053 : DoomCachedStatements(const nsACString& aQuery,
70 : nsCOMPtr<mozIStorageStatement>& aStatement,
71 : void* aUserArg)
72 : {
73 2053 : CommitHelper* helper = static_cast<CommitHelper*>(aUserArg);
74 2053 : helper->AddDoomedObject(aStatement);
75 2053 : return PL_DHASH_REMOVE;
76 : }
77 :
78 : // This runnable doesn't actually do anything beyond "prime the pump" and get
79 : // transactions in the right order on the transaction thread pool.
80 : class StartTransactionRunnable : public nsIRunnable
81 1464 : {
82 : public:
83 : NS_DECL_ISUPPORTS
84 :
85 469 : NS_IMETHOD Run()
86 : {
87 : // NOP
88 469 : return NS_OK;
89 : }
90 : };
91 :
92 : // Could really use those NS_REFCOUNTING_HAHA_YEAH_RIGHT macros here.
93 2050 : NS_IMETHODIMP_(nsrefcnt) StartTransactionRunnable::AddRef()
94 : {
95 2050 : return 2;
96 : }
97 :
98 2050 : NS_IMETHODIMP_(nsrefcnt) StartTransactionRunnable::Release()
99 : {
100 2050 : return 1;
101 : }
102 :
103 1025 : NS_IMPL_QUERY_INTERFACE1(StartTransactionRunnable, nsIRunnable)
104 :
105 1464 : StartTransactionRunnable gStartTransactionRunnable;
106 :
107 : } // anonymous namespace
108 :
109 : // static
110 : already_AddRefed<IDBTransaction>
111 540 : IDBTransaction::Create(IDBDatabase* aDatabase,
112 : nsTArray<nsString>& aObjectStoreNames,
113 : Mode aMode,
114 : bool aDispatchDelayed)
115 : {
116 540 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
117 :
118 1080 : nsRefPtr<IDBTransaction> transaction = new IDBTransaction();
119 :
120 540 : transaction->BindToOwner(aDatabase);
121 540 : if (!transaction->SetScriptOwner(aDatabase->GetScriptOwner())) {
122 0 : return nsnull;
123 : }
124 :
125 540 : transaction->mDatabase = aDatabase;
126 540 : transaction->mMode = aMode;
127 :
128 540 : transaction->mDatabaseInfo = aDatabase->Info();
129 :
130 540 : if (!transaction->mObjectStoreNames.AppendElements(aObjectStoreNames)) {
131 0 : NS_ERROR("Out of memory!");
132 0 : return nsnull;
133 : }
134 :
135 540 : if (!transaction->mCachedStatements.Init()) {
136 0 : NS_ERROR("Failed to initialize hash!");
137 0 : return nsnull;
138 : }
139 :
140 540 : if (!aDispatchDelayed) {
141 : nsCOMPtr<nsIThreadInternal> thread =
142 938 : do_QueryInterface(NS_GetCurrentThread());
143 469 : NS_ENSURE_TRUE(thread, nsnull);
144 :
145 : // We need the current recursion depth first.
146 : PRUint32 depth;
147 469 : nsresult rv = thread->GetRecursionDepth(&depth);
148 469 : NS_ENSURE_SUCCESS(rv, nsnull);
149 :
150 469 : NS_ASSERTION(depth, "This should never be 0!");
151 469 : transaction->mCreatedRecursionDepth = depth - 1;
152 :
153 469 : rv = thread->AddObserver(transaction);
154 469 : NS_ENSURE_SUCCESS(rv, nsnull);
155 :
156 938 : transaction->mCreating = true;
157 : }
158 :
159 540 : if (aMode != IDBTransaction::VERSION_CHANGE) {
160 469 : TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
161 469 : pool->Dispatch(transaction, &gStartTransactionRunnable, false, nsnull);
162 : }
163 :
164 540 : return transaction.forget();
165 : }
166 :
167 540 : IDBTransaction::IDBTransaction()
168 : : mReadyState(IDBTransaction::INITIAL),
169 : mMode(IDBTransaction::READ_ONLY),
170 : mPendingRequests(0),
171 : mCreatedRecursionDepth(0),
172 : mSavepointCount(0),
173 : mAborted(false),
174 : mCreating(false)
175 : #ifdef DEBUG
176 540 : , mFiredCompleteOrAbort(false)
177 : #endif
178 : {
179 540 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
180 540 : }
181 :
182 1620 : IDBTransaction::~IDBTransaction()
183 : {
184 540 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
185 540 : NS_ASSERTION(!mPendingRequests, "Should have no pending requests here!");
186 540 : NS_ASSERTION(!mSavepointCount, "Should have released them all!");
187 540 : NS_ASSERTION(!mConnection, "Should have called CommitOrRollback!");
188 540 : NS_ASSERTION(!mCreating, "Should have been cleared already!");
189 540 : NS_ASSERTION(mFiredCompleteOrAbort, "Should have fired event!");
190 :
191 540 : nsContentUtils::ReleaseWrapper(static_cast<nsIDOMEventTarget*>(this), this);
192 2160 : }
193 :
194 : void
195 4670 : IDBTransaction::OnNewRequest()
196 : {
197 4670 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
198 4670 : if (!mPendingRequests) {
199 529 : NS_ASSERTION(mReadyState == IDBTransaction::INITIAL,
200 : "Reusing a transaction!");
201 529 : mReadyState = IDBTransaction::LOADING;
202 : }
203 4670 : ++mPendingRequests;
204 4670 : }
205 :
206 : void
207 4670 : IDBTransaction::OnRequestFinished()
208 : {
209 4670 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
210 4670 : NS_ASSERTION(mPendingRequests, "Mismatched calls!");
211 4670 : --mPendingRequests;
212 4670 : if (!mPendingRequests) {
213 529 : NS_ASSERTION(mAborted || mReadyState == IDBTransaction::LOADING,
214 : "Bad state!");
215 529 : mReadyState = IDBTransaction::COMMITTING;
216 529 : CommitOrRollback();
217 : }
218 4670 : }
219 :
220 : void
221 123 : IDBTransaction::RemoveObjectStore(const nsAString& aName)
222 : {
223 123 : NS_ASSERTION(mMode == IDBTransaction::VERSION_CHANGE,
224 : "Only remove object stores on VERSION_CHANGE transactions");
225 :
226 123 : mDatabaseInfo->RemoveObjectStore(aName);
227 :
228 123 : for (PRUint32 i = 0; i < mCreatedObjectStores.Length(); i++) {
229 123 : if (mCreatedObjectStores[i]->Name() == aName) {
230 123 : mCreatedObjectStores.RemoveElementAt(i);
231 123 : break;
232 : }
233 : }
234 123 : }
235 :
236 : void
237 71 : IDBTransaction::SetTransactionListener(IDBTransactionListener* aListener)
238 : {
239 71 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
240 71 : NS_ASSERTION(!mListener, "Shouldn't already have a listener!");
241 71 : mListener = aListener;
242 71 : }
243 :
244 : nsresult
245 540 : IDBTransaction::CommitOrRollback()
246 : {
247 540 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
248 :
249 540 : TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
250 540 : NS_ENSURE_STATE(pool);
251 :
252 : nsRefPtr<CommitHelper> helper(new CommitHelper(this, mListener,
253 1620 : mCreatedObjectStores));
254 :
255 540 : mCachedStatements.Enumerate(DoomCachedStatements, helper);
256 540 : NS_ASSERTION(!mCachedStatements.Count(), "Statements left!");
257 :
258 540 : nsresult rv = pool->Dispatch(this, helper, true, helper);
259 540 : NS_ENSURE_SUCCESS(rv, rv);
260 :
261 540 : return NS_OK;
262 : }
263 :
264 : bool
265 4670 : IDBTransaction::StartSavepoint()
266 : {
267 4670 : NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!");
268 4670 : NS_PRECONDITION(mConnection, "No connection!");
269 :
270 4670 : nsCOMPtr<mozIStorageStatement> stmt = GetCachedStatement(NS_LITERAL_CSTRING(
271 : "SAVEPOINT " SAVEPOINT_NAME
272 9340 : ));
273 4670 : NS_ENSURE_TRUE(stmt, false);
274 :
275 9340 : mozStorageStatementScoper scoper(stmt);
276 :
277 4670 : nsresult rv = stmt->Execute();
278 4670 : NS_ENSURE_SUCCESS(rv, false);
279 :
280 4670 : ++mSavepointCount;
281 :
282 4670 : return true;
283 : }
284 :
285 : nsresult
286 4651 : IDBTransaction::ReleaseSavepoint()
287 : {
288 4651 : NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!");
289 4651 : NS_PRECONDITION(mConnection, "No connection!");
290 :
291 4651 : NS_ASSERTION(mSavepointCount, "Mismatch!");
292 :
293 4651 : nsCOMPtr<mozIStorageStatement> stmt = GetCachedStatement(NS_LITERAL_CSTRING(
294 : "RELEASE SAVEPOINT " SAVEPOINT_NAME
295 9302 : ));
296 4651 : NS_ENSURE_TRUE(stmt, false);
297 :
298 9302 : mozStorageStatementScoper scoper(stmt);
299 :
300 4651 : nsresult rv = stmt->Execute();
301 4651 : NS_ENSURE_SUCCESS(rv, false);
302 :
303 4651 : --mSavepointCount;
304 :
305 4651 : return NS_OK;
306 : }
307 :
308 : void
309 19 : IDBTransaction::RollbackSavepoint()
310 : {
311 19 : NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!");
312 19 : NS_PRECONDITION(mConnection, "No connection!");
313 :
314 19 : NS_ASSERTION(mSavepointCount == 1, "Mismatch!");
315 19 : mSavepointCount = 0;
316 :
317 19 : nsCOMPtr<mozIStorageStatement> stmt = GetCachedStatement(NS_LITERAL_CSTRING(
318 : "ROLLBACK TO SAVEPOINT " SAVEPOINT_NAME
319 38 : ));
320 19 : NS_ENSURE_TRUE(stmt,);
321 :
322 38 : mozStorageStatementScoper scoper(stmt);
323 :
324 19 : nsresult rv = stmt->Execute();
325 19 : NS_ENSURE_SUCCESS(rv,);
326 : }
327 :
328 : nsresult
329 4670 : IDBTransaction::GetOrCreateConnection(mozIStorageConnection** aResult)
330 : {
331 4670 : NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
332 :
333 4670 : if (mDatabase->IsInvalidated()) {
334 0 : return NS_ERROR_NOT_AVAILABLE;
335 : }
336 :
337 4670 : if (!mConnection) {
338 : nsCOMPtr<mozIStorageConnection> connection =
339 1058 : IDBFactory::GetConnection(mDatabase->FilePath());
340 529 : NS_ENSURE_TRUE(connection, NS_ERROR_FAILURE);
341 :
342 : nsresult rv;
343 :
344 1058 : nsRefPtr<UpdateRefcountFunction> function;
345 1058 : nsCString beginTransaction;
346 529 : if (mMode != IDBTransaction::READ_ONLY) {
347 734 : function = new UpdateRefcountFunction(Database()->Manager());
348 367 : NS_ENSURE_TRUE(function, NS_ERROR_OUT_OF_MEMORY);
349 :
350 367 : rv = function->Init();
351 367 : NS_ENSURE_SUCCESS(rv, rv);
352 :
353 367 : rv = connection->CreateFunction(
354 367 : NS_LITERAL_CSTRING("update_refcount"), 2, function);
355 367 : NS_ENSURE_SUCCESS(rv, rv);
356 :
357 367 : beginTransaction.AssignLiteral("BEGIN IMMEDIATE TRANSACTION;");
358 : }
359 : else {
360 162 : beginTransaction.AssignLiteral("BEGIN TRANSACTION;");
361 : }
362 :
363 1058 : nsCOMPtr<mozIStorageStatement> stmt;
364 529 : rv = connection->CreateStatement(beginTransaction, getter_AddRefs(stmt));
365 529 : NS_ENSURE_SUCCESS(rv, rv);
366 :
367 529 : rv = stmt->Execute();
368 529 : NS_ENSURE_SUCCESS(rv, rv);
369 :
370 529 : function.swap(mUpdateFileRefcountFunction);
371 1058 : connection.swap(mConnection);
372 : }
373 :
374 9340 : nsCOMPtr<mozIStorageConnection> result(mConnection);
375 4670 : result.forget(aResult);
376 4670 : return NS_OK;
377 : }
378 :
379 : already_AddRefed<mozIStorageStatement>
380 14453 : IDBTransaction::GetCachedStatement(const nsACString& aQuery)
381 : {
382 14453 : NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
383 14453 : NS_ASSERTION(!aQuery.IsEmpty(), "Empty sql statement!");
384 14453 : NS_ASSERTION(mConnection, "No connection!");
385 :
386 28906 : nsCOMPtr<mozIStorageStatement> stmt;
387 :
388 14453 : if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) {
389 2053 : nsresult rv = mConnection->CreateStatement(aQuery, getter_AddRefs(stmt));
390 : #ifdef DEBUG
391 2053 : if (NS_FAILED(rv)) {
392 0 : nsCString error;
393 0 : error.AppendLiteral("The statement `");
394 0 : error.Append(aQuery);
395 0 : error.AppendLiteral("` failed to compile with the error message `");
396 0 : nsCString msg;
397 0 : (void)mConnection->GetLastErrorString(msg);
398 0 : error.Append(msg);
399 0 : error.AppendLiteral("`.");
400 0 : NS_ERROR(error.get());
401 : }
402 : #endif
403 2053 : NS_ENSURE_SUCCESS(rv, nsnull);
404 :
405 2053 : if (!mCachedStatements.Put(aQuery, stmt)) {
406 0 : NS_ERROR("Out of memory?!");
407 : }
408 : }
409 :
410 14453 : return stmt.forget();
411 : }
412 :
413 : bool
414 9267 : IDBTransaction::IsOpen() const
415 : {
416 9267 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
417 :
418 : // If we haven't started anything then we're open.
419 9267 : if (mReadyState == IDBTransaction::INITIAL) {
420 958 : NS_ASSERTION(AsyncConnectionHelper::GetCurrentTransaction() != this,
421 : "This should be some other transaction (or null)!");
422 958 : return true;
423 : }
424 :
425 : // If we've already started then we need to check to see if we still have the
426 : // mCreating flag set. If we do (i.e. we haven't returned to the event loop
427 : // from the time we were created) then we are open. Otherwise check the
428 : // currently running transaction to see if it's the same. We only allow other
429 : // requests to be made if this transaction is currently running.
430 8309 : if (mReadyState == IDBTransaction::LOADING) {
431 8281 : if (mCreating) {
432 42 : return true;
433 : }
434 :
435 8239 : if (AsyncConnectionHelper::GetCurrentTransaction() == this) {
436 8239 : return true;
437 : }
438 : }
439 :
440 28 : return false;
441 : }
442 :
443 : already_AddRefed<IDBObjectStore>
444 679 : IDBTransaction::GetOrCreateObjectStore(const nsAString& aName,
445 : ObjectStoreInfo* aObjectStoreInfo)
446 : {
447 679 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
448 679 : NS_ASSERTION(aObjectStoreInfo, "Null pointer!");
449 :
450 1358 : nsRefPtr<IDBObjectStore> retval;
451 :
452 791 : for (PRUint32 index = 0; index < mCreatedObjectStores.Length(); index++) {
453 124 : nsRefPtr<IDBObjectStore>& objectStore = mCreatedObjectStores[index];
454 124 : if (objectStore->Name() == aName) {
455 12 : retval = objectStore;
456 12 : return retval.forget();
457 : }
458 : }
459 :
460 667 : retval = IDBObjectStore::Create(this, aObjectStoreInfo, mDatabaseInfo->id);
461 :
462 667 : mCreatedObjectStores.AppendElement(retval);
463 :
464 667 : return retval.forget();
465 : }
466 :
467 : void
468 0 : IDBTransaction::OnNewFileInfo(FileInfo* aFileInfo)
469 : {
470 0 : mCreatedFileInfos.AppendElement(aFileInfo);
471 0 : }
472 :
473 : void
474 540 : IDBTransaction::ClearCreatedFileInfos()
475 : {
476 540 : mCreatedFileInfos.Clear();
477 540 : }
478 :
479 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
480 :
481 519 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction,
482 : IDBWrapperCache)
483 519 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mDatabase,
484 : nsIDOMEventTarget)
485 519 : NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(error)
486 519 : NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(complete)
487 519 : NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(abort)
488 :
489 1063 : for (PRUint32 i = 0; i < tmp->mCreatedObjectStores.Length(); i++) {
490 544 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCreatedObjectStores[i]");
491 : cb.NoteXPCOMChild(static_cast<nsIIDBObjectStore*>(
492 544 : tmp->mCreatedObjectStores[i].get()));
493 : }
494 519 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
495 :
496 519 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction, IDBWrapperCache)
497 : // Don't unlink mDatabase!
498 519 : NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(error)
499 519 : NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(complete)
500 519 : NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(abort)
501 :
502 519 : tmp->mCreatedObjectStores.Clear();
503 :
504 519 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
505 :
506 47771 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBTransaction)
507 27694 : NS_INTERFACE_MAP_ENTRY(nsIIDBTransaction)
508 24495 : NS_INTERFACE_MAP_ENTRY(nsIThreadObserver)
509 24026 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(IDBTransaction)
510 23541 : NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache)
511 :
512 34997 : NS_IMPL_ADDREF_INHERITED(IDBTransaction, IDBWrapperCache)
513 34997 : NS_IMPL_RELEASE_INHERITED(IDBTransaction, IDBWrapperCache)
514 :
515 : DOMCI_DATA(IDBTransaction, IDBTransaction)
516 :
517 0 : NS_IMPL_EVENT_HANDLER(IDBTransaction, error);
518 278 : NS_IMPL_EVENT_HANDLER(IDBTransaction, complete);
519 8 : NS_IMPL_EVENT_HANDLER(IDBTransaction, abort);
520 :
521 : NS_IMETHODIMP
522 28 : IDBTransaction::GetDb(nsIIDBDatabase** aDB)
523 : {
524 28 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
525 :
526 28 : NS_ADDREF(*aDB = mDatabase);
527 28 : return NS_OK;
528 : }
529 :
530 : NS_IMETHODIMP
531 118 : IDBTransaction::GetMode(nsAString& aMode)
532 : {
533 118 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
534 :
535 118 : switch(mMode) {
536 : case READ_ONLY:
537 85 : aMode.AssignLiteral("readonly");
538 85 : break;
539 : case READ_WRITE:
540 0 : aMode.AssignLiteral("readwrite");
541 0 : break;
542 : case VERSION_CHANGE:
543 33 : aMode.AssignLiteral("versionchange");
544 : }
545 :
546 118 : return NS_OK;
547 : }
548 :
549 : NS_IMETHODIMP
550 142 : IDBTransaction::GetObjectStoreNames(nsIDOMDOMStringList** aObjectStores)
551 : {
552 142 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
553 :
554 284 : nsRefPtr<nsDOMStringList> list(new nsDOMStringList());
555 :
556 284 : nsAutoTArray<nsString, 10> stackArray;
557 : nsTArray<nsString>* arrayOfNames;
558 :
559 142 : if (mMode == IDBTransaction::VERSION_CHANGE) {
560 142 : mDatabaseInfo->GetObjectStoreNames(stackArray);
561 :
562 142 : arrayOfNames = &stackArray;
563 : }
564 : else {
565 0 : arrayOfNames = &mObjectStoreNames;
566 : }
567 :
568 142 : PRUint32 count = arrayOfNames->Length();
569 1173 : for (PRUint32 index = 0; index < count; index++) {
570 1031 : NS_ENSURE_TRUE(list->Add(arrayOfNames->ElementAt(index)),
571 : NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
572 : }
573 :
574 142 : list.forget(aObjectStores);
575 142 : return NS_OK;
576 : }
577 :
578 : NS_IMETHODIMP
579 481 : IDBTransaction::ObjectStore(const nsAString& aName,
580 : nsIIDBObjectStore** _retval)
581 : {
582 481 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
583 :
584 481 : if (!IsOpen()) {
585 2 : return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
586 : }
587 :
588 479 : ObjectStoreInfo* info = nsnull;
589 :
590 952 : if (mMode == IDBTransaction::VERSION_CHANGE ||
591 473 : mObjectStoreNames.Contains(aName)) {
592 479 : info = mDatabaseInfo->GetObjectStore(aName);
593 : }
594 :
595 479 : if (!info) {
596 1 : return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR;
597 : }
598 :
599 956 : nsRefPtr<IDBObjectStore> objectStore = GetOrCreateObjectStore(aName, info);
600 478 : NS_ENSURE_TRUE(objectStore, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
601 :
602 478 : objectStore.forget(_retval);
603 478 : return NS_OK;
604 : }
605 :
606 : NS_IMETHODIMP
607 14 : IDBTransaction::Abort()
608 : {
609 14 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
610 :
611 : // We can't use IsOpen here since we need it to be possible to call Abort()
612 : // even from outside of transaction callbacks.
613 14 : if (mReadyState != IDBTransaction::INITIAL &&
614 : mReadyState != IDBTransaction::LOADING) {
615 4 : return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
616 : }
617 :
618 10 : bool needToCommitOrRollback = mReadyState == IDBTransaction::INITIAL;
619 :
620 10 : mAborted = true;
621 10 : mReadyState = IDBTransaction::DONE;
622 :
623 : if (Mode() == IDBTransaction::VERSION_CHANGE) {
624 : // If a version change transaction is aborted, the db must be closed
625 : mDatabase->Close();
626 : }
627 :
628 : // Fire the abort event if there are no outstanding requests. Otherwise the
629 : // abort event will be fired when all outstanding requests finish.
630 10 : if (needToCommitOrRollback) {
631 1 : return CommitOrRollback();
632 : }
633 :
634 9 : return NS_OK;
635 : }
636 :
637 : nsresult
638 4777 : IDBTransaction::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
639 : {
640 4777 : aVisitor.mCanHandle = true;
641 4777 : aVisitor.mParentTarget = mDatabase;
642 4777 : return NS_OK;
643 : }
644 :
645 : // XXX Once nsIThreadObserver gets split this method will disappear.
646 : NS_IMETHODIMP
647 0 : IDBTransaction::OnDispatchedEvent(nsIThreadInternal* aThread)
648 : {
649 0 : NS_NOTREACHED("Don't call me!");
650 0 : return NS_ERROR_NOT_IMPLEMENTED;
651 : }
652 :
653 : NS_IMETHODIMP
654 1 : IDBTransaction::OnProcessNextEvent(nsIThreadInternal* aThread,
655 : bool aMayWait,
656 : PRUint32 aRecursionDepth)
657 : {
658 1 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
659 1 : NS_ASSERTION(aRecursionDepth > mCreatedRecursionDepth,
660 : "Should be impossible!");
661 1 : NS_ASSERTION(mCreating, "Should be true!");
662 1 : return NS_OK;
663 : }
664 :
665 : NS_IMETHODIMP
666 470 : IDBTransaction::AfterProcessNextEvent(nsIThreadInternal* aThread,
667 : PRUint32 aRecursionDepth)
668 : {
669 470 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
670 470 : NS_ASSERTION(aThread, "This should never be null!");
671 470 : NS_ASSERTION(aRecursionDepth >= mCreatedRecursionDepth,
672 : "Should be impossible!");
673 470 : NS_ASSERTION(mCreating, "Should be true!");
674 :
675 470 : if (aRecursionDepth == mCreatedRecursionDepth) {
676 : // We're back at the event loop, no longer newborn.
677 469 : mCreating = false;
678 :
679 : // Maybe set the readyState to DONE if there were no requests generated.
680 469 : if (mReadyState == IDBTransaction::INITIAL) {
681 10 : mReadyState = IDBTransaction::DONE;
682 :
683 10 : if (NS_FAILED(CommitOrRollback())) {
684 0 : NS_WARNING("Failed to commit!");
685 : }
686 : }
687 :
688 : // No longer need to observe thread events.
689 469 : if(NS_FAILED(aThread->RemoveObserver(this))) {
690 0 : NS_ERROR("Failed to remove observer!");
691 : }
692 : }
693 :
694 470 : return NS_OK;
695 : }
696 :
697 540 : CommitHelper::CommitHelper(
698 : IDBTransaction* aTransaction,
699 : IDBTransactionListener* aListener,
700 : const nsTArray<nsRefPtr<IDBObjectStore> >& aUpdatedObjectStores)
701 : : mTransaction(aTransaction),
702 : mListener(aListener),
703 540 : mAborted(!!aTransaction->mAborted)
704 : {
705 540 : mConnection.swap(aTransaction->mConnection);
706 540 : mUpdateFileRefcountFunction.swap(aTransaction->mUpdateFileRefcountFunction);
707 :
708 1084 : for (PRUint32 i = 0; i < aUpdatedObjectStores.Length(); i++) {
709 544 : ObjectStoreInfo* info = aUpdatedObjectStores[i]->Info();
710 544 : if (info->comittedAutoIncrementId != info->nextAutoIncrementId) {
711 277 : mAutoIncrementObjectStores.AppendElement(aUpdatedObjectStores[i]);
712 : }
713 : }
714 540 : }
715 :
716 540 : CommitHelper::~CommitHelper()
717 : {
718 540 : }
719 :
720 7020 : NS_IMPL_THREADSAFE_ISUPPORTS1(CommitHelper, nsIRunnable)
721 :
722 : NS_IMETHODIMP
723 1080 : CommitHelper::Run()
724 : {
725 1080 : if (NS_IsMainThread()) {
726 540 : NS_ASSERTION(mDoomedObjects.IsEmpty(), "Didn't release doomed objects!");
727 :
728 540 : mTransaction->mReadyState = IDBTransaction::DONE;
729 :
730 : // Release file infos on the main thread, so they will eventually get
731 : // destroyed on correct thread.
732 540 : mTransaction->ClearCreatedFileInfos();
733 540 : if (mUpdateFileRefcountFunction) {
734 367 : mUpdateFileRefcountFunction->ClearFileInfoEntries();
735 367 : mUpdateFileRefcountFunction = nsnull;
736 : }
737 :
738 1080 : nsCOMPtr<nsIDOMEvent> event;
739 540 : if (mAborted) {
740 10 : if (mTransaction->GetMode() == IDBTransaction::VERSION_CHANGE) {
741 : // This will make the database take a snapshot of it's DatabaseInfo
742 1 : mTransaction->Database()->Close();
743 : // Then remove the info from the hash as it contains invalid data.
744 1 : DatabaseInfo::Remove(mTransaction->Database()->Id());
745 : }
746 :
747 10 : event = CreateGenericEvent(NS_LITERAL_STRING(ABORT_EVT_STR),
748 10 : eDoesBubble, eNotCancelable);
749 : }
750 : else {
751 530 : event = CreateGenericEvent(NS_LITERAL_STRING(COMPLETE_EVT_STR),
752 530 : eDoesNotBubble, eNotCancelable);
753 : }
754 540 : NS_ENSURE_TRUE(event, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
755 :
756 : bool dummy;
757 540 : if (NS_FAILED(mTransaction->DispatchEvent(event, &dummy))) {
758 0 : NS_WARNING("Dispatch failed!");
759 : }
760 :
761 : #ifdef DEBUG
762 540 : mTransaction->mFiredCompleteOrAbort = true;
763 : #endif
764 :
765 : // Tell the listener (if we have one) that we're done
766 540 : if (mListener) {
767 71 : mListener->NotifyTransactionComplete(mTransaction);
768 : }
769 :
770 540 : mTransaction = nsnull;
771 :
772 540 : return NS_OK;
773 : }
774 :
775 540 : IDBDatabase* database = mTransaction->Database();
776 540 : if (database->IsInvalidated()) {
777 0 : mAborted = true;
778 : }
779 :
780 540 : if (mConnection) {
781 529 : IndexedDatabaseManager::SetCurrentWindow(database->GetOwner());
782 :
783 891 : if (!mAborted && mUpdateFileRefcountFunction &&
784 362 : NS_FAILED(mUpdateFileRefcountFunction->UpdateDatabase(mConnection))) {
785 0 : mAborted = true;
786 : }
787 :
788 529 : if (!mAborted && NS_FAILED(WriteAutoIncrementCounts())) {
789 0 : mAborted = true;
790 : }
791 :
792 529 : if (!mAborted) {
793 1040 : NS_NAMED_LITERAL_CSTRING(release, "COMMIT TRANSACTION");
794 520 : if (NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(release))) {
795 520 : if (mUpdateFileRefcountFunction) {
796 362 : mUpdateFileRefcountFunction->UpdateFileInfos();
797 : }
798 520 : CommitAutoIncrementCounts();
799 : }
800 : else {
801 0 : mAborted = true;
802 : }
803 : }
804 :
805 529 : if (mAborted) {
806 9 : RevertAutoIncrementCounts();
807 18 : NS_NAMED_LITERAL_CSTRING(rollback, "ROLLBACK TRANSACTION");
808 9 : if (NS_FAILED(mConnection->ExecuteSimpleSQL(rollback))) {
809 0 : NS_WARNING("Failed to rollback transaction!");
810 : }
811 : }
812 : }
813 :
814 540 : mDoomedObjects.Clear();
815 :
816 540 : if (mConnection) {
817 529 : if (mUpdateFileRefcountFunction) {
818 367 : nsresult rv = mConnection->RemoveFunction(
819 367 : NS_LITERAL_CSTRING("update_refcount"));
820 367 : if (NS_FAILED(rv)) {
821 0 : NS_WARNING("Failed to remove function!");
822 : }
823 : }
824 :
825 529 : mConnection->Close();
826 529 : mConnection = nsnull;
827 :
828 529 : IndexedDatabaseManager::SetCurrentWindow(nsnull);
829 : }
830 :
831 540 : return NS_OK;
832 : }
833 :
834 : nsresult
835 520 : CommitHelper::WriteAutoIncrementCounts()
836 : {
837 1040 : nsCOMPtr<mozIStorageStatement> stmt;
838 : nsresult rv;
839 794 : for (PRUint32 i = 0; i < mAutoIncrementObjectStores.Length(); i++) {
840 274 : ObjectStoreInfo* info = mAutoIncrementObjectStores[i]->Info();
841 274 : if (!stmt) {
842 546 : rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
843 : "UPDATE object_store SET auto_increment = :ai "
844 546 : "WHERE id = :osid;"), getter_AddRefs(stmt));
845 273 : NS_ENSURE_SUCCESS(rv, rv);
846 : }
847 : else {
848 1 : stmt->Reset();
849 : }
850 :
851 274 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), info->id);
852 274 : NS_ENSURE_SUCCESS(rv, rv);
853 :
854 548 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("ai"),
855 274 : info->nextAutoIncrementId);
856 274 : NS_ENSURE_SUCCESS(rv, rv);
857 :
858 274 : rv = stmt->Execute();
859 274 : NS_ENSURE_SUCCESS(rv, rv);
860 : }
861 :
862 520 : return NS_OK;
863 : }
864 :
865 : void
866 520 : CommitHelper::CommitAutoIncrementCounts()
867 : {
868 794 : for (PRUint32 i = 0; i < mAutoIncrementObjectStores.Length(); i++) {
869 274 : ObjectStoreInfo* info = mAutoIncrementObjectStores[i]->Info();
870 274 : info->comittedAutoIncrementId = info->nextAutoIncrementId;
871 : }
872 520 : }
873 :
874 : void
875 9 : CommitHelper::RevertAutoIncrementCounts()
876 : {
877 12 : for (PRUint32 i = 0; i < mAutoIncrementObjectStores.Length(); i++) {
878 3 : ObjectStoreInfo* info = mAutoIncrementObjectStores[i]->Info();
879 3 : info->nextAutoIncrementId = info->comittedAutoIncrementId;
880 : }
881 9 : }
882 :
883 : nsresult
884 367 : UpdateRefcountFunction::Init()
885 : {
886 367 : NS_ENSURE_TRUE(mFileInfoEntries.Init(), NS_ERROR_OUT_OF_MEMORY);
887 :
888 367 : return NS_OK;
889 : }
890 :
891 3303 : NS_IMPL_THREADSAFE_ISUPPORTS1(UpdateRefcountFunction, mozIStorageFunction)
892 :
893 : NS_IMETHODIMP
894 0 : UpdateRefcountFunction::OnFunctionCall(mozIStorageValueArray* aValues,
895 : nsIVariant** _retval)
896 : {
897 0 : *_retval = nsnull;
898 :
899 : PRUint32 numEntries;
900 0 : nsresult rv = aValues->GetNumEntries(&numEntries);
901 0 : NS_ENSURE_SUCCESS(rv, rv);
902 0 : NS_ASSERTION(numEntries == 2, "unexpected number of arguments");
903 :
904 : #ifdef DEBUG
905 0 : PRInt32 type1 = mozIStorageValueArray::VALUE_TYPE_NULL;
906 0 : aValues->GetTypeOfIndex(0, &type1);
907 :
908 0 : PRInt32 type2 = mozIStorageValueArray::VALUE_TYPE_NULL;
909 0 : aValues->GetTypeOfIndex(1, &type2);
910 :
911 0 : NS_ASSERTION(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL &&
912 : type2 == mozIStorageValueArray::VALUE_TYPE_NULL),
913 : "Shouldn't be called!");
914 : #endif
915 :
916 0 : rv = ProcessValue(aValues, 0, eDecrement);
917 0 : NS_ENSURE_SUCCESS(rv, rv);
918 :
919 0 : rv = ProcessValue(aValues, 1, eIncrement);
920 0 : NS_ENSURE_SUCCESS(rv, rv);
921 :
922 0 : return NS_OK;
923 : }
924 :
925 : nsresult
926 0 : UpdateRefcountFunction::ProcessValue(mozIStorageValueArray* aValues,
927 : PRInt32 aIndex,
928 : UpdateType aUpdateType)
929 : {
930 : PRInt32 type;
931 0 : aValues->GetTypeOfIndex(aIndex, &type);
932 0 : if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
933 0 : return NS_OK;
934 : }
935 :
936 0 : nsString ids;
937 0 : aValues->GetString(aIndex, ids);
938 :
939 0 : nsTArray<PRInt64> fileIds;
940 0 : nsresult rv = IDBObjectStore::ConvertFileIdsToArray(ids, fileIds);
941 0 : NS_ENSURE_SUCCESS(rv, rv);
942 :
943 0 : for (PRUint32 i = 0; i < fileIds.Length(); i++) {
944 0 : PRInt64 id = fileIds.ElementAt(i);
945 :
946 : FileInfoEntry* entry;
947 0 : if (!mFileInfoEntries.Get(id, &entry)) {
948 0 : nsRefPtr<FileInfo> fileInfo = mFileManager->GetFileInfo(id);
949 0 : NS_ASSERTION(fileInfo, "Shouldn't be null!");
950 :
951 0 : nsAutoPtr<FileInfoEntry> newEntry(new FileInfoEntry(fileInfo));
952 0 : if (!mFileInfoEntries.Put(id, newEntry)) {
953 0 : NS_WARNING("Out of memory?");
954 0 : return NS_ERROR_OUT_OF_MEMORY;
955 : }
956 0 : entry = newEntry.forget();
957 : }
958 :
959 0 : switch (aUpdateType) {
960 : case eIncrement:
961 0 : entry->mDelta++;
962 0 : break;
963 : case eDecrement:
964 0 : entry->mDelta--;
965 0 : break;
966 : default:
967 0 : NS_NOTREACHED("Unknown update type!");
968 : }
969 : }
970 :
971 0 : return NS_OK;
972 : }
973 :
974 : PLDHashOperator
975 0 : UpdateRefcountFunction::DatabaseUpdateCallback(const PRUint64& aKey,
976 : FileInfoEntry* aValue,
977 : void* aUserArg)
978 : {
979 0 : if (!aValue->mDelta) {
980 0 : return PL_DHASH_NEXT;
981 : }
982 :
983 : DatabaseUpdateFunction* function =
984 0 : static_cast<DatabaseUpdateFunction*>(aUserArg);
985 :
986 0 : if (!function->Update(aKey, aValue->mDelta)) {
987 0 : return PL_DHASH_STOP;
988 : }
989 :
990 0 : return PL_DHASH_NEXT;
991 : }
992 :
993 : PLDHashOperator
994 0 : UpdateRefcountFunction::FileInfoUpdateCallback(const PRUint64& aKey,
995 : FileInfoEntry* aValue,
996 : void* aUserArg)
997 : {
998 0 : if (aValue->mDelta) {
999 0 : aValue->mFileInfo->UpdateDBRefs(aValue->mDelta);
1000 : }
1001 :
1002 0 : return PL_DHASH_NEXT;
1003 : }
1004 :
1005 : bool
1006 0 : UpdateRefcountFunction::DatabaseUpdateFunction::Update(PRInt64 aId,
1007 : PRInt32 aDelta)
1008 : {
1009 0 : nsresult rv = UpdateInternal(aId, aDelta);
1010 0 : if (NS_FAILED(rv)) {
1011 0 : mErrorCode = rv;
1012 0 : return false;
1013 : }
1014 :
1015 0 : return true;
1016 : }
1017 :
1018 : nsresult
1019 0 : UpdateRefcountFunction::DatabaseUpdateFunction::UpdateInternal(PRInt64 aId,
1020 : PRInt32 aDelta)
1021 : {
1022 : nsresult rv;
1023 :
1024 0 : if (!mUpdateStatement) {
1025 0 : rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
1026 : "UPDATE file SET refcount = refcount + :delta WHERE id = :id"
1027 0 : ), getter_AddRefs(mUpdateStatement));
1028 0 : NS_ENSURE_SUCCESS(rv, rv);
1029 : }
1030 :
1031 0 : mozStorageStatementScoper updateScoper(mUpdateStatement);
1032 :
1033 0 : rv = mUpdateStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
1034 0 : NS_ENSURE_SUCCESS(rv, rv);
1035 :
1036 0 : rv = mUpdateStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
1037 0 : NS_ENSURE_SUCCESS(rv, rv);
1038 :
1039 0 : rv = mUpdateStatement->Execute();
1040 0 : NS_ENSURE_SUCCESS(rv, rv);
1041 :
1042 : PRInt32 rows;
1043 0 : rv = mConnection->GetAffectedRows(&rows);
1044 0 : NS_ENSURE_SUCCESS(rv, rv);
1045 :
1046 0 : if (rows > 0) {
1047 0 : return NS_OK;
1048 : }
1049 :
1050 0 : if (!mInsertStatement) {
1051 0 : rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
1052 : "INSERT INTO file (id, refcount) VALUES(:id, :delta)"
1053 0 : ), getter_AddRefs(mInsertStatement));
1054 0 : NS_ENSURE_SUCCESS(rv, rv);
1055 : }
1056 :
1057 0 : mozStorageStatementScoper insertScoper(mInsertStatement);
1058 :
1059 0 : rv = mInsertStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
1060 0 : NS_ENSURE_SUCCESS(rv, rv);
1061 :
1062 0 : rv = mInsertStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
1063 0 : NS_ENSURE_SUCCESS(rv, rv);
1064 :
1065 0 : rv = mInsertStatement->Execute();
1066 0 : NS_ENSURE_SUCCESS(rv, rv);
1067 :
1068 0 : return NS_OK;
1069 4392 : }
|