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(¤tSchemaVersion);
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