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 #include "mozilla/ScopeExit.h"
9 
10 #include "Database.h"
11 
12 #include "nsIAnnotationService.h"
13 #include "nsINavBookmarksService.h"
14 #include "nsIInterfaceRequestorUtils.h"
15 #include "nsIFile.h"
16 #include "nsIWritablePropertyBag2.h"
17 
18 #include "nsNavHistory.h"
19 #include "nsPlacesTables.h"
20 #include "nsPlacesIndexes.h"
21 #include "nsPlacesTriggers.h"
22 #include "nsPlacesMacros.h"
23 #include "nsVariant.h"
24 #include "SQLFunctions.h"
25 #include "Helpers.h"
26 
27 #include "nsAppDirectoryServiceDefs.h"
28 #include "nsDirectoryServiceUtils.h"
29 #include "prenv.h"
30 #include "prsystem.h"
31 #include "nsPrintfCString.h"
32 #include "mozilla/Preferences.h"
33 #include "mozilla/Services.h"
34 #include "mozilla/Unused.h"
35 #include "prtime.h"
36 
37 #include "nsXULAppAPI.h"
38 
39 // Time between corrupt database backups.
40 #define RECENT_BACKUP_TIME_MICROSEC (int64_t)86400 * PR_USEC_PER_SEC // 24H
41 
42 // Filename of the database.
43 #define DATABASE_FILENAME NS_LITERAL_STRING("places.sqlite")
44 // Filename used to backup corrupt databases.
45 #define DATABASE_CORRUPT_FILENAME NS_LITERAL_STRING("places.sqlite.corrupt")
46 
47 // Set when the database file was found corrupt by a previous maintenance.
48 #define PREF_FORCE_DATABASE_REPLACEMENT "places.database.replaceOnStartup"
49 
50 // Set to specify the size of the places database growth increments in kibibytes
51 #define PREF_GROWTH_INCREMENT_KIB "places.database.growthIncrementKiB"
52 
53 // Set to disable the default robust storage and use volatile, in-memory
54 // storage without robust transaction flushing guarantees. This makes
55 // SQLite use much less I/O at the cost of losing data when things crash.
56 // The pref is only honored if an environment variable is set. The env
57 // variable is intentionally named something scary to help prevent someone
58 // from thinking it is a useful performance optimization they should enable.
59 #define PREF_DISABLE_DURABILITY "places.database.disableDurability"
60 #define ENV_ALLOW_CORRUPTION "ALLOW_PLACES_DATABASE_TO_LOSE_DATA_AND_BECOME_CORRUPT"
61 
62 // The maximum url length we can store in history.
63 // We do not add to history URLs longer than this value.
64 #define PREF_HISTORY_MAXURLLEN "places.history.maxUrlLength"
65 // This number is mostly a guess based on various facts:
66 // * IE didn't support urls longer than 2083 chars
67 // * Sitemaps protocol used to support a maximum of 2048 chars
68 // * Various SEO guides suggest to not go over 2000 chars
69 // * Various apps/services are known to have issues over 2000 chars
70 // * RFC 2616 - HTTP/1.1 suggests being cautious about depending
71 //   on URI lengths above 255 bytes
72 #define PREF_HISTORY_MAXURLLEN_DEFAULT 2000
73 
74 // Maximum size for the WAL file.  It should be small enough since in case of
75 // crashes we could lose all the transactions in the file.  But a too small
76 // file could hurt performance.
77 #define DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES 512
78 
79 #define BYTES_PER_KIBIBYTE 1024
80 
81 // How much time Sqlite can wait before returning a SQLITE_BUSY error.
82 #define DATABASE_BUSY_TIMEOUT_MS 100
83 
84 // Old Sync GUID annotation.
85 #define SYNCGUID_ANNO NS_LITERAL_CSTRING("sync/guid")
86 
87 // Places string bundle, contains internationalized bookmark root names.
88 #define PLACES_BUNDLE "chrome://places/locale/places.properties"
89 
90 // Livemarks annotations.
91 #define LMANNO_FEEDURI "livemark/feedURI"
92 #define LMANNO_SITEURI "livemark/siteURI"
93 
94 #define MOBILE_ROOT_GUID "mobile______"
95 #define MOBILE_ROOT_ANNO "mobile/bookmarksRoot"
96 
97 // We use a fixed title for the mobile root to avoid marking the database as
98 // corrupt if we can't look up the localized title in the string bundle. Sync
99 // sets the title to the localized version when it creates the left pane query.
100 #define MOBILE_ROOT_TITLE "mobile"
101 
102 using namespace mozilla;
103 
104 namespace mozilla {
105 namespace places {
106 
107 namespace {
108 
109 ////////////////////////////////////////////////////////////////////////////////
110 //// Helpers
111 
112 /**
113  * Checks whether exists a database backup created not longer than
114  * RECENT_BACKUP_TIME_MICROSEC ago.
115  */
116 bool
hasRecentCorruptDB()117 hasRecentCorruptDB()
118 {
119   MOZ_ASSERT(NS_IsMainThread());
120 
121   nsCOMPtr<nsIFile> profDir;
122   NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profDir));
123   NS_ENSURE_TRUE(profDir, false);
124   nsCOMPtr<nsISimpleEnumerator> entries;
125   profDir->GetDirectoryEntries(getter_AddRefs(entries));
126   NS_ENSURE_TRUE(entries, false);
127   bool hasMore;
128   while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
129     nsCOMPtr<nsISupports> next;
130     entries->GetNext(getter_AddRefs(next));
131     NS_ENSURE_TRUE(next, false);
132     nsCOMPtr<nsIFile> currFile = do_QueryInterface(next);
133     NS_ENSURE_TRUE(currFile, false);
134 
135     nsAutoString leafName;
136     if (NS_SUCCEEDED(currFile->GetLeafName(leafName)) &&
137         leafName.Length() >= DATABASE_CORRUPT_FILENAME.Length() &&
138         leafName.Find(".corrupt", DATABASE_FILENAME.Length()) != -1) {
139       PRTime lastMod = 0;
140       currFile->GetLastModifiedTime(&lastMod);
141       NS_ENSURE_TRUE(lastMod > 0, false);
142       return (PR_Now() - lastMod) > RECENT_BACKUP_TIME_MICROSEC;
143     }
144   }
145   return false;
146 }
147 
148 /**
149  * Updates sqlite_stat1 table through ANALYZE.
150  * Since also nsPlacesExpiration.js executes ANALYZE, the analyzed tables
151  * must be the same in both components.  So ensure they are in sync.
152  *
153  * @param aDBConn
154  *        The database connection.
155  */
156 nsresult
updateSQLiteStatistics(mozIStorageConnection * aDBConn)157 updateSQLiteStatistics(mozIStorageConnection* aDBConn)
158 {
159   MOZ_ASSERT(NS_IsMainThread());
160   nsCOMPtr<mozIStorageAsyncStatement> analyzePlacesStmt;
161   aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
162     "ANALYZE moz_places"
163   ), getter_AddRefs(analyzePlacesStmt));
164   NS_ENSURE_STATE(analyzePlacesStmt);
165   nsCOMPtr<mozIStorageAsyncStatement> analyzeBookmarksStmt;
166   aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
167     "ANALYZE moz_bookmarks"
168   ), getter_AddRefs(analyzeBookmarksStmt));
169   NS_ENSURE_STATE(analyzeBookmarksStmt);
170   nsCOMPtr<mozIStorageAsyncStatement> analyzeVisitsStmt;
171   aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
172     "ANALYZE moz_historyvisits"
173   ), getter_AddRefs(analyzeVisitsStmt));
174   NS_ENSURE_STATE(analyzeVisitsStmt);
175   nsCOMPtr<mozIStorageAsyncStatement> analyzeInputStmt;
176   aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
177     "ANALYZE moz_inputhistory"
178   ), getter_AddRefs(analyzeInputStmt));
179   NS_ENSURE_STATE(analyzeInputStmt);
180 
181   mozIStorageBaseStatement *stmts[] = {
182     analyzePlacesStmt,
183     analyzeBookmarksStmt,
184     analyzeVisitsStmt,
185     analyzeInputStmt
186   };
187 
188   nsCOMPtr<mozIStoragePendingStatement> ps;
189   (void)aDBConn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
190                               getter_AddRefs(ps));
191   return NS_OK;
192 }
193 
194 /**
195  * Sets the connection journal mode to one of the JOURNAL_* types.
196  *
197  * @param aDBConn
198  *        The database connection.
199  * @param aJournalMode
200  *        One of the JOURNAL_* types.
201  * @returns the current journal mode.
202  * @note this may return a different journal mode than the required one, since
203  *       setting it may fail.
204  */
205 enum JournalMode
SetJournalMode(nsCOMPtr<mozIStorageConnection> & aDBConn,enum JournalMode aJournalMode)206 SetJournalMode(nsCOMPtr<mozIStorageConnection>& aDBConn,
207                              enum JournalMode aJournalMode)
208 {
209   MOZ_ASSERT(NS_IsMainThread());
210   nsAutoCString journalMode;
211   switch (aJournalMode) {
212     default:
213       MOZ_FALLTHROUGH_ASSERT("Trying to set an unknown journal mode.");
214       // Fall through to the default DELETE journal.
215     case JOURNAL_DELETE:
216       journalMode.AssignLiteral("delete");
217       break;
218     case JOURNAL_TRUNCATE:
219       journalMode.AssignLiteral("truncate");
220       break;
221     case JOURNAL_MEMORY:
222       journalMode.AssignLiteral("memory");
223       break;
224     case JOURNAL_WAL:
225       journalMode.AssignLiteral("wal");
226       break;
227   }
228 
229   nsCOMPtr<mozIStorageStatement> statement;
230   nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR
231 		      "PRAGMA journal_mode = ");
232   query.Append(journalMode);
233   aDBConn->CreateStatement(query, getter_AddRefs(statement));
234   NS_ENSURE_TRUE(statement, JOURNAL_DELETE);
235 
236   bool hasResult = false;
237   if (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && hasResult &&
238       NS_SUCCEEDED(statement->GetUTF8String(0, journalMode))) {
239     if (journalMode.EqualsLiteral("delete")) {
240       return JOURNAL_DELETE;
241     }
242     if (journalMode.EqualsLiteral("truncate")) {
243       return JOURNAL_TRUNCATE;
244     }
245     if (journalMode.EqualsLiteral("memory")) {
246       return JOURNAL_MEMORY;
247     }
248     if (journalMode.EqualsLiteral("wal")) {
249       return JOURNAL_WAL;
250     }
251     // This is an unknown journal.
252     MOZ_ASSERT(true);
253   }
254 
255   return JOURNAL_DELETE;
256 }
257 
258 nsresult
CreateRoot(nsCOMPtr<mozIStorageConnection> & aDBConn,const nsCString & aRootName,const nsCString & aGuid,const nsXPIDLString & titleString)259 CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
260            const nsCString& aRootName, const nsCString& aGuid,
261            const nsXPIDLString& titleString)
262 {
263   MOZ_ASSERT(NS_IsMainThread());
264 
265   // The position of the new item in its folder.
266   static int32_t itemPosition = 0;
267 
268   // A single creation timestamp for all roots so that the root folder's
269   // last modification time isn't earlier than its childrens' creation time.
270   static PRTime timestamp = 0;
271   if (!timestamp)
272     timestamp = RoundedPRNow();
273 
274   // Create a new bookmark folder for the root.
275   nsCOMPtr<mozIStorageStatement> stmt;
276   nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
277     "INSERT INTO moz_bookmarks "
278       "(type, position, title, dateAdded, lastModified, guid, parent) "
279     "VALUES (:item_type, :item_position, :item_title,"
280             ":date_added, :last_modified, :guid,"
281             "IFNULL((SELECT id FROM moz_bookmarks WHERE parent = 0), 0))"
282   ), getter_AddRefs(stmt));
283   if (NS_FAILED(rv)) return rv;
284 
285   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
286                              nsINavBookmarksService::TYPE_FOLDER);
287   if (NS_FAILED(rv)) return rv;
288   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"), itemPosition);
289   if (NS_FAILED(rv)) return rv;
290   rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
291                                   NS_ConvertUTF16toUTF8(titleString));
292   if (NS_FAILED(rv)) return rv;
293   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), timestamp);
294   if (NS_FAILED(rv)) return rv;
295   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), timestamp);
296   if (NS_FAILED(rv)) return rv;
297   rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGuid);
298   if (NS_FAILED(rv)) return rv;
299   rv = stmt->Execute();
300   if (NS_FAILED(rv)) return rv;
301 
302   // The 'places' root is a folder containing the other roots.
303   // The first bookmark in a folder has position 0.
304   if (!aRootName.EqualsLiteral("places"))
305     ++itemPosition;
306 
307   return NS_OK;
308 }
309 
310 
311 } // namespace
312 
313 ////////////////////////////////////////////////////////////////////////////////
314 //// Database
315 
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database,gDatabase)316 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase)
317 
318 NS_IMPL_ISUPPORTS(Database
319 , nsIObserver
320 , nsISupportsWeakReference
321 )
322 
323 Database::Database()
324   : mMainThreadStatements(mMainConn)
325   , mMainThreadAsyncStatements(mMainConn)
326   , mAsyncThreadStatements(mMainConn)
327   , mDBPageSize(0)
328   , mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK)
329   , mClosed(false)
330   , mClientsShutdown(new ClientsShutdownBlocker())
331   , mConnectionShutdown(new ConnectionShutdownBlocker(this))
332   , mMaxUrlLength(0)
333 {
334   MOZ_ASSERT(!XRE_IsContentProcess(),
335              "Cannot instantiate Places in the content process");
336   // Attempting to create two instances of the service?
337   MOZ_ASSERT(!gDatabase);
338   gDatabase = this;
339 }
340 
341 already_AddRefed<nsIAsyncShutdownClient>
GetProfileChangeTeardownPhase()342 Database::GetProfileChangeTeardownPhase()
343 {
344   nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc = services::GetAsyncShutdown();
345   MOZ_ASSERT(asyncShutdownSvc);
346   if (NS_WARN_IF(!asyncShutdownSvc)) {
347     return nullptr;
348   }
349 
350   // Consumers of Places should shutdown before us, at profile-change-teardown.
351   nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
352   DebugOnly<nsresult> rv = asyncShutdownSvc->
353     GetProfileChangeTeardown(getter_AddRefs(shutdownPhase));
354   MOZ_ASSERT(NS_SUCCEEDED(rv));
355   return shutdownPhase.forget();
356 }
357 
358 already_AddRefed<nsIAsyncShutdownClient>
GetProfileBeforeChangePhase()359 Database::GetProfileBeforeChangePhase()
360 {
361   nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc = services::GetAsyncShutdown();
362   MOZ_ASSERT(asyncShutdownSvc);
363   if (NS_WARN_IF(!asyncShutdownSvc)) {
364     return nullptr;
365   }
366 
367   // Consumers of Places should shutdown before us, at profile-change-teardown.
368   nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
369   DebugOnly<nsresult> rv = asyncShutdownSvc->
370     GetProfileBeforeChange(getter_AddRefs(shutdownPhase));
371   MOZ_ASSERT(NS_SUCCEEDED(rv));
372   return shutdownPhase.forget();
373 }
374 
~Database()375 Database::~Database()
376 {
377 }
378 
379 bool
IsShutdownStarted() const380 Database::IsShutdownStarted() const
381 {
382   if (!mConnectionShutdown) {
383     // We have already broken the cycle between `this` and `mConnectionShutdown`.
384     return true;
385   }
386   return mConnectionShutdown->IsStarted();
387 }
388 
389 already_AddRefed<mozIStorageAsyncStatement>
GetAsyncStatement(const nsACString & aQuery) const390 Database::GetAsyncStatement(const nsACString& aQuery) const
391 {
392   if (IsShutdownStarted()) {
393     return nullptr;
394   }
395   MOZ_ASSERT(NS_IsMainThread());
396   return mMainThreadAsyncStatements.GetCachedStatement(aQuery);
397 }
398 
399 already_AddRefed<mozIStorageStatement>
GetStatement(const nsACString & aQuery) const400 Database::GetStatement(const nsACString& aQuery) const
401 {
402   if (IsShutdownStarted()) {
403     return nullptr;
404   }
405   if (NS_IsMainThread()) {
406     return mMainThreadStatements.GetCachedStatement(aQuery);
407   }
408   return mAsyncThreadStatements.GetCachedStatement(aQuery);
409 }
410 
411 already_AddRefed<nsIAsyncShutdownClient>
GetClientsShutdown()412 Database::GetClientsShutdown()
413 {
414   MOZ_ASSERT(mClientsShutdown);
415   return mClientsShutdown->GetClient();
416 }
417 
418 // static
419 already_AddRefed<Database>
GetDatabase()420 Database::GetDatabase()
421 {
422   if (PlacesShutdownBlocker::IsStarted()) {
423     return nullptr;
424   }
425   return GetSingleton();
426 }
427 
428 nsresult
Init()429 Database::Init()
430 {
431   MOZ_ASSERT(NS_IsMainThread());
432 
433   nsCOMPtr<mozIStorageService> storage =
434     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
435   NS_ENSURE_STATE(storage);
436 
437   // Init the database file and connect to it.
438   bool databaseCreated = false;
439   nsresult rv = InitDatabaseFile(storage, &databaseCreated);
440   if (NS_SUCCEEDED(rv) && databaseCreated) {
441     mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE;
442   }
443   else if (rv == NS_ERROR_FILE_CORRUPTED) {
444     // The database is corrupt, backup and replace it with a new one.
445     mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
446     rv = BackupAndReplaceDatabaseFile(storage);
447     // Fallback to catch-all handler, that notifies a database locked failure.
448   }
449 
450   // If the database connection still cannot be opened, it may just be locked
451   // by third parties.  Send out a notification and interrupt initialization.
452   if (NS_FAILED(rv)) {
453     RefPtr<PlacesEvent> lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED);
454     (void)NS_DispatchToMainThread(lockedEvent);
455     return rv;
456   }
457 
458   // Initialize the database schema.  In case of failure the existing schema is
459   // is corrupt or incoherent, thus the database should be replaced.
460   bool databaseMigrated = false;
461   rv = InitSchema(&databaseMigrated);
462   if (NS_FAILED(rv)) {
463     mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
464     rv = BackupAndReplaceDatabaseFile(storage);
465     NS_ENSURE_SUCCESS(rv, rv);
466     // Try to initialize the schema again on the new database.
467     rv = InitSchema(&databaseMigrated);
468     NS_ENSURE_SUCCESS(rv, rv);
469   }
470 
471   if (databaseMigrated) {
472     mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
473   }
474 
475   if (mDatabaseStatus != nsINavHistoryService::DATABASE_STATUS_OK) {
476     rv = updateSQLiteStatistics(MainConn());
477     NS_ENSURE_SUCCESS(rv, rv);
478   }
479 
480   // Initialize here all the items that are not part of the on-disk database,
481   // like views, temp triggers or temp tables.  The database should not be
482   // considered corrupt if any of the following fails.
483 
484   rv = InitTempEntities();
485   NS_ENSURE_SUCCESS(rv, rv);
486 
487   // Notify we have finished database initialization.
488   // Enqueue the notification, so if we init another service that requires
489   // nsNavHistoryService we don't recursive try to get it.
490   RefPtr<PlacesEvent> completeEvent =
491     new PlacesEvent(TOPIC_PLACES_INIT_COMPLETE);
492   rv = NS_DispatchToMainThread(completeEvent);
493   NS_ENSURE_SUCCESS(rv, rv);
494 
495   // At this point we know the Database object points to a valid connection
496   // and we need to setup async shutdown.
497   {
498     // First of all Places clients should block profile-change-teardown.
499     nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileChangeTeardownPhase();
500     MOZ_ASSERT(shutdownPhase);
501     if (shutdownPhase) {
502       DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
503         static_cast<nsIAsyncShutdownBlocker*>(mClientsShutdown.get()),
504         NS_LITERAL_STRING(__FILE__),
505         __LINE__,
506         NS_LITERAL_STRING(""));
507       MOZ_ASSERT(NS_SUCCEEDED(rv));
508     }
509   }
510 
511   {
512     // Then connection closing should block profile-before-change.
513     nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileBeforeChangePhase();
514     MOZ_ASSERT(shutdownPhase);
515     if (shutdownPhase) {
516       DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
517         static_cast<nsIAsyncShutdownBlocker*>(mConnectionShutdown.get()),
518         NS_LITERAL_STRING(__FILE__),
519         __LINE__,
520         NS_LITERAL_STRING(""));
521       MOZ_ASSERT(NS_SUCCEEDED(rv));
522     }
523   }
524 
525   // Finally observe profile shutdown notifications.
526   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
527   if (os) {
528     (void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
529   }
530 
531   return NS_OK;
532 }
533 
534 nsresult
InitDatabaseFile(nsCOMPtr<mozIStorageService> & aStorage,bool * aNewDatabaseCreated)535 Database::InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
536                            bool* aNewDatabaseCreated)
537 {
538   MOZ_ASSERT(NS_IsMainThread());
539   *aNewDatabaseCreated = false;
540 
541   nsCOMPtr<nsIFile> databaseFile;
542   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
543                                        getter_AddRefs(databaseFile));
544   NS_ENSURE_SUCCESS(rv, rv);
545   rv = databaseFile->Append(DATABASE_FILENAME);
546   NS_ENSURE_SUCCESS(rv, rv);
547 
548   bool databaseFileExists = false;
549   rv = databaseFile->Exists(&databaseFileExists);
550   NS_ENSURE_SUCCESS(rv, rv);
551 
552   if (databaseFileExists &&
553       Preferences::GetBool(PREF_FORCE_DATABASE_REPLACEMENT, false)) {
554     // If this pref is set, Maintenance required a database replacement, due to
555     // integrity corruption.
556     // Be sure to clear the pref to avoid handling it more than once.
557     (void)Preferences::ClearUser(PREF_FORCE_DATABASE_REPLACEMENT);
558 
559     return NS_ERROR_FILE_CORRUPTED;
560   }
561 
562   // Open the database file.  If it does not exist a new one will be created.
563   // Use an unshared connection, it will consume more memory but avoid shared
564   // cache contentions across threads.
565   rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
566   NS_ENSURE_SUCCESS(rv, rv);
567 
568   *aNewDatabaseCreated = !databaseFileExists;
569   return NS_OK;
570 }
571 
572 nsresult
BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService> & aStorage)573 Database::BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage)
574 {
575   MOZ_ASSERT(NS_IsMainThread());
576   nsCOMPtr<nsIFile> profDir;
577   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
578                                        getter_AddRefs(profDir));
579   NS_ENSURE_SUCCESS(rv, rv);
580   nsCOMPtr<nsIFile> databaseFile;
581   rv = profDir->Clone(getter_AddRefs(databaseFile));
582   NS_ENSURE_SUCCESS(rv, rv);
583   rv = databaseFile->Append(DATABASE_FILENAME);
584   NS_ENSURE_SUCCESS(rv, rv);
585 
586   // If we have
587   // already failed in the last 24 hours avoid to create another corrupt file,
588   // since doing so, in some situation, could cause us to create a new corrupt
589   // file at every try to access any Places service.  That is bad because it
590   // would quickly fill the user's disk space without any notice.
591   if (!hasRecentCorruptDB()) {
592     nsCOMPtr<nsIFile> backup;
593     (void)aStorage->BackupDatabaseFile(databaseFile, DATABASE_CORRUPT_FILENAME,
594                                        profDir, getter_AddRefs(backup));
595   }
596 
597   // If anything fails from this point on, we have a stale connection or
598   // database file, and there's not much more we can do.
599   // The only thing we can try to do is to replace the database on the next
600   // startup, and report the problem through telemetry.
601   {
602     enum eCorruptDBReplaceStage : int8_t {
603       stage_closing = 0,
604       stage_removing,
605       stage_reopening,
606       stage_replaced
607     };
608     eCorruptDBReplaceStage stage = stage_closing;
609     auto guard = MakeScopeExit([&]() {
610       if (stage != stage_replaced) {
611         // Reaching this point means the database is corrupt and we failed to
612         // replace it.  For this session part of the application related to
613         // bookmarks and history will misbehave.  The frontend may show a
614         // "locked" notification to the user though.
615         // Set up a pref to try replacing the database at the next startup.
616         Preferences::SetBool(PREF_FORCE_DATABASE_REPLACEMENT, true);
617       }
618       // Report the corruption through telemetry.
619       Telemetry::Accumulate(Telemetry::PLACES_DATABASE_CORRUPTION_HANDLING_STAGE,
620                             static_cast<int8_t>(stage));
621     });
622 
623     // Close database connection if open.
624     if (mMainConn) {
625       rv = mMainConn->Close();
626       NS_ENSURE_SUCCESS(rv, rv);
627     }
628 
629     // Remove the broken database.
630     stage = stage_removing;
631     rv = databaseFile->Remove(false);
632     if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
633       return rv;
634     }
635 
636     // Create a new database file.
637     // Use an unshared connection, it will consume more memory but avoid shared
638     // cache contentions across threads.
639     stage = stage_reopening;
640     rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
641     NS_ENSURE_SUCCESS(rv, rv);
642 
643     stage = stage_replaced;
644   }
645 
646   return NS_OK;
647 }
648 
649 nsresult
InitSchema(bool * aDatabaseMigrated)650 Database::InitSchema(bool* aDatabaseMigrated)
651 {
652   MOZ_ASSERT(NS_IsMainThread());
653   *aDatabaseMigrated = false;
654 
655   // WARNING: any statement executed before setting the journal mode must be
656   // finalized, since SQLite doesn't allow changing the journal mode if there
657   // is any outstanding statement.
658 
659   {
660     // Get the page size.  This may be different than the default if the
661     // database file already existed with a different page size.
662     nsCOMPtr<mozIStorageStatement> statement;
663     nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
664       MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
665     ), getter_AddRefs(statement));
666     NS_ENSURE_SUCCESS(rv, rv);
667     bool hasResult = false;
668     rv = statement->ExecuteStep(&hasResult);
669     NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
670     rv = statement->GetInt32(0, &mDBPageSize);
671     NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0, NS_ERROR_UNEXPECTED);
672   }
673 
674   // Ensure that temp tables are held in memory, not on disk.
675   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
676       MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY"));
677   NS_ENSURE_SUCCESS(rv, rv);
678 
679   if (PR_GetEnv(ENV_ALLOW_CORRUPTION) && Preferences::GetBool(PREF_DISABLE_DURABILITY, false)) {
680     // Volatile storage was requested. Use the in-memory journal (no
681     // filesystem I/O) and don't sync the filesystem after writing.
682     SetJournalMode(mMainConn, JOURNAL_MEMORY);
683     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
684         "PRAGMA synchronous = OFF"));
685     NS_ENSURE_SUCCESS(rv, rv);
686   }
687   else {
688     // Be sure to set journal mode after page_size.  WAL would prevent the change
689     // otherwise.
690     if (JOURNAL_WAL == SetJournalMode(mMainConn, JOURNAL_WAL)) {
691       // Set the WAL journal size limit.  We want it to be small, since in
692       // synchronous = NORMAL mode a crash could cause loss of all the
693       // transactions in the journal.  For added safety we will also force
694       // checkpointing at strategic moments.
695       int32_t checkpointPages =
696         static_cast<int32_t>(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 1024 / mDBPageSize);
697       nsAutoCString checkpointPragma("PRAGMA wal_autocheckpoint = ");
698       checkpointPragma.AppendInt(checkpointPages);
699       rv = mMainConn->ExecuteSimpleSQL(checkpointPragma);
700       NS_ENSURE_SUCCESS(rv, rv);
701     }
702     else {
703       // Ignore errors, if we fail here the database could be considered corrupt
704       // and we won't be able to go on, even if it's just matter of a bogus file
705       // system.  The default mode (DELETE) will be fine in such a case.
706       (void)SetJournalMode(mMainConn, JOURNAL_TRUNCATE);
707 
708       // Set synchronous to FULL to ensure maximum data integrity, even in
709       // case of crashes or unclean shutdowns.
710       rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
711           "PRAGMA synchronous = FULL"));
712       NS_ENSURE_SUCCESS(rv, rv);
713     }
714   }
715 
716   // The journal is usually free to grow for performance reasons, but it never
717   // shrinks back.  Since the space taken may be problematic, especially on
718   // mobile devices, limit its size.
719   // Since exceeding the limit will cause a truncate, allow a slightly
720   // larger limit than DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES to reduce the number
721   // of times it is needed.
722   nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
723   journalSizePragma.AppendInt(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 3);
724   (void)mMainConn->ExecuteSimpleSQL(journalSizePragma);
725 
726   // Grow places in |growthIncrementKiB| increments to limit fragmentation on disk.
727   // By default, it's 10 MB.
728   int32_t growthIncrementKiB =
729     Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 10 * BYTES_PER_KIBIBYTE);
730   if (growthIncrementKiB > 0) {
731     (void)mMainConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE, EmptyCString());
732   }
733 
734   nsAutoCString busyTimeoutPragma("PRAGMA busy_timeout = ");
735   busyTimeoutPragma.AppendInt(DATABASE_BUSY_TIMEOUT_MS);
736   (void)mMainConn->ExecuteSimpleSQL(busyTimeoutPragma);
737 
738   // We use our functions during migration, so initialize them now.
739   rv = InitFunctions();
740   NS_ENSURE_SUCCESS(rv, rv);
741 
742   // Get the database schema version.
743   int32_t currentSchemaVersion;
744   rv = mMainConn->GetSchemaVersion(&currentSchemaVersion);
745   NS_ENSURE_SUCCESS(rv, rv);
746   bool databaseInitialized = currentSchemaVersion > 0;
747 
748   if (databaseInitialized && currentSchemaVersion == DATABASE_SCHEMA_VERSION) {
749     // The database is up to date and ready to go.
750     return NS_OK;
751   }
752 
753   // We are going to update the database, so everything from now on should be in
754   // a transaction for performances.
755   mozStorageTransaction transaction(mMainConn, false);
756 
757   if (databaseInitialized) {
758     // Migration How-to:
759     //
760     // 1. increment PLACES_SCHEMA_VERSION.
761     // 2. implement a method that performs upgrade to your version from the
762     //    previous one.
763     //
764     // NOTE: The downgrade process is pretty much complicated by the fact old
765     //       versions cannot know what a new version is going to implement.
766     //       The only thing we will do for downgrades is setting back the schema
767     //       version, so that next upgrades will run again the migration step.
768 
769     if (currentSchemaVersion > 36) {
770       // These versions are not downgradable.
771       return NS_ERROR_FILE_CORRUPTED;
772     }
773 
774     if (currentSchemaVersion < DATABASE_SCHEMA_VERSION) {
775       *aDatabaseMigrated = true;
776 
777       if (currentSchemaVersion < 11) {
778         // These are versions older than Firefox 4 that are not supported
779         // anymore.  In this case it's safer to just replace the database.
780         return NS_ERROR_FILE_CORRUPTED;
781       }
782 
783       // Firefox 4 uses schema version 11.
784 
785       // Firefox 8 uses schema version 12.
786 
787       if (currentSchemaVersion < 13) {
788         rv = MigrateV13Up();
789         NS_ENSURE_SUCCESS(rv, rv);
790       }
791 
792       if (currentSchemaVersion < 15) {
793         rv = MigrateV15Up();
794         NS_ENSURE_SUCCESS(rv, rv);
795       }
796 
797       if (currentSchemaVersion < 17) {
798         rv = MigrateV17Up();
799         NS_ENSURE_SUCCESS(rv, rv);
800       }
801 
802       // Firefox 12 uses schema version 17.
803 
804       if (currentSchemaVersion < 18) {
805         rv = MigrateV18Up();
806         NS_ENSURE_SUCCESS(rv, rv);
807       }
808 
809       if (currentSchemaVersion < 19) {
810         rv = MigrateV19Up();
811         NS_ENSURE_SUCCESS(rv, rv);
812       }
813 
814       // Firefox 13 uses schema version 19.
815 
816       if (currentSchemaVersion < 20) {
817         rv = MigrateV20Up();
818         NS_ENSURE_SUCCESS(rv, rv);
819       }
820 
821       if (currentSchemaVersion < 21) {
822         rv = MigrateV21Up();
823         NS_ENSURE_SUCCESS(rv, rv);
824       }
825 
826       // Firefox 14 uses schema version 21.
827 
828       if (currentSchemaVersion < 22) {
829         rv = MigrateV22Up();
830         NS_ENSURE_SUCCESS(rv, rv);
831       }
832 
833       // Firefox 22 uses schema version 22.
834 
835       if (currentSchemaVersion < 23) {
836         rv = MigrateV23Up();
837         NS_ENSURE_SUCCESS(rv, rv);
838       }
839 
840       // Firefox 24 uses schema version 23.
841 
842       if (currentSchemaVersion < 24) {
843         rv = MigrateV24Up();
844         NS_ENSURE_SUCCESS(rv, rv);
845       }
846 
847       // Firefox 34 uses schema version 24.
848 
849       if (currentSchemaVersion < 25) {
850         rv = MigrateV25Up();
851         NS_ENSURE_SUCCESS(rv, rv);
852       }
853 
854       // Firefox 36 uses schema version 25.
855 
856       if (currentSchemaVersion < 26) {
857         rv = MigrateV26Up();
858         NS_ENSURE_SUCCESS(rv, rv);
859       }
860 
861       // Firefox 37 uses schema version 26.
862 
863       if (currentSchemaVersion < 27) {
864         rv = MigrateV27Up();
865         NS_ENSURE_SUCCESS(rv, rv);
866       }
867 
868       if (currentSchemaVersion < 28) {
869         rv = MigrateV28Up();
870         NS_ENSURE_SUCCESS(rv, rv);
871       }
872 
873       // Firefox 39 uses schema version 28.
874 
875       if (currentSchemaVersion < 30) {
876         rv = MigrateV30Up();
877         NS_ENSURE_SUCCESS(rv, rv);
878       }
879 
880       // Firefox 41 uses schema version 30.
881 
882       if (currentSchemaVersion < 31) {
883         rv = MigrateV31Up();
884         NS_ENSURE_SUCCESS(rv, rv);
885       }
886 
887       // Firefox 48 uses schema version 31.
888 
889       if (currentSchemaVersion < 32) {
890         rv = MigrateV32Up();
891         NS_ENSURE_SUCCESS(rv, rv);
892       }
893 
894       // Firefox 49 uses schema version 32.
895 
896       if (currentSchemaVersion < 33) {
897         rv = MigrateV33Up();
898         NS_ENSURE_SUCCESS(rv, rv);
899       }
900 
901       // Firefox 50 uses schema version 33.
902 
903       if (currentSchemaVersion < 34) {
904         rv = MigrateV34Up();
905         NS_ENSURE_SUCCESS(rv, rv);
906       }
907 
908       // Firefox 51 uses schema version 34.
909 
910       if (currentSchemaVersion < 35) {
911         rv = MigrateV35Up();
912         NS_ENSURE_SUCCESS(rv, rv);
913       }
914 
915       // Firefox 52 uses schema version 35.
916 
917       // Schema Upgrades must add migration code here.
918 
919       rv = UpdateBookmarkRootTitles();
920       // We don't want a broken localization to cause us to think
921       // the database is corrupt and needs to be replaced.
922       MOZ_ASSERT(NS_SUCCEEDED(rv));
923     }
924   }
925   else {
926     // This is a new database, so we have to create all the tables and indices.
927 
928     // moz_places.
929     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES);
930     NS_ENSURE_SUCCESS(rv, rv);
931     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
932     NS_ENSURE_SUCCESS(rv, rv);
933     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FAVICON);
934     NS_ENSURE_SUCCESS(rv, rv);
935     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_REVHOST);
936     NS_ENSURE_SUCCESS(rv, rv);
937     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_VISITCOUNT);
938     NS_ENSURE_SUCCESS(rv, rv);
939     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
940     NS_ENSURE_SUCCESS(rv, rv);
941     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
942     NS_ENSURE_SUCCESS(rv, rv);
943     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
944     NS_ENSURE_SUCCESS(rv, rv);
945 
946     // moz_historyvisits.
947     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS);
948     NS_ENSURE_SUCCESS(rv, rv);
949     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
950     NS_ENSURE_SUCCESS(rv, rv);
951     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT);
952     NS_ENSURE_SUCCESS(rv, rv);
953     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE);
954     NS_ENSURE_SUCCESS(rv, rv);
955 
956     // moz_inputhistory.
957     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
958     NS_ENSURE_SUCCESS(rv, rv);
959 
960     // moz_hosts.
961     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
962     NS_ENSURE_SUCCESS(rv, rv);
963 
964     // moz_bookmarks.
965     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS);
966     NS_ENSURE_SUCCESS(rv, rv);
967     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE);
968     NS_ENSURE_SUCCESS(rv, rv);
969     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION);
970     NS_ENSURE_SUCCESS(rv, rv);
971     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
972     NS_ENSURE_SUCCESS(rv, rv);
973     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
974     NS_ENSURE_SUCCESS(rv, rv);
975 
976     // moz_keywords.
977     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
978     NS_ENSURE_SUCCESS(rv, rv);
979     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
980     NS_ENSURE_SUCCESS(rv, rv);
981 
982     // moz_favicons.
983     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_FAVICONS);
984     NS_ENSURE_SUCCESS(rv, rv);
985 
986     // moz_anno_attributes.
987     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNO_ATTRIBUTES);
988     NS_ENSURE_SUCCESS(rv, rv);
989 
990     // moz_annos.
991     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNOS);
992     NS_ENSURE_SUCCESS(rv, rv);
993     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
994     NS_ENSURE_SUCCESS(rv, rv);
995 
996     // moz_items_annos.
997     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ITEMS_ANNOS);
998     NS_ENSURE_SUCCESS(rv, rv);
999     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
1000     NS_ENSURE_SUCCESS(rv, rv);
1001 
1002     // Initialize the bookmark roots in the new DB.
1003     rv = CreateBookmarkRoots();
1004     NS_ENSURE_SUCCESS(rv, rv);
1005   }
1006 
1007   // Set the schema version to the current one.
1008   rv = mMainConn->SetSchemaVersion(DATABASE_SCHEMA_VERSION);
1009   NS_ENSURE_SUCCESS(rv, rv);
1010 
1011   rv = transaction.Commit();
1012   NS_ENSURE_SUCCESS(rv, rv);
1013 
1014   ForceWALCheckpoint();
1015 
1016   // ANY FAILURE IN THIS METHOD WILL CAUSE US TO MARK THE DATABASE AS CORRUPT
1017   // AND TRY TO REPLACE IT.
1018   // DO NOT PUT HERE ANYTHING THAT IS NOT RELATED TO INITIALIZATION OR MODIFYING
1019   // THE DISK DATABASE.
1020 
1021   return NS_OK;
1022 }
1023 
1024 nsresult
CreateBookmarkRoots()1025 Database::CreateBookmarkRoots()
1026 {
1027   MOZ_ASSERT(NS_IsMainThread());
1028 
1029   nsCOMPtr<nsIStringBundleService> bundleService =
1030     services::GetStringBundleService();
1031   NS_ENSURE_STATE(bundleService);
1032   nsCOMPtr<nsIStringBundle> bundle;
1033   nsresult rv = bundleService->CreateBundle(PLACES_BUNDLE, getter_AddRefs(bundle));
1034   if (NS_FAILED(rv)) return rv;
1035 
1036   nsXPIDLString rootTitle;
1037   // The first root's title is an empty string.
1038   rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"),
1039                   NS_LITERAL_CSTRING("root________"), rootTitle);
1040   if (NS_FAILED(rv)) return rv;
1041 
1042   // Fetch the internationalized folder name from the string bundle.
1043   rv = bundle->GetStringFromName(u"BookmarksMenuFolderTitle",
1044                                  getter_Copies(rootTitle));
1045   if (NS_FAILED(rv)) return rv;
1046   rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"),
1047                   NS_LITERAL_CSTRING("menu________"), rootTitle);
1048   if (NS_FAILED(rv)) return rv;
1049 
1050   rv = bundle->GetStringFromName(u"BookmarksToolbarFolderTitle",
1051                                  getter_Copies(rootTitle));
1052   if (NS_FAILED(rv)) return rv;
1053   rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"),
1054                   NS_LITERAL_CSTRING("toolbar_____"), rootTitle);
1055   if (NS_FAILED(rv)) return rv;
1056 
1057   rv = bundle->GetStringFromName(u"TagsFolderTitle",
1058                                  getter_Copies(rootTitle));
1059   if (NS_FAILED(rv)) return rv;
1060   rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"),
1061                   NS_LITERAL_CSTRING("tags________"), rootTitle);
1062   if (NS_FAILED(rv)) return rv;
1063 
1064   rv = bundle->GetStringFromName(u"OtherBookmarksFolderTitle",
1065                                  getter_Copies(rootTitle));
1066   if (NS_FAILED(rv)) return rv;
1067   rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"),
1068                   NS_LITERAL_CSTRING("unfiled_____"), rootTitle);
1069   if (NS_FAILED(rv)) return rv;
1070 
1071   int64_t mobileRootId = CreateMobileRoot();
1072   if (mobileRootId <= 0) return NS_ERROR_FAILURE;
1073 
1074 #if DEBUG
1075   nsCOMPtr<mozIStorageStatement> stmt;
1076   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1077     "SELECT count(*), sum(position) FROM moz_bookmarks"
1078   ), getter_AddRefs(stmt));
1079   if (NS_FAILED(rv)) return rv;
1080 
1081   bool hasResult;
1082   rv = stmt->ExecuteStep(&hasResult);
1083   if (NS_FAILED(rv)) return rv;
1084   MOZ_ASSERT(hasResult);
1085   int32_t bookmarkCount = stmt->AsInt32(0);
1086   int32_t positionSum = stmt->AsInt32(1);
1087   MOZ_ASSERT(bookmarkCount == 6 && positionSum == 10);
1088 #endif
1089 
1090   return NS_OK;
1091 }
1092 
1093 nsresult
InitFunctions()1094 Database::InitFunctions()
1095 {
1096   MOZ_ASSERT(NS_IsMainThread());
1097 
1098   nsresult rv = GetUnreversedHostFunction::create(mMainConn);
1099   NS_ENSURE_SUCCESS(rv, rv);
1100   rv = MatchAutoCompleteFunction::create(mMainConn);
1101   NS_ENSURE_SUCCESS(rv, rv);
1102   rv = CalculateFrecencyFunction::create(mMainConn);
1103   NS_ENSURE_SUCCESS(rv, rv);
1104   rv = GenerateGUIDFunction::create(mMainConn);
1105   NS_ENSURE_SUCCESS(rv, rv);
1106   rv = FixupURLFunction::create(mMainConn);
1107   NS_ENSURE_SUCCESS(rv, rv);
1108   rv = FrecencyNotificationFunction::create(mMainConn);
1109   NS_ENSURE_SUCCESS(rv, rv);
1110   rv = StoreLastInsertedIdFunction::create(mMainConn);
1111   NS_ENSURE_SUCCESS(rv, rv);
1112   rv = HashFunction::create(mMainConn);
1113   NS_ENSURE_SUCCESS(rv, rv);
1114 
1115   return NS_OK;
1116 }
1117 
1118 nsresult
InitTempEntities()1119 Database::InitTempEntities()
1120 {
1121   MOZ_ASSERT(NS_IsMainThread());
1122 
1123   nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
1124   NS_ENSURE_SUCCESS(rv, rv);
1125   rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
1126   NS_ENSURE_SUCCESS(rv, rv);
1127 
1128   // Add the triggers that update the moz_hosts table as necessary.
1129   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER);
1130   NS_ENSURE_SUCCESS(rv, rv);
1131   rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTS_TEMP);
1132   NS_ENSURE_SUCCESS(rv, rv);
1133   rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTS_AFTERDELETE_TRIGGER);
1134   NS_ENSURE_SUCCESS(rv, rv);
1135   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER);
1136   NS_ENSURE_SUCCESS(rv, rv);
1137   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER);
1138   NS_ENSURE_SUCCESS(rv, rv);
1139   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_TYPED_TRIGGER);
1140   NS_ENSURE_SUCCESS(rv, rv);
1141 
1142   rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
1143   NS_ENSURE_SUCCESS(rv, rv);
1144   rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
1145   NS_ENSURE_SUCCESS(rv, rv);
1146   rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
1147   NS_ENSURE_SUCCESS(rv, rv);
1148 
1149   rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
1150   NS_ENSURE_SUCCESS(rv, rv);
1151   rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
1152   NS_ENSURE_SUCCESS(rv, rv);
1153   rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
1154   NS_ENSURE_SUCCESS(rv, rv);
1155 
1156   return NS_OK;
1157 }
1158 
1159 nsresult
UpdateBookmarkRootTitles()1160 Database::UpdateBookmarkRootTitles()
1161 {
1162   MOZ_ASSERT(NS_IsMainThread());
1163 
1164   nsCOMPtr<nsIStringBundleService> bundleService =
1165     services::GetStringBundleService();
1166   NS_ENSURE_STATE(bundleService);
1167 
1168   nsCOMPtr<nsIStringBundle> bundle;
1169   nsresult rv = bundleService->CreateBundle(PLACES_BUNDLE, getter_AddRefs(bundle));
1170   if (NS_FAILED(rv)) return rv;
1171 
1172   nsCOMPtr<mozIStorageAsyncStatement> stmt;
1173   rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1174     "UPDATE moz_bookmarks SET title = :new_title WHERE guid = :guid"
1175   ), getter_AddRefs(stmt));
1176   if (NS_FAILED(rv)) return rv;
1177 
1178   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
1179   rv = stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
1180   if (NS_FAILED(rv)) return rv;
1181 
1182   const char *rootGuids[] = { "menu________"
1183                             , "toolbar_____"
1184                             , "tags________"
1185                             , "unfiled_____"
1186                             , "mobile______"
1187                             };
1188   const char *titleStringIDs[] = { "BookmarksMenuFolderTitle"
1189                                  , "BookmarksToolbarFolderTitle"
1190                                  , "TagsFolderTitle"
1191                                  , "OtherBookmarksFolderTitle"
1192                                  , "MobileBookmarksFolderTitle"
1193                                  };
1194 
1195   for (uint32_t i = 0; i < ArrayLength(rootGuids); ++i) {
1196     nsXPIDLString title;
1197     rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(titleStringIDs[i]).get(),
1198                                    getter_Copies(title));
1199     if (NS_FAILED(rv)) return rv;
1200 
1201     nsCOMPtr<mozIStorageBindingParams> params;
1202     rv = paramsArray->NewBindingParams(getter_AddRefs(params));
1203     if (NS_FAILED(rv)) return rv;
1204     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
1205                                       nsDependentCString(rootGuids[i]));
1206     if (NS_FAILED(rv)) return rv;
1207     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("new_title"),
1208                                       NS_ConvertUTF16toUTF8(title));
1209     if (NS_FAILED(rv)) return rv;
1210     rv = paramsArray->AddParams(params);
1211     if (NS_FAILED(rv)) return rv;
1212   }
1213 
1214   rv = stmt->BindParameters(paramsArray);
1215   if (NS_FAILED(rv)) return rv;
1216   nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
1217   rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt));
1218   if (NS_FAILED(rv)) return rv;
1219 
1220   return NS_OK;
1221 }
1222 
1223 nsresult
MigrateV13Up()1224 Database::MigrateV13Up()
1225 {
1226   MOZ_ASSERT(NS_IsMainThread());
1227 
1228   // Dynamic containers are no longer supported.
1229   nsCOMPtr<mozIStorageAsyncStatement> deleteDynContainersStmt;
1230   nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1231       "DELETE FROM moz_bookmarks WHERE type = :item_type"),
1232     getter_AddRefs(deleteDynContainersStmt));
1233   rv = deleteDynContainersStmt->BindInt32ByName(
1234     NS_LITERAL_CSTRING("item_type"),
1235     nsINavBookmarksService::TYPE_DYNAMIC_CONTAINER
1236   );
1237   NS_ENSURE_SUCCESS(rv, rv);
1238   nsCOMPtr<mozIStoragePendingStatement> ps;
1239   rv = deleteDynContainersStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
1240   NS_ENSURE_SUCCESS(rv, rv);
1241 
1242   return NS_OK;
1243 }
1244 
1245 nsresult
MigrateV15Up()1246 Database::MigrateV15Up()
1247 {
1248   MOZ_ASSERT(NS_IsMainThread());
1249 
1250   // Drop moz_bookmarks_beforedelete_v1_trigger, since it's more expensive than
1251   // useful.
1252   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1253     "DROP TRIGGER IF EXISTS moz_bookmarks_beforedelete_v1_trigger"
1254   ));
1255   NS_ENSURE_SUCCESS(rv, rv);
1256 
1257   // Remove any orphan keywords.
1258   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1259     "DELETE FROM moz_keywords "
1260     "WHERE NOT EXISTS ( "
1261       "SELECT id "
1262       "FROM moz_bookmarks "
1263       "WHERE keyword_id = moz_keywords.id "
1264     ")"
1265   ));
1266   NS_ENSURE_SUCCESS(rv, rv);
1267 
1268   return NS_OK;
1269 }
1270 
1271 nsresult
MigrateV17Up()1272 Database::MigrateV17Up()
1273 {
1274   MOZ_ASSERT(NS_IsMainThread());
1275 
1276   bool tableExists = false;
1277 
1278   nsresult rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
1279   NS_ENSURE_SUCCESS(rv, rv);
1280 
1281   if (!tableExists) {
1282     // For anyone who used in-development versions of this autocomplete,
1283     // drop the old tables and its indexes.
1284     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1285       "DROP INDEX IF EXISTS moz_hostnames_frecencyindex"
1286     ));
1287     NS_ENSURE_SUCCESS(rv, rv);
1288     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1289       "DROP TABLE IF EXISTS moz_hostnames"
1290     ));
1291     NS_ENSURE_SUCCESS(rv, rv);
1292 
1293     // Add the moz_hosts table so we can get hostnames for URL autocomplete.
1294     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
1295     NS_ENSURE_SUCCESS(rv, rv);
1296   }
1297 
1298   // Fill the moz_hosts table with all the domains in moz_places.
1299   nsCOMPtr<mozIStorageAsyncStatement> fillHostsStmt;
1300   rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1301     "INSERT OR IGNORE INTO moz_hosts (host, frecency) "
1302         "SELECT fixup_url(get_unreversed_host(h.rev_host)) AS host, "
1303                "(SELECT MAX(frecency) FROM moz_places "
1304                 "WHERE rev_host = h.rev_host "
1305                    "OR rev_host = h.rev_host || 'www.' "
1306                ") AS frecency "
1307         "FROM moz_places h "
1308         "WHERE LENGTH(h.rev_host) > 1 "
1309         "GROUP BY h.rev_host"
1310   ), getter_AddRefs(fillHostsStmt));
1311   NS_ENSURE_SUCCESS(rv, rv);
1312 
1313   nsCOMPtr<mozIStoragePendingStatement> ps;
1314   rv = fillHostsStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
1315   NS_ENSURE_SUCCESS(rv, rv);
1316 
1317   return NS_OK;
1318 }
1319 
1320 nsresult
MigrateV18Up()1321 Database::MigrateV18Up()
1322 {
1323   MOZ_ASSERT(NS_IsMainThread());
1324 
1325   // moz_hosts should distinguish on typed entries.
1326 
1327   // Check if the profile already has a typed column.
1328   nsCOMPtr<mozIStorageStatement> stmt;
1329   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1330     "SELECT typed FROM moz_hosts"
1331   ), getter_AddRefs(stmt));
1332   if (NS_FAILED(rv)) {
1333     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1334       "ALTER TABLE moz_hosts ADD COLUMN typed NOT NULL DEFAULT 0"
1335     ));
1336     NS_ENSURE_SUCCESS(rv, rv);
1337   }
1338 
1339   // With the addition of the typed column the covering index loses its
1340   // advantages.  On the other side querying on host and (optionally) typed
1341   // largely restricts the number of results, making scans decently fast.
1342   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1343     "DROP INDEX IF EXISTS moz_hosts_frecencyhostindex"
1344   ));
1345   NS_ENSURE_SUCCESS(rv, rv);
1346 
1347   // Update typed data.
1348   nsCOMPtr<mozIStorageAsyncStatement> updateTypedStmt;
1349   rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1350     "UPDATE moz_hosts SET typed = 1 WHERE host IN ( "
1351       "SELECT fixup_url(get_unreversed_host(rev_host)) "
1352       "FROM moz_places WHERE typed = 1 "
1353     ") "
1354   ), getter_AddRefs(updateTypedStmt));
1355   NS_ENSURE_SUCCESS(rv, rv);
1356 
1357   nsCOMPtr<mozIStoragePendingStatement> ps;
1358   rv = updateTypedStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
1359   NS_ENSURE_SUCCESS(rv, rv);
1360 
1361   return NS_OK;
1362 }
1363 
1364 nsresult
MigrateV19Up()1365 Database::MigrateV19Up()
1366 {
1367   MOZ_ASSERT(NS_IsMainThread());
1368 
1369   // Livemarks children are no longer bookmarks.
1370 
1371   // Remove all children of folders annotated as livemarks.
1372   nsCOMPtr<mozIStorageStatement> deleteLivemarksChildrenStmt;
1373   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1374     "DELETE FROM moz_bookmarks WHERE parent IN("
1375       "SELECT b.id FROM moz_bookmarks b "
1376       "JOIN moz_items_annos a ON a.item_id = b.id "
1377       "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
1378       "WHERE b.type = :item_type AND n.name = :anno_name "
1379     ")"
1380   ), getter_AddRefs(deleteLivemarksChildrenStmt));
1381   NS_ENSURE_SUCCESS(rv, rv);
1382   rv = deleteLivemarksChildrenStmt->BindUTF8StringByName(
1383     NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(LMANNO_FEEDURI)
1384   );
1385   NS_ENSURE_SUCCESS(rv, rv);
1386   rv = deleteLivemarksChildrenStmt->BindInt32ByName(
1387     NS_LITERAL_CSTRING("item_type"), nsINavBookmarksService::TYPE_FOLDER
1388   );
1389   NS_ENSURE_SUCCESS(rv, rv);
1390   rv = deleteLivemarksChildrenStmt->Execute();
1391   NS_ENSURE_SUCCESS(rv, rv);
1392 
1393   // Clear obsolete livemark prefs.
1394   (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_seconds");
1395   (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_limit_count");
1396   (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_delay_time");
1397 
1398   // Remove the old status annotations.
1399   nsCOMPtr<mozIStorageStatement> deleteLivemarksAnnosStmt;
1400   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1401     "DELETE FROM moz_items_annos WHERE anno_attribute_id IN("
1402       "SELECT id FROM moz_anno_attributes "
1403       "WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) "
1404     ")"
1405   ), getter_AddRefs(deleteLivemarksAnnosStmt));
1406   NS_ENSURE_SUCCESS(rv, rv);
1407   rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1408     NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading")
1409   );
1410   NS_ENSURE_SUCCESS(rv, rv);
1411   rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1412     NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed")
1413   );
1414   NS_ENSURE_SUCCESS(rv, rv);
1415   rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1416     NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration")
1417   );
1418   NS_ENSURE_SUCCESS(rv, rv);
1419   rv = deleteLivemarksAnnosStmt->Execute();
1420   NS_ENSURE_SUCCESS(rv, rv);
1421 
1422   // Remove orphan annotation names.
1423   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1424     "DELETE FROM moz_anno_attributes "
1425       "WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) "
1426   ), getter_AddRefs(deleteLivemarksAnnosStmt));
1427   NS_ENSURE_SUCCESS(rv, rv);
1428   rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1429     NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading")
1430   );
1431   NS_ENSURE_SUCCESS(rv, rv);
1432   rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1433     NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed")
1434   );
1435   NS_ENSURE_SUCCESS(rv, rv);
1436   rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
1437     NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration")
1438   );
1439   NS_ENSURE_SUCCESS(rv, rv);
1440   rv = deleteLivemarksAnnosStmt->Execute();
1441   NS_ENSURE_SUCCESS(rv, rv);
1442 
1443   return NS_OK;
1444 }
1445 
1446 nsresult
MigrateV20Up()1447 Database::MigrateV20Up()
1448 {
1449   MOZ_ASSERT(NS_IsMainThread());
1450 
1451   // Remove obsolete bookmark GUID annotations.
1452   nsCOMPtr<mozIStorageStatement> deleteOldBookmarkGUIDAnnosStmt;
1453   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1454     "DELETE FROM moz_items_annos WHERE anno_attribute_id = ("
1455       "SELECT id FROM moz_anno_attributes "
1456       "WHERE name = :anno_guid"
1457     ")"
1458   ), getter_AddRefs(deleteOldBookmarkGUIDAnnosStmt));
1459   NS_ENSURE_SUCCESS(rv, rv);
1460   rv = deleteOldBookmarkGUIDAnnosStmt->BindUTF8StringByName(
1461     NS_LITERAL_CSTRING("anno_guid"), NS_LITERAL_CSTRING("placesInternal/GUID")
1462   );
1463   NS_ENSURE_SUCCESS(rv, rv);
1464   rv = deleteOldBookmarkGUIDAnnosStmt->Execute();
1465   NS_ENSURE_SUCCESS(rv, rv);
1466 
1467   // Remove the orphan annotation name.
1468   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1469     "DELETE FROM moz_anno_attributes "
1470       "WHERE name = :anno_guid"
1471   ), getter_AddRefs(deleteOldBookmarkGUIDAnnosStmt));
1472   NS_ENSURE_SUCCESS(rv, rv);
1473   rv = deleteOldBookmarkGUIDAnnosStmt->BindUTF8StringByName(
1474     NS_LITERAL_CSTRING("anno_guid"), NS_LITERAL_CSTRING("placesInternal/GUID")
1475   );
1476   NS_ENSURE_SUCCESS(rv, rv);
1477   rv = deleteOldBookmarkGUIDAnnosStmt->Execute();
1478   NS_ENSURE_SUCCESS(rv, rv);
1479 
1480   return NS_OK;
1481 }
1482 
1483 nsresult
MigrateV21Up()1484 Database::MigrateV21Up()
1485 {
1486   MOZ_ASSERT(NS_IsMainThread());
1487 
1488   // Add a prefix column to moz_hosts.
1489   nsCOMPtr<mozIStorageStatement> stmt;
1490   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1491     "SELECT prefix FROM moz_hosts"
1492   ), getter_AddRefs(stmt));
1493   if (NS_FAILED(rv)) {
1494     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1495       "ALTER TABLE moz_hosts ADD COLUMN prefix"
1496     ));
1497     NS_ENSURE_SUCCESS(rv, rv);
1498   }
1499 
1500   return NS_OK;
1501 }
1502 
1503 nsresult
MigrateV22Up()1504 Database::MigrateV22Up()
1505 {
1506   MOZ_ASSERT(NS_IsMainThread());
1507 
1508   // Reset all session IDs to 0 since we don't support them anymore.
1509   // We don't set them to NULL to avoid breaking downgrades.
1510   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1511     "UPDATE moz_historyvisits SET session = 0"
1512   ));
1513   NS_ENSURE_SUCCESS(rv, rv);
1514 
1515   return NS_OK;
1516 }
1517 
1518 
1519 nsresult
MigrateV23Up()1520 Database::MigrateV23Up()
1521 {
1522   MOZ_ASSERT(NS_IsMainThread());
1523 
1524   // Recalculate hosts prefixes.
1525   nsCOMPtr<mozIStorageAsyncStatement> updatePrefixesStmt;
1526   nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1527     "UPDATE moz_hosts SET prefix = ( " HOSTS_PREFIX_PRIORITY_FRAGMENT ") "
1528   ), getter_AddRefs(updatePrefixesStmt));
1529   NS_ENSURE_SUCCESS(rv, rv);
1530 
1531   nsCOMPtr<mozIStoragePendingStatement> ps;
1532   rv = updatePrefixesStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
1533   NS_ENSURE_SUCCESS(rv, rv);
1534 
1535   return NS_OK;
1536 }
1537 
1538 nsresult
MigrateV24Up()1539 Database::MigrateV24Up()
1540 {
1541   MOZ_ASSERT(NS_IsMainThread());
1542 
1543  // Add a foreign_count column to moz_places
1544   nsCOMPtr<mozIStorageStatement> stmt;
1545   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1546     "SELECT foreign_count FROM moz_places"
1547   ), getter_AddRefs(stmt));
1548   if (NS_FAILED(rv)) {
1549     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1550       "ALTER TABLE moz_places ADD COLUMN foreign_count INTEGER DEFAULT 0 NOT NULL"));
1551     NS_ENSURE_SUCCESS(rv, rv);
1552   }
1553 
1554   // Adjust counts for all the rows
1555   nsCOMPtr<mozIStorageStatement> updateStmt;
1556   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1557     "UPDATE moz_places SET foreign_count = "
1558     "(SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id) "
1559   ), getter_AddRefs(updateStmt));
1560   NS_ENSURE_SUCCESS(rv, rv);
1561   mozStorageStatementScoper updateScoper(updateStmt);
1562   rv = updateStmt->Execute();
1563   NS_ENSURE_SUCCESS(rv, rv);
1564 
1565   return NS_OK;
1566 }
1567 
1568 nsresult
MigrateV25Up()1569 Database::MigrateV25Up()
1570 {
1571   MOZ_ASSERT(NS_IsMainThread());
1572 
1573   // Change bookmark roots GUIDs to constant values.
1574 
1575   // If moz_bookmarks_roots doesn't exist anymore, it's because we finally have
1576   // been able to remove it.  In such a case, we already assigned constant GUIDs
1577   // to the roots and we can skip this migration.
1578   {
1579     nsCOMPtr<mozIStorageStatement> stmt;
1580     nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1581       "SELECT root_name FROM moz_bookmarks_roots"
1582     ), getter_AddRefs(stmt));
1583     if (NS_FAILED(rv)) {
1584       return NS_OK;
1585     }
1586   }
1587 
1588   nsCOMPtr<mozIStorageStatement> stmt;
1589   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1590     "UPDATE moz_bookmarks SET guid = :guid "
1591     "WHERE id = (SELECT folder_id FROM moz_bookmarks_roots WHERE root_name = :name) "
1592   ), getter_AddRefs(stmt));
1593   NS_ENSURE_SUCCESS(rv, rv);
1594 
1595   const char *rootNames[] = { "places", "menu", "toolbar", "tags", "unfiled" };
1596   const char *rootGuids[] = { "root________"
1597                             , "menu________"
1598                             , "toolbar_____"
1599                             , "tags________"
1600                             , "unfiled_____"
1601                             };
1602 
1603   for (uint32_t i = 0; i < ArrayLength(rootNames); ++i) {
1604     // Since this is using the synchronous API, we cannot use
1605     // a BindingParamsArray.
1606     mozStorageStatementScoper scoper(stmt);
1607 
1608     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
1609                                       nsDependentCString(rootNames[i]));
1610     NS_ENSURE_SUCCESS(rv, rv);
1611     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
1612                                       nsDependentCString(rootGuids[i]));
1613     NS_ENSURE_SUCCESS(rv, rv);
1614 
1615     rv = stmt->Execute();
1616     NS_ENSURE_SUCCESS(rv, rv);
1617   }
1618 
1619   return NS_OK;
1620 }
1621 
1622 nsresult
MigrateV26Up()1623 Database::MigrateV26Up() {
1624   MOZ_ASSERT(NS_IsMainThread());
1625 
1626   // Round down dateAdded and lastModified values to milliseconds precision.
1627   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1628     "UPDATE moz_bookmarks SET dateAdded = dateAdded - dateAdded % 1000, "
1629     "                         lastModified = lastModified - lastModified % 1000"));
1630   NS_ENSURE_SUCCESS(rv, rv);
1631 
1632   return NS_OK;
1633 }
1634 
1635 nsresult
MigrateV27Up()1636 Database::MigrateV27Up() {
1637   MOZ_ASSERT(NS_IsMainThread());
1638 
1639   // Change keywords store, moving their relation from bookmarks to urls.
1640   nsCOMPtr<mozIStorageStatement> stmt;
1641   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1642     "SELECT place_id FROM moz_keywords"
1643   ), getter_AddRefs(stmt));
1644   if (NS_FAILED(rv)) {
1645     // Even if these 2 columns have a unique constraint, we allow NULL values
1646     // for backwards compatibility. NULL never breaks a unique constraint.
1647     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1648       "ALTER TABLE moz_keywords ADD COLUMN place_id INTEGER"));
1649     NS_ENSURE_SUCCESS(rv, rv);
1650     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1651       "ALTER TABLE moz_keywords ADD COLUMN post_data TEXT"));
1652     NS_ENSURE_SUCCESS(rv, rv);
1653     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
1654     NS_ENSURE_SUCCESS(rv, rv);
1655   }
1656 
1657   // Associate keywords with uris.  A keyword could be associated to multiple
1658   // bookmarks uris, or multiple keywords could be associated to the same uri.
1659   // The new system only allows multiple uris per keyword, provided they have
1660   // a different post_data value.
1661   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1662     "INSERT OR REPLACE INTO moz_keywords (id, keyword, place_id, post_data) "
1663     "SELECT k.id, k.keyword, h.id, MAX(a.content) "
1664     "FROM moz_places h "
1665     "JOIN moz_bookmarks b ON b.fk = h.id "
1666     "JOIN moz_keywords k ON k.id = b.keyword_id "
1667     "LEFT JOIN moz_items_annos a ON a.item_id = b.id "
1668                                "AND a.anno_attribute_id = (SELECT id FROM moz_anno_attributes "
1669                                                           "WHERE name = 'bookmarkProperties/POSTData') "
1670     "WHERE k.place_id ISNULL "
1671     "GROUP BY keyword"));
1672   NS_ENSURE_SUCCESS(rv, rv);
1673 
1674   // Remove any keyword that points to a non-existing place id.
1675   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1676     "DELETE FROM moz_keywords "
1677     "WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = moz_keywords.place_id)"));
1678   NS_ENSURE_SUCCESS(rv, rv);
1679   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1680     "UPDATE moz_bookmarks SET keyword_id = NULL "
1681     "WHERE NOT EXISTS (SELECT 1 FROM moz_keywords WHERE id = moz_bookmarks.keyword_id)"));
1682   NS_ENSURE_SUCCESS(rv, rv);
1683 
1684   // Adjust foreign_count for all the rows.
1685   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1686     "UPDATE moz_places SET foreign_count = "
1687     "(SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id) + "
1688     "(SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id) "
1689   ));
1690   NS_ENSURE_SUCCESS(rv, rv);
1691 
1692   return NS_OK;
1693 }
1694 
1695 nsresult
MigrateV28Up()1696 Database::MigrateV28Up() {
1697   MOZ_ASSERT(NS_IsMainThread());
1698 
1699   // v27 migration was bogus and set some unrelated annotations as post_data for
1700   // keywords having an annotated bookmark.
1701   // The current v27 migration function is fixed, but we still need to handle
1702   // users that hit the bogus version.  Since we can't distinguish, we'll just
1703   // set again all of the post data.
1704   DebugOnly<nsresult> rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1705     "UPDATE moz_keywords "
1706     "SET post_data = ( "
1707       "SELECT content FROM moz_items_annos a "
1708       "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
1709       "JOIN moz_bookmarks b on b.id = a.item_id "
1710       "WHERE n.name = 'bookmarkProperties/POSTData' "
1711       "AND b.keyword_id = moz_keywords.id "
1712       "ORDER BY b.lastModified DESC "
1713       "LIMIT 1 "
1714     ") "
1715     "WHERE EXISTS(SELECT 1 FROM moz_bookmarks WHERE keyword_id = moz_keywords.id) "
1716   ));
1717   // In case the update fails a constraint, we don't want to throw away the
1718   // whole database for just a few keywords.  In rare cases the user might have
1719   // to recreate them.  Though, at this point, there shouldn't be 2 keywords
1720   // pointing to the same url and post data, cause the previous migration step
1721   // removed them.
1722   MOZ_ASSERT(NS_SUCCEEDED(rv));
1723 
1724   return NS_OK;
1725 }
1726 
1727 nsresult
MigrateV30Up()1728 Database::MigrateV30Up() {
1729   MOZ_ASSERT(NS_IsMainThread());
1730 
1731   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1732     "DROP INDEX IF EXISTS moz_favicons_guid_uniqueindex"
1733   ));
1734   NS_ENSURE_SUCCESS(rv, rv);
1735 
1736   return NS_OK;
1737 }
1738 
1739 nsresult
MigrateV31Up()1740 Database::MigrateV31Up() {
1741   MOZ_ASSERT(NS_IsMainThread());
1742 
1743   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1744     "DROP TABLE IF EXISTS moz_bookmarks_roots"
1745   ));
1746   NS_ENSURE_SUCCESS(rv, rv);
1747 
1748   return NS_OK;
1749 }
1750 
1751 nsresult
MigrateV32Up()1752 Database::MigrateV32Up() {
1753   MOZ_ASSERT(NS_IsMainThread());
1754 
1755   // Remove some old and no more used Places preferences that may be confusing
1756   // for the user.
1757   mozilla::Unused << Preferences::ClearUser("places.history.expiration.transient_optimal_database_size");
1758   mozilla::Unused << Preferences::ClearUser("places.last_vacuum");
1759   mozilla::Unused << Preferences::ClearUser("browser.history_expire_sites");
1760   mozilla::Unused << Preferences::ClearUser("browser.history_expire_days.mirror");
1761   mozilla::Unused << Preferences::ClearUser("browser.history_expire_days_min");
1762 
1763   // For performance reasons we want to remove too long urls from history.
1764   // We cannot use the moz_places triggers here, cause they are defined only
1765   // after the schema migration.  Thus we need to collect the hosts that need to
1766   // be updated first.
1767   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1768     "CREATE TEMP TABLE moz_migrate_v32_temp ("
1769       "host TEXT PRIMARY KEY "
1770     ") WITHOUT ROWID "
1771   ));
1772   NS_ENSURE_SUCCESS(rv, rv);
1773   {
1774     nsCOMPtr<mozIStorageStatement> stmt;
1775     rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1776       "INSERT OR IGNORE INTO moz_migrate_v32_temp (host) "
1777         "SELECT fixup_url(get_unreversed_host(rev_host)) "
1778         "FROM moz_places WHERE LENGTH(url) > :maxlen AND foreign_count = 0"
1779     ), getter_AddRefs(stmt));
1780     NS_ENSURE_SUCCESS(rv, rv);
1781     mozStorageStatementScoper scoper(stmt);
1782     rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("maxlen"), MaxUrlLength());
1783     NS_ENSURE_SUCCESS(rv, rv);
1784     rv = stmt->Execute();
1785     NS_ENSURE_SUCCESS(rv, rv);
1786   }
1787   // Now remove the pages with a long url.
1788   {
1789     nsCOMPtr<mozIStorageStatement> stmt;
1790     rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1791       "DELETE FROM moz_places WHERE LENGTH(url) > :maxlen AND foreign_count = 0"
1792     ), getter_AddRefs(stmt));
1793     NS_ENSURE_SUCCESS(rv, rv);
1794     mozStorageStatementScoper scoper(stmt);
1795     rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("maxlen"), MaxUrlLength());
1796     NS_ENSURE_SUCCESS(rv, rv);
1797     rv = stmt->Execute();
1798     NS_ENSURE_SUCCESS(rv, rv);
1799   }
1800 
1801   // Expire orphan visits and update moz_hosts.
1802   // These may be a bit more expensive and are not critical for the DB
1803   // functionality, so we execute them asynchronously.
1804   nsCOMPtr<mozIStorageAsyncStatement> expireOrphansStmt;
1805   rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1806     "DELETE FROM moz_historyvisits "
1807     "WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = place_id)"
1808   ), getter_AddRefs(expireOrphansStmt));
1809   NS_ENSURE_SUCCESS(rv, rv);
1810   nsCOMPtr<mozIStorageAsyncStatement> deleteHostsStmt;
1811   rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1812     "DELETE FROM moz_hosts "
1813     "WHERE host IN (SELECT host FROM moz_migrate_v32_temp) "
1814       "AND NOT EXISTS("
1815         "SELECT 1 FROM moz_places "
1816           "WHERE rev_host = get_unreversed_host(host || '.') || '.' "
1817              "OR rev_host = get_unreversed_host(host || '.') || '.www.' "
1818       "); "
1819   ), getter_AddRefs(deleteHostsStmt));
1820   NS_ENSURE_SUCCESS(rv, rv);
1821   nsCOMPtr<mozIStorageAsyncStatement> updateHostsStmt;
1822   rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1823     "UPDATE moz_hosts "
1824     "SET prefix = (" HOSTS_PREFIX_PRIORITY_FRAGMENT ") "
1825     "WHERE host IN (SELECT host FROM moz_migrate_v32_temp) "
1826   ), getter_AddRefs(updateHostsStmt));
1827   NS_ENSURE_SUCCESS(rv, rv);
1828   nsCOMPtr<mozIStorageAsyncStatement> dropTableStmt;
1829   rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1830     "DROP TABLE IF EXISTS moz_migrate_v32_temp"
1831   ), getter_AddRefs(dropTableStmt));
1832   NS_ENSURE_SUCCESS(rv, rv);
1833 
1834   mozIStorageBaseStatement *stmts[] = {
1835     expireOrphansStmt,
1836     deleteHostsStmt,
1837     updateHostsStmt,
1838     dropTableStmt
1839   };
1840   nsCOMPtr<mozIStoragePendingStatement> ps;
1841   rv = mMainConn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
1842                                getter_AddRefs(ps));
1843   NS_ENSURE_SUCCESS(rv, rv);
1844 
1845   return NS_OK;
1846 }
1847 
1848 nsresult
MigrateV33Up()1849 Database::MigrateV33Up() {
1850   MOZ_ASSERT(NS_IsMainThread());
1851 
1852   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1853     "DROP INDEX IF EXISTS moz_places_url_uniqueindex"
1854   ));
1855   NS_ENSURE_SUCCESS(rv, rv);
1856 
1857   // Add an url_hash column to moz_places.
1858   nsCOMPtr<mozIStorageStatement> stmt;
1859   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1860     "SELECT url_hash FROM moz_places"
1861   ), getter_AddRefs(stmt));
1862   if (NS_FAILED(rv)) {
1863     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1864       "ALTER TABLE moz_places ADD COLUMN url_hash INTEGER DEFAULT 0 NOT NULL"
1865     ));
1866     NS_ENSURE_SUCCESS(rv, rv);
1867   }
1868 
1869   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1870     "UPDATE moz_places SET url_hash = hash(url) WHERE url_hash = 0"
1871   ));
1872   NS_ENSURE_SUCCESS(rv, rv);
1873 
1874   // Create an index on url_hash.
1875   rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
1876   NS_ENSURE_SUCCESS(rv, rv);
1877 
1878   return NS_OK;
1879 }
1880 
1881 nsresult
MigrateV34Up()1882 Database::MigrateV34Up() {
1883   MOZ_ASSERT(NS_IsMainThread());
1884 
1885   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1886     "DELETE FROM moz_keywords WHERE id IN ( "
1887       "SELECT id FROM moz_keywords k "
1888       "WHERE NOT EXISTS (SELECT 1 FROM moz_places h WHERE k.place_id = h.id) "
1889     ")"
1890   ));
1891   NS_ENSURE_SUCCESS(rv, rv);
1892 
1893   return NS_OK;
1894 }
1895 
1896 nsresult
MigrateV35Up()1897 Database::MigrateV35Up() {
1898   MOZ_ASSERT(NS_IsMainThread());
1899 
1900   int64_t mobileRootId = CreateMobileRoot();
1901   if (mobileRootId <= 0)  {
1902     // Either the schema is broken or there isn't any root. The latter can
1903     // happen if a consumer, for example Thunderbird, never used bookmarks.
1904     // If there are no roots, this migration should not run.
1905     nsCOMPtr<mozIStorageStatement> checkRootsStmt;
1906     nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1907       "SELECT id FROM moz_bookmarks WHERE parent = 0"
1908     ), getter_AddRefs(checkRootsStmt));
1909     NS_ENSURE_SUCCESS(rv, rv);
1910     mozStorageStatementScoper scoper(checkRootsStmt);
1911     bool hasResult = false;
1912     rv = checkRootsStmt->ExecuteStep(&hasResult);
1913     if (NS_SUCCEEDED(rv) && !hasResult) {
1914       return NS_OK;
1915     }
1916     return NS_ERROR_FAILURE;
1917   }
1918 
1919   // At this point, we should have no more than two folders with the mobile
1920   // bookmarks anno: the new root, and the old folder if one exists. If, for
1921   // some reason, we have multiple folders with the anno, we append their
1922   // children to the new root.
1923   nsTArray<int64_t> folderIds;
1924   nsresult rv = GetItemsWithAnno(NS_LITERAL_CSTRING(MOBILE_ROOT_ANNO),
1925                                  nsINavBookmarksService::TYPE_FOLDER,
1926                                  folderIds);
1927   if (NS_FAILED(rv)) return rv;
1928 
1929   for (uint32_t i = 0; i < folderIds.Length(); ++i) {
1930     if (folderIds[i] == mobileRootId) {
1931       // Ignore the new mobile root. We'll remove this anno from the root in
1932       // bug 1306445.
1933       continue;
1934     }
1935 
1936     // Append the folder's children to the new root.
1937     nsCOMPtr<mozIStorageStatement> moveStmt;
1938     rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1939       "UPDATE moz_bookmarks "
1940       "SET parent = :root_id, "
1941           "position = position + IFNULL("
1942             "(SELECT MAX(position) + 1 FROM moz_bookmarks "
1943              "WHERE parent = :root_id), 0)"
1944       "WHERE parent = :folder_id"
1945     ), getter_AddRefs(moveStmt));
1946     if (NS_FAILED(rv)) return rv;
1947     mozStorageStatementScoper moveScoper(moveStmt);
1948 
1949     rv = moveStmt->BindInt64ByName(NS_LITERAL_CSTRING("root_id"),
1950                                    mobileRootId);
1951     if (NS_FAILED(rv)) return rv;
1952     rv = moveStmt->BindInt64ByName(NS_LITERAL_CSTRING("folder_id"),
1953                                    folderIds[i]);
1954     if (NS_FAILED(rv)) return rv;
1955 
1956     rv = moveStmt->Execute();
1957     if (NS_FAILED(rv)) return rv;
1958 
1959     // Delete the old folder.
1960     rv = DeleteBookmarkItem(folderIds[i]);
1961     if (NS_FAILED(rv)) return rv;
1962   }
1963 
1964   return NS_OK;
1965 }
1966 
1967 nsresult
GetItemsWithAnno(const nsACString & aAnnoName,int32_t aItemType,nsTArray<int64_t> & aItemIds)1968 Database::GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
1969                            nsTArray<int64_t>& aItemIds)
1970 {
1971   nsCOMPtr<mozIStorageStatement> stmt;
1972   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1973     "SELECT b.id FROM moz_items_annos a "
1974     "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
1975     "JOIN moz_bookmarks b ON b.id = a.item_id "
1976     "WHERE n.name = :anno_name AND "
1977           "b.type = :item_type"
1978   ), getter_AddRefs(stmt));
1979   if (NS_FAILED(rv)) return rv;
1980   mozStorageStatementScoper scoper(stmt);
1981 
1982   rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aAnnoName);
1983   if (NS_FAILED(rv)) return rv;
1984   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_type"), aItemType);
1985   if (NS_FAILED(rv)) return rv;
1986 
1987   bool hasMore = false;
1988   while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
1989     int64_t itemId;
1990     rv = stmt->GetInt64(0, &itemId);
1991     if (NS_FAILED(rv)) return rv;
1992     aItemIds.AppendElement(itemId);
1993   }
1994 
1995   return NS_OK;
1996 }
1997 
1998 nsresult
DeleteBookmarkItem(int32_t aItemId)1999 Database::DeleteBookmarkItem(int32_t aItemId)
2000 {
2001   // Delete the old bookmark.
2002   nsCOMPtr<mozIStorageStatement> deleteStmt;
2003   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2004     "DELETE FROM moz_bookmarks WHERE id = :item_id"
2005   ), getter_AddRefs(deleteStmt));
2006   if (NS_FAILED(rv)) return rv;
2007   mozStorageStatementScoper deleteScoper(deleteStmt);
2008 
2009   rv = deleteStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
2010                                    aItemId);
2011   if (NS_FAILED(rv)) return rv;
2012 
2013   rv = deleteStmt->Execute();
2014   if (NS_FAILED(rv)) return rv;
2015 
2016   // Clean up orphan annotations.
2017   nsCOMPtr<mozIStorageStatement> removeAnnosStmt;
2018   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2019     "DELETE FROM moz_items_annos WHERE item_id = :item_id"
2020   ), getter_AddRefs(removeAnnosStmt));
2021   if (NS_FAILED(rv)) return rv;
2022   mozStorageStatementScoper removeAnnosScoper(removeAnnosStmt);
2023 
2024   rv = removeAnnosStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
2025                                         aItemId);
2026   if (NS_FAILED(rv)) return rv;
2027 
2028   rv = removeAnnosStmt->Execute();
2029   if (NS_FAILED(rv)) return rv;
2030 
2031   return NS_OK;
2032 }
2033 
2034 int64_t
CreateMobileRoot()2035 Database::CreateMobileRoot()
2036 {
2037   MOZ_ASSERT(NS_IsMainThread());
2038 
2039   // Create the mobile root, ignoring conflicts if one already exists (for
2040   // example, if the user downgraded to an earlier release channel).
2041   nsCOMPtr<mozIStorageStatement> createStmt;
2042   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2043     "INSERT OR IGNORE INTO moz_bookmarks "
2044       "(type, title, dateAdded, lastModified, guid, position, parent) "
2045     "SELECT :item_type, :item_title, :timestamp, :timestamp, :guid, "
2046       "(SELECT COUNT(*) FROM moz_bookmarks p WHERE p.parent = b.id), b.id "
2047     "FROM moz_bookmarks b WHERE b.parent = 0"
2048   ), getter_AddRefs(createStmt));
2049   if (NS_FAILED(rv)) return -1;
2050   mozStorageStatementScoper createScoper(createStmt);
2051 
2052   rv = createStmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
2053                                    nsINavBookmarksService::TYPE_FOLDER);
2054   if (NS_FAILED(rv)) return -1;
2055   rv = createStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
2056                                         NS_LITERAL_CSTRING(MOBILE_ROOT_TITLE));
2057   if (NS_FAILED(rv)) return -1;
2058   rv = createStmt->BindInt64ByName(NS_LITERAL_CSTRING("timestamp"),
2059                                    RoundedPRNow());
2060   if (NS_FAILED(rv)) return -1;
2061   rv = createStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
2062                                         NS_LITERAL_CSTRING(MOBILE_ROOT_GUID));
2063   if (NS_FAILED(rv)) return -1;
2064 
2065   rv = createStmt->Execute();
2066   if (NS_FAILED(rv)) return -1;
2067 
2068   // Find the mobile root ID. We can't use the last inserted ID because the
2069   // root might already exist, and we ignore on conflict.
2070   nsCOMPtr<mozIStorageStatement> findIdStmt;
2071   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2072     "SELECT id FROM moz_bookmarks WHERE guid = :guid"
2073   ), getter_AddRefs(findIdStmt));
2074   if (NS_FAILED(rv)) return -1;
2075   mozStorageStatementScoper findIdScoper(findIdStmt);
2076 
2077   rv = findIdStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
2078                                         NS_LITERAL_CSTRING(MOBILE_ROOT_GUID));
2079   if (NS_FAILED(rv)) return -1;
2080 
2081   bool hasResult = false;
2082   rv = findIdStmt->ExecuteStep(&hasResult);
2083   if (NS_FAILED(rv) || !hasResult) return -1;
2084 
2085   int64_t rootId;
2086   rv = findIdStmt->GetInt64(0, &rootId);
2087   if (NS_FAILED(rv)) return -1;
2088 
2089   // Set the mobile bookmarks anno on the new root, so that Sync code on an
2090   // older channel can still find it in case of a downgrade. This can be
2091   // removed in bug 1306445.
2092   nsCOMPtr<mozIStorageStatement> addAnnoNameStmt;
2093   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2094     "INSERT OR IGNORE INTO moz_anno_attributes (name) VALUES (:anno_name)"
2095   ), getter_AddRefs(addAnnoNameStmt));
2096   if (NS_FAILED(rv)) return -1;
2097   mozStorageStatementScoper addAnnoNameScoper(addAnnoNameStmt);
2098 
2099   rv = addAnnoNameStmt->BindUTF8StringByName(
2100     NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(MOBILE_ROOT_ANNO));
2101   if (NS_FAILED(rv)) return -1;
2102   rv = addAnnoNameStmt->Execute();
2103   if (NS_FAILED(rv)) return -1;
2104 
2105   nsCOMPtr<mozIStorageStatement> addAnnoStmt;
2106   rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2107     "INSERT OR IGNORE INTO moz_items_annos "
2108       "(id, item_id, anno_attribute_id, content, flags, "
2109        "expiration, type, dateAdded, lastModified) "
2110     "SELECT "
2111       "(SELECT a.id FROM moz_items_annos a "
2112        "WHERE a.anno_attribute_id = n.id AND "
2113              "a.item_id = :root_id), "
2114       ":root_id, n.id, 1, 0, :expiration, :type, :timestamp, :timestamp "
2115     "FROM moz_anno_attributes n WHERE name = :anno_name"
2116   ), getter_AddRefs(addAnnoStmt));
2117   if (NS_FAILED(rv)) return -1;
2118   mozStorageStatementScoper addAnnoScoper(addAnnoStmt);
2119 
2120   rv = addAnnoStmt->BindInt64ByName(NS_LITERAL_CSTRING("root_id"), rootId);
2121   if (NS_FAILED(rv)) return -1;
2122   rv = addAnnoStmt->BindUTF8StringByName(
2123     NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(MOBILE_ROOT_ANNO));
2124   if (NS_FAILED(rv)) return -1;
2125   rv = addAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("expiration"),
2126                                     nsIAnnotationService::EXPIRE_NEVER);
2127   if (NS_FAILED(rv)) return -1;
2128   rv = addAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("type"),
2129                                     nsIAnnotationService::TYPE_INT32);
2130   if (NS_FAILED(rv)) return -1;
2131   rv = addAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("timestamp"),
2132                                     RoundedPRNow());
2133   if (NS_FAILED(rv)) return -1;
2134 
2135   rv = addAnnoStmt->Execute();
2136   if (NS_FAILED(rv)) return -1;
2137 
2138   return rootId;
2139 }
2140 
2141 void
Shutdown()2142 Database::Shutdown()
2143 {
2144   // As the last step in the shutdown path, finalize the database handle.
2145   MOZ_ASSERT(NS_IsMainThread());
2146   MOZ_ASSERT(!mClosed);
2147 
2148   // Break cycles with the shutdown blockers.
2149   mClientsShutdown = nullptr;
2150   nsCOMPtr<mozIStorageCompletionCallback> connectionShutdown = mConnectionShutdown.forget();
2151 
2152   if (!mMainConn) {
2153     // The connection has never been initialized. Just mark it as closed.
2154     mClosed = true;
2155     (void)connectionShutdown->Complete(NS_OK, nullptr);
2156     return;
2157   }
2158 
2159 #ifdef DEBUG
2160   { // Sanity check for missing guids.
2161     bool haveNullGuids = false;
2162     nsCOMPtr<mozIStorageStatement> stmt;
2163 
2164     nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2165       "SELECT 1 "
2166       "FROM moz_places "
2167       "WHERE guid IS NULL "
2168     ), getter_AddRefs(stmt));
2169     MOZ_ASSERT(NS_SUCCEEDED(rv));
2170     rv = stmt->ExecuteStep(&haveNullGuids);
2171     MOZ_ASSERT(NS_SUCCEEDED(rv));
2172     MOZ_ASSERT(!haveNullGuids, "Found a page without a GUID!");
2173 
2174     rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2175       "SELECT 1 "
2176       "FROM moz_bookmarks "
2177       "WHERE guid IS NULL "
2178     ), getter_AddRefs(stmt));
2179     MOZ_ASSERT(NS_SUCCEEDED(rv));
2180     rv = stmt->ExecuteStep(&haveNullGuids);
2181     MOZ_ASSERT(NS_SUCCEEDED(rv));
2182     MOZ_ASSERT(!haveNullGuids, "Found a bookmark without a GUID!");
2183   }
2184 
2185   { // Sanity check for unrounded dateAdded and lastModified values (bug
2186     // 1107308).
2187     bool hasUnroundedDates = false;
2188     nsCOMPtr<mozIStorageStatement> stmt;
2189 
2190     nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2191         "SELECT 1 "
2192         "FROM moz_bookmarks "
2193         "WHERE dateAdded % 1000 > 0 OR lastModified % 1000 > 0 LIMIT 1"
2194       ), getter_AddRefs(stmt));
2195     MOZ_ASSERT(NS_SUCCEEDED(rv));
2196     rv = stmt->ExecuteStep(&hasUnroundedDates);
2197     MOZ_ASSERT(NS_SUCCEEDED(rv));
2198     MOZ_ASSERT(!hasUnroundedDates, "Found unrounded dates!");
2199   }
2200 
2201   { // Sanity check url_hash
2202     bool hasNullHash = false;
2203     nsCOMPtr<mozIStorageStatement> stmt;
2204     nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2205       "SELECT 1 FROM moz_places WHERE url_hash = 0"
2206     ), getter_AddRefs(stmt));
2207     MOZ_ASSERT(NS_SUCCEEDED(rv));
2208     rv = stmt->ExecuteStep(&hasNullHash);
2209     MOZ_ASSERT(NS_SUCCEEDED(rv));
2210     MOZ_ASSERT(!hasNullHash, "Found a place without a hash!");
2211   }
2212 
2213   { // Sanity check unique urls
2214     bool hasDupeUrls = false;
2215     nsCOMPtr<mozIStorageStatement> stmt;
2216     nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2217       "SELECT 1 FROM moz_places GROUP BY url HAVING count(*) > 1 "
2218     ), getter_AddRefs(stmt));
2219     MOZ_ASSERT(NS_SUCCEEDED(rv));
2220     rv = stmt->ExecuteStep(&hasDupeUrls);
2221     MOZ_ASSERT(NS_SUCCEEDED(rv));
2222     MOZ_ASSERT(!hasDupeUrls, "Found a duplicate url!");
2223   }
2224 #endif
2225 
2226   mMainThreadStatements.FinalizeStatements();
2227   mMainThreadAsyncStatements.FinalizeStatements();
2228 
2229   RefPtr< FinalizeStatementCacheProxy<mozIStorageStatement> > event =
2230     new FinalizeStatementCacheProxy<mozIStorageStatement>(
2231           mAsyncThreadStatements,
2232           NS_ISUPPORTS_CAST(nsIObserver*, this)
2233         );
2234   DispatchToAsyncThread(event);
2235 
2236   mClosed = true;
2237 
2238   (void)mMainConn->AsyncClose(connectionShutdown);
2239 }
2240 
2241 ////////////////////////////////////////////////////////////////////////////////
2242 //// nsIObserver
2243 
2244 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)2245 Database::Observe(nsISupports *aSubject,
2246                   const char *aTopic,
2247                   const char16_t *aData)
2248 {
2249   MOZ_ASSERT(NS_IsMainThread());
2250   if (strcmp(aTopic, TOPIC_PROFILE_CHANGE_TEARDOWN) == 0) {
2251     // Tests simulating shutdown may cause multiple notifications.
2252     if (IsShutdownStarted()) {
2253       return NS_OK;
2254     }
2255 
2256     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
2257     NS_ENSURE_STATE(os);
2258 
2259     // If shutdown happens in the same mainthread loop as init, observers could
2260     // handle the places-init-complete notification after xpcom-shutdown, when
2261     // the connection does not exist anymore.  Removing those observers would
2262     // be less expensive but may cause their RemoveObserver calls to throw.
2263     // Thus notify the topic now, so they stop listening for it.
2264     nsCOMPtr<nsISimpleEnumerator> e;
2265     if (NS_SUCCEEDED(os->EnumerateObservers(TOPIC_PLACES_INIT_COMPLETE,
2266                      getter_AddRefs(e))) && e) {
2267       bool hasMore = false;
2268       while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
2269 	nsCOMPtr<nsISupports> supports;
2270         if (NS_SUCCEEDED(e->GetNext(getter_AddRefs(supports)))) {
2271           nsCOMPtr<nsIObserver> observer = do_QueryInterface(supports);
2272           (void)observer->Observe(observer, TOPIC_PLACES_INIT_COMPLETE, nullptr);
2273         }
2274       }
2275     }
2276 
2277     // Notify all Places users that we are about to shutdown.
2278     (void)os->NotifyObservers(nullptr, TOPIC_PLACES_SHUTDOWN, nullptr);
2279   } else if (strcmp(aTopic, TOPIC_SIMULATE_PLACES_SHUTDOWN) == 0) {
2280     // This notification is (and must be) only used by tests that are trying
2281     // to simulate Places shutdown out of the normal shutdown path.
2282 
2283     // Tests simulating shutdown may cause re-entrance.
2284     if (IsShutdownStarted()) {
2285       return NS_OK;
2286     }
2287 
2288     // We are simulating a shutdown, so invoke the shutdown blockers,
2289     // wait for them, then proceed with connection shutdown.
2290     // Since we are already going through shutdown, but it's not the real one,
2291     // we won't need to block the real one anymore, so we can unblock it.
2292     {
2293       nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileChangeTeardownPhase();
2294       if (shutdownPhase) {
2295         shutdownPhase->RemoveBlocker(mClientsShutdown.get());
2296       }
2297       (void)mClientsShutdown->BlockShutdown(nullptr);
2298     }
2299 
2300     // Spin the events loop until the clients are done.
2301     // Note, this is just for tests, specifically test_clearHistory_shutdown.js
2302     while (mClientsShutdown->State() != PlacesShutdownBlocker::States::RECEIVED_DONE) {
2303       (void)NS_ProcessNextEvent();
2304     }
2305 
2306     {
2307       nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileBeforeChangePhase();
2308       if (shutdownPhase) {
2309         shutdownPhase->RemoveBlocker(mConnectionShutdown.get());
2310       }
2311       (void)mConnectionShutdown->BlockShutdown(nullptr);
2312     }
2313   }
2314   return NS_OK;
2315 }
2316 
2317 uint32_t
MaxUrlLength()2318 Database::MaxUrlLength() {
2319   MOZ_ASSERT(NS_IsMainThread());
2320   if (!mMaxUrlLength) {
2321     mMaxUrlLength = Preferences::GetInt(PREF_HISTORY_MAXURLLEN,
2322                                         PREF_HISTORY_MAXURLLEN_DEFAULT);
2323     if (mMaxUrlLength < 255 || mMaxUrlLength > INT32_MAX) {
2324       mMaxUrlLength = PREF_HISTORY_MAXURLLEN_DEFAULT;
2325     }
2326   }
2327   return mMaxUrlLength;
2328 }
2329 
2330 
2331 
2332 } // namespace places
2333 } // namespace mozilla
2334