1 : /* ***** BEGIN LICENSE BLOCK *****
2 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 : *
4 : * The contents of this file are subject to the Mozilla Public License Version
5 : * 1.1 (the "License"); you may not use this file except in compliance with
6 : * the License. You may obtain a copy of the License at
7 : * http://www.mozilla.org/MPL/
8 : *
9 : * Software distributed under the License is distributed on an "AS IS" basis,
10 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 : * for the specific language governing rights and limitations under the
12 : * License.
13 : *
14 : * The Original Code is Places code.
15 : *
16 : * The Initial Developer of the Original Code is
17 : * the Mozilla Foundation.
18 : * Portions created by the Initial Developer are Copyright (C) 2011
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Marco Bonardo <mak77@bonardo.net>
23 : *
24 : * Original contributor(s) of code moved from nsNavHistory.cpp:
25 : * Brett Wilson <brettw@gmail.com> (original author)
26 : * Dietrich Ayala <dietrich@mozilla.com>
27 : * Seth Spitzer <sspitzer@mozilla.com>
28 : * Asaf Romano <mano@mozilla.com>
29 : * Marco Bonardo <mak77@bonardo.net>
30 : * Edward Lee <edward.lee@engineering.uiuc.edu>
31 : * Michael Ventnor <m.ventnor@gmail.com>
32 : * Ehsan Akhgari <ehsan.akhgari@gmail.com>
33 : * Drew Willcoxon <adw@mozilla.com>
34 : * Philipp von Weitershausen <philipp@weitershausen.de>
35 : * Paolo Amadini <http://www.amadzone.org/>
36 : * Richard Newman <rnewman@mozilla.com>
37 : *
38 : * Alternatively, the contents of this file may be used under the terms of
39 : * either the GNU General Public License Version 2 or later (the "GPL"), or
40 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
41 : * in which case the provisions of the GPL or the LGPL are applicable instead
42 : * of those above. If you wish to allow use of your version of this file only
43 : * under the terms of either the GPL or the LGPL, and not to allow others to
44 : * use your version of this file under the terms of the MPL, indicate your
45 : * decision by deleting the provisions above and replace them with the notice
46 : * and other provisions required by the GPL or the LGPL. If you do not delete
47 : * the provisions above, a recipient may use your version of this file under
48 : * the terms of any one of the MPL, the GPL or the LGPL.
49 : *
50 : * ***** END LICENSE BLOCK ***** */
51 :
52 : #include "Database.h"
53 :
54 : #include "nsINavBookmarksService.h"
55 : #include "nsIInterfaceRequestorUtils.h"
56 : #include "nsILocalFile.h"
57 :
58 : #include "nsNavHistory.h"
59 : #include "nsPlacesTables.h"
60 : #include "nsPlacesIndexes.h"
61 : #include "nsPlacesTriggers.h"
62 : #include "nsPlacesMacros.h"
63 : #include "SQLFunctions.h"
64 : #include "Helpers.h"
65 :
66 : #include "nsAppDirectoryServiceDefs.h"
67 : #include "nsDirectoryServiceUtils.h"
68 : #include "prsystem.h"
69 : #include "nsPrintfCString.h"
70 : #include "mozilla/Util.h"
71 : #include "mozilla/Preferences.h"
72 : #include "mozilla/Services.h"
73 :
74 : // Time between corrupt database backups.
75 : #define RECENT_BACKUP_TIME_MICROSEC (PRInt64)86400 * PR_USEC_PER_SEC // 24H
76 :
77 : // Filename of the database.
78 : #define DATABASE_FILENAME NS_LITERAL_STRING("places.sqlite")
79 : // Filename used to backup corrupt databases.
80 : #define DATABASE_CORRUPT_FILENAME NS_LITERAL_STRING("places.sqlite.corrupt")
81 :
82 : // Set when the database file was found corrupt by a previous maintenance.
83 : #define PREF_FORCE_DATABASE_REPLACEMENT "places.database.replaceOnStartup"
84 :
85 : // Maximum size for the WAL file. It should be small enough since in case of
86 : // crashes we could lose all the transactions in the file. But a too small
87 : // file could hurt performance.
88 : #define DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES 512
89 :
90 : #define BYTES_PER_MEBIBYTE 1048576
91 :
92 : // Old Sync GUID annotation.
93 : #define SYNCGUID_ANNO NS_LITERAL_CSTRING("sync/guid")
94 :
95 : // Places string bundle, contains internationalized bookmark root names.
96 : #define PLACES_BUNDLE "chrome://places/locale/places.properties"
97 :
98 : // Livemarks annotations.
99 : #define LMANNO_FEEDURI "livemark/feedURI"
100 : #define LMANNO_SITEURI "livemark/siteURI"
101 :
102 : using namespace mozilla;
103 :
104 : namespace mozilla {
105 : namespace places {
106 :
107 : namespace {
108 :
109 : ////////////////////////////////////////////////////////////////////////////////
110 : //// Helpers
111 :
112 : /**
113 : * Checks whether exists a database backup created not longer than
114 : * RECENT_BACKUP_TIME_MICROSEC ago.
115 : */
116 : bool
117 6 : hasRecentCorruptDB()
118 : {
119 6 : MOZ_ASSERT(NS_IsMainThread());
120 :
121 12 : nsCOMPtr<nsIFile> profDir;
122 6 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profDir));
123 6 : NS_ENSURE_TRUE(profDir, false);
124 12 : nsCOMPtr<nsISimpleEnumerator> entries;
125 6 : profDir->GetDirectoryEntries(getter_AddRefs(entries));
126 6 : NS_ENSURE_TRUE(entries, false);
127 : bool hasMore;
128 34 : while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
129 44 : nsCOMPtr<nsISupports> next;
130 22 : entries->GetNext(getter_AddRefs(next));
131 22 : NS_ENSURE_TRUE(next, false);
132 44 : nsCOMPtr<nsIFile> currFile = do_QueryInterface(next);
133 22 : NS_ENSURE_TRUE(currFile, false);
134 :
135 44 : nsAutoString leafName;
136 100 : if (NS_SUCCEEDED(currFile->GetLeafName(leafName)) &&
137 66 : leafName.Length() >= DATABASE_CORRUPT_FILENAME.Length() &&
138 34 : leafName.Find(".corrupt", DATABASE_FILENAME.Length()) != -1) {
139 0 : PRInt64 lastMod = 0;
140 0 : currFile->GetLastModifiedTime(&lastMod);
141 0 : NS_ENSURE_TRUE(lastMod > 0, false);
142 0 : return (PR_Now() - lastMod) > RECENT_BACKUP_TIME_MICROSEC;
143 : }
144 : }
145 6 : return false;
146 : }
147 :
148 : /**
149 : * Updates sqlite_stat1 table through ANALYZE.
150 : * Since also nsPlacesExpiration.js executes ANALYZE, the analyzed tables
151 : * must be the same in both components. So ensure they are in sync.
152 : *
153 : * @param aDBConn
154 : * The database connection.
155 : */
156 : nsresult
157 265 : updateSQLiteStatistics(mozIStorageConnection* aDBConn)
158 : {
159 265 : MOZ_ASSERT(NS_IsMainThread());
160 530 : nsCOMPtr<mozIStorageAsyncStatement> analyzePlacesStmt;
161 265 : aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
162 : "ANALYZE moz_places"
163 530 : ), getter_AddRefs(analyzePlacesStmt));
164 265 : NS_ENSURE_STATE(analyzePlacesStmt);
165 530 : nsCOMPtr<mozIStorageAsyncStatement> analyzeBookmarksStmt;
166 265 : aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
167 : "ANALYZE moz_bookmarks"
168 530 : ), getter_AddRefs(analyzeBookmarksStmt));
169 265 : NS_ENSURE_STATE(analyzeBookmarksStmt);
170 530 : nsCOMPtr<mozIStorageAsyncStatement> analyzeVisitsStmt;
171 265 : aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
172 : "ANALYZE moz_historyvisits"
173 530 : ), getter_AddRefs(analyzeVisitsStmt));
174 265 : NS_ENSURE_STATE(analyzeVisitsStmt);
175 530 : nsCOMPtr<mozIStorageAsyncStatement> analyzeInputStmt;
176 265 : aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
177 : "ANALYZE moz_inputhistory"
178 530 : ), getter_AddRefs(analyzeInputStmt));
179 265 : NS_ENSURE_STATE(analyzeInputStmt);
180 :
181 : mozIStorageBaseStatement *stmts[] = {
182 : analyzePlacesStmt,
183 : analyzeBookmarksStmt,
184 : analyzeVisitsStmt,
185 : analyzeInputStmt
186 265 : };
187 :
188 530 : nsCOMPtr<mozIStoragePendingStatement> ps;
189 : (void)aDBConn->ExecuteAsync(stmts, ArrayLength(stmts), nsnull,
190 265 : getter_AddRefs(ps));
191 265 : return NS_OK;
192 : }
193 :
194 : /**
195 : * Sets the connection journal mode to one of the JOURNAL_* types.
196 : *
197 : * @param aDBConn
198 : * The database connection.
199 : * @param aJournalMode
200 : * One of the JOURNAL_* types.
201 : * @returns the current journal mode.
202 : * @note this may return a different journal mode than the required one, since
203 : * setting it may fail.
204 : */
205 : enum JournalMode
206 267 : SetJournalMode(nsCOMPtr<mozIStorageConnection>& aDBConn,
207 : enum JournalMode aJournalMode)
208 : {
209 267 : MOZ_ASSERT(NS_IsMainThread());
210 534 : nsCAutoString journalMode;
211 267 : switch (aJournalMode) {
212 : default:
213 : MOZ_ASSERT("Trying to set an unknown journal mode.");
214 : // Fall through to the default DELETE journal.
215 : case JOURNAL_DELETE:
216 0 : journalMode.AssignLiteral("delete");
217 0 : break;
218 : case JOURNAL_TRUNCATE:
219 0 : journalMode.AssignLiteral("truncate");
220 0 : break;
221 : case JOURNAL_MEMORY:
222 0 : journalMode.AssignLiteral("memory");
223 0 : break;
224 : case JOURNAL_WAL:
225 267 : journalMode.AssignLiteral("wal");
226 267 : break;
227 : }
228 :
229 534 : nsCOMPtr<mozIStorageStatement> statement;
230 : nsCAutoString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR
231 534 : "PRAGMA journal_mode = ");
232 267 : query.Append(journalMode);
233 267 : aDBConn->CreateStatement(query, getter_AddRefs(statement));
234 267 : NS_ENSURE_TRUE(statement, JOURNAL_DELETE);
235 :
236 267 : bool hasResult = false;
237 534 : if (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && hasResult &&
238 267 : NS_SUCCEEDED(statement->GetUTF8String(0, journalMode))) {
239 267 : if (journalMode.EqualsLiteral("delete")) {
240 0 : return JOURNAL_DELETE;
241 : }
242 267 : if (journalMode.EqualsLiteral("truncate")) {
243 0 : return JOURNAL_TRUNCATE;
244 : }
245 267 : if (journalMode.EqualsLiteral("memory")) {
246 0 : return JOURNAL_MEMORY;
247 : }
248 267 : if (journalMode.EqualsLiteral("wal")) {
249 267 : return JOURNAL_WAL;
250 : }
251 : // This is an unknown journal.
252 : MOZ_ASSERT(true);
253 : }
254 :
255 0 : return JOURNAL_DELETE;
256 : }
257 :
258 : class BlockingConnectionCloseCallback : public mozIStorageCompletionCallback {
259 : bool mDone;
260 :
261 : public:
262 : NS_DECL_ISUPPORTS
263 : NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
264 : BlockingConnectionCloseCallback();
265 : void Spin();
266 : };
267 :
268 : NS_IMETHODIMP
269 265 : BlockingConnectionCloseCallback::Complete()
270 : {
271 265 : mDone = true;
272 530 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
273 265 : MOZ_ASSERT(os);
274 265 : if (!os)
275 0 : return NS_OK;
276 265 : DebugOnly<nsresult> rv = os->NotifyObservers(nsnull,
277 : TOPIC_PLACES_CONNECTION_CLOSED,
278 530 : nsnull);
279 265 : MOZ_ASSERT(NS_SUCCEEDED(rv));
280 265 : return NS_OK;
281 : }
282 :
283 265 : BlockingConnectionCloseCallback::BlockingConnectionCloseCallback()
284 265 : : mDone(false)
285 : {
286 265 : MOZ_ASSERT(NS_IsMainThread());
287 265 : }
288 :
289 265 : void BlockingConnectionCloseCallback::Spin() {
290 530 : nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
291 3650 : while (!mDone) {
292 3120 : NS_ProcessNextEvent(thread);
293 : }
294 265 : }
295 :
296 2120 : NS_IMPL_THREADSAFE_ISUPPORTS1(
297 : BlockingConnectionCloseCallback
298 : , mozIStorageCompletionCallback
299 : )
300 :
301 : nsresult
302 1300 : CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
303 : const nsCString& aRootName,
304 : const nsXPIDLString& titleString)
305 : {
306 1300 : MOZ_ASSERT(NS_IsMainThread());
307 :
308 : // The position of the new item in its folder.
309 : static PRInt32 itemPosition = 0;
310 :
311 : // A single creation timestamp for all roots so that the root folder's
312 : // last modification time isn't earlier than its childrens' creation time.
313 : static PRTime timestamp = 0;
314 1300 : if (!timestamp)
315 260 : timestamp = PR_Now();
316 :
317 : // Create a new bookmark folder for the root.
318 2600 : nsCOMPtr<mozIStorageStatement> stmt;
319 2600 : nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
320 : "INSERT INTO moz_bookmarks "
321 : "(type, position, title, dateAdded, lastModified, guid, parent) "
322 : "VALUES (:item_type, :item_position, :item_title,"
323 : ":date_added, :last_modified, GENERATE_GUID(),"
324 : "IFNULL((SELECT id FROM moz_bookmarks WHERE parent = 0), 0))"
325 2600 : ), getter_AddRefs(stmt));
326 1300 : if (NS_FAILED(rv)) return rv;
327 :
328 2600 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
329 1300 : nsINavBookmarksService::TYPE_FOLDER);
330 1300 : if (NS_FAILED(rv)) return rv;
331 1300 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"), itemPosition);
332 1300 : if (NS_FAILED(rv)) return rv;
333 2600 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
334 2600 : NS_ConvertUTF16toUTF8(titleString));
335 1300 : if (NS_FAILED(rv)) return rv;
336 1300 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), timestamp);
337 1300 : if (NS_FAILED(rv)) return rv;
338 1300 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), timestamp);
339 1300 : if (NS_FAILED(rv)) return rv;
340 1300 : rv = stmt->Execute();
341 1300 : if (NS_FAILED(rv)) return rv;
342 :
343 : // Create an entry in moz_bookmarks_roots to link the folder to the root.
344 2600 : nsCOMPtr<mozIStorageStatement> newRootStmt;
345 2600 : rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
346 : "INSERT INTO moz_bookmarks_roots (root_name, folder_id) "
347 : "VALUES (:root_name, "
348 : "(SELECT id from moz_bookmarks WHERE "
349 : " position = :item_position AND "
350 : " parent = IFNULL((SELECT MIN(folder_id) FROM moz_bookmarks_roots), 0)))"
351 2600 : ), getter_AddRefs(newRootStmt));
352 1300 : if (NS_FAILED(rv)) return rv;
353 :
354 2600 : rv = newRootStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("root_name"),
355 1300 : aRootName);
356 1300 : if (NS_FAILED(rv)) return rv;
357 2600 : rv = newRootStmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"),
358 1300 : itemPosition);
359 1300 : if (NS_FAILED(rv)) return rv;
360 1300 : rv = newRootStmt->Execute();
361 1300 : if (NS_FAILED(rv)) return rv;
362 :
363 : // The 'places' root is a folder containing the other roots.
364 : // The first bookmark in a folder has position 0.
365 1300 : if (!aRootName.Equals("places"))
366 1040 : ++itemPosition;
367 :
368 1300 : return NS_OK;
369 : }
370 :
371 :
372 : } // Anonymous namespace
373 :
374 : ////////////////////////////////////////////////////////////////////////////////
375 : //// Database
376 :
377 7675 : PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase)
378 :
379 21444 : NS_IMPL_THREADSAFE_ISUPPORTS2(Database
380 : , nsIObserver
381 : , nsISupportsWeakReference
382 : )
383 :
384 266 : Database::Database()
385 : : mMainThreadStatements(mMainConn)
386 : , mMainThreadAsyncStatements(mMainConn)
387 : , mAsyncThreadStatements(mMainConn)
388 : , mDBPageSize(0)
389 : , mCurrentJournalMode(JOURNAL_DELETE)
390 : , mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK)
391 266 : , mShuttingDown(false)
392 : {
393 : // Attempting to create two instances of the service?
394 266 : MOZ_ASSERT(!gDatabase);
395 266 : gDatabase = this;
396 266 : }
397 :
398 532 : Database::~Database()
399 : {
400 : // Check to make sure it's us, in case somebody wrongly creates an extra
401 : // instance of this singleton class.
402 266 : MOZ_ASSERT(gDatabase == this);
403 :
404 : // Remove the static reference to the service.
405 266 : if (gDatabase == this) {
406 266 : gDatabase = nsnull;
407 : }
408 266 : }
409 :
410 : nsresult
411 266 : Database::Init()
412 : {
413 : #ifdef MOZ_ANDROID_HISTORY
414 : // Currently places has deeply weaved it way throughout the gecko codebase.
415 : // Here we disable all database creation and loading of places.
416 : return NS_ERROR_NOT_IMPLEMENTED;
417 : #endif
418 :
419 266 : MOZ_ASSERT(NS_IsMainThread());
420 :
421 : nsCOMPtr<mozIStorageService> storage =
422 532 : do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
423 266 : NS_ENSURE_STATE(storage);
424 :
425 : // Init the database file and connect to it.
426 266 : bool databaseCreated = false;
427 266 : nsresult rv = InitDatabaseFile(storage, &databaseCreated);
428 266 : if (NS_SUCCEEDED(rv) && databaseCreated) {
429 254 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE;
430 : }
431 12 : else if (rv == NS_ERROR_FILE_CORRUPTED) {
432 : // The database is corrupt, backup and replace it with a new one.
433 4 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
434 4 : rv = BackupAndReplaceDatabaseFile(storage);
435 : // Fallback to catch-all handler, that notifies a database locked failure.
436 : }
437 :
438 : // If the database connection still cannot be opened, it may just be locked
439 : // by third parties. Send out a notification and interrupt initialization.
440 266 : if (NS_FAILED(rv)) {
441 2 : nsRefPtr<PlacesEvent> lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED);
442 1 : (void)NS_DispatchToMainThread(lockedEvent);
443 1 : return rv;
444 : }
445 :
446 : // Initialize the database schema. In case of failure the existing schema is
447 : // is corrupt or incoherent, thus the database should be replaced.
448 265 : bool databaseMigrated = false;
449 265 : rv = InitSchema(&databaseMigrated);
450 265 : if (NS_FAILED(rv)) {
451 2 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
452 2 : rv = BackupAndReplaceDatabaseFile(storage);
453 2 : NS_ENSURE_SUCCESS(rv, rv);
454 : // Try to initialize the schema again on the new database.
455 2 : rv = InitSchema(&databaseMigrated);
456 2 : NS_ENSURE_SUCCESS(rv, rv);
457 : }
458 :
459 265 : if (databaseMigrated) {
460 5 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
461 : }
462 :
463 265 : if (mDatabaseStatus != nsINavHistoryService::DATABASE_STATUS_OK) {
464 265 : rv = updateSQLiteStatistics(MainConn());
465 265 : NS_ENSURE_SUCCESS(rv, rv);
466 : }
467 :
468 : // Initialize here all the items that are not part of the on-disk database,
469 : // like views, temp triggers or temp tables. The database should not be
470 : // considered corrupt if any of the following fails.
471 :
472 265 : rv = InitTempTriggers();
473 265 : NS_ENSURE_SUCCESS(rv, rv);
474 :
475 : // Notify we have finished database initialization.
476 : // Enqueue the notification, so if we init another service that requires
477 : // nsNavHistoryService we don't recursive try to get it.
478 : nsRefPtr<PlacesEvent> completeEvent =
479 530 : new PlacesEvent(TOPIC_PLACES_INIT_COMPLETE);
480 265 : rv = NS_DispatchToMainThread(completeEvent);
481 265 : NS_ENSURE_SUCCESS(rv, rv);
482 :
483 : // Finally observe profile shutdown notifications.
484 530 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
485 265 : if (os) {
486 265 : (void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
487 265 : (void)os->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, true);
488 : }
489 :
490 265 : return NS_OK;
491 : }
492 :
493 : nsresult
494 266 : Database::InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
495 : bool* aNewDatabaseCreated)
496 : {
497 266 : MOZ_ASSERT(NS_IsMainThread());
498 266 : *aNewDatabaseCreated = false;
499 :
500 532 : nsCOMPtr<nsIFile> databaseFile;
501 : nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
502 266 : getter_AddRefs(databaseFile));
503 266 : NS_ENSURE_SUCCESS(rv, rv);
504 266 : rv = databaseFile->Append(DATABASE_FILENAME);
505 266 : NS_ENSURE_SUCCESS(rv, rv);
506 :
507 266 : bool databaseFileExists = false;
508 266 : rv = databaseFile->Exists(&databaseFileExists);
509 266 : NS_ENSURE_SUCCESS(rv, rv);
510 :
511 278 : if (databaseFileExists &&
512 12 : Preferences::GetBool(PREF_FORCE_DATABASE_REPLACEMENT, false)) {
513 : // If this pref is set, Maintenance required a database replacement, due to
514 : // integrity corruption.
515 : // Be sure to clear the pref to avoid handling it more than once.
516 1 : (void)Preferences::ClearUser(PREF_FORCE_DATABASE_REPLACEMENT);
517 :
518 1 : return NS_ERROR_FILE_CORRUPTED;
519 : }
520 :
521 : // Open the database file. If it does not exist a new one will be created.
522 : // Use an unshared connection, it will consume more memory but avoid shared
523 : // cache contentions across threads.
524 265 : rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
525 265 : NS_ENSURE_SUCCESS(rv, rv);
526 :
527 261 : *aNewDatabaseCreated = !databaseFileExists;
528 261 : return NS_OK;
529 : }
530 :
531 : nsresult
532 6 : Database::BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage)
533 : {
534 6 : MOZ_ASSERT(NS_IsMainThread());
535 12 : nsCOMPtr<nsIFile> profDir;
536 : nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
537 6 : getter_AddRefs(profDir));
538 6 : NS_ENSURE_SUCCESS(rv, rv);
539 12 : nsCOMPtr<nsIFile> databaseFile;
540 6 : rv = profDir->Clone(getter_AddRefs(databaseFile));
541 6 : NS_ENSURE_SUCCESS(rv, rv);
542 6 : rv = databaseFile->Append(DATABASE_FILENAME);
543 6 : NS_ENSURE_SUCCESS(rv, rv);
544 :
545 : // If we have
546 : // already failed in the last 24 hours avoid to create another corrupt file,
547 : // since doing so, in some situation, could cause us to create a new corrupt
548 : // file at every try to access any Places service. That is bad because it
549 : // would quickly fill the user's disk space without any notice.
550 6 : if (!hasRecentCorruptDB()) {
551 12 : nsCOMPtr<nsIFile> backup;
552 12 : (void)aStorage->BackupDatabaseFile(databaseFile, DATABASE_CORRUPT_FILENAME,
553 12 : profDir, getter_AddRefs(backup));
554 : }
555 :
556 : // Close database connection if open.
557 6 : if (mMainConn) {
558 : // If there's any not finalized statement or this fails for any reason
559 : // we won't be able to remove the database.
560 2 : rv = mMainConn->Close();
561 2 : NS_ENSURE_SUCCESS(rv, rv);
562 : }
563 :
564 : // Remove the broken database.
565 6 : rv = databaseFile->Remove(false);
566 6 : NS_ENSURE_SUCCESS(rv, rv);
567 :
568 : // Create a new database file.
569 : // Use an unshared connection, it will consume more memory but avoid shared
570 : // cache contentions across threads.
571 6 : rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
572 6 : NS_ENSURE_SUCCESS(rv, rv);
573 :
574 6 : return NS_OK;
575 : }
576 :
577 : nsresult
578 267 : Database::InitSchema(bool* aDatabaseMigrated)
579 : {
580 267 : MOZ_ASSERT(NS_IsMainThread());
581 267 : *aDatabaseMigrated = false;
582 :
583 : // WARNING: any statement executed before setting the journal mode must be
584 : // finalized, since SQLite doesn't allow changing the journal mode if there
585 : // is any outstanding statement.
586 :
587 : {
588 : // Get the page size. This may be different than the default if the
589 : // database file already existed with a different page size.
590 534 : nsCOMPtr<mozIStorageStatement> statement;
591 534 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
592 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
593 534 : ), getter_AddRefs(statement));
594 267 : NS_ENSURE_SUCCESS(rv, rv);
595 267 : bool hasResult = false;
596 267 : rv = statement->ExecuteStep(&hasResult);
597 267 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
598 267 : rv = statement->GetInt32(0, &mDBPageSize);
599 267 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0, NS_ERROR_UNEXPECTED);
600 : }
601 :
602 : // Ensure that temp tables are held in memory, not on disk.
603 534 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
604 267 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY"));
605 267 : NS_ENSURE_SUCCESS(rv, rv);
606 :
607 : // Be sure to set journal mode after page_size. WAL would prevent the change
608 : // otherwise.
609 267 : if (NS_SUCCEEDED(SetJournalMode(mMainConn, JOURNAL_WAL))) {
610 : // Set the WAL journal size limit. We want it to be small, since in
611 : // synchronous = NORMAL mode a crash could cause loss of all the
612 : // transactions in the journal. For added safety we will also force
613 : // checkpointing at strategic moments.
614 : PRInt32 checkpointPages =
615 267 : static_cast<PRInt32>(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 1024 / mDBPageSize);
616 534 : nsCAutoString checkpointPragma("PRAGMA wal_autocheckpoint = ");
617 267 : checkpointPragma.AppendInt(checkpointPages);
618 267 : rv = mMainConn->ExecuteSimpleSQL(checkpointPragma);
619 267 : NS_ENSURE_SUCCESS(rv, rv);
620 : }
621 : else {
622 : // Ignore errors, if we fail here the database could be considered corrupt
623 : // and we won't be able to go on, even if it's just matter of a bogus file
624 : // system. The default mode (DELETE) will be fine in such a case.
625 0 : (void)SetJournalMode(mMainConn, JOURNAL_TRUNCATE);
626 :
627 : // Set synchronous to FULL to ensure maximum data integrity, even in
628 : // case of crashes or unclean shutdowns.
629 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
630 0 : "PRAGMA synchronous = FULL"));
631 0 : NS_ENSURE_SUCCESS(rv, rv);
632 : }
633 :
634 : // The journal is usually free to grow for performance reasons, but it never
635 : // shrinks back. Since the space taken may be problematic, especially on
636 : // mobile devices, limit its size.
637 : // Since exceeding the limit will cause a truncate, allow a slightly
638 : // larger limit than DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES to reduce the number
639 : // of times it is needed.
640 534 : nsCAutoString journalSizePragma("PRAGMA journal_size_limit = ");
641 267 : journalSizePragma.AppendInt(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 3);
642 267 : (void)mMainConn->ExecuteSimpleSQL(journalSizePragma);
643 :
644 : // Grow places in 10MiB increments to limit fragmentation on disk.
645 267 : (void)mMainConn->SetGrowthIncrement(10 * BYTES_PER_MEBIBYTE, EmptyCString());
646 :
647 : // We use our functions during migration, so initialize them now.
648 267 : rv = InitFunctions();
649 267 : NS_ENSURE_SUCCESS(rv, rv);
650 :
651 : // Get the database schema version.
652 : PRInt32 currentSchemaVersion;
653 267 : rv = mMainConn->GetSchemaVersion(¤tSchemaVersion);
654 267 : NS_ENSURE_SUCCESS(rv, rv);
655 267 : bool databaseInitialized = currentSchemaVersion > 0;
656 :
657 267 : if (databaseInitialized && currentSchemaVersion == DATABASE_SCHEMA_VERSION) {
658 : // The database is up to date and ready to go.
659 0 : return NS_OK;
660 : }
661 :
662 : // We are going to update the database, so everything from now on should be in
663 : // a transaction for performances.
664 534 : mozStorageTransaction transaction(mMainConn, false);
665 :
666 267 : if (databaseInitialized) {
667 : // Migration How-to:
668 : //
669 : // 1. increment PLACES_SCHEMA_VERSION.
670 : // 2. implement a method that performs upgrade to your version from the
671 : // previous one.
672 : //
673 : // NOTE: The downgrade process is pretty much complicated by the fact old
674 : // versions cannot know what a new version is going to implement.
675 : // The only thing we will do for downgrades is setting back the schema
676 : // version, so that next upgrades will run again the migration step.
677 :
678 7 : if (currentSchemaVersion < DATABASE_SCHEMA_VERSION) {
679 7 : *aDatabaseMigrated = true;
680 :
681 7 : if (currentSchemaVersion < 6) {
682 : // These are early Firefox 3.0 alpha versions that are not supported
683 : // anymore. In this case it's safer to just replace the database.
684 1 : return NS_ERROR_FILE_CORRUPTED;
685 : }
686 :
687 : // Firefox 3.0 uses schema version 6.
688 :
689 6 : if (currentSchemaVersion < 7) {
690 3 : rv = MigrateV7Up();
691 3 : NS_ENSURE_SUCCESS(rv, rv);
692 : }
693 :
694 5 : if (currentSchemaVersion < 8) {
695 2 : rv = MigrateV8Up();
696 2 : NS_ENSURE_SUCCESS(rv, rv);
697 : }
698 :
699 : // Firefox 3.5 uses schema version 8.
700 :
701 5 : if (currentSchemaVersion < 9) {
702 2 : rv = MigrateV9Up();
703 2 : NS_ENSURE_SUCCESS(rv, rv);
704 : }
705 :
706 5 : if (currentSchemaVersion < 10) {
707 2 : rv = MigrateV10Up();
708 2 : NS_ENSURE_SUCCESS(rv, rv);
709 : }
710 :
711 : // Firefox 3.6 uses schema version 10.
712 :
713 5 : if (currentSchemaVersion < 11) {
714 5 : rv = MigrateV11Up();
715 5 : NS_ENSURE_SUCCESS(rv, rv);
716 : }
717 :
718 : // Firefox 4 uses schema version 11.
719 :
720 : // Firefox 8 uses schema version 12.
721 :
722 5 : if (currentSchemaVersion < 13) {
723 5 : rv = MigrateV13Up();
724 5 : NS_ENSURE_SUCCESS(rv, rv);
725 : }
726 :
727 5 : if (currentSchemaVersion < 14) {
728 5 : rv = MigrateV14Up();
729 5 : NS_ENSURE_SUCCESS(rv, rv);
730 : }
731 :
732 5 : if (currentSchemaVersion < 15) {
733 5 : rv = MigrateV15Up();
734 5 : NS_ENSURE_SUCCESS(rv, rv);
735 : }
736 :
737 5 : if (currentSchemaVersion < 16) {
738 5 : rv = MigrateV16Up();
739 5 : NS_ENSURE_SUCCESS(rv, rv);
740 : }
741 :
742 : // Firefox 11 uses schema version 16.
743 :
744 5 : if (currentSchemaVersion < 17) {
745 5 : rv = MigrateV17Up();
746 5 : NS_ENSURE_SUCCESS(rv, rv);
747 : }
748 :
749 : // Firefox 12 uses schema version 17.
750 :
751 5 : if (currentSchemaVersion < 18) {
752 5 : rv = MigrateV18Up();
753 5 : NS_ENSURE_SUCCESS(rv, rv);
754 : }
755 :
756 5 : if (currentSchemaVersion < 19) {
757 5 : rv = MigrateV19Up();
758 5 : NS_ENSURE_SUCCESS(rv, rv);
759 : }
760 :
761 : // Firefox 13 uses schema version 19.
762 :
763 : // Schema Upgrades must add migration code here.
764 :
765 5 : rv = UpdateBookmarkRootTitles();
766 : // We don't want a broken localization to cause us to think
767 : // the database is corrupt and needs to be replaced.
768 5 : MOZ_ASSERT(NS_SUCCEEDED(rv));
769 : }
770 : }
771 : else {
772 : // This is a new database, so we have to create all the tables and indices.
773 :
774 : // moz_places.
775 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES);
776 260 : NS_ENSURE_SUCCESS(rv, rv);
777 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL);
778 260 : NS_ENSURE_SUCCESS(rv, rv);
779 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FAVICON);
780 260 : NS_ENSURE_SUCCESS(rv, rv);
781 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_REVHOST);
782 260 : NS_ENSURE_SUCCESS(rv, rv);
783 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_VISITCOUNT);
784 260 : NS_ENSURE_SUCCESS(rv, rv);
785 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
786 260 : NS_ENSURE_SUCCESS(rv, rv);
787 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
788 260 : NS_ENSURE_SUCCESS(rv, rv);
789 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
790 260 : NS_ENSURE_SUCCESS(rv, rv);
791 :
792 : // moz_historyvisits.
793 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS);
794 260 : NS_ENSURE_SUCCESS(rv, rv);
795 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
796 260 : NS_ENSURE_SUCCESS(rv, rv);
797 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT);
798 260 : NS_ENSURE_SUCCESS(rv, rv);
799 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE);
800 260 : NS_ENSURE_SUCCESS(rv, rv);
801 :
802 : // moz_inputhistory.
803 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
804 260 : NS_ENSURE_SUCCESS(rv, rv);
805 :
806 : // moz_hosts.
807 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
808 260 : NS_ENSURE_SUCCESS(rv, rv);
809 :
810 : // moz_bookmarks.
811 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS);
812 260 : NS_ENSURE_SUCCESS(rv, rv);
813 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE);
814 260 : NS_ENSURE_SUCCESS(rv, rv);
815 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION);
816 260 : NS_ENSURE_SUCCESS(rv, rv);
817 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
818 260 : NS_ENSURE_SUCCESS(rv, rv);
819 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
820 260 : NS_ENSURE_SUCCESS(rv, rv);
821 :
822 : // moz_bookmarks_roots.
823 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_ROOTS);
824 260 : NS_ENSURE_SUCCESS(rv, rv);
825 :
826 : // moz_keywords.
827 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
828 260 : NS_ENSURE_SUCCESS(rv, rv);
829 :
830 : // moz_favicons.
831 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_FAVICONS);
832 260 : NS_ENSURE_SUCCESS(rv, rv);
833 :
834 : // moz_anno_attributes.
835 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNO_ATTRIBUTES);
836 260 : NS_ENSURE_SUCCESS(rv, rv);
837 :
838 : // moz_annos.
839 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNOS);
840 260 : NS_ENSURE_SUCCESS(rv, rv);
841 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
842 260 : NS_ENSURE_SUCCESS(rv, rv);
843 :
844 : // moz_items_annos.
845 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ITEMS_ANNOS);
846 260 : NS_ENSURE_SUCCESS(rv, rv);
847 260 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
848 260 : NS_ENSURE_SUCCESS(rv, rv);
849 :
850 : // Initialize the bookmark roots in the new DB.
851 260 : rv = CreateBookmarkRoots();
852 260 : NS_ENSURE_SUCCESS(rv, rv);
853 : }
854 :
855 : // Set the schema version to the current one.
856 265 : rv = mMainConn->SetSchemaVersion(DATABASE_SCHEMA_VERSION);
857 265 : NS_ENSURE_SUCCESS(rv, rv);
858 :
859 265 : rv = transaction.Commit();
860 265 : NS_ENSURE_SUCCESS(rv, rv);
861 :
862 265 : ForceWALCheckpoint();
863 :
864 : // ANY FAILURE IN THIS METHOD WILL CAUSE US TO MARK THE DATABASE AS CORRUPT
865 : // AND TRY TO REPLACE IT.
866 : // DO NOT PUT HERE ANYTHING THAT IS NOT RELATED TO INITIALIZATION OR MODIFYING
867 : // THE DISK DATABASE.
868 :
869 265 : return NS_OK;
870 : }
871 :
872 : nsresult
873 260 : Database::CreateBookmarkRoots()
874 : {
875 260 : MOZ_ASSERT(NS_IsMainThread());
876 :
877 : nsCOMPtr<nsIStringBundleService> bundleService =
878 520 : services::GetStringBundleService();
879 260 : NS_ENSURE_STATE(bundleService);
880 520 : nsCOMPtr<nsIStringBundle> bundle;
881 260 : nsresult rv = bundleService->CreateBundle(PLACES_BUNDLE, getter_AddRefs(bundle));
882 260 : if (NS_FAILED(rv)) return rv;
883 :
884 520 : nsXPIDLString rootTitle;
885 : // The first root's title is an empty string.
886 260 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"), rootTitle);
887 260 : if (NS_FAILED(rv)) return rv;
888 :
889 : // Fetch the internationalized folder name from the string bundle.
890 520 : rv = bundle->GetStringFromName(NS_LITERAL_STRING("BookmarksMenuFolderTitle").get(),
891 520 : getter_Copies(rootTitle));
892 260 : if (NS_FAILED(rv)) return rv;
893 260 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"), rootTitle);
894 260 : if (NS_FAILED(rv)) return rv;
895 :
896 520 : rv = bundle->GetStringFromName(NS_LITERAL_STRING("BookmarksToolbarFolderTitle").get(),
897 520 : getter_Copies(rootTitle));
898 260 : if (NS_FAILED(rv)) return rv;
899 260 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"), rootTitle);
900 260 : if (NS_FAILED(rv)) return rv;
901 :
902 520 : rv = bundle->GetStringFromName(NS_LITERAL_STRING("TagsFolderTitle").get(),
903 520 : getter_Copies(rootTitle));
904 260 : if (NS_FAILED(rv)) return rv;
905 260 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"), rootTitle);
906 260 : if (NS_FAILED(rv)) return rv;
907 :
908 520 : rv = bundle->GetStringFromName(NS_LITERAL_STRING("UnsortedBookmarksFolderTitle").get(),
909 520 : getter_Copies(rootTitle));
910 260 : if (NS_FAILED(rv)) return rv;
911 260 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"), rootTitle);
912 260 : if (NS_FAILED(rv)) return rv;
913 :
914 : #if DEBUG
915 520 : nsCOMPtr<mozIStorageStatement> stmt;
916 520 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
917 : "SELECT "
918 : "(SELECT COUNT(*) FROM moz_bookmarks), "
919 : "(SELECT COUNT(*) FROM moz_bookmarks_roots), "
920 : "(SELECT SUM(position) FROM moz_bookmarks WHERE "
921 : "id IN (SELECT folder_id FROM moz_bookmarks_roots))"
922 520 : ), getter_AddRefs(stmt));
923 260 : if (NS_FAILED(rv)) return rv;
924 :
925 : bool hasResult;
926 260 : rv = stmt->ExecuteStep(&hasResult);
927 260 : if (NS_FAILED(rv)) return rv;
928 260 : MOZ_ASSERT(hasResult);
929 260 : PRInt32 bookmarkCount = 0;
930 260 : rv = stmt->GetInt32(0, &bookmarkCount);
931 260 : if (NS_FAILED(rv)) return rv;
932 260 : PRInt32 rootCount = 0;
933 260 : rv = stmt->GetInt32(1, &rootCount);
934 260 : if (NS_FAILED(rv)) return rv;
935 260 : PRInt32 positionSum = 0;
936 260 : rv = stmt->GetInt32(2, &positionSum);
937 260 : if (NS_FAILED(rv)) return rv;
938 260 : MOZ_ASSERT(bookmarkCount == 5 && rootCount == 5 && positionSum == 6);
939 : #endif
940 :
941 260 : return NS_OK;
942 : }
943 :
944 : nsresult
945 267 : Database::InitFunctions()
946 : {
947 267 : MOZ_ASSERT(NS_IsMainThread());
948 :
949 267 : nsresult rv = GetUnreversedHostFunction::create(mMainConn);
950 267 : NS_ENSURE_SUCCESS(rv, rv);
951 267 : rv = MatchAutoCompleteFunction::create(mMainConn);
952 267 : NS_ENSURE_SUCCESS(rv, rv);
953 267 : rv = CalculateFrecencyFunction::create(mMainConn);
954 267 : NS_ENSURE_SUCCESS(rv, rv);
955 267 : rv = GenerateGUIDFunction::create(mMainConn);
956 267 : NS_ENSURE_SUCCESS(rv, rv);
957 267 : rv = FixupURLFunction::create(mMainConn);
958 267 : NS_ENSURE_SUCCESS(rv, rv);
959 :
960 267 : return NS_OK;
961 : }
962 :
963 : nsresult
964 265 : Database::InitTempTriggers()
965 : {
966 265 : MOZ_ASSERT(NS_IsMainThread());
967 :
968 265 : nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
969 265 : NS_ENSURE_SUCCESS(rv, rv);
970 265 : rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
971 265 : NS_ENSURE_SUCCESS(rv, rv);
972 :
973 : // Add the triggers that update the moz_hosts table as necessary.
974 265 : rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER);
975 265 : NS_ENSURE_SUCCESS(rv, rv);
976 265 : rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER);
977 265 : NS_ENSURE_SUCCESS(rv, rv);
978 265 : rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER);
979 265 : NS_ENSURE_SUCCESS(rv, rv);
980 265 : rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_TYPED_TRIGGER);
981 265 : NS_ENSURE_SUCCESS(rv, rv);
982 :
983 265 : return NS_OK;
984 : }
985 :
986 : nsresult
987 5 : Database::UpdateBookmarkRootTitles()
988 : {
989 5 : MOZ_ASSERT(NS_IsMainThread());
990 :
991 : nsCOMPtr<nsIStringBundleService> bundleService =
992 10 : services::GetStringBundleService();
993 5 : NS_ENSURE_STATE(bundleService);
994 :
995 10 : nsCOMPtr<nsIStringBundle> bundle;
996 5 : nsresult rv = bundleService->CreateBundle(PLACES_BUNDLE, getter_AddRefs(bundle));
997 5 : if (NS_FAILED(rv)) return rv;
998 :
999 10 : nsCOMPtr<mozIStorageAsyncStatement> stmt;
1000 10 : rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1001 : "UPDATE moz_bookmarks SET title = :new_title WHERE id = "
1002 : "(SELECT folder_id FROM moz_bookmarks_roots WHERE root_name = :root_name)"
1003 10 : ), getter_AddRefs(stmt));
1004 5 : if (NS_FAILED(rv)) return rv;
1005 :
1006 10 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
1007 5 : rv = stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
1008 5 : if (NS_FAILED(rv)) return rv;
1009 :
1010 5 : const char *rootNames[] = { "menu", "toolbar", "tags", "unfiled" };
1011 : const char *titleStringIDs[] = {
1012 : "BookmarksMenuFolderTitle", "BookmarksToolbarFolderTitle",
1013 : "TagsFolderTitle", "UnsortedBookmarksFolderTitle"
1014 5 : };
1015 :
1016 25 : for (PRUint32 i = 0; i < ArrayLength(rootNames); ++i) {
1017 40 : nsXPIDLString title;
1018 40 : rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(titleStringIDs[i]).get(),
1019 40 : getter_Copies(title));
1020 20 : if (NS_FAILED(rv)) return rv;
1021 :
1022 40 : nsCOMPtr<mozIStorageBindingParams> params;
1023 20 : rv = paramsArray->NewBindingParams(getter_AddRefs(params));
1024 20 : if (NS_FAILED(rv)) return rv;
1025 40 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("root_name"),
1026 40 : nsDependentCString(rootNames[i]));
1027 20 : if (NS_FAILED(rv)) return rv;
1028 40 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("new_title"),
1029 40 : NS_ConvertUTF16toUTF8(title));
1030 20 : if (NS_FAILED(rv)) return rv;
1031 20 : rv = paramsArray->AddParams(params);
1032 20 : if (NS_FAILED(rv)) return rv;
1033 : }
1034 :
1035 5 : rv = stmt->BindParameters(paramsArray);
1036 5 : if (NS_FAILED(rv)) return rv;
1037 10 : nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
1038 5 : rv = stmt->ExecuteAsync(nsnull, getter_AddRefs(pendingStmt));
1039 5 : if (NS_FAILED(rv)) return rv;
1040 :
1041 5 : return NS_OK;
1042 : }
1043 :
1044 : nsresult
1045 5 : Database::CheckAndUpdateGUIDs()
1046 : {
1047 5 : MOZ_ASSERT(NS_IsMainThread());
1048 :
1049 : // First, import any bookmark guids already set by Sync.
1050 10 : nsCOMPtr<mozIStorageStatement> updateStmt;
1051 10 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1052 : "UPDATE moz_bookmarks "
1053 : "SET guid = :guid "
1054 : "WHERE id = :item_id "
1055 10 : ), getter_AddRefs(updateStmt));
1056 5 : NS_ENSURE_SUCCESS(rv, rv);
1057 :
1058 10 : nsCOMPtr<mozIStorageStatement> stmt;
1059 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1060 : "SELECT item_id, content "
1061 : "FROM moz_items_annos "
1062 : "JOIN moz_anno_attributes "
1063 : "WHERE name = :anno_name "
1064 10 : ), getter_AddRefs(stmt));
1065 5 : NS_ENSURE_SUCCESS(rv, rv);
1066 10 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
1067 10 : SYNCGUID_ANNO);
1068 5 : NS_ENSURE_SUCCESS(rv, rv);
1069 :
1070 : bool hasResult;
1071 23 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1072 : PRInt64 itemId;
1073 13 : rv = stmt->GetInt64(0, &itemId);
1074 13 : NS_ENSURE_SUCCESS(rv, rv);
1075 26 : nsCAutoString guid;
1076 13 : rv = stmt->GetUTF8String(1, guid);
1077 13 : NS_ENSURE_SUCCESS(rv, rv);
1078 :
1079 : // If we have an invalid guid, we don't need to do any more work.
1080 13 : if (!IsValidGUID(guid)) {
1081 10 : continue;
1082 : }
1083 :
1084 6 : mozStorageStatementScoper updateScoper(updateStmt);
1085 3 : rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), itemId);
1086 3 : NS_ENSURE_SUCCESS(rv, rv);
1087 3 : rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
1088 3 : NS_ENSURE_SUCCESS(rv, rv);
1089 3 : rv = updateStmt->Execute();
1090 3 : if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
1091 : // We just tried to insert a duplicate guid. Ignore this error, and we
1092 : // will generate a new one next.
1093 1 : continue;
1094 : }
1095 2 : NS_ENSURE_SUCCESS(rv, rv);
1096 : }
1097 :
1098 : // Now, remove all the bookmark guid annotations that we just imported.
1099 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1100 : "DELETE FROM moz_items_annos "
1101 : "WHERE anno_attribute_id = ( "
1102 : "SELECT id "
1103 : "FROM moz_anno_attributes "
1104 : "WHERE name = :anno_name "
1105 : ") "
1106 10 : ), getter_AddRefs(stmt));
1107 5 : NS_ENSURE_SUCCESS(rv, rv);
1108 10 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
1109 10 : SYNCGUID_ANNO);
1110 5 : NS_ENSURE_SUCCESS(rv, rv);
1111 :
1112 5 : rv = stmt->Execute();
1113 5 : NS_ENSURE_SUCCESS(rv, rv);
1114 :
1115 : // Next, generate guids for any bookmark that does not already have one.
1116 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1117 : "UPDATE moz_bookmarks "
1118 : "SET guid = GENERATE_GUID() "
1119 : "WHERE guid IS NULL "
1120 10 : ), getter_AddRefs(stmt));
1121 5 : NS_ENSURE_SUCCESS(rv, rv);
1122 :
1123 5 : rv = stmt->Execute();
1124 5 : NS_ENSURE_SUCCESS(rv, rv);
1125 :
1126 : // Now, import any history guids already set by Sync.
1127 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1128 : "UPDATE moz_places "
1129 : "SET guid = :guid "
1130 : "WHERE id = :place_id "
1131 10 : ), getter_AddRefs(updateStmt));
1132 5 : NS_ENSURE_SUCCESS(rv, rv);
1133 :
1134 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1135 : "SELECT place_id, content "
1136 : "FROM moz_annos "
1137 : "JOIN moz_anno_attributes "
1138 : "WHERE name = :anno_name "
1139 10 : ), getter_AddRefs(stmt));
1140 5 : NS_ENSURE_SUCCESS(rv, rv);
1141 10 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
1142 10 : SYNCGUID_ANNO);
1143 5 : NS_ENSURE_SUCCESS(rv, rv);
1144 :
1145 15 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1146 : PRInt64 placeId;
1147 5 : rv = stmt->GetInt64(0, &placeId);
1148 5 : NS_ENSURE_SUCCESS(rv, rv);
1149 10 : nsCAutoString guid;
1150 5 : rv = stmt->GetUTF8String(1, guid);
1151 5 : NS_ENSURE_SUCCESS(rv, rv);
1152 :
1153 : // If we have an invalid guid, we don't need to do any more work.
1154 5 : if (!IsValidGUID(guid)) {
1155 2 : continue;
1156 : }
1157 :
1158 6 : mozStorageStatementScoper updateScoper(updateStmt);
1159 3 : rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), placeId);
1160 3 : NS_ENSURE_SUCCESS(rv, rv);
1161 3 : rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
1162 3 : NS_ENSURE_SUCCESS(rv, rv);
1163 3 : rv = updateStmt->Execute();
1164 3 : if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
1165 : // We just tried to insert a duplicate guid. Ignore this error, and we
1166 : // will generate a new one next.
1167 1 : continue;
1168 : }
1169 2 : NS_ENSURE_SUCCESS(rv, rv);
1170 : }
1171 :
1172 : // Now, remove all the place guid annotations that we just imported.
1173 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1174 : "DELETE FROM moz_annos "
1175 : "WHERE anno_attribute_id = ( "
1176 : "SELECT id "
1177 : "FROM moz_anno_attributes "
1178 : "WHERE name = :anno_name "
1179 : ") "
1180 10 : ), getter_AddRefs(stmt));
1181 5 : NS_ENSURE_SUCCESS(rv, rv);
1182 10 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
1183 10 : SYNCGUID_ANNO);
1184 5 : NS_ENSURE_SUCCESS(rv, rv);
1185 :
1186 5 : rv = stmt->Execute();
1187 5 : NS_ENSURE_SUCCESS(rv, rv);
1188 :
1189 : // Finally, generate guids for any places that do not already have one.
1190 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1191 : "UPDATE moz_places "
1192 : "SET guid = GENERATE_GUID() "
1193 : "WHERE guid IS NULL "
1194 10 : ), getter_AddRefs(stmt));
1195 5 : NS_ENSURE_SUCCESS(rv, rv);
1196 :
1197 5 : rv = stmt->Execute();
1198 5 : NS_ENSURE_SUCCESS(rv, rv);
1199 :
1200 5 : return NS_OK;
1201 : }
1202 :
1203 : nsresult
1204 3 : Database::MigrateV7Up()
1205 : {
1206 3 : MOZ_ASSERT(NS_IsMainThread());
1207 :
1208 : // Some old v6 databases come from alpha versions that missed indices.
1209 : // Just bail out and replace the database in such a case.
1210 3 : bool URLUniqueIndexExists = false;
1211 6 : nsresult rv = mMainConn->IndexExists(NS_LITERAL_CSTRING(
1212 : "moz_places_url_uniqueindex"
1213 3 : ), &URLUniqueIndexExists);
1214 3 : NS_ENSURE_SUCCESS(rv, rv);
1215 3 : if (!URLUniqueIndexExists) {
1216 1 : return NS_ERROR_FILE_CORRUPTED;
1217 : }
1218 :
1219 4 : mozStorageTransaction transaction(mMainConn, false);
1220 :
1221 : // We need an index on lastModified to catch quickly last modified bookmark
1222 : // title for tag container's children. This will be useful for Sync, too.
1223 2 : bool lastModIndexExists = false;
1224 2 : rv = mMainConn->IndexExists(
1225 2 : NS_LITERAL_CSTRING("moz_bookmarks_itemlastmodifiedindex"),
1226 2 : &lastModIndexExists);
1227 2 : NS_ENSURE_SUCCESS(rv, rv);
1228 :
1229 2 : if (!lastModIndexExists) {
1230 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
1231 0 : NS_ENSURE_SUCCESS(rv, rv);
1232 : }
1233 :
1234 : // We need to do a one-time change of the moz_historyvisits.pageindex
1235 : // to speed up finding last visit date when joinin with moz_places.
1236 : // See bug 392399 for more details.
1237 2 : bool pageIndexExists = false;
1238 2 : rv = mMainConn->IndexExists(
1239 2 : NS_LITERAL_CSTRING("moz_historyvisits_pageindex"), &pageIndexExists);
1240 2 : NS_ENSURE_SUCCESS(rv, rv);
1241 :
1242 2 : if (pageIndexExists) {
1243 : // drop old index
1244 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1245 0 : "DROP INDEX IF EXISTS moz_historyvisits_pageindex"));
1246 0 : NS_ENSURE_SUCCESS(rv, rv);
1247 :
1248 : // create the new multi-column index
1249 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
1250 0 : NS_ENSURE_SUCCESS(rv, rv);
1251 : }
1252 :
1253 : // for existing profiles, we may not have a frecency column
1254 4 : nsCOMPtr<mozIStorageStatement> hasFrecencyStatement;
1255 4 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1256 : "SELECT frecency FROM moz_places"),
1257 4 : getter_AddRefs(hasFrecencyStatement));
1258 :
1259 2 : if (NS_FAILED(rv)) {
1260 : // Add frecency column to moz_places, default to -1 so that all the
1261 : // frecencies are invalid
1262 2 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1263 1 : "ALTER TABLE moz_places ADD frecency INTEGER DEFAULT -1 NOT NULL"));
1264 1 : NS_ENSURE_SUCCESS(rv, rv);
1265 :
1266 : // create index for the frecency column
1267 : // XXX multi column index with typed, and visit_count?
1268 1 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
1269 1 : NS_ENSURE_SUCCESS(rv, rv);
1270 :
1271 : // Invalidate all frecencies, since they need recalculation.
1272 : nsCOMPtr<mozIStorageAsyncStatement> stmt = GetAsyncStatement(
1273 : "UPDATE moz_places SET frecency = ( "
1274 : "CASE "
1275 : "WHEN url BETWEEN 'place:' AND 'place;' "
1276 : "THEN 0 "
1277 : "ELSE -1 "
1278 : "END "
1279 : ") "
1280 2 : );
1281 1 : NS_ENSURE_STATE(stmt);
1282 3 : nsCOMPtr<mozIStoragePendingStatement> ps;
1283 1 : (void)stmt->ExecuteAsync(nsnull, getter_AddRefs(ps));
1284 : }
1285 :
1286 : // Temporary migration code for bug 396300
1287 4 : nsCOMPtr<mozIStorageStatement> moveUnfiledBookmarks;
1288 4 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1289 : "UPDATE moz_bookmarks "
1290 : "SET parent = ("
1291 : "SELECT folder_id "
1292 : "FROM moz_bookmarks_roots "
1293 : "WHERE root_name = :root_name "
1294 : ") "
1295 : "WHERE type = :item_type "
1296 : "AND parent = ("
1297 : "SELECT folder_id "
1298 : "FROM moz_bookmarks_roots "
1299 : "WHERE root_name = :parent_name "
1300 : ")"),
1301 4 : getter_AddRefs(moveUnfiledBookmarks));
1302 2 : rv = moveUnfiledBookmarks->BindUTF8StringByName(
1303 2 : NS_LITERAL_CSTRING("root_name"), NS_LITERAL_CSTRING("unfiled")
1304 2 : );
1305 2 : NS_ENSURE_SUCCESS(rv, rv);
1306 2 : rv = moveUnfiledBookmarks->BindInt32ByName(
1307 2 : NS_LITERAL_CSTRING("item_type"), nsINavBookmarksService::TYPE_BOOKMARK
1308 2 : );
1309 2 : NS_ENSURE_SUCCESS(rv, rv);
1310 2 : rv = moveUnfiledBookmarks->BindUTF8StringByName(
1311 2 : NS_LITERAL_CSTRING("parent_name"), NS_LITERAL_CSTRING("places")
1312 2 : );
1313 2 : NS_ENSURE_SUCCESS(rv, rv);
1314 2 : rv = moveUnfiledBookmarks->Execute();
1315 2 : NS_ENSURE_SUCCESS(rv, rv);
1316 :
1317 : // Create a statement to test for trigger creation
1318 4 : nsCOMPtr<mozIStorageStatement> triggerDetection;
1319 4 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1320 : "SELECT name "
1321 : "FROM sqlite_master "
1322 : "WHERE type = 'trigger' "
1323 : "AND name = :trigger_name"),
1324 4 : getter_AddRefs(triggerDetection));
1325 2 : NS_ENSURE_SUCCESS(rv, rv);
1326 :
1327 : // Check for existence
1328 : bool triggerExists;
1329 2 : rv = triggerDetection->BindUTF8StringByName(
1330 2 : NS_LITERAL_CSTRING("trigger_name"),
1331 2 : NS_LITERAL_CSTRING("moz_historyvisits_afterinsert_v1_trigger")
1332 2 : );
1333 2 : NS_ENSURE_SUCCESS(rv, rv);
1334 2 : rv = triggerDetection->ExecuteStep(&triggerExists);
1335 2 : NS_ENSURE_SUCCESS(rv, rv);
1336 2 : rv = triggerDetection->Reset();
1337 2 : NS_ENSURE_SUCCESS(rv, rv);
1338 :
1339 : // We need to create two triggers on moz_historyvists to maintain the
1340 : // accuracy of moz_places.visit_count. For this to work, we must ensure that
1341 : // all moz_places.visit_count values are correct.
1342 : // See bug 416313 for details.
1343 2 : if (!triggerExists) {
1344 : // First, we do a one-time reset of all the moz_places.visit_count values.
1345 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1346 : "UPDATE moz_places SET visit_count = "
1347 : "(SELECT count(*) FROM moz_historyvisits "
1348 : "WHERE place_id = moz_places.id "
1349 : "AND visit_type NOT IN ") +
1350 : nsPrintfCString("(0,%d,%d,%d) ",
1351 : nsINavHistoryService::TRANSITION_EMBED,
1352 : nsINavHistoryService::TRANSITION_FRAMED_LINK,
1353 0 : nsINavHistoryService::TRANSITION_DOWNLOAD) +
1354 0 : NS_LITERAL_CSTRING(")"));
1355 0 : NS_ENSURE_SUCCESS(rv, rv);
1356 :
1357 : // We used to create two triggers here, but we no longer need that with
1358 : // schema version eight and greater. We've removed their creation here as
1359 : // a result.
1360 : }
1361 :
1362 : // Check for existence
1363 2 : rv = triggerDetection->BindUTF8StringByName(
1364 2 : NS_LITERAL_CSTRING("trigger_name"),
1365 2 : NS_LITERAL_CSTRING("moz_bookmarks_beforedelete_v1_trigger")
1366 2 : );
1367 2 : NS_ENSURE_SUCCESS(rv, rv);
1368 2 : rv = triggerDetection->ExecuteStep(&triggerExists);
1369 2 : NS_ENSURE_SUCCESS(rv, rv);
1370 2 : rv = triggerDetection->Reset();
1371 2 : NS_ENSURE_SUCCESS(rv, rv);
1372 :
1373 : // We need to create one trigger on moz_bookmarks to remove unused keywords.
1374 : // See bug 421180 for details.
1375 2 : if (!triggerExists) {
1376 : // First, remove any existing dangling keywords
1377 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1378 : "DELETE FROM moz_keywords "
1379 : "WHERE id IN ("
1380 : "SELECT k.id "
1381 : "FROM moz_keywords k "
1382 : "LEFT OUTER JOIN moz_bookmarks b "
1383 : "ON b.keyword_id = k.id "
1384 : "WHERE b.id IS NULL"
1385 0 : ")"));
1386 0 : NS_ENSURE_SUCCESS(rv, rv);
1387 : }
1388 :
1389 : // Add the moz_inputhistory table, if missing.
1390 2 : bool tableExists = false;
1391 4 : rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_inputhistory"),
1392 2 : &tableExists);
1393 2 : NS_ENSURE_SUCCESS(rv, rv);
1394 2 : if (!tableExists) {
1395 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
1396 0 : NS_ENSURE_SUCCESS(rv, rv);
1397 : }
1398 :
1399 2 : return transaction.Commit();
1400 : }
1401 :
1402 :
1403 : nsresult
1404 2 : Database::MigrateV8Up()
1405 : {
1406 2 : MOZ_ASSERT(NS_IsMainThread());
1407 4 : mozStorageTransaction transaction(mMainConn, false);
1408 :
1409 4 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1410 2 : "DROP TRIGGER IF EXISTS moz_historyvisits_afterinsert_v1_trigger"));
1411 2 : NS_ENSURE_SUCCESS(rv, rv);
1412 :
1413 4 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1414 2 : "DROP TRIGGER IF EXISTS moz_historyvisits_afterdelete_v1_trigger"));
1415 2 : NS_ENSURE_SUCCESS(rv, rv);
1416 :
1417 :
1418 : // bug #381795 - remove unused indexes
1419 4 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1420 2 : "DROP INDEX IF EXISTS moz_places_titleindex"));
1421 2 : NS_ENSURE_SUCCESS(rv, rv);
1422 4 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1423 2 : "DROP INDEX IF EXISTS moz_annos_item_idindex"));
1424 2 : NS_ENSURE_SUCCESS(rv, rv);
1425 4 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1426 2 : "DROP INDEX IF EXISTS moz_annos_place_idindex"));
1427 2 : NS_ENSURE_SUCCESS(rv, rv);
1428 :
1429 : // Do a one-time re-creation of the moz_annos indexes (bug 415201)
1430 2 : bool oldIndexExists = false;
1431 2 : rv = mMainConn->IndexExists(NS_LITERAL_CSTRING("moz_annos_attributesindex"), &oldIndexExists);
1432 2 : NS_ENSURE_SUCCESS(rv, rv);
1433 2 : if (oldIndexExists) {
1434 : // drop old uri annos index
1435 0 : rv = mMainConn->ExecuteSimpleSQL(
1436 0 : NS_LITERAL_CSTRING("DROP INDEX moz_annos_attributesindex"));
1437 0 : NS_ENSURE_SUCCESS(rv, rv);
1438 :
1439 : // create new uri annos index
1440 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
1441 0 : NS_ENSURE_SUCCESS(rv, rv);
1442 :
1443 : // drop old item annos index
1444 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1445 0 : "DROP INDEX IF EXISTS moz_items_annos_attributesindex"));
1446 0 : NS_ENSURE_SUCCESS(rv, rv);
1447 :
1448 : // create new item annos index
1449 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
1450 0 : NS_ENSURE_SUCCESS(rv, rv);
1451 : }
1452 :
1453 2 : return transaction.Commit();
1454 : }
1455 :
1456 :
1457 : nsresult
1458 2 : Database::MigrateV9Up()
1459 : {
1460 2 : MOZ_ASSERT(NS_IsMainThread());
1461 4 : mozStorageTransaction transaction(mMainConn, false);
1462 : // Added in Bug 488966. The last_visit_date column caches the last
1463 : // visit date, this enhances SELECT performances when we
1464 : // need to sort visits by visit date.
1465 : // The cached value is synced by triggers on every added or removed visit.
1466 : // See nsPlacesTriggers.h for details on the triggers.
1467 2 : bool oldIndexExists = false;
1468 2 : nsresult rv = mMainConn->IndexExists(
1469 2 : NS_LITERAL_CSTRING("moz_places_lastvisitdateindex"), &oldIndexExists);
1470 2 : NS_ENSURE_SUCCESS(rv, rv);
1471 :
1472 2 : if (!oldIndexExists) {
1473 : // Add last_visit_date column to moz_places.
1474 4 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1475 2 : "ALTER TABLE moz_places ADD last_visit_date INTEGER"));
1476 2 : NS_ENSURE_SUCCESS(rv, rv);
1477 :
1478 2 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
1479 2 : NS_ENSURE_SUCCESS(rv, rv);
1480 :
1481 : // Now let's sync the column contents with real visit dates.
1482 : // This query can be really slow due to disk access, since it will basically
1483 : // dupe the table contents in the journal file, and then write them down
1484 : // in the database.
1485 4 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1486 : "UPDATE moz_places SET last_visit_date = "
1487 : "(SELECT MAX(visit_date) "
1488 : "FROM moz_historyvisits "
1489 2 : "WHERE place_id = moz_places.id)"));
1490 2 : NS_ENSURE_SUCCESS(rv, rv);
1491 : }
1492 :
1493 2 : return transaction.Commit();
1494 : }
1495 :
1496 :
1497 : nsresult
1498 2 : Database::MigrateV10Up()
1499 : {
1500 2 : MOZ_ASSERT(NS_IsMainThread());
1501 : // LastModified is set to the same value as dateAdded on item creation.
1502 : // This way we can use lastModified index to sort.
1503 4 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1504 : "UPDATE moz_bookmarks SET lastModified = dateAdded "
1505 2 : "WHERE lastModified IS NULL"));
1506 2 : NS_ENSURE_SUCCESS(rv, rv);
1507 :
1508 2 : return NS_OK;
1509 : }
1510 :
1511 :
1512 : nsresult
1513 5 : Database::MigrateV11Up()
1514 : {
1515 5 : MOZ_ASSERT(NS_IsMainThread());
1516 : // Temp tables are going away.
1517 : // For triggers correctness, every time we pass through this migration
1518 : // step, we must ensure correctness of visit_count values.
1519 10 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1520 : "UPDATE moz_places SET visit_count = "
1521 : "(SELECT count(*) FROM moz_historyvisits "
1522 : "WHERE place_id = moz_places.id "
1523 : "AND visit_type NOT IN ") +
1524 : nsPrintfCString("(0,%d,%d,%d) ",
1525 : nsINavHistoryService::TRANSITION_EMBED,
1526 : nsINavHistoryService::TRANSITION_FRAMED_LINK,
1527 10 : nsINavHistoryService::TRANSITION_DOWNLOAD) +
1528 15 : NS_LITERAL_CSTRING(")")
1529 10 : );
1530 5 : NS_ENSURE_SUCCESS(rv, rv);
1531 :
1532 : // For existing profiles, we may not have a moz_bookmarks.guid column
1533 10 : nsCOMPtr<mozIStorageStatement> hasGuidStatement;
1534 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1535 : "SELECT guid FROM moz_bookmarks"),
1536 10 : getter_AddRefs(hasGuidStatement));
1537 :
1538 5 : if (NS_FAILED(rv)) {
1539 : // moz_bookmarks grew a guid column. Add the column, but do not populate it
1540 : // with anything just yet. We will do that soon.
1541 6 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1542 : "ALTER TABLE moz_bookmarks "
1543 : "ADD COLUMN guid TEXT"
1544 3 : ));
1545 3 : NS_ENSURE_SUCCESS(rv, rv);
1546 :
1547 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
1548 3 : NS_ENSURE_SUCCESS(rv, rv);
1549 :
1550 : // moz_places grew a guid column. Add the column, but do not populate it
1551 : // with anything just yet. We will do that soon.
1552 6 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1553 : "ALTER TABLE moz_places "
1554 : "ADD COLUMN guid TEXT"
1555 3 : ));
1556 3 : NS_ENSURE_SUCCESS(rv, rv);
1557 :
1558 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
1559 3 : NS_ENSURE_SUCCESS(rv, rv);
1560 : }
1561 :
1562 : // We need to update our guids before we do any real database work.
1563 5 : rv = CheckAndUpdateGUIDs();
1564 5 : NS_ENSURE_SUCCESS(rv, rv);
1565 :
1566 5 : return NS_OK;
1567 : }
1568 :
1569 : nsresult
1570 5 : Database::MigrateV13Up()
1571 : {
1572 5 : MOZ_ASSERT(NS_IsMainThread());
1573 :
1574 : // Dynamic containers are no longer supported.
1575 10 : nsCOMPtr<mozIStorageAsyncStatement> deleteDynContainersStmt;
1576 10 : nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1577 : "DELETE FROM moz_bookmarks WHERE type = :item_type"),
1578 10 : getter_AddRefs(deleteDynContainersStmt));
1579 5 : rv = deleteDynContainersStmt->BindInt32ByName(
1580 5 : NS_LITERAL_CSTRING("item_type"),
1581 : nsINavBookmarksService::TYPE_DYNAMIC_CONTAINER
1582 5 : );
1583 5 : NS_ENSURE_SUCCESS(rv, rv);
1584 10 : nsCOMPtr<mozIStoragePendingStatement> ps;
1585 5 : rv = deleteDynContainersStmt->ExecuteAsync(nsnull, getter_AddRefs(ps));
1586 5 : NS_ENSURE_SUCCESS(rv, rv);
1587 :
1588 5 : return NS_OK;
1589 : }
1590 :
1591 : nsresult
1592 5 : Database::MigrateV14Up()
1593 : {
1594 5 : MOZ_ASSERT(NS_IsMainThread());
1595 :
1596 : // For existing profiles, we may not have a moz_favicons.guid column.
1597 : // Add it here. We want it to be unique, but ALTER TABLE doesn't allow
1598 : // a uniqueness constraint, so the index must be created separately.
1599 10 : nsCOMPtr<mozIStorageStatement> hasGuidStatement;
1600 10 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1601 : "SELECT guid FROM moz_favicons"),
1602 10 : getter_AddRefs(hasGuidStatement));
1603 :
1604 5 : if (NS_FAILED(rv)) {
1605 8 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1606 : "ALTER TABLE moz_favicons "
1607 : "ADD COLUMN guid TEXT"
1608 4 : ));
1609 4 : NS_ENSURE_SUCCESS(rv, rv);
1610 :
1611 4 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_FAVICONS_GUID);
1612 4 : NS_ENSURE_SUCCESS(rv, rv);
1613 : }
1614 :
1615 : // Generate GUID for any favicon missing it.
1616 10 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1617 : "UPDATE moz_favicons "
1618 : "SET guid = GENERATE_GUID() "
1619 : "WHERE guid ISNULL "
1620 5 : ));
1621 5 : NS_ENSURE_SUCCESS(rv, rv);
1622 :
1623 5 : return NS_OK;
1624 : }
1625 :
1626 : nsresult
1627 5 : Database::MigrateV15Up()
1628 : {
1629 5 : MOZ_ASSERT(NS_IsMainThread());
1630 :
1631 : // Drop moz_bookmarks_beforedelete_v1_trigger, since it's more expensive than
1632 : // useful.
1633 10 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1634 : "DROP TRIGGER IF EXISTS moz_bookmarks_beforedelete_v1_trigger"
1635 5 : ));
1636 5 : NS_ENSURE_SUCCESS(rv, rv);
1637 :
1638 : // Remove any orphan keywords.
1639 10 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1640 : "DELETE FROM moz_keywords "
1641 : "WHERE NOT EXISTS ( "
1642 : "SELECT id "
1643 : "FROM moz_bookmarks "
1644 : "WHERE keyword_id = moz_keywords.id "
1645 : ")"
1646 5 : ));
1647 5 : NS_ENSURE_SUCCESS(rv, rv);
1648 :
1649 5 : return NS_OK;
1650 : }
1651 :
1652 : nsresult
1653 5 : Database::MigrateV16Up()
1654 : {
1655 5 : MOZ_ASSERT(NS_IsMainThread());
1656 :
1657 : // Due to Bug 715268 downgraded and then upgraded profiles may lack favicons
1658 : // guids, so fillup any missing ones.
1659 10 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1660 : "UPDATE moz_favicons "
1661 : "SET guid = GENERATE_GUID() "
1662 : "WHERE guid ISNULL "
1663 5 : ));
1664 5 : NS_ENSURE_SUCCESS(rv, rv);
1665 :
1666 5 : return NS_OK;
1667 : }
1668 :
1669 : nsresult
1670 5 : Database::MigrateV17Up()
1671 : {
1672 5 : MOZ_ASSERT(NS_IsMainThread());
1673 :
1674 5 : bool tableExists = false;
1675 :
1676 5 : nsresult rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
1677 5 : NS_ENSURE_SUCCESS(rv, rv);
1678 :
1679 5 : if (!tableExists) {
1680 : // For anyone who used in-development versions of this autocomplete,
1681 : // drop the old tables and its indexes.
1682 10 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1683 : "DROP INDEX IF EXISTS moz_hostnames_frecencyindex"
1684 5 : ));
1685 5 : NS_ENSURE_SUCCESS(rv, rv);
1686 10 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1687 : "DROP TABLE IF EXISTS moz_hostnames"
1688 5 : ));
1689 5 : NS_ENSURE_SUCCESS(rv, rv);
1690 :
1691 : // Add the moz_hosts table so we can get hostnames for URL autocomplete.
1692 5 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
1693 5 : NS_ENSURE_SUCCESS(rv, rv);
1694 : }
1695 :
1696 : // Fill the moz_hosts table with all the domains in moz_places.
1697 10 : nsCOMPtr<mozIStorageAsyncStatement> fillHostsStmt;
1698 10 : rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1699 : "INSERT OR IGNORE INTO moz_hosts (host, frecency) "
1700 : "SELECT fixup_url(get_unreversed_host(h.rev_host)) AS host, "
1701 : "(SELECT MAX(frecency) FROM moz_places "
1702 : "WHERE rev_host = h.rev_host "
1703 : "OR rev_host = h.rev_host || 'www.' "
1704 : ") AS frecency "
1705 : "FROM moz_places h "
1706 : "WHERE LENGTH(h.rev_host) > 1 "
1707 : "GROUP BY h.rev_host"
1708 10 : ), getter_AddRefs(fillHostsStmt));
1709 5 : NS_ENSURE_SUCCESS(rv, rv);
1710 :
1711 10 : nsCOMPtr<mozIStoragePendingStatement> ps;
1712 5 : rv = fillHostsStmt->ExecuteAsync(nsnull, getter_AddRefs(ps));
1713 5 : NS_ENSURE_SUCCESS(rv, rv);
1714 :
1715 5 : return NS_OK;
1716 : }
1717 :
1718 : nsresult
1719 5 : Database::MigrateV18Up()
1720 : {
1721 5 : MOZ_ASSERT(NS_IsMainThread());
1722 :
1723 : // moz_hosts should distinguish on typed entries.
1724 :
1725 : // Check if the profile already has a typed column.
1726 10 : nsCOMPtr<mozIStorageStatement> stmt;
1727 10 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1728 : "SELECT typed FROM moz_hosts"
1729 10 : ), getter_AddRefs(stmt));
1730 5 : if (NS_FAILED(rv)) {
1731 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1732 : "ALTER TABLE moz_hosts ADD COLUMN typed NOT NULL DEFAULT 0"
1733 0 : ));
1734 0 : NS_ENSURE_SUCCESS(rv, rv);
1735 : }
1736 :
1737 : // With the addition of the typed column the covering index loses its
1738 : // advantages. On the other side querying on host and (optionally) typed
1739 : // largely restricts the number of results, making scans decently fast.
1740 10 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1741 : "DROP INDEX IF EXISTS moz_hosts_frecencyhostindex"
1742 5 : ));
1743 5 : NS_ENSURE_SUCCESS(rv, rv);
1744 :
1745 : // Update typed data.
1746 10 : nsCOMPtr<mozIStorageAsyncStatement> updateTypedStmt;
1747 10 : rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1748 : "UPDATE moz_hosts SET typed = 1 WHERE host IN ( "
1749 : "SELECT fixup_url(get_unreversed_host(rev_host)) "
1750 : "FROM moz_places WHERE typed = 1 "
1751 : ") "
1752 10 : ), getter_AddRefs(updateTypedStmt));
1753 5 : NS_ENSURE_SUCCESS(rv, rv);
1754 :
1755 10 : nsCOMPtr<mozIStoragePendingStatement> ps;
1756 5 : rv = updateTypedStmt->ExecuteAsync(nsnull, getter_AddRefs(ps));
1757 5 : NS_ENSURE_SUCCESS(rv, rv);
1758 :
1759 5 : return NS_OK;
1760 : }
1761 :
1762 : nsresult
1763 5 : Database::MigrateV19Up()
1764 : {
1765 5 : MOZ_ASSERT(NS_IsMainThread());
1766 :
1767 : // Livemarks children are no longer bookmarks.
1768 :
1769 : // Remove all children of folders annotated as livemarks.
1770 10 : nsCOMPtr<mozIStorageStatement> deleteLivemarksChildrenStmt;
1771 10 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1772 : "DELETE FROM moz_bookmarks WHERE parent IN("
1773 : "SELECT b.id FROM moz_bookmarks b "
1774 : "JOIN moz_items_annos a ON a.item_id = b.id "
1775 : "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
1776 : "WHERE b.type = :item_type AND n.name = :anno_name "
1777 : ")"
1778 10 : ), getter_AddRefs(deleteLivemarksChildrenStmt));
1779 5 : rv = deleteLivemarksChildrenStmt->BindUTF8StringByName(
1780 5 : NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(LMANNO_FEEDURI)
1781 5 : );
1782 5 : NS_ENSURE_SUCCESS(rv, rv);
1783 5 : rv = deleteLivemarksChildrenStmt->BindInt32ByName(
1784 5 : NS_LITERAL_CSTRING("item_type"), nsINavBookmarksService::TYPE_FOLDER
1785 5 : );
1786 5 : NS_ENSURE_SUCCESS(rv, rv);
1787 5 : rv = deleteLivemarksChildrenStmt->Execute();
1788 5 : NS_ENSURE_SUCCESS(rv, rv);
1789 :
1790 : // Clear obsolete livemark prefs.
1791 5 : (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_seconds");
1792 5 : (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_limit_count");
1793 5 : (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_delay_time");
1794 :
1795 : // Remove the old status annotations.
1796 10 : nsCOMPtr<mozIStorageStatement> deleteLivemarksAnnosStmt;
1797 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1798 : "DELETE FROM moz_items_annos WHERE anno_attribute_id IN("
1799 : "SELECT id FROM moz_anno_attributes "
1800 : "WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) "
1801 : ")"
1802 10 : ), getter_AddRefs(deleteLivemarksAnnosStmt));
1803 5 : rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1804 5 : NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading")
1805 5 : );
1806 5 : NS_ENSURE_SUCCESS(rv, rv);
1807 5 : rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1808 5 : NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed")
1809 5 : );
1810 5 : NS_ENSURE_SUCCESS(rv, rv);
1811 5 : rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1812 5 : NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration")
1813 5 : );
1814 5 : NS_ENSURE_SUCCESS(rv, rv);
1815 5 : rv = deleteLivemarksAnnosStmt->Execute();
1816 5 : NS_ENSURE_SUCCESS(rv, rv);
1817 :
1818 : // Remove orphan annotation names.
1819 10 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1820 : "DELETE FROM moz_anno_attributes "
1821 : "WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) "
1822 10 : ), getter_AddRefs(deleteLivemarksAnnosStmt));
1823 5 : rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1824 5 : NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading")
1825 5 : );
1826 5 : NS_ENSURE_SUCCESS(rv, rv);
1827 5 : rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1828 5 : NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed")
1829 5 : );
1830 5 : NS_ENSURE_SUCCESS(rv, rv);
1831 5 : rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1832 5 : NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration")
1833 5 : );
1834 5 : NS_ENSURE_SUCCESS(rv, rv);
1835 5 : rv = deleteLivemarksAnnosStmt->Execute();
1836 5 : NS_ENSURE_SUCCESS(rv, rv);
1837 :
1838 5 : return NS_OK;
1839 : }
1840 :
1841 : void
1842 265 : Database::Shutdown()
1843 : {
1844 265 : MOZ_ASSERT(NS_IsMainThread());
1845 265 : MOZ_ASSERT(!mShuttingDown);
1846 :
1847 265 : mMainThreadStatements.FinalizeStatements();
1848 265 : mMainThreadAsyncStatements.FinalizeStatements();
1849 :
1850 : nsRefPtr< FinalizeStatementCacheProxy<mozIStorageStatement> > event =
1851 : new FinalizeStatementCacheProxy<mozIStorageStatement>(
1852 : mAsyncThreadStatements, NS_ISUPPORTS_CAST(nsIObserver*, this)
1853 530 : );
1854 265 : DispatchToAsyncThread(event);
1855 :
1856 : nsRefPtr<BlockingConnectionCloseCallback> closeListener =
1857 530 : new BlockingConnectionCloseCallback();
1858 265 : (void)mMainConn->AsyncClose(closeListener);
1859 265 : closeListener->Spin();
1860 :
1861 : // Don't set this earlier, otherwise some internal helper used on shutdown
1862 : // may bail out.
1863 265 : mShuttingDown = true;
1864 265 : }
1865 :
1866 : ////////////////////////////////////////////////////////////////////////////////
1867 : //// nsIObserver
1868 :
1869 : NS_IMETHODIMP
1870 534 : Database::Observe(nsISupports *aSubject,
1871 : const char *aTopic,
1872 : const PRUnichar *aData)
1873 : {
1874 534 : MOZ_ASSERT(NS_IsMainThread());
1875 :
1876 534 : if (strcmp(aTopic, TOPIC_PROFILE_CHANGE_TEARDOWN) == 0) {
1877 : // Tests simulating shutdown may cause multiple notifications.
1878 267 : if (mShuttingDown) {
1879 2 : return NS_OK;
1880 : }
1881 :
1882 530 : nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1883 265 : NS_ENSURE_STATE(os);
1884 :
1885 : // If shutdown happens in the same mainthread loop as init, observers could
1886 : // handle the places-init-complete notification after xpcom-shutdown, when
1887 : // the connection does not exist anymore. Removing those observers would
1888 : // be less expensive but may cause their RemoveObserver calls to throw.
1889 : // Thus notify the topic now, so they stop listening for it.
1890 795 : nsCOMPtr<nsISimpleEnumerator> e;
1891 530 : if (NS_SUCCEEDED(os->EnumerateObservers(TOPIC_PLACES_INIT_COMPLETE,
1892 265 : getter_AddRefs(e))) && e) {
1893 265 : bool hasMore = false;
1894 531 : while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
1895 2 : nsCOMPtr<nsIObserver> observer;
1896 1 : if (NS_SUCCEEDED(e->GetNext(getter_AddRefs(observer)))) {
1897 1 : (void)observer->Observe(observer, TOPIC_PLACES_INIT_COMPLETE, nsnull);
1898 : }
1899 : }
1900 : }
1901 :
1902 : // Notify all Places users that we are about to shutdown.
1903 265 : (void)os->NotifyObservers(nsnull, TOPIC_PLACES_SHUTDOWN, nsnull);
1904 : }
1905 :
1906 267 : else if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
1907 : // Tests simulating shutdown may cause re-entrance.
1908 267 : if (mShuttingDown) {
1909 2 : return NS_OK;
1910 : }
1911 :
1912 : // Fire internal shutdown notifications.
1913 530 : nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1914 265 : if (os) {
1915 265 : (void)os->NotifyObservers(nsnull, TOPIC_PLACES_WILL_CLOSE_CONNECTION, nsnull);
1916 : }
1917 :
1918 : #ifdef DEBUG
1919 : { // Sanity check for missing guids.
1920 265 : bool haveNullGuids = false;
1921 530 : nsCOMPtr<mozIStorageStatement> stmt;
1922 :
1923 530 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1924 : "SELECT 1 "
1925 : "FROM moz_places "
1926 : "WHERE guid IS NULL "
1927 530 : ), getter_AddRefs(stmt));
1928 265 : NS_ENSURE_SUCCESS(rv, rv);
1929 265 : rv = stmt->ExecuteStep(&haveNullGuids);
1930 265 : NS_ENSURE_SUCCESS(rv, rv);
1931 265 : MOZ_ASSERT(!haveNullGuids && "Found a page without a GUID!");
1932 :
1933 530 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1934 : "SELECT 1 "
1935 : "FROM moz_bookmarks "
1936 : "WHERE guid IS NULL "
1937 530 : ), getter_AddRefs(stmt));
1938 265 : NS_ENSURE_SUCCESS(rv, rv);
1939 265 : rv = stmt->ExecuteStep(&haveNullGuids);
1940 265 : NS_ENSURE_SUCCESS(rv, rv);
1941 265 : MOZ_ASSERT(!haveNullGuids && "Found a bookmark without a GUID!");
1942 :
1943 530 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1944 : "SELECT 1 "
1945 : "FROM moz_favicons "
1946 : "WHERE guid IS NULL "
1947 530 : ), getter_AddRefs(stmt));
1948 265 : NS_ENSURE_SUCCESS(rv, rv);
1949 265 : rv = stmt->ExecuteStep(&haveNullGuids);
1950 265 : NS_ENSURE_SUCCESS(rv, rv);
1951 265 : MOZ_ASSERT(!haveNullGuids && "Found a favicon without a GUID!");
1952 : }
1953 : #endif
1954 :
1955 : // As the last step in the shutdown path, finalize the database handle.
1956 530 : Shutdown();
1957 : }
1958 :
1959 530 : return NS_OK;
1960 : }
1961 :
1962 : } // namespace places
1963 : } // namespace mozilla
|