toolkit/components/places/Database.cpp

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

mercurial