toolkit/components/places/Database.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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/. */
     5 #include "mozilla/ArrayUtils.h"
     6 #include "mozilla/Attributes.h"
     7 #include "mozilla/DebugOnly.h"
     9 #include "Database.h"
    11 #include "nsINavBookmarksService.h"
    12 #include "nsIInterfaceRequestorUtils.h"
    13 #include "nsIFile.h"
    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"
    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"
    31 // Time between corrupt database backups.
    32 #define RECENT_BACKUP_TIME_MICROSEC (int64_t)86400 * PR_USEC_PER_SEC // 24H
    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")
    39 // Set when the database file was found corrupt by a previous maintenance.
    40 #define PREF_FORCE_DATABASE_REPLACEMENT "places.database.replaceOnStartup"
    42 // Set to specify the size of the places database growth increments in kibibytes
    43 #define PREF_GROWTH_INCREMENT_KIB "places.database.growthIncrementKiB"
    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
    50 #define BYTES_PER_KIBIBYTE 1024
    52 // Old Sync GUID annotation.
    53 #define SYNCGUID_ANNO NS_LITERAL_CSTRING("sync/guid")
    55 // Places string bundle, contains internationalized bookmark root names.
    56 #define PLACES_BUNDLE "chrome://places/locale/places.properties"
    58 // Livemarks annotations.
    59 #define LMANNO_FEEDURI "livemark/feedURI"
    60 #define LMANNO_SITEURI "livemark/siteURI"
    62 using namespace mozilla;
    64 namespace mozilla {
    65 namespace places {
    67 namespace {
    69 ////////////////////////////////////////////////////////////////////////////////
    70 //// Helpers
    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());
    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);
    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 }
   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);
   141   mozIStorageBaseStatement *stmts[] = {
   142     analyzePlacesStmt,
   143     analyzeBookmarksStmt,
   144     analyzeVisitsStmt,
   145     analyzeInputStmt
   146   };
   148   nsCOMPtr<mozIStoragePendingStatement> ps;
   149   (void)aDBConn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
   150                               getter_AddRefs(ps));
   151   return NS_OK;
   152 }
   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   }
   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);
   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   }
   215   return JOURNAL_DELETE;
   216 }
   218 class ConnectionCloseCallback MOZ_FINAL : public mozIStorageCompletionCallback {
   219   bool mDone;
   221 public:
   222   NS_DECL_THREADSAFE_ISUPPORTS
   223   NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
   224   ConnectionCloseCallback();
   225 };
   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 }
   242 ConnectionCloseCallback::ConnectionCloseCallback()
   243   : mDone(false)
   244 {
   245   MOZ_ASSERT(NS_IsMainThread());
   246 }
   248 NS_IMPL_ISUPPORTS(
   249   ConnectionCloseCallback
   250 , mozIStorageCompletionCallback
   251 )
   253 nsresult
   254 CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
   255            const nsCString& aRootName,
   256            const nsXPIDLString& titleString)
   257 {
   258   MOZ_ASSERT(NS_IsMainThread());
   260   // The position of the new item in its folder.
   261   static int32_t itemPosition = 0;
   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();
   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;
   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;
   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;
   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;
   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;
   320   return NS_OK;
   321 }
   324 } // Anonymous namespace
   326 ////////////////////////////////////////////////////////////////////////////////
   327 //// Database
   329 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase)
   331 NS_IMPL_ISUPPORTS(Database
   332 , nsIObserver
   333 , nsISupportsWeakReference
   334 )
   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 }
   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);
   356   // Remove the static reference to the service.
   357   if (gDatabase == this) {
   358     gDatabase = nullptr;
   359   }
   360 }
   362 nsresult
   363 Database::Init()
   364 {
   365   MOZ_ASSERT(NS_IsMainThread());
   367   nsCOMPtr<mozIStorageService> storage =
   368     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
   369   NS_ENSURE_STATE(storage);
   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   }
   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   }
   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   }
   405   if (databaseMigrated) {
   406     mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
   407   }
   409   if (mDatabaseStatus != nsINavHistoryService::DATABASE_STATUS_OK) {
   410     rv = updateSQLiteStatistics(MainConn());
   411     NS_ENSURE_SUCCESS(rv, rv);
   412   }
   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.
   418   rv = InitTempTriggers();
   419   NS_ENSURE_SUCCESS(rv, rv);
   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);
   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   }
   436   return NS_OK;
   437 }
   439 nsresult
   440 Database::InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
   441                            bool* aNewDatabaseCreated)
   442 {
   443   MOZ_ASSERT(NS_IsMainThread());
   444   *aNewDatabaseCreated = false;
   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);
   453   bool databaseFileExists = false;
   454   rv = databaseFile->Exists(&databaseFileExists);
   455   NS_ENSURE_SUCCESS(rv, rv);
   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);
   464     return NS_ERROR_FILE_CORRUPTED;
   465   }
   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);
   473   *aNewDatabaseCreated = !databaseFileExists;
   474   return NS_OK;
   475 }
   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);
   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   }
   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   }
   510   // Remove the broken database.
   511   rv = databaseFile->Remove(false);
   512   NS_ENSURE_SUCCESS(rv, rv);
   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);
   520   return NS_OK;
   521 }
   523 nsresult
   524 Database::InitSchema(bool* aDatabaseMigrated)
   525 {
   526   MOZ_ASSERT(NS_IsMainThread());
   527   *aDatabaseMigrated = false;
   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.
   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   }
   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);
   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);
   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   }
   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);
   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   }
   598   // We use our functions during migration, so initialize them now.
   599   rv = InitFunctions();
   600   NS_ENSURE_SUCCESS(rv, rv);
   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;
   608   if (databaseInitialized && currentSchemaVersion == DATABASE_SCHEMA_VERSION) {
   609     // The database is up to date and ready to go.
   610     return NS_OK;
   611   }
   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);
   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.
   629     if (currentSchemaVersion < DATABASE_SCHEMA_VERSION) {
   630       *aDatabaseMigrated = true;
   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       }
   638       // Firefox 3.0 uses schema version 6.
   640       if (currentSchemaVersion < 7) {
   641         rv = MigrateV7Up();
   642         NS_ENSURE_SUCCESS(rv, rv);
   643       }
   645       if (currentSchemaVersion < 8) {
   646         rv = MigrateV8Up();
   647         NS_ENSURE_SUCCESS(rv, rv);
   648       }
   650       // Firefox 3.5 uses schema version 8.
   652       if (currentSchemaVersion < 9) {
   653         rv = MigrateV9Up();
   654         NS_ENSURE_SUCCESS(rv, rv);
   655       }
   657       if (currentSchemaVersion < 10) {
   658         rv = MigrateV10Up();
   659         NS_ENSURE_SUCCESS(rv, rv);
   660       }
   662       // Firefox 3.6 uses schema version 10.
   664       if (currentSchemaVersion < 11) {
   665         rv = MigrateV11Up();
   666         NS_ENSURE_SUCCESS(rv, rv);
   667       }
   669       // Firefox 4 uses schema version 11.
   671       // Firefox 8 uses schema version 12.
   673       if (currentSchemaVersion < 13) {
   674         rv = MigrateV13Up();
   675         NS_ENSURE_SUCCESS(rv, rv);
   676       }
   678       if (currentSchemaVersion < 14) {
   679         rv = MigrateV14Up();
   680         NS_ENSURE_SUCCESS(rv, rv);
   681       }
   683       if (currentSchemaVersion < 15) {
   684         rv = MigrateV15Up();
   685         NS_ENSURE_SUCCESS(rv, rv);
   686       }
   688       if (currentSchemaVersion < 16) {
   689         rv = MigrateV16Up();
   690         NS_ENSURE_SUCCESS(rv, rv);
   691       }
   693       // Firefox 11 uses schema version 16.
   695       if (currentSchemaVersion < 17) {
   696         rv = MigrateV17Up();
   697         NS_ENSURE_SUCCESS(rv, rv);
   698       }
   700       // Firefox 12 uses schema version 17.
   702       if (currentSchemaVersion < 18) {
   703         rv = MigrateV18Up();
   704         NS_ENSURE_SUCCESS(rv, rv);
   705       }
   707       if (currentSchemaVersion < 19) {
   708         rv = MigrateV19Up();
   709         NS_ENSURE_SUCCESS(rv, rv);
   710       }
   712       // Firefox 13 uses schema version 19.
   714       if (currentSchemaVersion < 20) {
   715         rv = MigrateV20Up();
   716         NS_ENSURE_SUCCESS(rv, rv);
   717       }
   719       if (currentSchemaVersion < 21) {
   720         rv = MigrateV21Up();
   721         NS_ENSURE_SUCCESS(rv, rv);
   722       }
   724       // Firefox 14 uses schema version 21.
   726       if (currentSchemaVersion < 22) {
   727         rv = MigrateV22Up();
   728         NS_ENSURE_SUCCESS(rv, rv);
   729       }
   731       // Firefox 22 uses schema version 22.
   733       if (currentSchemaVersion < 23) {
   734         rv = MigrateV23Up();
   735         NS_ENSURE_SUCCESS(rv, rv);
   736       }
   738       // Firefox 24 uses schema version 23.
   740       // Schema Upgrades must add migration code here.
   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.
   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);
   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);
   779     // moz_inputhistory.
   780     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
   781     NS_ENSURE_SUCCESS(rv, rv);
   783     // moz_hosts.
   784     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
   785     NS_ENSURE_SUCCESS(rv, rv);
   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);
   799     // moz_bookmarks_roots.
   800     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_ROOTS);
   801     NS_ENSURE_SUCCESS(rv, rv);
   803     // moz_keywords.
   804     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
   805     NS_ENSURE_SUCCESS(rv, rv);
   807     // moz_favicons.
   808     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_FAVICONS);
   809     NS_ENSURE_SUCCESS(rv, rv);
   811     // moz_anno_attributes.
   812     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNO_ATTRIBUTES);
   813     NS_ENSURE_SUCCESS(rv, rv);
   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);
   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);
   827     // Initialize the bookmark roots in the new DB.
   828     rv = CreateBookmarkRoots();
   829     NS_ENSURE_SUCCESS(rv, rv);
   830   }
   832   // Set the schema version to the current one.
   833   rv = mMainConn->SetSchemaVersion(DATABASE_SCHEMA_VERSION);
   834   NS_ENSURE_SUCCESS(rv, rv);
   836   rv = transaction.Commit();
   837   NS_ENSURE_SUCCESS(rv, rv);
   839   ForceWALCheckpoint();
   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.
   846   return NS_OK;
   847 }
   849 nsresult
   850 Database::CreateBookmarkRoots()
   851 {
   852   MOZ_ASSERT(NS_IsMainThread());
   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;
   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;
   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;
   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;
   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;
   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;
   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;
   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
   918   return NS_OK;
   919 }
   921 nsresult
   922 Database::InitFunctions()
   923 {
   924   MOZ_ASSERT(NS_IsMainThread());
   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);
   939   return NS_OK;
   940 }
   942 nsresult
   943 Database::InitTempTriggers()
   944 {
   945   MOZ_ASSERT(NS_IsMainThread());
   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);
   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);
   962   return NS_OK;
   963 }
   965 nsresult
   966 Database::UpdateBookmarkRootTitles()
   967 {
   968   MOZ_ASSERT(NS_IsMainThread());
   970   nsCOMPtr<nsIStringBundleService> bundleService =
   971     services::GetStringBundleService();
   972   NS_ENSURE_STATE(bundleService);
   974   nsCOMPtr<nsIStringBundle> bundle;
   975   nsresult rv = bundleService->CreateBundle(PLACES_BUNDLE, getter_AddRefs(bundle));
   976   if (NS_FAILED(rv)) return rv;
   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;
   985   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
   986   rv = stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
   987   if (NS_FAILED(rv)) return rv;
   989   const char *rootNames[] = { "menu", "toolbar", "tags", "unfiled" };
   990   const char *titleStringIDs[] = {
   991     "BookmarksMenuFolderTitle", "BookmarksToolbarFolderTitle",
   992     "TagsFolderTitle", "UnsortedBookmarksFolderTitle"
   993   };
   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;
  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;
  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;
  1020   return NS_OK;
  1023 nsresult
  1024 Database::CheckAndUpdateGUIDs()
  1026   MOZ_ASSERT(NS_IsMainThread());
  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);
  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);
  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);
  1058     // If we have an invalid guid, we don't need to do any more work.
  1059     if (!IsValidGUID(guid)) {
  1060       continue;
  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;
  1074     NS_ENSURE_SUCCESS(rv, rv);
  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);
  1091   rv = stmt->Execute();
  1092   NS_ENSURE_SUCCESS(rv, rv);
  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);
  1102   rv = stmt->Execute();
  1103   NS_ENSURE_SUCCESS(rv, rv);
  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);
  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);
  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);
  1132     // If we have an invalid guid, we don't need to do any more work.
  1133     if (!IsValidGUID(guid)) {
  1134       continue;
  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;
  1148     NS_ENSURE_SUCCESS(rv, rv);
  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);
  1165   rv = stmt->Execute();
  1166   NS_ENSURE_SUCCESS(rv, rv);
  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);
  1176   rv = stmt->Execute();
  1177   NS_ENSURE_SUCCESS(rv, rv);
  1179   return NS_OK;
  1182 nsresult
  1183 Database::MigrateV7Up() 
  1185   MOZ_ASSERT(NS_IsMainThread());
  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;
  1198   mozStorageTransaction transaction(mMainConn, false);
  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);
  1208   if (!lastModIndexExists) {
  1209     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
  1210     NS_ENSURE_SUCCESS(rv, rv);
  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);
  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);
  1227     // create the new multi-column index
  1228     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
  1229     NS_ENSURE_SUCCESS(rv, rv);
  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));
  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);
  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);
  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));
  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);
  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);
  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);
  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);
  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.
  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);
  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);
  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);
  1379   return transaction.Commit();
  1383 nsresult
  1384 Database::MigrateV8Up()
  1386   MOZ_ASSERT(NS_IsMainThread());
  1387   mozStorageTransaction transaction(mMainConn, false);
  1389   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1390       "DROP TRIGGER IF EXISTS moz_historyvisits_afterinsert_v1_trigger"));
  1391   NS_ENSURE_SUCCESS(rv, rv);
  1393   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1394       "DROP TRIGGER IF EXISTS moz_historyvisits_afterdelete_v1_trigger"));
  1395   NS_ENSURE_SUCCESS(rv, rv);
  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);
  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);
  1419     // create new uri annos index
  1420     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
  1421     NS_ENSURE_SUCCESS(rv, rv);
  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);
  1428     // create new item annos index
  1429     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
  1430     NS_ENSURE_SUCCESS(rv, rv);
  1433   return transaction.Commit();
  1437 nsresult
  1438 Database::MigrateV9Up()
  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);
  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);
  1458     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
  1459     NS_ENSURE_SUCCESS(rv, rv);
  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);
  1473   return transaction.Commit();
  1477 nsresult
  1478 Database::MigrateV10Up()
  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);
  1488   return NS_OK;
  1492 nsresult
  1493 Database::MigrateV11Up()
  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);
  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));
  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);
  1527     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
  1528     NS_ENSURE_SUCCESS(rv, rv);
  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);
  1538     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
  1539     NS_ENSURE_SUCCESS(rv, rv);
  1542   // We need to update our guids before we do any real database work.
  1543   rv = CheckAndUpdateGUIDs();
  1544   NS_ENSURE_SUCCESS(rv, rv);
  1546   return NS_OK;
  1549 nsresult
  1550 Database::MigrateV13Up()
  1552   MOZ_ASSERT(NS_IsMainThread());
  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);
  1568   return NS_OK;
  1571 nsresult
  1572 Database::MigrateV14Up()
  1574   MOZ_ASSERT(NS_IsMainThread());
  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));
  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);
  1591     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_FAVICONS_GUID);
  1592     NS_ENSURE_SUCCESS(rv, rv);
  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);
  1603   return NS_OK;
  1606 nsresult
  1607 Database::MigrateV15Up()
  1609   MOZ_ASSERT(NS_IsMainThread());
  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);
  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);
  1629   return NS_OK;
  1632 nsresult
  1633 Database::MigrateV16Up()
  1635   MOZ_ASSERT(NS_IsMainThread());
  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);
  1646   return NS_OK;
  1649 nsresult
  1650 Database::MigrateV17Up()
  1652   MOZ_ASSERT(NS_IsMainThread());
  1654   bool tableExists = false;
  1656   nsresult rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
  1657   NS_ENSURE_SUCCESS(rv, rv);
  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);
  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);
  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);
  1691   nsCOMPtr<mozIStoragePendingStatement> ps;
  1692   rv = fillHostsStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
  1693   NS_ENSURE_SUCCESS(rv, rv);
  1695   return NS_OK;
  1698 nsresult
  1699 Database::MigrateV18Up()
  1701   MOZ_ASSERT(NS_IsMainThread());
  1703   // moz_hosts should distinguish on typed entries.
  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);
  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);
  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);
  1735   nsCOMPtr<mozIStoragePendingStatement> ps;
  1736   rv = updateTypedStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
  1737   NS_ENSURE_SUCCESS(rv, rv);
  1739   return NS_OK;
  1742 nsresult
  1743 Database::MigrateV19Up()
  1745   MOZ_ASSERT(NS_IsMainThread());
  1747   // Livemarks children are no longer bookmarks.
  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);
  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");
  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);
  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);
  1821   return NS_OK;
  1824 nsresult
  1825 Database::MigrateV20Up()
  1827   MOZ_ASSERT(NS_IsMainThread());
  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);
  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);
  1858   return NS_OK;
  1861 nsresult
  1862 Database::MigrateV21Up()
  1864   MOZ_ASSERT(NS_IsMainThread());
  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);
  1878   return NS_OK;
  1881 nsresult
  1882 Database::MigrateV22Up()
  1884   MOZ_ASSERT(NS_IsMainThread());
  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);
  1893   return NS_OK;
  1897 nsresult
  1898 Database::MigrateV23Up()
  1900   MOZ_ASSERT(NS_IsMainThread());
  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);
  1909   nsCOMPtr<mozIStoragePendingStatement> ps;
  1910   rv = updatePrefixesStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
  1911   NS_ENSURE_SUCCESS(rv, rv);
  1913   return NS_OK;
  1916 void
  1917 Database::Shutdown()
  1919   MOZ_ASSERT(NS_IsMainThread());
  1920   MOZ_ASSERT(!mShuttingDown);
  1921   MOZ_ASSERT(!mClosed);
  1923   mShuttingDown = true;
  1925   mMainThreadStatements.FinalizeStatements();
  1926   mMainThreadAsyncStatements.FinalizeStatements();
  1928   nsRefPtr< FinalizeStatementCacheProxy<mozIStorageStatement> > event =
  1929     new FinalizeStatementCacheProxy<mozIStorageStatement>(
  1930           mAsyncThreadStatements, NS_ISUPPORTS_CAST(nsIObserver*, this)
  1931         );
  1932   DispatchToAsyncThread(event);
  1934   mClosed = true;
  1936   nsRefPtr<ConnectionCloseCallback> closeListener =
  1937     new ConnectionCloseCallback();
  1938   (void)mMainConn->AsyncClose(closeListener);
  1941 ////////////////////////////////////////////////////////////////////////////////
  1942 //// nsIObserver
  1944 NS_IMETHODIMP
  1945 Database::Observe(nsISupports *aSubject,
  1946                   const char *aTopic,
  1947                   const char16_t *aData)
  1949   MOZ_ASSERT(NS_IsMainThread());
  1951   if (strcmp(aTopic, TOPIC_PROFILE_CHANGE_TEARDOWN) == 0) {
  1952     // Tests simulating shutdown may cause multiple notifications.
  1953     if (mShuttingDown) {
  1954       return NS_OK;
  1957     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
  1958     NS_ENSURE_STATE(os);
  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);
  1978     // Notify all Places users that we are about to shutdown.
  1979     (void)os->NotifyObservers(nullptr, TOPIC_PLACES_SHUTDOWN, nullptr);
  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;
  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);
  1994 #ifdef DEBUG
  1995     { // Sanity check for missing guids.
  1996       bool haveNullGuids = false;
  1997       nsCOMPtr<mozIStorageStatement> stmt;
  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!");
  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!");
  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!");
  2029 #endif
  2031     // As the last step in the shutdown path, finalize the database handle.
  2032     Shutdown();
  2035   return NS_OK;
  2038 } // namespace places
  2039 } // namespace mozilla

mercurial