Line data Source code
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 : #include "mozilla/JSONWriter.h"
10 :
11 : #include "Database.h"
12 :
13 : #include "nsIAnnotationService.h"
14 : #include "nsIInterfaceRequestorUtils.h"
15 : #include "nsIFile.h"
16 : #include "nsIWritablePropertyBag2.h"
17 :
18 : #include "nsNavBookmarks.h"
19 : #include "nsNavHistory.h"
20 : #include "nsPlacesTables.h"
21 : #include "nsPlacesIndexes.h"
22 : #include "nsPlacesTriggers.h"
23 : #include "nsPlacesMacros.h"
24 : #include "nsVariant.h"
25 : #include "SQLFunctions.h"
26 : #include "Helpers.h"
27 : #include "nsFaviconService.h"
28 :
29 : #include "nsAppDirectoryServiceDefs.h"
30 : #include "nsDirectoryServiceUtils.h"
31 : #include "prenv.h"
32 : #include "prsystem.h"
33 : #include "nsPrintfCString.h"
34 : #include "mozilla/Preferences.h"
35 : #include "mozilla/Services.h"
36 : #include "mozilla/Unused.h"
37 : #include "prtime.h"
38 :
39 : #include "nsXULAppAPI.h"
40 :
41 : // Time between corrupt database backups.
42 : #define RECENT_BACKUP_TIME_MICROSEC (int64_t)86400 * PR_USEC_PER_SEC // 24H
43 :
44 : // Filename of the database.
45 : #define DATABASE_FILENAME NS_LITERAL_STRING("places.sqlite")
46 : // Filename of the icons database.
47 : #define DATABASE_FAVICONS_FILENAME NS_LITERAL_STRING("favicons.sqlite")
48 :
49 : // Set to the database file name when it was found corrupt by a previous
50 : // maintenance run.
51 : #define PREF_FORCE_DATABASE_REPLACEMENT "places.database.replaceDatabaseOnStartup"
52 :
53 : // Whether on corruption we should try to fix the database by cloning it.
54 : #define PREF_DATABASE_CLONEONCORRUPTION "places.database.cloneOnCorruption"
55 :
56 : // Set to specify the size of the places database growth increments in kibibytes
57 : #define PREF_GROWTH_INCREMENT_KIB "places.database.growthIncrementKiB"
58 :
59 : // Set to disable the default robust storage and use volatile, in-memory
60 : // storage without robust transaction flushing guarantees. This makes
61 : // SQLite use much less I/O at the cost of losing data when things crash.
62 : // The pref is only honored if an environment variable is set. The env
63 : // variable is intentionally named something scary to help prevent someone
64 : // from thinking it is a useful performance optimization they should enable.
65 : #define PREF_DISABLE_DURABILITY "places.database.disableDurability"
66 : #define ENV_ALLOW_CORRUPTION "ALLOW_PLACES_DATABASE_TO_LOSE_DATA_AND_BECOME_CORRUPT"
67 :
68 : // The maximum url length we can store in history.
69 : // We do not add to history URLs longer than this value.
70 : #define PREF_HISTORY_MAXURLLEN "places.history.maxUrlLength"
71 : // This number is mostly a guess based on various facts:
72 : // * IE didn't support urls longer than 2083 chars
73 : // * Sitemaps protocol used to support a maximum of 2048 chars
74 : // * Various SEO guides suggest to not go over 2000 chars
75 : // * Various apps/services are known to have issues over 2000 chars
76 : // * RFC 2616 - HTTP/1.1 suggests being cautious about depending
77 : // on URI lengths above 255 bytes
78 : #define PREF_HISTORY_MAXURLLEN_DEFAULT 2000
79 :
80 : #define PREF_MIGRATE_V48_FRECENCIES "places.database.migrateV48Frecencies"
81 :
82 : // Maximum size for the WAL file.
83 : // For performance reasons this should be as large as possible, so that more
84 : // transactions can fit into it, and the checkpoint cost is paid less often.
85 : // At the same time, since we use synchronous = NORMAL, an fsync happens only
86 : // at checkpoint time, so we don't want the WAL to grow too much and risk to
87 : // lose all the contained transactions on a crash.
88 : #define DATABASE_MAX_WAL_BYTES 2048000
89 :
90 : // Since exceeding the journal limit will cause a truncate, we allow a slightly
91 : // larger limit than DATABASE_MAX_WAL_BYTES to reduce the number of truncates.
92 : // This is the number of bytes the journal can grow over the maximum wal size
93 : // before being truncated.
94 : #define DATABASE_JOURNAL_OVERHEAD_BYTES 2048000
95 :
96 : #define BYTES_PER_KIBIBYTE 1024
97 :
98 : // How much time Sqlite can wait before returning a SQLITE_BUSY error.
99 : #define DATABASE_BUSY_TIMEOUT_MS 100
100 :
101 : // Old Sync GUID annotation.
102 : #define SYNCGUID_ANNO NS_LITERAL_CSTRING("sync/guid")
103 :
104 : // Places string bundle, contains internationalized bookmark root names.
105 : #define PLACES_BUNDLE "chrome://places/locale/places.properties"
106 :
107 : // Livemarks annotations.
108 : #define LMANNO_FEEDURI "livemark/feedURI"
109 : #define LMANNO_SITEURI "livemark/siteURI"
110 :
111 : // This is no longer used & obsolete except for during migration.
112 : // Note: it may still be found in older places databases.
113 : #define MOBILE_ROOT_ANNO "mobile/bookmarksRoot"
114 :
115 : // This annotation is no longer used & is obsolete, but here for migration.
116 : #define LAST_USED_ANNO NS_LITERAL_CSTRING("bookmarkPropertiesDialog/folderLastUsed")
117 : // This is key in the meta table that the LAST_USED_ANNO is migrated to.
118 : #define LAST_USED_FOLDERS_META_KEY NS_LITERAL_CSTRING("places/bookmarks/edit/lastusedfolder")
119 :
120 : // We use a fixed title for the mobile root to avoid marking the database as
121 : // corrupt if we can't look up the localized title in the string bundle. Sync
122 : // sets the title to the localized version when it creates the left pane query.
123 : #define MOBILE_ROOT_TITLE "mobile"
124 :
125 : using namespace mozilla;
126 :
127 : namespace mozilla {
128 : namespace places {
129 :
130 : namespace {
131 :
132 : ////////////////////////////////////////////////////////////////////////////////
133 : //// Helpers
134 :
135 :
136 : /**
137 : * Get the filename for a corrupt database.
138 : */
139 0 : nsString getCorruptFilename(const nsString& aDbFilename) {
140 0 : return aDbFilename + NS_LITERAL_STRING(".corrupt");
141 : }
142 : /**
143 : * Get the filename for a recover database.
144 : */
145 0 : nsString getRecoverFilename(const nsString& aDbFilename) {
146 0 : return aDbFilename + NS_LITERAL_STRING(".recover");
147 : }
148 :
149 : /**
150 : * Checks whether exists a corrupt database file created not longer than
151 : * RECENT_BACKUP_TIME_MICROSEC ago.
152 : */
153 : bool
154 0 : isRecentCorruptFile(const nsCOMPtr<nsIFile>& aCorruptFile)
155 : {
156 0 : MOZ_ASSERT(NS_IsMainThread());
157 0 : bool fileExists = false;
158 0 : if (NS_FAILED(aCorruptFile->Exists(&fileExists)) || !fileExists) {
159 : return false;
160 : }
161 0 : PRTime lastMod = 0;
162 0 : if (NS_FAILED(aCorruptFile->GetLastModifiedTime(&lastMod)) ||
163 0 : lastMod <= 0 ||
164 0 : (PR_Now() - lastMod) > RECENT_BACKUP_TIME_MICROSEC) {
165 : return false;
166 : }
167 0 : return true;
168 : }
169 :
170 : /**
171 : * Sets the connection journal mode to one of the JOURNAL_* types.
172 : *
173 : * @param aDBConn
174 : * The database connection.
175 : * @param aJournalMode
176 : * One of the JOURNAL_* types.
177 : * @returns the current journal mode.
178 : * @note this may return a different journal mode than the required one, since
179 : * setting it may fail.
180 : */
181 : enum JournalMode
182 2 : SetJournalMode(nsCOMPtr<mozIStorageConnection>& aDBConn,
183 : enum JournalMode aJournalMode)
184 : {
185 2 : MOZ_ASSERT(NS_IsMainThread());
186 1 : nsAutoCString journalMode;
187 1 : switch (aJournalMode) {
188 : default:
189 0 : MOZ_FALLTHROUGH_ASSERT("Trying to set an unknown journal mode.");
190 : // Fall through to the default DELETE journal.
191 : case JOURNAL_DELETE:
192 0 : journalMode.AssignLiteral("delete");
193 0 : break;
194 : case JOURNAL_TRUNCATE:
195 0 : journalMode.AssignLiteral("truncate");
196 0 : break;
197 : case JOURNAL_MEMORY:
198 0 : journalMode.AssignLiteral("memory");
199 0 : break;
200 : case JOURNAL_WAL:
201 0 : journalMode.AssignLiteral("wal");
202 0 : break;
203 : }
204 :
205 0 : nsCOMPtr<mozIStorageStatement> statement;
206 0 : nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
207 0 : query.Append(journalMode);
208 0 : aDBConn->CreateStatement(query, getter_AddRefs(statement));
209 2 : NS_ENSURE_TRUE(statement, JOURNAL_DELETE);
210 :
211 0 : bool hasResult = false;
212 4 : if (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && hasResult &&
213 2 : NS_SUCCEEDED(statement->GetUTF8String(0, journalMode))) {
214 0 : if (journalMode.EqualsLiteral("delete")) {
215 : return JOURNAL_DELETE;
216 : }
217 0 : if (journalMode.EqualsLiteral("truncate")) {
218 : return JOURNAL_TRUNCATE;
219 : }
220 1 : if (journalMode.EqualsLiteral("memory")) {
221 : return JOURNAL_MEMORY;
222 : }
223 2 : if (journalMode.EqualsLiteral("wal")) {
224 : return JOURNAL_WAL;
225 : }
226 0 : MOZ_ASSERT(false, "Got an unknown journal mode.");
227 : }
228 :
229 : return JOURNAL_DELETE;
230 : }
231 :
232 : nsresult
233 5 : CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
234 : const nsCString& aRootName, const nsCString& aGuid,
235 : const nsCString& titleString, const int32_t position, int64_t& newId)
236 : {
237 0 : MOZ_ASSERT(NS_IsMainThread());
238 :
239 : // A single creation timestamp for all roots so that the root folder's
240 : // last modification time isn't earlier than its childrens' creation time.
241 : static PRTime timestamp = 0;
242 5 : if (!timestamp)
243 1 : timestamp = RoundedPRNow();
244 :
245 : // Create a new bookmark folder for the root.
246 10 : nsCOMPtr<mozIStorageStatement> stmt;
247 15 : nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
248 : "INSERT INTO moz_bookmarks "
249 : "(type, position, title, dateAdded, lastModified, guid, parent, "
250 : "syncChangeCounter, syncStatus) "
251 : "VALUES (:item_type, :item_position, :item_title,"
252 : ":date_added, :last_modified, :guid, "
253 : "IFNULL((SELECT id FROM moz_bookmarks WHERE parent = 0), 0), "
254 : "1, :sync_status)"
255 0 : ), getter_AddRefs(stmt));
256 0 : if (NS_FAILED(rv)) return rv;
257 :
258 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
259 0 : nsINavBookmarksService::TYPE_FOLDER);
260 0 : if (NS_FAILED(rv)) return rv;
261 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"), position);
262 0 : if (NS_FAILED(rv)) return rv;
263 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
264 0 : titleString);
265 0 : if (NS_FAILED(rv)) return rv;
266 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), timestamp);
267 0 : if (NS_FAILED(rv)) return rv;
268 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), timestamp);
269 0 : if (NS_FAILED(rv)) return rv;
270 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGuid);
271 5 : if (NS_FAILED(rv)) return rv;
272 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("sync_status"),
273 15 : nsINavBookmarksService::SYNC_STATUS_NEW);
274 5 : if (NS_FAILED(rv)) return rv;
275 5 : rv = stmt->Execute();
276 5 : if (NS_FAILED(rv)) return rv;
277 :
278 5 : newId = nsNavBookmarks::sLastInsertedItemId;
279 : return NS_OK;
280 : }
281 :
282 : nsresult
283 1 : SetupDurability(nsCOMPtr<mozIStorageConnection>& aDBConn, int32_t aDBPageSize) {
284 : nsresult rv;
285 1 : if (PR_GetEnv(ENV_ALLOW_CORRUPTION) &&
286 0 : Preferences::GetBool(PREF_DISABLE_DURABILITY, false)) {
287 : // Volatile storage was requested. Use the in-memory journal (no
288 : // filesystem I/O) and don't sync the filesystem after writing.
289 0 : SetJournalMode(aDBConn, JOURNAL_MEMORY);
290 0 : rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
291 0 : "PRAGMA synchronous = OFF"));
292 0 : NS_ENSURE_SUCCESS(rv, rv);
293 : } else {
294 : // Be sure to set journal mode after page_size. WAL would prevent the change
295 : // otherwise.
296 0 : if (JOURNAL_WAL == SetJournalMode(aDBConn, JOURNAL_WAL)) {
297 : // Set the WAL journal size limit.
298 : int32_t checkpointPages =
299 2 : static_cast<int32_t>(DATABASE_MAX_WAL_BYTES / aDBPageSize);
300 4 : nsAutoCString checkpointPragma("PRAGMA wal_autocheckpoint = ");
301 2 : checkpointPragma.AppendInt(checkpointPages);
302 2 : rv = aDBConn->ExecuteSimpleSQL(checkpointPragma);
303 1 : NS_ENSURE_SUCCESS(rv, rv);
304 : }
305 : else {
306 : // Ignore errors, if we fail here the database could be considered corrupt
307 : // and we won't be able to go on, even if it's just matter of a bogus file
308 : // system. The default mode (DELETE) will be fine in such a case.
309 0 : (void)SetJournalMode(aDBConn, JOURNAL_TRUNCATE);
310 :
311 : // Set synchronous to FULL to ensure maximum data integrity, even in
312 : // case of crashes or unclean shutdowns.
313 0 : rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
314 0 : "PRAGMA synchronous = FULL"));
315 0 : NS_ENSURE_SUCCESS(rv, rv);
316 : }
317 : }
318 :
319 : // The journal is usually free to grow for performance reasons, but it never
320 : // shrinks back. Since the space taken may be problematic, limit its size.
321 2 : nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
322 0 : journalSizePragma.AppendInt(DATABASE_MAX_WAL_BYTES + DATABASE_JOURNAL_OVERHEAD_BYTES);
323 0 : (void)aDBConn->ExecuteSimpleSQL(journalSizePragma);
324 :
325 : // Grow places in |growthIncrementKiB| increments to limit fragmentation on disk.
326 : // By default, it's 5 MB.
327 : int32_t growthIncrementKiB =
328 2 : Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 5 * BYTES_PER_KIBIBYTE);
329 2 : if (growthIncrementKiB > 0) {
330 0 : (void)aDBConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE, EmptyCString());
331 : }
332 2 : return NS_OK;
333 : }
334 :
335 : nsresult
336 0 : AttachDatabase(nsCOMPtr<mozIStorageConnection>& aDBConn,
337 : const nsACString& aPath,
338 : const nsACString& aName) {
339 0 : nsCOMPtr<mozIStorageStatement> stmt;
340 0 : nsresult rv = aDBConn->CreateStatement(
341 0 : NS_LITERAL_CSTRING("ATTACH DATABASE :path AS ") + aName,
342 4 : getter_AddRefs(stmt));
343 1 : NS_ENSURE_SUCCESS(rv, rv);
344 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), aPath);
345 0 : NS_ENSURE_SUCCESS(rv, rv);
346 0 : rv = stmt->Execute();
347 1 : NS_ENSURE_SUCCESS(rv, rv);
348 :
349 : // The journal limit must be set apart for each database.
350 1 : nsAutoCString journalSizePragma("PRAGMA favicons.journal_size_limit = ");
351 1 : journalSizePragma.AppendInt(DATABASE_MAX_WAL_BYTES + DATABASE_JOURNAL_OVERHEAD_BYTES);
352 1 : Unused << aDBConn->ExecuteSimpleSQL(journalSizePragma);
353 :
354 1 : return NS_OK;
355 : }
356 :
357 : } // namespace
358 :
359 : ////////////////////////////////////////////////////////////////////////////////
360 : //// Database
361 :
362 69 : PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase)
363 :
364 135 : NS_IMPL_ISUPPORTS(Database
365 : , nsIObserver
366 : , nsISupportsWeakReference
367 : )
368 :
369 1 : Database::Database()
370 : : mMainThreadStatements(mMainConn)
371 : , mMainThreadAsyncStatements(mMainConn)
372 : , mAsyncThreadStatements(mMainConn)
373 : , mDBPageSize(0)
374 : , mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK)
375 : , mClosed(false)
376 : , mShouldConvertIconPayloads(false)
377 : , mShouldVacuumIcons(false)
378 1 : , mClientsShutdown(new ClientsShutdownBlocker())
379 1 : , mConnectionShutdown(new ConnectionShutdownBlocker(this))
380 : , mMaxUrlLength(0)
381 : , mCacheObservers(TOPIC_PLACES_INIT_COMPLETE)
382 : , mRootId(-1)
383 : , mMenuRootId(-1)
384 : , mTagsRootId(-1)
385 : , mUnfiledRootId(-1)
386 : , mToolbarRootId(-1)
387 0 : , mMobileRootId(-1)
388 : {
389 1 : MOZ_ASSERT(!XRE_IsContentProcess(),
390 : "Cannot instantiate Places in the content process");
391 : // Attempting to create two instances of the service?
392 1 : MOZ_ASSERT(!gDatabase);
393 0 : gDatabase = this;
394 0 : }
395 :
396 : already_AddRefed<nsIAsyncShutdownClient>
397 1 : Database::GetProfileChangeTeardownPhase()
398 : {
399 2 : nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc = services::GetAsyncShutdown();
400 0 : MOZ_ASSERT(asyncShutdownSvc);
401 0 : if (NS_WARN_IF(!asyncShutdownSvc)) {
402 : return nullptr;
403 : }
404 :
405 : // Consumers of Places should shutdown before us, at profile-change-teardown.
406 2 : nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
407 1 : DebugOnly<nsresult> rv = asyncShutdownSvc->
408 0 : GetProfileChangeTeardown(getter_AddRefs(shutdownPhase));
409 1 : MOZ_ASSERT(NS_SUCCEEDED(rv));
410 0 : return shutdownPhase.forget();
411 : }
412 :
413 : already_AddRefed<nsIAsyncShutdownClient>
414 1 : Database::GetProfileBeforeChangePhase()
415 : {
416 2 : nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc = services::GetAsyncShutdown();
417 0 : MOZ_ASSERT(asyncShutdownSvc);
418 0 : if (NS_WARN_IF(!asyncShutdownSvc)) {
419 : return nullptr;
420 : }
421 :
422 : // Consumers of Places should shutdown before us, at profile-change-teardown.
423 2 : nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
424 0 : DebugOnly<nsresult> rv = asyncShutdownSvc->
425 4 : GetProfileBeforeChange(getter_AddRefs(shutdownPhase));
426 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
427 1 : return shutdownPhase.forget();
428 : }
429 :
430 0 : Database::~Database()
431 : {
432 0 : }
433 :
434 : bool
435 0 : Database::IsShutdownStarted() const
436 : {
437 64 : if (!mConnectionShutdown) {
438 : // We have already broken the cycle between `this` and `mConnectionShutdown`.
439 : return true;
440 : }
441 0 : return mConnectionShutdown->IsStarted();
442 : }
443 :
444 : already_AddRefed<mozIStorageAsyncStatement>
445 0 : Database::GetAsyncStatement(const nsACString& aQuery)
446 : {
447 1 : if (IsShutdownStarted() || NS_FAILED(EnsureConnection())) {
448 : return nullptr;
449 : }
450 :
451 1 : MOZ_ASSERT(NS_IsMainThread());
452 0 : return mMainThreadAsyncStatements.GetCachedStatement(aQuery);
453 : }
454 :
455 : already_AddRefed<mozIStorageStatement>
456 0 : Database::GetStatement(const nsACString& aQuery)
457 : {
458 30 : if (IsShutdownStarted()) {
459 : return nullptr;
460 : }
461 30 : if (NS_IsMainThread()) {
462 2 : if (NS_FAILED(EnsureConnection())) {
463 : return nullptr;
464 : }
465 2 : return mMainThreadStatements.GetCachedStatement(aQuery);
466 : }
467 : // In the async case, the connection must have been started on the main-thread
468 : // already.
469 56 : MOZ_ASSERT(mMainConn);
470 0 : return mAsyncThreadStatements.GetCachedStatement(aQuery);
471 : }
472 :
473 : already_AddRefed<nsIAsyncShutdownClient>
474 1 : Database::GetClientsShutdown()
475 : {
476 0 : if (mClientsShutdown)
477 1 : return mClientsShutdown->GetClient();
478 : return nullptr;
479 : }
480 :
481 : already_AddRefed<nsIAsyncShutdownClient>
482 3 : Database::GetConnectionShutdown()
483 : {
484 6 : if (mConnectionShutdown)
485 0 : return mConnectionShutdown->GetClient();
486 : return nullptr;
487 : }
488 :
489 : // static
490 : already_AddRefed<Database>
491 34 : Database::GetDatabase()
492 : {
493 34 : if (PlacesShutdownBlocker::IsStarted()) {
494 : return nullptr;
495 : }
496 0 : return GetSingleton();
497 : }
498 :
499 : nsresult
500 1 : Database::Init()
501 : {
502 1 : MOZ_ASSERT(NS_IsMainThread());
503 :
504 : // DO NOT FAIL HERE, otherwise we would never break the cycle between this
505 : // object and the shutdown blockers, causing unexpected leaks.
506 :
507 : {
508 : // First of all Places clients should block profile-change-teardown.
509 2 : nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileChangeTeardownPhase();
510 0 : MOZ_ASSERT(shutdownPhase);
511 0 : if (shutdownPhase) {
512 3 : DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
513 2 : static_cast<nsIAsyncShutdownBlocker*>(mClientsShutdown.get()),
514 1 : NS_LITERAL_STRING(__FILE__),
515 : __LINE__,
516 7 : NS_LITERAL_STRING(""));
517 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
518 : }
519 : }
520 :
521 : {
522 : // Then connection closing should block profile-before-change.
523 2 : nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileBeforeChangePhase();
524 0 : MOZ_ASSERT(shutdownPhase);
525 0 : if (shutdownPhase) {
526 3 : DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
527 2 : static_cast<nsIAsyncShutdownBlocker*>(mConnectionShutdown.get()),
528 1 : NS_LITERAL_STRING(__FILE__),
529 : __LINE__,
530 0 : NS_LITERAL_STRING(""));
531 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
532 : }
533 : }
534 :
535 : // Finally observe profile shutdown notifications.
536 2 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
537 1 : if (os) {
538 0 : (void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
539 : }
540 1 : return NS_OK;
541 : }
542 :
543 : nsresult
544 31 : Database::EnsureConnection()
545 : {
546 : // Run this only once.
547 32 : if (mMainConn ||
548 63 : mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_LOCKED) {
549 : return NS_OK;
550 : }
551 : // Don't try to create a database too late.
552 1 : if (IsShutdownStarted()) {
553 : return NS_ERROR_FAILURE;
554 : }
555 :
556 1 : MOZ_ASSERT(NS_IsMainThread(),
557 : "Database initialization must happen on the main-thread");
558 :
559 : {
560 0 : bool initSucceeded = false;
561 1 : auto notify = MakeScopeExit([&] () {
562 : // If the database connection cannot be opened, it may just be locked
563 : // by third parties. Set a locked state.
564 0 : if (!initSucceeded) {
565 1 : mMainConn = nullptr;
566 0 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_LOCKED;
567 : }
568 : // Notify at the next tick, to avoid re-entrancy problems.
569 2 : NS_DispatchToMainThread(
570 0 : NewRunnableMethod("places::Database::EnsureConnection()",
571 : this, &Database::NotifyConnectionInitalized)
572 1 : );
573 0 : });
574 :
575 : nsCOMPtr<mozIStorageService> storage =
576 0 : do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
577 1 : NS_ENSURE_STATE(storage);
578 :
579 0 : nsCOMPtr<nsIFile> profileDir;
580 0 : nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
581 0 : getter_AddRefs(profileDir));
582 0 : NS_ENSURE_SUCCESS(rv, rv);
583 :
584 0 : nsCOMPtr<nsIFile> databaseFile;
585 0 : rv = profileDir->Clone(getter_AddRefs(databaseFile));
586 1 : NS_ENSURE_SUCCESS(rv, rv);
587 0 : rv = databaseFile->Append(DATABASE_FILENAME);
588 0 : NS_ENSURE_SUCCESS(rv, rv);
589 0 : bool databaseExisted = false;
590 0 : rv = databaseFile->Exists(&databaseExisted);
591 1 : NS_ENSURE_SUCCESS(rv, rv);
592 :
593 2 : nsAutoString corruptDbName;
594 0 : if (NS_SUCCEEDED(Preferences::GetString(PREF_FORCE_DATABASE_REPLACEMENT,
595 1 : corruptDbName)) &&
596 0 : !corruptDbName.IsEmpty()) {
597 : // If this pref is set, maintenance required a database replacement, due to
598 : // integrity corruption.
599 : // Be sure to clear the pref to avoid handling it more than once.
600 0 : (void)Preferences::ClearUser(PREF_FORCE_DATABASE_REPLACEMENT);
601 :
602 : // The database is corrupt, backup and replace it with a new one.
603 0 : nsCOMPtr<nsIFile> fileToBeReplaced;
604 0 : bool fileExists = false;
605 0 : if (NS_SUCCEEDED(profileDir->Clone(getter_AddRefs(fileToBeReplaced))) &&
606 0 : NS_SUCCEEDED(fileToBeReplaced->Exists(&fileExists)) &&
607 : fileExists) {
608 0 : rv = BackupAndReplaceDatabaseFile(storage, corruptDbName, true, false);
609 0 : NS_ENSURE_SUCCESS(rv, rv);
610 : }
611 : }
612 :
613 : // Open the database file. If it does not exist a new one will be created.
614 : // Use an unshared connection, it will consume more memory but avoid shared
615 : // cache contentions across threads.
616 0 : rv = storage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
617 1 : if (NS_SUCCEEDED(rv) && !databaseExisted) {
618 1 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE;
619 : }
620 0 : else if (rv == NS_ERROR_FILE_CORRUPTED) {
621 : // The database is corrupt, backup and replace it with a new one.
622 0 : rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FILENAME, true, true);
623 : // Fallback to catch-all handler.
624 : }
625 0 : NS_ENSURE_SUCCESS(rv, rv);
626 :
627 : // Initialize the database schema. In case of failure the existing schema is
628 : // is corrupt or incoherent, thus the database should be replaced.
629 0 : bool databaseMigrated = false;
630 1 : rv = SetupDatabaseConnection(storage);
631 1 : bool shouldTryToCloneDb = true;
632 0 : if (NS_SUCCEEDED(rv)) {
633 : // Failing to initialize the schema may indicate a corruption.
634 0 : rv = InitSchema(&databaseMigrated);
635 0 : if (NS_FAILED(rv)) {
636 : // Cloning the db on a schema migration may not be a good idea, since we
637 : // may end up cloning the schema problems.
638 0 : shouldTryToCloneDb = false;
639 0 : if (rv == NS_ERROR_STORAGE_BUSY ||
640 0 : rv == NS_ERROR_FILE_IS_LOCKED ||
641 0 : rv == NS_ERROR_FILE_NO_DEVICE_SPACE ||
642 0 : rv == NS_ERROR_OUT_OF_MEMORY) {
643 : // The database is not corrupt, though some migration step failed.
644 : // This may be caused by concurrent use of sync and async Storage APIs
645 : // or by a system issue.
646 : // The best we can do is trying again. If it should still fail, Places
647 : // won't work properly and will be handled as LOCKED.
648 0 : rv = InitSchema(&databaseMigrated);
649 0 : if (NS_FAILED(rv)) {
650 0 : rv = NS_ERROR_FILE_IS_LOCKED;
651 : }
652 : } else {
653 : rv = NS_ERROR_FILE_CORRUPTED;
654 : }
655 : }
656 : }
657 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
658 0 : if (rv != NS_ERROR_FILE_IS_LOCKED) {
659 0 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
660 : }
661 : // Some errors may not indicate a database corruption, for those cases we
662 : // just bail out without throwing away a possibly valid places.sqlite.
663 0 : if (rv == NS_ERROR_FILE_CORRUPTED) {
664 : // Since we don't know which database is corrupt, we must replace both.
665 0 : rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FAVICONS_FILENAME, false, false);
666 0 : NS_ENSURE_SUCCESS(rv, rv);
667 0 : rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FILENAME, shouldTryToCloneDb, true);
668 0 : NS_ENSURE_SUCCESS(rv, rv);
669 : // Try to initialize the new database again.
670 0 : rv = SetupDatabaseConnection(storage);
671 0 : NS_ENSURE_SUCCESS(rv, rv);
672 0 : rv = InitSchema(&databaseMigrated);
673 : }
674 : // Bail out if we couldn't fix the database.
675 0 : NS_ENSURE_SUCCESS(rv, rv);
676 : }
677 :
678 1 : if (databaseMigrated) {
679 0 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
680 : }
681 :
682 : // Initialize here all the items that are not part of the on-disk database,
683 : // like views, temp triggers or temp tables. The database should not be
684 : // considered corrupt if any of the following fails.
685 :
686 0 : rv = InitTempEntities();
687 1 : NS_ENSURE_SUCCESS(rv, rv);
688 :
689 1 : rv = CheckRoots();
690 1 : NS_ENSURE_SUCCESS(rv, rv);
691 :
692 0 : initSucceeded = true;
693 : }
694 0 : return NS_OK;
695 : }
696 :
697 : nsresult
698 0 : Database::NotifyConnectionInitalized()
699 : {
700 1 : MOZ_ASSERT(NS_IsMainThread());
701 : // Notify about Places initialization.
702 0 : nsCOMArray<nsIObserver> entries;
703 0 : mCacheObservers.GetEntries(entries);
704 4 : for (int32_t idx = 0; idx < entries.Count(); ++idx) {
705 0 : MOZ_ALWAYS_SUCCEEDS(entries[idx]->Observe(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
706 : }
707 2 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
708 1 : if (obs) {
709 0 : MOZ_ALWAYS_SUCCEEDS(obs->NotifyObservers(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
710 : }
711 0 : return NS_OK;
712 : }
713 :
714 : nsresult
715 0 : Database::EnsureFaviconsDatabaseAttached(const nsCOMPtr<mozIStorageService>& aStorage)
716 : {
717 0 : MOZ_ASSERT(NS_IsMainThread());
718 :
719 0 : nsCOMPtr<nsIFile> databaseFile;
720 0 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(databaseFile));
721 1 : NS_ENSURE_STATE(databaseFile);
722 0 : nsresult rv = databaseFile->Append(DATABASE_FAVICONS_FILENAME);
723 0 : NS_ENSURE_SUCCESS(rv, rv);
724 0 : nsString iconsPath;
725 0 : rv = databaseFile->GetPath(iconsPath);
726 1 : NS_ENSURE_SUCCESS(rv, rv);
727 :
728 1 : bool fileExists = false;
729 0 : if (NS_SUCCEEDED(databaseFile->Exists(&fileExists)) && fileExists) {
730 0 : return AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
731 0 : NS_LITERAL_CSTRING("favicons"));
732 : }
733 :
734 : // Open the database file, this will also create it.
735 0 : nsCOMPtr<mozIStorageConnection> conn;
736 3 : rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(conn));
737 1 : NS_ENSURE_SUCCESS(rv, rv);
738 :
739 : {
740 : // Ensure we'll close the connection when done.
741 1 : auto cleanup = MakeScopeExit([&] () {
742 : // We cannot use AsyncClose() here, because by the time we try to ATTACH
743 : // this database, its transaction could be still be running and that would
744 : // cause the ATTACH query to fail.
745 0 : MOZ_ALWAYS_TRUE(NS_SUCCEEDED(conn->Close()));
746 3 : });
747 :
748 : // Enable incremental vacuum for this database. Since it will contain even
749 : // large blobs and can be cleared with history, it's worth to have it.
750 : // Note that it will be necessary to manually use PRAGMA incremental_vacuum.
751 0 : rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
752 : "PRAGMA auto_vacuum = INCREMENTAL"
753 0 : ));
754 0 : NS_ENSURE_SUCCESS(rv, rv);
755 :
756 : int32_t defaultPageSize;
757 1 : rv = conn->GetDefaultPageSize(&defaultPageSize);
758 0 : NS_ENSURE_SUCCESS(rv, rv);
759 0 : rv = SetupDurability(conn, defaultPageSize);
760 0 : NS_ENSURE_SUCCESS(rv, rv);
761 :
762 : // We are going to update the database, so everything from now on should be
763 : // in a transaction for performances.
764 0 : mozStorageTransaction transaction(conn, false);
765 0 : rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS);
766 0 : NS_ENSURE_SUCCESS(rv, rv);
767 0 : rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ICONS_ICONURLHASH);
768 0 : NS_ENSURE_SUCCESS(rv, rv);
769 0 : rv = conn->ExecuteSimpleSQL(CREATE_MOZ_PAGES_W_ICONS);
770 0 : NS_ENSURE_SUCCESS(rv, rv);
771 3 : rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PAGES_W_ICONS_ICONURLHASH);
772 1 : NS_ENSURE_SUCCESS(rv, rv);
773 3 : rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS_TO_PAGES);
774 1 : NS_ENSURE_SUCCESS(rv, rv);
775 0 : rv = transaction.Commit();
776 0 : NS_ENSURE_SUCCESS(rv, rv);
777 :
778 : // The scope exit will take care of closing the connection.
779 : }
780 :
781 2 : rv = AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
782 4 : NS_LITERAL_CSTRING("favicons"));
783 1 : NS_ENSURE_SUCCESS(rv, rv);
784 :
785 : return NS_OK;
786 : }
787 :
788 :
789 : nsresult
790 0 : Database::BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
791 : const nsString& aDbFilename,
792 : bool aTryToClone,
793 : bool aReopenConnection)
794 : {
795 0 : MOZ_ASSERT(NS_IsMainThread());
796 :
797 0 : if (aDbFilename.Equals(DATABASE_FILENAME)) {
798 0 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
799 : } else {
800 : // Due to OS file lockings, attached databases can't be cloned properly,
801 : // otherwise trying to reattach them later would fail.
802 : aTryToClone = false;
803 : }
804 :
805 0 : nsCOMPtr<nsIFile> profDir;
806 0 : nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
807 0 : getter_AddRefs(profDir));
808 0 : NS_ENSURE_SUCCESS(rv, rv);
809 0 : nsCOMPtr<nsIFile> databaseFile;
810 0 : rv = profDir->Clone(getter_AddRefs(databaseFile));
811 0 : NS_ENSURE_SUCCESS(rv, rv);
812 0 : rv = databaseFile->Append(aDbFilename);
813 0 : NS_ENSURE_SUCCESS(rv, rv);
814 :
815 : // If we already failed in the last 24 hours avoid to create another corrupt file,
816 : // since doing so, in some situation, could cause us to create a new corrupt
817 : // file at every try to access any Places service. That is bad because it
818 : // would quickly fill the user's disk space without any notice.
819 0 : nsCOMPtr<nsIFile> corruptFile;
820 0 : rv = profDir->Clone(getter_AddRefs(corruptFile));
821 0 : NS_ENSURE_SUCCESS(rv, rv);
822 0 : nsString corruptFilename = getCorruptFilename(aDbFilename);
823 0 : rv = corruptFile->Append(corruptFilename);
824 0 : NS_ENSURE_SUCCESS(rv, rv);
825 0 : if (!isRecentCorruptFile(corruptFile)) {
826 : // Ensure we never create more than one corrupt file.
827 0 : nsCOMPtr<nsIFile> corruptFile;
828 0 : rv = profDir->Clone(getter_AddRefs(corruptFile));
829 0 : NS_ENSURE_SUCCESS(rv, rv);
830 0 : nsString corruptFilename = getCorruptFilename(aDbFilename);
831 0 : rv = corruptFile->Append(corruptFilename);
832 0 : NS_ENSURE_SUCCESS(rv, rv);
833 0 : rv = corruptFile->Remove(false);
834 0 : if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
835 0 : rv != NS_ERROR_FILE_NOT_FOUND) {
836 : return rv;
837 : }
838 :
839 0 : nsCOMPtr<nsIFile> backup;
840 0 : Unused << aStorage->BackupDatabaseFile(databaseFile, corruptFilename,
841 0 : profDir, getter_AddRefs(backup));
842 : }
843 :
844 : // If anything fails from this point on, we have a stale connection or
845 : // database file, and there's not much more we can do.
846 : // The only thing we can try to do is to replace the database on the next
847 : // startup, and report the problem through telemetry.
848 : {
849 : enum eCorruptDBReplaceStage : int8_t {
850 : stage_closing = 0,
851 : stage_removing,
852 : stage_reopening,
853 : stage_replaced,
854 : stage_cloning,
855 : stage_cloned
856 : };
857 0 : eCorruptDBReplaceStage stage = stage_closing;
858 0 : auto guard = MakeScopeExit([&]() {
859 0 : if (stage != stage_replaced) {
860 : // Reaching this point means the database is corrupt and we failed to
861 : // replace it. For this session part of the application related to
862 : // bookmarks and history will misbehave. The frontend may show a
863 : // "locked" notification to the user though.
864 : // Set up a pref to try replacing the database at the next startup.
865 0 : Preferences::SetString(PREF_FORCE_DATABASE_REPLACEMENT, aDbFilename);
866 : }
867 : // Report the corruption through telemetry.
868 0 : Telemetry::Accumulate(Telemetry::PLACES_DATABASE_CORRUPTION_HANDLING_STAGE,
869 0 : static_cast<int8_t>(stage));
870 0 : });
871 :
872 : // Close database connection if open.
873 0 : if (mMainConn) {
874 0 : rv = mMainConn->SpinningSynchronousClose();
875 0 : NS_ENSURE_SUCCESS(rv, rv);
876 0 : mMainConn = nullptr;
877 : }
878 :
879 : // Remove the broken database.
880 0 : stage = stage_removing;
881 0 : rv = databaseFile->Remove(false);
882 0 : if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
883 0 : rv != NS_ERROR_FILE_NOT_FOUND) {
884 : return rv;
885 : }
886 :
887 : // Create a new database file and try to clone tables from the corrupt one.
888 0 : bool cloned = false;
889 0 : if (aTryToClone && Preferences::GetBool(PREF_DATABASE_CLONEONCORRUPTION, true)) {
890 0 : stage = stage_cloning;
891 0 : rv = TryToCloneTablesFromCorruptDatabase(aStorage, databaseFile);
892 0 : if (NS_SUCCEEDED(rv)) {
893 : // If we cloned successfully, we should not consider the database
894 : // corrupt anymore, otherwise we could reimport default bookmarks.
895 0 : mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_OK;
896 0 : cloned = true;
897 : }
898 : }
899 :
900 0 : if (aReopenConnection) {
901 : // Use an unshared connection, it will consume more memory but avoid shared
902 : // cache contentions across threads.
903 0 : stage = stage_reopening;
904 0 : rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
905 0 : NS_ENSURE_SUCCESS(rv, rv);
906 : }
907 :
908 0 : stage = cloned ? stage_cloned : stage_replaced;
909 : }
910 :
911 0 : return NS_OK;
912 : }
913 :
914 : nsresult
915 0 : Database::TryToCloneTablesFromCorruptDatabase(const nsCOMPtr<mozIStorageService>& aStorage,
916 : const nsCOMPtr<nsIFile>& aDatabaseFile)
917 : {
918 0 : MOZ_ASSERT(NS_IsMainThread());
919 :
920 0 : nsAutoString filename;
921 0 : nsresult rv = aDatabaseFile->GetLeafName(filename);
922 :
923 0 : nsCOMPtr<nsIFile> corruptFile;
924 0 : rv = aDatabaseFile->Clone(getter_AddRefs(corruptFile));
925 0 : NS_ENSURE_SUCCESS(rv, rv);
926 0 : rv = corruptFile->SetLeafName(getCorruptFilename(filename));
927 0 : NS_ENSURE_SUCCESS(rv, rv);
928 0 : nsAutoString path;
929 0 : rv = corruptFile->GetPath(path);
930 0 : NS_ENSURE_SUCCESS(rv, rv);
931 :
932 0 : nsCOMPtr<nsIFile> recoverFile;
933 0 : rv = aDatabaseFile->Clone(getter_AddRefs(recoverFile));
934 0 : NS_ENSURE_SUCCESS(rv, rv);
935 0 : rv = recoverFile->SetLeafName(getRecoverFilename(filename));
936 0 : NS_ENSURE_SUCCESS(rv, rv);
937 : // Ensure there's no previous recover file.
938 0 : rv = recoverFile->Remove(false);
939 0 : if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
940 0 : rv != NS_ERROR_FILE_NOT_FOUND) {
941 : return rv;
942 : }
943 :
944 0 : nsCOMPtr<mozIStorageConnection> conn;
945 0 : auto guard = MakeScopeExit([&]() {
946 0 : if (conn) {
947 0 : Unused << conn->Close();
948 : }
949 0 : Unused << recoverFile->Remove(false);
950 0 : });
951 :
952 0 : rv = aStorage->OpenUnsharedDatabase(recoverFile, getter_AddRefs(conn));
953 0 : NS_ENSURE_SUCCESS(rv, rv);
954 0 : rv = AttachDatabase(conn, NS_ConvertUTF16toUTF8(path),
955 0 : NS_LITERAL_CSTRING("corrupt"));
956 0 : NS_ENSURE_SUCCESS(rv, rv);
957 :
958 0 : mozStorageTransaction transaction(conn, false);
959 :
960 : // Copy the schema version.
961 0 : nsCOMPtr<mozIStorageStatement> stmt;
962 0 : (void)conn->CreateStatement(NS_LITERAL_CSTRING("PRAGMA corrupt.user_version"),
963 0 : getter_AddRefs(stmt));
964 0 : NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY);
965 : bool hasResult;
966 0 : rv = stmt->ExecuteStep(&hasResult);
967 0 : NS_ENSURE_SUCCESS(rv, rv);
968 0 : int32_t schemaVersion = stmt->AsInt32(0);
969 0 : rv = conn->SetSchemaVersion(schemaVersion);
970 0 : NS_ENSURE_SUCCESS(rv, rv);
971 :
972 : // Recreate the tables.
973 0 : rv = conn->CreateStatement(NS_LITERAL_CSTRING(
974 : "SELECT name, sql FROM corrupt.sqlite_master "
975 : "WHERE type = 'table' AND name BETWEEN 'moz_' AND 'moza'"
976 0 : ), getter_AddRefs(stmt));
977 0 : NS_ENSURE_SUCCESS(rv, rv);
978 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
979 0 : nsAutoCString name;
980 0 : rv = stmt->GetUTF8String(0, name);
981 0 : NS_ENSURE_SUCCESS(rv, rv);
982 0 : nsAutoCString query;
983 0 : rv = stmt->GetUTF8String(1, query);
984 0 : NS_ENSURE_SUCCESS(rv, rv);
985 0 : rv = conn->ExecuteSimpleSQL(query);
986 0 : NS_ENSURE_SUCCESS(rv, rv);
987 : // Copy the table contents.
988 0 : rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("INSERT INTO main.") +
989 0 : name + NS_LITERAL_CSTRING(" SELECT * FROM corrupt.") + name);
990 0 : if (NS_FAILED(rv)) {
991 0 : rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("INSERT INTO main.") +
992 0 : name + NS_LITERAL_CSTRING(" SELECT * FROM corrupt.") + name +
993 0 : NS_LITERAL_CSTRING(" ORDER BY rowid DESC"));
994 : }
995 0 : NS_ENSURE_SUCCESS(rv, rv);
996 : }
997 :
998 : // Recreate the indices. Doing this after data addition is faster.
999 0 : rv = conn->CreateStatement(NS_LITERAL_CSTRING(
1000 : "SELECT sql FROM corrupt.sqlite_master "
1001 : "WHERE type <> 'table' AND name BETWEEN 'moz_' AND 'moza'"
1002 0 : ), getter_AddRefs(stmt));
1003 0 : NS_ENSURE_SUCCESS(rv, rv);
1004 0 : hasResult = false;
1005 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1006 0 : nsAutoCString query;
1007 0 : rv = stmt->GetUTF8String(0, query);
1008 0 : NS_ENSURE_SUCCESS(rv, rv);
1009 0 : rv = conn->ExecuteSimpleSQL(query);
1010 0 : NS_ENSURE_SUCCESS(rv, rv);
1011 : }
1012 0 : rv = stmt->Finalize();
1013 0 : NS_ENSURE_SUCCESS(rv, rv);
1014 :
1015 0 : rv = transaction.Commit();
1016 0 : NS_ENSURE_SUCCESS(rv, rv);
1017 :
1018 0 : Unused << conn->Close();
1019 0 : conn = nullptr;
1020 0 : rv = recoverFile->RenameTo(nullptr, filename);
1021 0 : NS_ENSURE_SUCCESS(rv, rv);
1022 0 : Unused << corruptFile->Remove(false);
1023 :
1024 0 : guard.release();
1025 0 : return NS_OK;
1026 : }
1027 :
1028 : nsresult
1029 1 : Database::SetupDatabaseConnection(nsCOMPtr<mozIStorageService>& aStorage)
1030 : {
1031 0 : MOZ_ASSERT(NS_IsMainThread());
1032 :
1033 : // Using immediate transactions allows the main connection to retry writes
1034 : // that fail with `SQLITE_BUSY` because a cloned connection has locked the
1035 : // database for writing.
1036 1 : nsresult rv = mMainConn->SetDefaultTransactionType(
1037 1 : mozIStorageConnection::TRANSACTION_IMMEDIATE);
1038 1 : NS_ENSURE_SUCCESS(rv, rv);
1039 :
1040 : // WARNING: any statement executed before setting the journal mode must be
1041 : // finalized, since SQLite doesn't allow changing the journal mode if there
1042 : // is any outstanding statement.
1043 :
1044 : {
1045 : // Get the page size. This may be different than the default if the
1046 : // database file already existed with a different page size.
1047 0 : nsCOMPtr<mozIStorageStatement> statement;
1048 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1049 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
1050 0 : ), getter_AddRefs(statement));
1051 1 : NS_ENSURE_SUCCESS(rv, rv);
1052 1 : bool hasResult = false;
1053 1 : rv = statement->ExecuteStep(&hasResult);
1054 0 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FILE_CORRUPTED);
1055 1 : rv = statement->GetInt32(0, &mDBPageSize);
1056 0 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0, NS_ERROR_FILE_CORRUPTED);
1057 : }
1058 :
1059 : // Ensure that temp tables are held in memory, not on disk.
1060 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1061 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY")
1062 0 : );
1063 0 : NS_ENSURE_SUCCESS(rv, rv);
1064 :
1065 1 : rv = SetupDurability(mMainConn, mDBPageSize);
1066 1 : NS_ENSURE_SUCCESS(rv, rv);
1067 :
1068 1 : nsAutoCString busyTimeoutPragma("PRAGMA busy_timeout = ");
1069 0 : busyTimeoutPragma.AppendInt(DATABASE_BUSY_TIMEOUT_MS);
1070 0 : (void)mMainConn->ExecuteSimpleSQL(busyTimeoutPragma);
1071 :
1072 : // Enable FOREIGN KEY support. This is a strict requirement.
1073 2 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1074 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA foreign_keys = ON")
1075 3 : );
1076 1 : NS_ENSURE_SUCCESS(rv, NS_ERROR_FILE_CORRUPTED);
1077 : #ifdef DEBUG
1078 : {
1079 : // There are a few cases where setting foreign_keys doesn't work:
1080 : // * in the middle of a multi-statement transaction
1081 : // * if the SQLite library in use doesn't support them
1082 : // Since we need foreign_keys, let's at least assert in debug mode.
1083 0 : nsCOMPtr<mozIStorageStatement> stmt;
1084 3 : mMainConn->CreateStatement(NS_LITERAL_CSTRING("PRAGMA foreign_keys"),
1085 5 : getter_AddRefs(stmt));
1086 1 : bool hasResult = false;
1087 1 : if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1088 1 : int32_t fkState = stmt->AsInt32(0);
1089 0 : MOZ_ASSERT(fkState, "Foreign keys should be enabled");
1090 : }
1091 : }
1092 : #endif
1093 :
1094 : // Attach the favicons database to the main connection.
1095 0 : rv = EnsureFaviconsDatabaseAttached(aStorage);
1096 0 : if (NS_FAILED(rv)) {
1097 : // The favicons database may be corrupt. Try to replace and reattach it.
1098 0 : nsCOMPtr<nsIFile> iconsFile;
1099 0 : rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1100 0 : getter_AddRefs(iconsFile));
1101 0 : NS_ENSURE_SUCCESS(rv, rv);
1102 0 : rv = iconsFile->Append(DATABASE_FAVICONS_FILENAME);
1103 0 : NS_ENSURE_SUCCESS(rv, rv);
1104 0 : rv = iconsFile->Remove(false);
1105 0 : if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
1106 0 : rv != NS_ERROR_FILE_NOT_FOUND) {
1107 : return rv;
1108 : }
1109 0 : rv = EnsureFaviconsDatabaseAttached(aStorage);
1110 0 : NS_ENSURE_SUCCESS(rv, rv);
1111 : }
1112 :
1113 : // Create favicons temp entities.
1114 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_ICONS_AFTERINSERT_TRIGGER);
1115 1 : NS_ENSURE_SUCCESS(rv, rv);
1116 :
1117 : // We use our functions during migration, so initialize them now.
1118 1 : rv = InitFunctions();
1119 0 : NS_ENSURE_SUCCESS(rv, rv);
1120 :
1121 : return NS_OK;
1122 : }
1123 :
1124 : nsresult
1125 1 : Database::InitSchema(bool* aDatabaseMigrated)
1126 : {
1127 0 : MOZ_ASSERT(NS_IsMainThread());
1128 0 : *aDatabaseMigrated = false;
1129 :
1130 : // Get the database schema version.
1131 : int32_t currentSchemaVersion;
1132 1 : nsresult rv = mMainConn->GetSchemaVersion(¤tSchemaVersion);
1133 1 : NS_ENSURE_SUCCESS(rv, rv);
1134 1 : bool databaseInitialized = currentSchemaVersion > 0;
1135 :
1136 1 : if (databaseInitialized && currentSchemaVersion == DATABASE_SCHEMA_VERSION) {
1137 : // The database is up to date and ready to go.
1138 : return NS_OK;
1139 : }
1140 :
1141 1 : auto guard = MakeScopeExit([&]() {
1142 : // This runs at the end of the migration, out of the transaction,
1143 : // regardless of its success.
1144 0 : if (mShouldVacuumIcons) {
1145 0 : mShouldVacuumIcons = false;
1146 0 : MOZ_ALWAYS_SUCCEEDS(mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1147 : "VACUUM favicons"
1148 : )));
1149 : }
1150 1 : if (mShouldConvertIconPayloads) {
1151 0 : mShouldConvertIconPayloads = false;
1152 0 : nsFaviconService::ConvertUnsupportedPayloads(mMainConn);
1153 : }
1154 1 : MigrateV48Frecencies();
1155 0 : });
1156 :
1157 : // We are going to update the database, so everything from now on should be in
1158 : // a transaction for performances.
1159 3 : mozStorageTransaction transaction(mMainConn, false);
1160 :
1161 1 : if (databaseInitialized) {
1162 : // Migration How-to:
1163 : //
1164 : // 1. increment PLACES_SCHEMA_VERSION.
1165 : // 2. implement a method that performs upgrade to your version from the
1166 : // previous one.
1167 : //
1168 : // NOTE: The downgrade process is pretty much complicated by the fact old
1169 : // versions cannot know what a new version is going to implement.
1170 : // The only thing we will do for downgrades is setting back the schema
1171 : // version, so that next upgrades will run again the migration step.
1172 :
1173 0 : if (currentSchemaVersion < DATABASE_SCHEMA_VERSION) {
1174 0 : *aDatabaseMigrated = true;
1175 :
1176 0 : if (currentSchemaVersion < 30) {
1177 : // These are versions older than Firefox 45 that are not supported
1178 : // anymore. In this case it's safer to just replace the database.
1179 : // Note that Firefox 45 is the ESR release before the latest one (52),
1180 : // and Firefox 48 is a watershed release, so any version older than 48
1181 : // will first have to go through it.
1182 : return NS_ERROR_FILE_CORRUPTED;
1183 : }
1184 :
1185 : // Firefox 45 ESR uses schema version 30.
1186 :
1187 0 : if (currentSchemaVersion < 31) {
1188 0 : rv = MigrateV31Up();
1189 0 : NS_ENSURE_SUCCESS(rv, rv);
1190 : }
1191 :
1192 : // Firefox 48 uses schema version 31.
1193 :
1194 0 : if (currentSchemaVersion < 32) {
1195 0 : rv = MigrateV32Up();
1196 0 : NS_ENSURE_SUCCESS(rv, rv);
1197 : }
1198 :
1199 : // Firefox 49 uses schema version 32.
1200 :
1201 0 : if (currentSchemaVersion < 33) {
1202 0 : rv = MigrateV33Up();
1203 0 : NS_ENSURE_SUCCESS(rv, rv);
1204 : }
1205 :
1206 : // Firefox 50 uses schema version 33.
1207 :
1208 0 : if (currentSchemaVersion < 34) {
1209 0 : rv = MigrateV34Up();
1210 0 : NS_ENSURE_SUCCESS(rv, rv);
1211 : }
1212 :
1213 : // Firefox 51 uses schema version 34.
1214 :
1215 0 : if (currentSchemaVersion < 35) {
1216 0 : rv = MigrateV35Up();
1217 0 : NS_ENSURE_SUCCESS(rv, rv);
1218 : }
1219 :
1220 : // Firefox 52 uses schema version 35.
1221 :
1222 0 : if (currentSchemaVersion < 36) {
1223 0 : rv = MigrateV36Up();
1224 0 : NS_ENSURE_SUCCESS(rv, rv);
1225 : }
1226 :
1227 0 : if (currentSchemaVersion < 37) {
1228 0 : rv = MigrateV37Up();
1229 0 : NS_ENSURE_SUCCESS(rv, rv);
1230 : }
1231 :
1232 : // Firefox 55 uses schema version 37.
1233 :
1234 0 : if (currentSchemaVersion < 38) {
1235 0 : rv = MigrateV38Up();
1236 0 : NS_ENSURE_SUCCESS(rv, rv);
1237 : }
1238 :
1239 : // Firefox 56 uses schema version 38.
1240 :
1241 0 : if (currentSchemaVersion < 39) {
1242 0 : rv = MigrateV39Up();
1243 0 : NS_ENSURE_SUCCESS(rv, rv);
1244 : }
1245 :
1246 : // Firefox 57 uses schema version 39.
1247 :
1248 0 : if (currentSchemaVersion < 40) {
1249 0 : rv = MigrateV40Up();
1250 0 : NS_ENSURE_SUCCESS(rv, rv);
1251 : }
1252 :
1253 0 : if (currentSchemaVersion < 41) {
1254 0 : rv = MigrateV41Up();
1255 0 : NS_ENSURE_SUCCESS(rv, rv);
1256 : }
1257 :
1258 : // Firefox 58 uses schema version 41.
1259 :
1260 0 : if (currentSchemaVersion < 42) {
1261 0 : rv = MigrateV42Up();
1262 0 : NS_ENSURE_SUCCESS(rv, rv);
1263 : }
1264 :
1265 0 : if (currentSchemaVersion < 43) {
1266 0 : rv = MigrateV43Up();
1267 0 : NS_ENSURE_SUCCESS(rv, rv);
1268 : }
1269 :
1270 : // Firefox 60 uses schema version 43.
1271 :
1272 0 : if (currentSchemaVersion < 44) {
1273 0 : rv = MigrateV44Up();
1274 0 : NS_ENSURE_SUCCESS(rv, rv);
1275 : }
1276 :
1277 0 : if (currentSchemaVersion < 45) {
1278 0 : rv = MigrateV45Up();
1279 0 : NS_ENSURE_SUCCESS(rv, rv);
1280 : }
1281 :
1282 0 : if (currentSchemaVersion < 46) {
1283 0 : rv = MigrateV46Up();
1284 0 : NS_ENSURE_SUCCESS(rv, rv);
1285 : }
1286 :
1287 0 : if (currentSchemaVersion < 47) {
1288 0 : rv = MigrateV47Up();
1289 0 : NS_ENSURE_SUCCESS(rv, rv);
1290 : }
1291 :
1292 : // Firefox 61 uses schema version 47.
1293 :
1294 0 : if (currentSchemaVersion < 48) {
1295 0 : rv = MigrateV48Up();
1296 0 : NS_ENSURE_SUCCESS(rv, rv);
1297 : }
1298 :
1299 0 : if (currentSchemaVersion < 49) {
1300 0 : rv = MigrateV49Up();
1301 0 : NS_ENSURE_SUCCESS(rv, rv);
1302 : }
1303 :
1304 0 : if (currentSchemaVersion < 50) {
1305 0 : rv = MigrateV50Up();
1306 0 : NS_ENSURE_SUCCESS(rv, rv);
1307 : }
1308 :
1309 0 : if (currentSchemaVersion < 51) {
1310 0 : rv = MigrateV51Up();
1311 0 : NS_ENSURE_SUCCESS(rv, rv);
1312 : }
1313 :
1314 : // Firefox 62 uses schema version 51.
1315 :
1316 : // Schema Upgrades must add migration code here.
1317 : // >>> IMPORTANT! <<<
1318 : // NEVER MIX UP SYNC AND ASYNC EXECUTION IN MIGRATORS, YOU MAY LOCK THE
1319 : // CONNECTION AND CAUSE FURTHER STEPS TO FAIL.
1320 : // In case, set a bool and do the async work in the ScopeExit guard just
1321 : // before the migration steps.
1322 : }
1323 : }
1324 : else {
1325 : // This is a new database, so we have to create all the tables and indices.
1326 :
1327 : // moz_origins.
1328 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ORIGINS);
1329 0 : NS_ENSURE_SUCCESS(rv, rv);
1330 :
1331 : // moz_places.
1332 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES);
1333 0 : NS_ENSURE_SUCCESS(rv, rv);
1334 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
1335 0 : NS_ENSURE_SUCCESS(rv, rv);
1336 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_REVHOST);
1337 1 : NS_ENSURE_SUCCESS(rv, rv);
1338 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_VISITCOUNT);
1339 0 : NS_ENSURE_SUCCESS(rv, rv);
1340 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
1341 0 : NS_ENSURE_SUCCESS(rv, rv);
1342 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
1343 0 : NS_ENSURE_SUCCESS(rv, rv);
1344 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
1345 0 : NS_ENSURE_SUCCESS(rv, rv);
1346 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_ORIGIN_ID);
1347 1 : NS_ENSURE_SUCCESS(rv, rv);
1348 :
1349 : // moz_historyvisits.
1350 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS);
1351 1 : NS_ENSURE_SUCCESS(rv, rv);
1352 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
1353 0 : NS_ENSURE_SUCCESS(rv, rv);
1354 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT);
1355 0 : NS_ENSURE_SUCCESS(rv, rv);
1356 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE);
1357 0 : NS_ENSURE_SUCCESS(rv, rv);
1358 :
1359 : // moz_inputhistory.
1360 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
1361 0 : NS_ENSURE_SUCCESS(rv, rv);
1362 :
1363 : // moz_bookmarks.
1364 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS);
1365 0 : NS_ENSURE_SUCCESS(rv, rv);
1366 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_DELETED);
1367 1 : NS_ENSURE_SUCCESS(rv, rv);
1368 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE);
1369 0 : NS_ENSURE_SUCCESS(rv, rv);
1370 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION);
1371 0 : NS_ENSURE_SUCCESS(rv, rv);
1372 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
1373 1 : NS_ENSURE_SUCCESS(rv, rv);
1374 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_DATEADDED);
1375 0 : NS_ENSURE_SUCCESS(rv, rv);
1376 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
1377 1 : NS_ENSURE_SUCCESS(rv, rv);
1378 :
1379 : // moz_keywords.
1380 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
1381 0 : NS_ENSURE_SUCCESS(rv, rv);
1382 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
1383 1 : NS_ENSURE_SUCCESS(rv, rv);
1384 :
1385 : // moz_anno_attributes.
1386 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNO_ATTRIBUTES);
1387 0 : NS_ENSURE_SUCCESS(rv, rv);
1388 :
1389 : // moz_annos.
1390 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNOS);
1391 0 : NS_ENSURE_SUCCESS(rv, rv);
1392 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
1393 1 : NS_ENSURE_SUCCESS(rv, rv);
1394 :
1395 : // moz_items_annos.
1396 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ITEMS_ANNOS);
1397 1 : NS_ENSURE_SUCCESS(rv, rv);
1398 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
1399 0 : NS_ENSURE_SUCCESS(rv, rv);
1400 :
1401 : // moz_meta.
1402 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_META);
1403 1 : NS_ENSURE_SUCCESS(rv, rv);
1404 :
1405 : // The bookmarks roots get initialized in CheckRoots().
1406 : }
1407 :
1408 : // Set the schema version to the current one.
1409 1 : rv = mMainConn->SetSchemaVersion(DATABASE_SCHEMA_VERSION);
1410 1 : NS_ENSURE_SUCCESS(rv, rv);
1411 :
1412 1 : rv = transaction.Commit();
1413 0 : NS_ENSURE_SUCCESS(rv, rv);
1414 :
1415 : // ANY FAILURE IN THIS METHOD WILL CAUSE US TO MARK THE DATABASE AS CORRUPT
1416 : // AND TRY TO REPLACE IT.
1417 : // DO NOT PUT HERE ANYTHING THAT IS NOT RELATED TO INITIALIZATION OR MODIFYING
1418 : // THE DISK DATABASE.
1419 :
1420 : return NS_OK;
1421 : }
1422 :
1423 : nsresult
1424 0 : Database::CheckRoots()
1425 : {
1426 1 : MOZ_ASSERT(NS_IsMainThread());
1427 :
1428 : // If the database has just been created, skip straight to the part where
1429 : // we create the roots.
1430 1 : if (mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_CREATE) {
1431 1 : return EnsureBookmarkRoots(0);
1432 : }
1433 :
1434 0 : nsCOMPtr<mozIStorageStatement> stmt;
1435 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1436 : "SELECT guid, id, position FROM moz_bookmarks WHERE guid IN ( "
1437 : "'" ROOT_GUID "', '" MENU_ROOT_GUID "', '" TOOLBAR_ROOT_GUID "', "
1438 : "'" TAGS_ROOT_GUID "', '" UNFILED_ROOT_GUID "', '" MOBILE_ROOT_GUID "' )"
1439 0 : ), getter_AddRefs(stmt));
1440 0 : NS_ENSURE_SUCCESS(rv, rv);
1441 :
1442 : bool hasResult;
1443 0 : nsAutoCString guid;
1444 0 : int32_t maxPosition = 0;
1445 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1446 0 : rv = stmt->GetUTF8String(0, guid);
1447 0 : NS_ENSURE_SUCCESS(rv, rv);
1448 :
1449 0 : if (guid.EqualsLiteral(ROOT_GUID)) {
1450 0 : mRootId = stmt->AsInt64(1);
1451 : }
1452 : else {
1453 0 : maxPosition = std::max(stmt->AsInt32(2), maxPosition);
1454 :
1455 0 : if (guid.EqualsLiteral(MENU_ROOT_GUID)) {
1456 0 : mMenuRootId = stmt->AsInt64(1);
1457 : }
1458 0 : else if (guid.EqualsLiteral(TOOLBAR_ROOT_GUID)) {
1459 0 : mToolbarRootId = stmt->AsInt64(1);
1460 : }
1461 0 : else if (guid.EqualsLiteral(TAGS_ROOT_GUID)) {
1462 0 : mTagsRootId = stmt->AsInt64(1);
1463 : }
1464 0 : else if (guid.EqualsLiteral(UNFILED_ROOT_GUID)) {
1465 0 : mUnfiledRootId = stmt->AsInt64(1);
1466 : }
1467 0 : else if (guid.EqualsLiteral(MOBILE_ROOT_GUID)) {
1468 0 : mMobileRootId = stmt->AsInt64(1);
1469 : }
1470 : }
1471 : }
1472 :
1473 0 : rv = EnsureBookmarkRoots(maxPosition + 1);
1474 0 : NS_ENSURE_SUCCESS(rv, rv);
1475 :
1476 : return NS_OK;
1477 : }
1478 :
1479 : nsresult
1480 1 : Database::EnsureBookmarkRoots(const int32_t startPosition)
1481 : {
1482 0 : MOZ_ASSERT(NS_IsMainThread());
1483 :
1484 : nsresult rv;
1485 :
1486 : // Note: If the root is missing, we recreate it but we don't fix any
1487 : // remaining built-in folder parent ids. We leave these to a maintenance task,
1488 : // so that we're not needing to do extra checks on startup.
1489 :
1490 1 : if (mRootId < 1) {
1491 : // The first root's title is an empty string.
1492 0 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"),
1493 0 : NS_LITERAL_CSTRING("root________"), EmptyCString(),
1494 0 : 0, mRootId);
1495 :
1496 0 : if (NS_FAILED(rv)) return rv;
1497 : }
1498 :
1499 1 : int32_t position = startPosition;
1500 :
1501 : // For the other roots, the UI doesn't rely on the value in the database, so
1502 : // just set it to something simple to make it easier for humans to read.
1503 0 : if (mMenuRootId < 1) {
1504 0 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"),
1505 0 : NS_LITERAL_CSTRING("menu________"), NS_LITERAL_CSTRING("menu"),
1506 7 : position, mMenuRootId);
1507 1 : if (NS_FAILED(rv)) return rv;
1508 0 : position++;
1509 : }
1510 :
1511 0 : if (mToolbarRootId < 1) {
1512 0 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"),
1513 0 : NS_LITERAL_CSTRING("toolbar_____"), NS_LITERAL_CSTRING("toolbar"),
1514 7 : position, mToolbarRootId);
1515 1 : if (NS_FAILED(rv)) return rv;
1516 0 : position++;
1517 : }
1518 :
1519 0 : if (mTagsRootId < 1) {
1520 0 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"),
1521 0 : NS_LITERAL_CSTRING("tags________"), NS_LITERAL_CSTRING("tags"),
1522 7 : position, mTagsRootId);
1523 1 : if (NS_FAILED(rv)) return rv;
1524 1 : position++;
1525 : }
1526 :
1527 0 : if (mUnfiledRootId < 1) {
1528 1 : if (NS_FAILED(rv)) return rv;
1529 0 : rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"),
1530 0 : NS_LITERAL_CSTRING("unfiled_____"), NS_LITERAL_CSTRING("unfiled"),
1531 7 : position, mUnfiledRootId);
1532 0 : if (NS_FAILED(rv)) return rv;
1533 : position++;
1534 : }
1535 :
1536 0 : if (mMobileRootId < 1) {
1537 1 : int64_t mobileRootId = CreateMobileRoot();
1538 0 : if (mobileRootId <= 0) return NS_ERROR_FAILURE;
1539 : {
1540 0 : nsCOMPtr<mozIStorageStatement> mobileRootSyncStatusStmt;
1541 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1542 : "UPDATE moz_bookmarks SET syncStatus = :sync_status WHERE id = :id"
1543 5 : ), getter_AddRefs(mobileRootSyncStatusStmt));
1544 0 : if (NS_FAILED(rv)) return rv;
1545 :
1546 1 : rv = mobileRootSyncStatusStmt->BindInt32ByName(
1547 0 : NS_LITERAL_CSTRING("sync_status"),
1548 : nsINavBookmarksService::SYNC_STATUS_NEW
1549 3 : );
1550 1 : if (NS_FAILED(rv)) return rv;
1551 2 : rv = mobileRootSyncStatusStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"),
1552 3 : mobileRootId);
1553 1 : if (NS_FAILED(rv)) return rv;
1554 :
1555 0 : rv = mobileRootSyncStatusStmt->Execute();
1556 1 : NS_ENSURE_SUCCESS(rv, rv);
1557 :
1558 1 : mMobileRootId = mobileRootId;
1559 : }
1560 : }
1561 :
1562 : return NS_OK;
1563 : }
1564 :
1565 : nsresult
1566 0 : Database::InitFunctions()
1567 : {
1568 0 : MOZ_ASSERT(NS_IsMainThread());
1569 :
1570 0 : nsresult rv = GetUnreversedHostFunction::create(mMainConn);
1571 0 : NS_ENSURE_SUCCESS(rv, rv);
1572 0 : rv = MatchAutoCompleteFunction::create(mMainConn);
1573 0 : NS_ENSURE_SUCCESS(rv, rv);
1574 0 : rv = CalculateFrecencyFunction::create(mMainConn);
1575 0 : NS_ENSURE_SUCCESS(rv, rv);
1576 0 : rv = GenerateGUIDFunction::create(mMainConn);
1577 0 : NS_ENSURE_SUCCESS(rv, rv);
1578 0 : rv = IsValidGUIDFunction::create(mMainConn);
1579 0 : NS_ENSURE_SUCCESS(rv, rv);
1580 0 : rv = FixupURLFunction::create(mMainConn);
1581 0 : NS_ENSURE_SUCCESS(rv, rv);
1582 0 : rv = FrecencyNotificationFunction::create(mMainConn);
1583 0 : NS_ENSURE_SUCCESS(rv, rv);
1584 0 : rv = StoreLastInsertedIdFunction::create(mMainConn);
1585 0 : NS_ENSURE_SUCCESS(rv, rv);
1586 0 : rv = HashFunction::create(mMainConn);
1587 0 : NS_ENSURE_SUCCESS(rv, rv);
1588 0 : rv = GetQueryParamFunction::create(mMainConn);
1589 1 : NS_ENSURE_SUCCESS(rv, rv);
1590 2 : rv = GetPrefixFunction::create(mMainConn);
1591 1 : NS_ENSURE_SUCCESS(rv, rv);
1592 2 : rv = GetHostAndPortFunction::create(mMainConn);
1593 1 : NS_ENSURE_SUCCESS(rv, rv);
1594 0 : rv = StripPrefixAndUserinfoFunction::create(mMainConn);
1595 1 : NS_ENSURE_SUCCESS(rv, rv);
1596 0 : rv = IsFrecencyDecayingFunction::create(mMainConn);
1597 1 : NS_ENSURE_SUCCESS(rv, rv);
1598 0 : rv = SqrtFunction::create(mMainConn);
1599 0 : NS_ENSURE_SUCCESS(rv, rv);
1600 :
1601 : return NS_OK;
1602 : }
1603 :
1604 : nsresult
1605 0 : Database::InitTempEntities()
1606 : {
1607 0 : MOZ_ASSERT(NS_IsMainThread());
1608 :
1609 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
1610 0 : NS_ENSURE_SUCCESS(rv, rv);
1611 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
1612 0 : NS_ENSURE_SUCCESS(rv, rv);
1613 :
1614 : // Add the triggers that update the moz_origins table as necessary.
1615 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSINSERT_TEMP);
1616 0 : NS_ENSURE_SUCCESS(rv, rv);
1617 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSINSERT_AFTERDELETE_TRIGGER);
1618 1 : NS_ENSURE_SUCCESS(rv, rv);
1619 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER);
1620 0 : NS_ENSURE_SUCCESS(rv, rv);
1621 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSDELETE_TEMP);
1622 0 : NS_ENSURE_SUCCESS(rv, rv);
1623 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSDELETE_AFTERDELETE_TRIGGER);
1624 0 : NS_ENSURE_SUCCESS(rv, rv);
1625 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER);
1626 0 : NS_ENSURE_SUCCESS(rv, rv);
1627 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER);
1628 0 : NS_ENSURE_SUCCESS(rv, rv);
1629 :
1630 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
1631 0 : NS_ENSURE_SUCCESS(rv, rv);
1632 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
1633 1 : NS_ENSURE_SUCCESS(rv, rv);
1634 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
1635 1 : NS_ENSURE_SUCCESS(rv, rv);
1636 :
1637 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
1638 0 : NS_ENSURE_SUCCESS(rv, rv);
1639 3 : rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
1640 0 : NS_ENSURE_SUCCESS(rv, rv);
1641 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
1642 1 : NS_ENSURE_SUCCESS(rv, rv);
1643 :
1644 : return NS_OK;
1645 : }
1646 :
1647 : nsresult
1648 0 : Database::MigrateV31Up() {
1649 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1650 : "DROP TABLE IF EXISTS moz_bookmarks_roots"
1651 0 : ));
1652 0 : NS_ENSURE_SUCCESS(rv, rv);
1653 :
1654 : return NS_OK;
1655 : }
1656 :
1657 : nsresult
1658 0 : Database::MigrateV32Up() {
1659 : // Remove some old and no more used Places preferences that may be confusing
1660 : // for the user.
1661 0 : mozilla::Unused << Preferences::ClearUser("places.history.expiration.transient_optimal_database_size");
1662 0 : mozilla::Unused << Preferences::ClearUser("places.last_vacuum");
1663 0 : mozilla::Unused << Preferences::ClearUser("browser.history_expire_sites");
1664 0 : mozilla::Unused << Preferences::ClearUser("browser.history_expire_days.mirror");
1665 0 : mozilla::Unused << Preferences::ClearUser("browser.history_expire_days_min");
1666 :
1667 : // For performance reasons we want to remove too long urls from history.
1668 : // We cannot use the moz_places triggers here, cause they are defined only
1669 : // after the schema migration. Thus we need to collect the hosts that need to
1670 : // be updated first.
1671 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1672 : "CREATE TEMP TABLE moz_migrate_v32_temp ("
1673 : "host TEXT PRIMARY KEY "
1674 : ") WITHOUT ROWID "
1675 0 : ));
1676 0 : NS_ENSURE_SUCCESS(rv, rv);
1677 : {
1678 0 : nsCOMPtr<mozIStorageStatement> stmt;
1679 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1680 : "INSERT OR IGNORE INTO moz_migrate_v32_temp (host) "
1681 : "SELECT fixup_url(get_unreversed_host(rev_host)) "
1682 : "FROM moz_places WHERE LENGTH(url) > :maxlen AND foreign_count = 0"
1683 0 : ), getter_AddRefs(stmt));
1684 0 : NS_ENSURE_SUCCESS(rv, rv);
1685 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("maxlen"), MaxUrlLength());
1686 0 : NS_ENSURE_SUCCESS(rv, rv);
1687 0 : rv = stmt->Execute();
1688 0 : NS_ENSURE_SUCCESS(rv, rv);
1689 : }
1690 : // Now remove the pages with a long url.
1691 : {
1692 0 : nsCOMPtr<mozIStorageStatement> stmt;
1693 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1694 : "DELETE FROM moz_places WHERE LENGTH(url) > :maxlen AND foreign_count = 0"
1695 0 : ), getter_AddRefs(stmt));
1696 0 : NS_ENSURE_SUCCESS(rv, rv);
1697 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("maxlen"), MaxUrlLength());
1698 0 : NS_ENSURE_SUCCESS(rv, rv);
1699 0 : rv = stmt->Execute();
1700 0 : NS_ENSURE_SUCCESS(rv, rv);
1701 : }
1702 :
1703 : // Expire orphan visits and update moz_hosts.
1704 : // These may be a bit more expensive and are not critical for the DB
1705 : // functionality, so we execute them asynchronously.
1706 0 : nsCOMPtr<mozIStorageAsyncStatement> expireOrphansStmt;
1707 0 : rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1708 : "DELETE FROM moz_historyvisits "
1709 : "WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = place_id)"
1710 0 : ), getter_AddRefs(expireOrphansStmt));
1711 0 : NS_ENSURE_SUCCESS(rv, rv);
1712 0 : nsCOMPtr<mozIStorageAsyncStatement> deleteHostsStmt;
1713 0 : rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1714 : "DELETE FROM moz_hosts "
1715 : "WHERE host IN (SELECT host FROM moz_migrate_v32_temp) "
1716 : "AND NOT EXISTS("
1717 : "SELECT 1 FROM moz_places "
1718 : "WHERE rev_host = get_unreversed_host(host || '.') || '.' "
1719 : "OR rev_host = get_unreversed_host(host || '.') || '.www.' "
1720 : "); "
1721 0 : ), getter_AddRefs(deleteHostsStmt));
1722 0 : NS_ENSURE_SUCCESS(rv, rv);
1723 :
1724 : #define HOST_TO_REVHOST_PREDICATE \
1725 : "rev_host = get_unreversed_host(host || '.') || '.' " \
1726 : "OR rev_host = get_unreversed_host(host || '.') || '.www.'"
1727 : #define HOSTS_PREFIX_PRIORITY_FRAGMENT \
1728 : "SELECT CASE " \
1729 : "WHEN ( " \
1730 : "SELECT round(avg(substr(url,1,12) = 'https://www.')) FROM moz_places h " \
1731 : "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
1732 : ") THEN 'https://www.' " \
1733 : "WHEN ( " \
1734 : "SELECT round(avg(substr(url,1,8) = 'https://')) FROM moz_places h " \
1735 : "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
1736 : ") THEN 'https://' " \
1737 : "WHEN 1 = ( " \
1738 : "SELECT min(substr(url,1,4) = 'ftp:') FROM moz_places h " \
1739 : "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
1740 : ") THEN 'ftp://' " \
1741 : "WHEN ( " \
1742 : "SELECT round(avg(substr(url,1,11) = 'http://www.')) FROM moz_places h " \
1743 : "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
1744 : ") THEN 'www.' " \
1745 : "END "
1746 :
1747 0 : nsCOMPtr<mozIStorageAsyncStatement> updateHostsStmt;
1748 0 : rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1749 : "UPDATE moz_hosts "
1750 : "SET prefix = (" HOSTS_PREFIX_PRIORITY_FRAGMENT ") "
1751 : "WHERE host IN (SELECT host FROM moz_migrate_v32_temp) "
1752 0 : ), getter_AddRefs(updateHostsStmt));
1753 0 : NS_ENSURE_SUCCESS(rv, rv);
1754 :
1755 : #undef HOST_TO_REVHOST_PREDICATE
1756 : #undef HOSTS_PREFIX_PRIORITY_FRAGMENT
1757 :
1758 0 : nsCOMPtr<mozIStorageAsyncStatement> dropTableStmt;
1759 0 : rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1760 : "DROP TABLE IF EXISTS moz_migrate_v32_temp"
1761 0 : ), getter_AddRefs(dropTableStmt));
1762 0 : NS_ENSURE_SUCCESS(rv, rv);
1763 :
1764 : mozIStorageBaseStatement *stmts[] = {
1765 : expireOrphansStmt,
1766 : deleteHostsStmt,
1767 : updateHostsStmt,
1768 : dropTableStmt
1769 0 : };
1770 0 : nsCOMPtr<mozIStoragePendingStatement> ps;
1771 0 : rv = mMainConn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
1772 0 : getter_AddRefs(ps));
1773 0 : NS_ENSURE_SUCCESS(rv, rv);
1774 :
1775 : return NS_OK;
1776 : }
1777 :
1778 : nsresult
1779 0 : Database::MigrateV33Up() {
1780 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1781 : "DROP INDEX IF EXISTS moz_places_url_uniqueindex"
1782 0 : ));
1783 0 : NS_ENSURE_SUCCESS(rv, rv);
1784 :
1785 : // Add an url_hash column to moz_places.
1786 0 : nsCOMPtr<mozIStorageStatement> stmt;
1787 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1788 : "SELECT url_hash FROM moz_places"
1789 0 : ), getter_AddRefs(stmt));
1790 0 : if (NS_FAILED(rv)) {
1791 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1792 : "ALTER TABLE moz_places ADD COLUMN url_hash INTEGER DEFAULT 0 NOT NULL"
1793 0 : ));
1794 0 : NS_ENSURE_SUCCESS(rv, rv);
1795 : }
1796 :
1797 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1798 : "UPDATE moz_places SET url_hash = hash(url) WHERE url_hash = 0"
1799 0 : ));
1800 0 : NS_ENSURE_SUCCESS(rv, rv);
1801 :
1802 : // Create an index on url_hash.
1803 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
1804 0 : NS_ENSURE_SUCCESS(rv, rv);
1805 :
1806 : return NS_OK;
1807 : }
1808 :
1809 : nsresult
1810 0 : Database::MigrateV34Up() {
1811 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1812 : "DELETE FROM moz_keywords WHERE id IN ( "
1813 : "SELECT id FROM moz_keywords k "
1814 : "WHERE NOT EXISTS (SELECT 1 FROM moz_places h WHERE k.place_id = h.id) "
1815 : ")"
1816 0 : ));
1817 0 : NS_ENSURE_SUCCESS(rv, rv);
1818 :
1819 : return NS_OK;
1820 : }
1821 :
1822 : nsresult
1823 0 : Database::MigrateV35Up() {
1824 0 : int64_t mobileRootId = CreateMobileRoot();
1825 0 : if (mobileRootId <= 0) {
1826 : // Either the schema is broken or there isn't any root. The latter can
1827 : // happen if a consumer, for example Thunderbird, never used bookmarks.
1828 : // If there are no roots, this migration should not run.
1829 0 : nsCOMPtr<mozIStorageStatement> checkRootsStmt;
1830 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1831 : "SELECT id FROM moz_bookmarks WHERE parent = 0"
1832 0 : ), getter_AddRefs(checkRootsStmt));
1833 0 : NS_ENSURE_SUCCESS(rv, rv);
1834 0 : bool hasResult = false;
1835 0 : rv = checkRootsStmt->ExecuteStep(&hasResult);
1836 0 : if (NS_SUCCEEDED(rv) && !hasResult) {
1837 : return NS_OK;
1838 : }
1839 0 : return NS_ERROR_FAILURE;
1840 : }
1841 :
1842 : // At this point, we should have no more than two folders with the mobile
1843 : // bookmarks anno: the new root, and the old folder if one exists. If, for
1844 : // some reason, we have multiple folders with the anno, we append their
1845 : // children to the new root.
1846 0 : nsTArray<int64_t> folderIds;
1847 0 : nsresult rv = GetItemsWithAnno(NS_LITERAL_CSTRING(MOBILE_ROOT_ANNO),
1848 : nsINavBookmarksService::TYPE_FOLDER,
1849 0 : folderIds);
1850 0 : if (NS_FAILED(rv)) return rv;
1851 :
1852 0 : for (uint32_t i = 0; i < folderIds.Length(); ++i) {
1853 0 : if (folderIds[i] == mobileRootId) {
1854 : // Ignore the new mobile root. We'll remove this anno from the root in
1855 : // bug 1306445.
1856 0 : continue;
1857 : }
1858 :
1859 : // Append the folder's children to the new root.
1860 0 : nsCOMPtr<mozIStorageStatement> moveStmt;
1861 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1862 : "UPDATE moz_bookmarks "
1863 : "SET parent = :root_id, "
1864 : "position = position + IFNULL("
1865 : "(SELECT MAX(position) + 1 FROM moz_bookmarks "
1866 : "WHERE parent = :root_id), 0)"
1867 : "WHERE parent = :folder_id"
1868 0 : ), getter_AddRefs(moveStmt));
1869 0 : if (NS_FAILED(rv)) return rv;
1870 :
1871 0 : rv = moveStmt->BindInt64ByName(NS_LITERAL_CSTRING("root_id"),
1872 0 : mobileRootId);
1873 0 : if (NS_FAILED(rv)) return rv;
1874 0 : rv = moveStmt->BindInt64ByName(NS_LITERAL_CSTRING("folder_id"),
1875 0 : folderIds[i]);
1876 0 : if (NS_FAILED(rv)) return rv;
1877 :
1878 0 : rv = moveStmt->Execute();
1879 0 : if (NS_FAILED(rv)) return rv;
1880 :
1881 : // Delete the old folder.
1882 0 : rv = DeleteBookmarkItem(folderIds[i]);
1883 0 : if (NS_FAILED(rv)) return rv;
1884 : }
1885 :
1886 : return NS_OK;
1887 : }
1888 :
1889 : nsresult
1890 0 : Database::MigrateV36Up() {
1891 : // Add sync status and change counter tracking columns for bookmarks.
1892 0 : nsCOMPtr<mozIStorageStatement> syncStatusStmt;
1893 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1894 : "SELECT syncStatus FROM moz_bookmarks"
1895 0 : ), getter_AddRefs(syncStatusStmt));
1896 0 : if (NS_FAILED(rv)) {
1897 : // We default to SYNC_STATUS_UNKNOWN = 0 for existing bookmarks, matching
1898 : // the bookmark restore behavior. If Sync is set up, we'll update the status
1899 : // to SYNC_STATUS_NORMAL = 2 before the first post-migration sync.
1900 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1901 : "ALTER TABLE moz_bookmarks "
1902 : "ADD COLUMN syncStatus INTEGER DEFAULT 0 NOT NULL"
1903 0 : ));
1904 0 : NS_ENSURE_SUCCESS(rv, rv);
1905 : }
1906 :
1907 0 : nsCOMPtr<mozIStorageStatement> syncChangeCounterStmt;
1908 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1909 : "SELECT syncChangeCounter FROM moz_bookmarks"
1910 0 : ), getter_AddRefs(syncChangeCounterStmt));
1911 0 : if (NS_FAILED(rv)) {
1912 : // The change counter starts at 1 for all local bookmarks. It's incremented
1913 : // for each modification that should trigger a sync, and decremented after
1914 : // the modified bookmark is uploaded to the server.
1915 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1916 : "ALTER TABLE moz_bookmarks "
1917 0 : "ADD COLUMN syncChangeCounter INTEGER DEFAULT 1 NOT NULL"));
1918 0 : NS_ENSURE_SUCCESS(rv, rv);
1919 : }
1920 :
1921 0 : nsCOMPtr<mozIStorageStatement> tombstoneTableStmt;
1922 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1923 : "SELECT 1 FROM moz_bookmarks_deleted"
1924 0 : ), getter_AddRefs(tombstoneTableStmt));
1925 0 : if (NS_FAILED(rv)) {
1926 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_DELETED);
1927 0 : NS_ENSURE_SUCCESS(rv, rv);
1928 : }
1929 :
1930 : return NS_OK;
1931 : }
1932 :
1933 : nsresult
1934 0 : Database::MigrateV37Up() {
1935 : // Move favicons to the new database.
1936 : // For now we retain the old moz_favicons table, but we empty it.
1937 : // This allows for a "safer" downgrade, even if icons will be lost in the
1938 : // process. In a couple versions we shall drop moz_favicons completely.
1939 :
1940 : // First, check if the old favicons table still exists.
1941 0 : nsCOMPtr<mozIStorageStatement> stmt;
1942 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
1943 : "SELECT url FROM moz_favicons"
1944 0 : ), getter_AddRefs(stmt));
1945 0 : if (NS_FAILED(rv)) {
1946 : // The table has already been removed, nothing to do.
1947 : return NS_OK;
1948 : }
1949 :
1950 : // The new table accepts only png or svg payloads, so we set a valid width
1951 : // only for them, the mime-type for the others. Later we will asynchronously
1952 : // try to convert the unsupported payloads, or remove them.
1953 :
1954 : // Add pages.
1955 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1956 : "INSERT INTO moz_pages_w_icons (page_url, page_url_hash) "
1957 : "SELECT h.url, hash(h.url) "
1958 : "FROM moz_places h "
1959 : "JOIN moz_favicons f ON f.id = h.favicon_id"
1960 0 : ));
1961 0 : NS_ENSURE_SUCCESS(rv, rv);
1962 : // Set icons as expired, so we will replace them with proper versions at the
1963 : // first load.
1964 : // Note: we use a peculiarity of Sqlite here, where the column affinity
1965 : // is not enforced, thanks to that we can store a string in an integer column.
1966 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1967 : "INSERT INTO moz_icons (icon_url, fixed_icon_url_hash, width, data) "
1968 : "SELECT url, hash(fixup_url(url)), "
1969 : "(CASE WHEN mime_type = 'image/png' THEN 16 "
1970 : "WHEN mime_type = 'image/svg+xml' THEN 65535 "
1971 : "ELSE mime_type END), "
1972 : "data FROM moz_favicons "
1973 : "WHERE LENGTH(data) > 0 "
1974 0 : ));
1975 0 : NS_ENSURE_SUCCESS(rv, rv);
1976 : // Create relations.
1977 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1978 : "INSERT OR IGNORE INTO moz_icons_to_pages (page_id, icon_id) "
1979 : "SELECT (SELECT id FROM moz_pages_w_icons "
1980 : "WHERE page_url_hash = h.url_hash "
1981 : "AND page_url = h.url), "
1982 : "(SELECT id FROM moz_icons "
1983 : "WHERE fixed_icon_url_hash = hash(fixup_url(f.url)) "
1984 : "AND icon_url = f.url) "
1985 : "FROM moz_favicons f "
1986 : "JOIN moz_places h on f.id = h.favicon_id"
1987 0 : ));
1988 0 : NS_ENSURE_SUCCESS(rv, rv);
1989 : // Remove old favicons and relations.
1990 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1991 : "DELETE FROM moz_favicons"
1992 0 : ));
1993 0 : NS_ENSURE_SUCCESS(rv, rv);
1994 :
1995 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1996 : "UPDATE moz_places SET favicon_id = NULL"
1997 0 : ));
1998 0 : NS_ENSURE_SUCCESS(rv, rv);
1999 :
2000 : // The async favicons conversion will happen at the end of the normal schema
2001 : // migration.
2002 0 : mShouldConvertIconPayloads = true;
2003 :
2004 0 : return NS_OK;
2005 : }
2006 :
2007 : nsresult
2008 0 : Database::MigrateV38Up()
2009 : {
2010 0 : nsCOMPtr<mozIStorageStatement> stmt;
2011 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2012 : "SELECT description, preview_image_url FROM moz_places"
2013 0 : ), getter_AddRefs(stmt));
2014 0 : if (NS_FAILED(rv)) {
2015 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2016 : "ALTER TABLE moz_places ADD COLUMN description TEXT"
2017 0 : ));
2018 0 : NS_ENSURE_SUCCESS(rv, rv);
2019 :
2020 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2021 : "ALTER TABLE moz_places ADD COLUMN preview_image_url TEXT"
2022 0 : ));
2023 0 : NS_ENSURE_SUCCESS(rv, rv);
2024 : }
2025 :
2026 : return NS_OK;
2027 : }
2028 :
2029 : nsresult
2030 0 : Database::MigrateV39Up() {
2031 : // Create an index on dateAdded.
2032 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_DATEADDED);
2033 0 : NS_ENSURE_SUCCESS(rv, rv);
2034 :
2035 : return NS_OK;
2036 : }
2037 :
2038 : nsresult
2039 0 : Database::MigrateV40Up() {
2040 : // We are changing the hashing function to crop the hashed text to a maximum
2041 : // length, thus we must recalculate the hashes.
2042 : // Due to this, on downgrade some of these may not match, it should be limited
2043 : // to unicode and very long urls though.
2044 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2045 : "UPDATE moz_places "
2046 : "SET url_hash = hash(url) "
2047 0 : "WHERE url_hash <> hash(url)"));
2048 0 : NS_ENSURE_SUCCESS(rv, rv);
2049 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2050 : "UPDATE moz_icons "
2051 : "SET fixed_icon_url_hash = hash(fixup_url(icon_url)) "
2052 0 : "WHERE fixed_icon_url_hash <> hash(fixup_url(icon_url))"));
2053 0 : NS_ENSURE_SUCCESS(rv, rv);
2054 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2055 : "UPDATE moz_pages_w_icons "
2056 : "SET page_url_hash = hash(page_url) "
2057 0 : "WHERE page_url_hash <> hash(page_url)"));
2058 0 : NS_ENSURE_SUCCESS(rv, rv);
2059 : return NS_OK;
2060 : }
2061 :
2062 : nsresult
2063 0 : Database::MigrateV41Up() {
2064 : // Remove old favicons entities.
2065 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2066 0 : "DROP INDEX IF EXISTS moz_places_faviconindex"));
2067 0 : NS_ENSURE_SUCCESS(rv, rv);
2068 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2069 0 : "DROP TABLE IF EXISTS moz_favicons"));
2070 0 : NS_ENSURE_SUCCESS(rv, rv);
2071 : return NS_OK;
2072 : }
2073 :
2074 : nsresult
2075 0 : Database::MigrateV42Up() {
2076 : // auto_vacuum of the favicons database was broken, we may have to set it again.
2077 0 : int32_t vacuum = 0;
2078 : {
2079 0 : nsCOMPtr<mozIStorageStatement> stmt;
2080 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2081 : "PRAGMA favicons.auto_vacuum"
2082 0 : ), getter_AddRefs(stmt));
2083 0 : NS_ENSURE_SUCCESS(rv, rv);
2084 0 : bool hasResult = false;
2085 0 : if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
2086 0 : vacuum = stmt->AsInt32(0);
2087 : }
2088 : }
2089 0 : if (vacuum != 2) {
2090 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2091 0 : "PRAGMA favicons.auto_vacuum = INCREMENTAL"));
2092 0 : NS_ENSURE_SUCCESS(rv, rv);
2093 : // For the change to be effective, we must vacuum the database.
2094 0 : mShouldVacuumIcons = true;
2095 : }
2096 : return NS_OK;
2097 : }
2098 :
2099 : nsresult
2100 0 : Database::MigrateV43Up() {
2101 : // moz_keywords doesn't properly disallow multiple keyword for the same URI
2102 : // because for postData NULL != NULL. We should use an empty string instead.
2103 :
2104 : // To avoid constraint failures, we must first remove duplicate keywords.
2105 : // This may cause a dataloss, but the only alternative would be to modify the
2106 : // related url, and that's far more complex.
2107 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2108 : "DELETE FROM moz_keywords "
2109 : "WHERE post_data ISNULL "
2110 : "AND id NOT IN ( "
2111 : "SELECT MAX(id) "
2112 : "FROM moz_keywords "
2113 : "WHERE post_data ISNULL "
2114 : "GROUP BY place_id "
2115 : ")"
2116 0 : ));
2117 0 : NS_ENSURE_SUCCESS(rv, rv);
2118 : // We must recalculate foreign_count for all the touched places.
2119 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2120 : "UPDATE moz_places "
2121 : "SET foreign_count = (SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id) + "
2122 : "(SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id) "
2123 : "WHERE id IN (SELECT DISTINCT place_id FROM moz_keywords) "
2124 0 : ));
2125 0 : NS_ENSURE_SUCCESS(rv, rv);
2126 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2127 : "UPDATE moz_keywords "
2128 : "SET post_data = '' "
2129 : "WHERE post_data ISNULL "
2130 0 : ));
2131 0 : NS_ENSURE_SUCCESS(rv, rv);
2132 :
2133 : return NS_OK;
2134 : }
2135 :
2136 : nsresult
2137 0 : Database::MigrateV44Up() {
2138 : // We need to remove any non-builtin roots and their descendants.
2139 :
2140 : // Install a temp trigger to clean up linked tables when the main
2141 : // bookmarks are deleted.
2142 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2143 : "CREATE TEMP TRIGGER moz_migrate_bookmarks_trigger "
2144 : "AFTER DELETE ON moz_bookmarks FOR EACH ROW "
2145 : "BEGIN "
2146 : // Insert tombstones.
2147 : "INSERT OR IGNORE INTO moz_bookmarks_deleted (guid, dateRemoved) "
2148 : "VALUES (OLD.guid, strftime('%s', 'now', 'localtime', 'utc') * 1000); "
2149 : // Remove old annotations for the bookmarks.
2150 : "DELETE FROM moz_items_annos "
2151 : "WHERE item_id = OLD.id; "
2152 : // Decrease the foreign_count in moz_places.
2153 : "UPDATE moz_places "
2154 : "SET foreign_count = foreign_count - 1 "
2155 : "WHERE id = OLD.fk; "
2156 : "END "
2157 0 : ));
2158 0 : if (NS_FAILED(rv)) return rv;
2159 :
2160 : // This trigger listens for moz_places deletes, and updates moz_annos and
2161 : // moz_keywords accordingly.
2162 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2163 : "CREATE TEMP TRIGGER moz_migrate_annos_trigger "
2164 : "AFTER UPDATE ON moz_places FOR EACH ROW "
2165 : // Only remove from moz_places if we don't have any remaining keywords pointing to
2166 : // this place, and it hasn't been visited. Note: orphan keywords are tidied up below.
2167 : "WHEN NEW.visit_count = 0 AND "
2168 : " NEW.foreign_count = (SELECT COUNT(*) FROM moz_keywords WHERE place_id = NEW.id) "
2169 : "BEGIN "
2170 : // No more references to the place, so we can delete the place itself.
2171 : "DELETE FROM moz_places "
2172 : "WHERE id = NEW.id; "
2173 : // Delete annotations relating to the place.
2174 : "DELETE FROM moz_annos "
2175 : "WHERE place_id = NEW.id; "
2176 : // Delete keywords relating to the place.
2177 : "DELETE FROM moz_keywords "
2178 : "WHERE place_id = NEW.id; "
2179 : "END "
2180 0 : ));
2181 0 : if (NS_FAILED(rv)) return rv;
2182 :
2183 : // Listens to moz_keyword deletions, to ensure moz_places gets the
2184 : // foreign_count updated corrrectly.
2185 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2186 : "CREATE TEMP TRIGGER moz_migrate_keyword_trigger "
2187 : "AFTER DELETE ON moz_keywords FOR EACH ROW "
2188 : "BEGIN "
2189 : // If we remove a keyword, then reduce the foreign_count.
2190 : "UPDATE moz_places "
2191 : "SET foreign_count = foreign_count - 1 "
2192 : "WHERE id = OLD.place_id; "
2193 : "END "
2194 0 : ));
2195 0 : if (NS_FAILED(rv)) return rv;
2196 :
2197 : // First of all, find the non-builtin roots.
2198 0 : nsCOMPtr<mozIStorageStatement> deleteStmt;
2199 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2200 : "WITH RECURSIVE "
2201 : "itemsToRemove(id, guid) AS ( "
2202 : "SELECT b.id, b.guid FROM moz_bookmarks b "
2203 : "JOIN moz_bookmarks p ON b.parent = p.id "
2204 : "WHERE p.guid = 'root________' AND "
2205 : "b.guid NOT IN ('menu________', 'toolbar_____', 'tags________', 'unfiled_____', 'mobile______') "
2206 : "UNION ALL "
2207 : "SELECT b.id, b.guid FROM moz_bookmarks b "
2208 : "JOIN itemsToRemove d ON d.id = b.parent "
2209 : ") "
2210 : "DELETE FROM moz_bookmarks "
2211 : "WHERE id IN (SELECT id FROM itemsToRemove) "
2212 0 : ), getter_AddRefs(deleteStmt));
2213 0 : if (NS_FAILED(rv)) return rv;
2214 :
2215 0 : rv = deleteStmt->Execute();
2216 0 : if (NS_FAILED(rv)) return rv;
2217 :
2218 : // Before we remove the triggers, check for keywords attached to places which
2219 : // no longer have a bookmark to them. We do this before removing the triggers,
2220 : // so that we can make use of the keyword trigger to update the counts in
2221 : // moz_places.
2222 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2223 : "DELETE FROM moz_keywords WHERE place_id IN ( "
2224 : "SELECT h.id FROM moz_keywords k "
2225 : "JOIN moz_places h ON h.id = k.place_id "
2226 : "GROUP BY place_id HAVING h.foreign_count = count(*) "
2227 : ")"
2228 0 : ));
2229 0 : if (NS_FAILED(rv)) return rv;
2230 :
2231 : // Now remove the temp triggers.
2232 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2233 : "DROP TRIGGER moz_migrate_bookmarks_trigger "
2234 0 : ));
2235 0 : if (NS_FAILED(rv)) return rv;
2236 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2237 : "DROP TRIGGER moz_migrate_annos_trigger "
2238 0 : ));
2239 0 : if (NS_FAILED(rv)) return rv;
2240 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2241 : "DROP TRIGGER moz_migrate_keyword_trigger "
2242 0 : ));
2243 0 : if (NS_FAILED(rv)) return rv;
2244 :
2245 : // Cleanup any orphan annotation attributes.
2246 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2247 : "DELETE FROM moz_anno_attributes WHERE id IN ( "
2248 : "SELECT id FROM moz_anno_attributes n "
2249 : "EXCEPT "
2250 : "SELECT DISTINCT anno_attribute_id FROM moz_annos "
2251 : "EXCEPT "
2252 : "SELECT DISTINCT anno_attribute_id FROM moz_items_annos "
2253 : ")"
2254 0 : ));
2255 0 : if (NS_FAILED(rv)) return rv;
2256 :
2257 0 : return NS_OK;
2258 : }
2259 :
2260 : nsresult
2261 0 : Database::MigrateV45Up() {
2262 0 : nsCOMPtr<mozIStorageStatement> metaTableStmt;
2263 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2264 : "SELECT 1 FROM moz_meta"
2265 0 : ), getter_AddRefs(metaTableStmt));
2266 0 : if (NS_FAILED(rv)) {
2267 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_META);
2268 0 : NS_ENSURE_SUCCESS(rv, rv);
2269 : }
2270 : return NS_OK;
2271 : }
2272 :
2273 : nsresult
2274 0 : Database::MigrateV46Up() {
2275 : // Convert the existing queries. For simplicity we assume the user didn't
2276 : // edit these queries, and just do a 1:1 conversion.
2277 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2278 : "UPDATE moz_places "
2279 : "SET url = IFNULL('place:tag=' || ( "
2280 : "SELECT title FROM moz_bookmarks "
2281 : "WHERE id = CAST(get_query_param(substr(url, 7), 'folder') AS INT) "
2282 : "), url) "
2283 : "WHERE url_hash BETWEEN hash('place', 'prefix_lo') AND "
2284 : "hash('place', 'prefix_hi') "
2285 : "AND url LIKE '%type=7%' "
2286 : "AND EXISTS(SELECT 1 FROM moz_bookmarks "
2287 : "WHERE id = CAST(get_query_param(substr(url, 7), 'folder') AS INT)) "
2288 0 : ));
2289 :
2290 : // Recalculate hashes for all tag queries.
2291 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2292 : "UPDATE moz_places SET url_hash = hash(url) "
2293 : "WHERE url_hash BETWEEN hash('place', 'prefix_lo') AND "
2294 : "hash('place', 'prefix_hi') "
2295 : "AND url LIKE '%tag=%' "
2296 0 : ));
2297 0 : NS_ENSURE_SUCCESS(rv, rv);
2298 :
2299 : // Update Sync fields for all tag queries.
2300 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2301 : "UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + 1 "
2302 : "WHERE fk IN ( "
2303 : "SELECT id FROM moz_places "
2304 : "WHERE url_hash BETWEEN hash('place', 'prefix_lo') AND "
2305 : "hash('place', 'prefix_hi') "
2306 : "AND url LIKE '%tag=%' "
2307 : ") "
2308 0 : ));
2309 0 : NS_ENSURE_SUCCESS(rv, rv);
2310 : return NS_OK;
2311 : }
2312 :
2313 : nsresult
2314 0 : Database::MigrateV47Up() {
2315 : // v46 may have mistakenly set some url to NULL, we must fix those.
2316 : // Since the original url was an invalid query, we replace NULLs with an
2317 : // empty query.
2318 0 : nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2319 : "UPDATE moz_places "
2320 : "SET url = 'place:excludeItems=1', url_hash = hash('place:excludeItems=1') "
2321 : "WHERE url ISNULL "
2322 0 : ));
2323 0 : NS_ENSURE_SUCCESS(rv, rv);
2324 : // Update Sync fields for these queries.
2325 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2326 : "UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + 1 "
2327 : "WHERE fk IN ( "
2328 : "SELECT id FROM moz_places "
2329 : "WHERE url_hash = hash('place:excludeItems=1') "
2330 : "AND url = 'place:excludeItems=1' "
2331 : ") "
2332 0 : ));
2333 0 : NS_ENSURE_SUCCESS(rv, rv);
2334 : return NS_OK;
2335 : }
2336 :
2337 : nsresult
2338 0 : Database::MigrateV48Up() {
2339 : // Create and populate moz_origins.
2340 0 : nsCOMPtr<mozIStorageStatement> stmt;
2341 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2342 : "SELECT * FROM moz_origins; "
2343 0 : ), getter_AddRefs(stmt));
2344 0 : if (NS_FAILED(rv)) {
2345 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ORIGINS);
2346 0 : NS_ENSURE_SUCCESS(rv, rv);
2347 : }
2348 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2349 : "INSERT OR IGNORE INTO moz_origins (prefix, host, frecency) "
2350 : "SELECT get_prefix(url), get_host_and_port(url), -1 "
2351 : "FROM moz_places; "
2352 0 : ));
2353 0 : NS_ENSURE_SUCCESS(rv, rv);
2354 :
2355 : // Add and populate moz_places.origin_id.
2356 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2357 : "SELECT origin_id FROM moz_places; "
2358 0 : ), getter_AddRefs(stmt));
2359 0 : if (NS_FAILED(rv)) {
2360 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2361 : "ALTER TABLE moz_places "
2362 : "ADD COLUMN origin_id INTEGER REFERENCES moz_origins(id); "
2363 0 : ));
2364 0 : NS_ENSURE_SUCCESS(rv, rv);
2365 : }
2366 0 : rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_ORIGIN_ID);
2367 0 : NS_ENSURE_SUCCESS(rv, rv);
2368 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2369 : "UPDATE moz_places "
2370 : "SET origin_id = ( "
2371 : "SELECT id FROM moz_origins "
2372 : "WHERE prefix = get_prefix(url) AND host = get_host_and_port(url) "
2373 : "); "
2374 0 : ));
2375 0 : NS_ENSURE_SUCCESS(rv, rv);
2376 :
2377 : // Setting this pref will cause InitSchema to begin async migration of
2378 : // frecencies to moz_origins. The reason we don't defer the other steps
2379 : // above, like we do this one here, is because we want to make sure that the
2380 : // main data in moz_origins, prefix and host, are coherent in relation to
2381 : // moz_places.
2382 0 : Unused << Preferences::SetBool(PREF_MIGRATE_V48_FRECENCIES, true);
2383 :
2384 : // From this point on, nobody should use moz_hosts again. Empty it so that we
2385 : // don't leak the user's history, but don't remove it yet so that the user can
2386 : // downgrade.
2387 0 : rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2388 : "DELETE FROM moz_hosts; "
2389 0 : ));
2390 0 : NS_ENSURE_SUCCESS(rv, rv);
2391 :
2392 : return NS_OK;
2393 : }
2394 :
2395 : namespace {
2396 :
2397 0 : class MigrateV48FrecenciesRunnable final : public Runnable
2398 : {
2399 : public:
2400 : NS_DECL_NSIRUNNABLE
2401 : explicit MigrateV48FrecenciesRunnable(mozIStorageConnection* aDBConn);
2402 : private:
2403 : nsCOMPtr<mozIStorageConnection> mDBConn;
2404 : };
2405 :
2406 0 : MigrateV48FrecenciesRunnable::MigrateV48FrecenciesRunnable(mozIStorageConnection* aDBConn)
2407 : : Runnable("places::MigrateV48FrecenciesRunnable")
2408 0 : , mDBConn(aDBConn)
2409 : {
2410 0 : }
2411 :
2412 : NS_IMETHODIMP
2413 0 : MigrateV48FrecenciesRunnable::Run()
2414 : {
2415 0 : if (NS_IsMainThread()) {
2416 : // Migration done. Clear the pref.
2417 0 : Unused << Preferences::ClearUser(PREF_MIGRATE_V48_FRECENCIES);
2418 0 : return NS_OK;
2419 : }
2420 :
2421 : // We do the work in chunks, or the wal journal may grow too much.
2422 0 : nsCOMPtr<mozIStorageStatement> updateStmt;
2423 0 : nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2424 : "UPDATE moz_origins "
2425 : "SET frecency = ( "
2426 : "SELECT MAX(frecency) "
2427 : "FROM moz_places "
2428 : "WHERE moz_places.origin_id = moz_origins.id "
2429 : ") "
2430 : "WHERE rowid IN ( "
2431 : "SELECT rowid "
2432 : "FROM moz_origins "
2433 : "WHERE frecency = -1 "
2434 : "LIMIT 400 "
2435 : ") "
2436 0 : ));
2437 0 : NS_ENSURE_SUCCESS(rv, rv);
2438 :
2439 0 : nsCOMPtr<mozIStorageStatement> selectStmt;
2440 0 : rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
2441 : "SELECT id FROM moz_origins WHERE frecency = -1 "
2442 0 : ), getter_AddRefs(selectStmt));
2443 0 : NS_ENSURE_SUCCESS(rv, rv);
2444 0 : bool hasResult = false;
2445 0 : rv = selectStmt->ExecuteStep(&hasResult);
2446 0 : NS_ENSURE_SUCCESS(rv, rv);
2447 0 : if (hasResult) {
2448 : // There are more results to handle. Re-dispatch to the same thread for the
2449 : // next chunk.
2450 0 : return NS_DispatchToCurrentThread(this);
2451 : }
2452 :
2453 : // Re-dispatch to the main-thread to flip the migration pref.
2454 0 : return NS_DispatchToMainThread(this);
2455 : }
2456 :
2457 : } // namespace
2458 :
2459 : void
2460 0 : Database::MigrateV48Frecencies()
2461 : {
2462 1 : MOZ_ASSERT(NS_IsMainThread());
2463 :
2464 1 : if (!Preferences::GetBool(PREF_MIGRATE_V48_FRECENCIES)) {
2465 0 : return;
2466 : }
2467 :
2468 : RefPtr<MigrateV48FrecenciesRunnable> runnable =
2469 0 : new MigrateV48FrecenciesRunnable(mMainConn);
2470 0 : nsCOMPtr<nsIEventTarget> target = do_GetInterface(mMainConn);
2471 0 : MOZ_ASSERT(target);
2472 0 : Unused << target->Dispatch(runnable, NS_DISPATCH_NORMAL);
2473 : }
2474 :
2475 : nsresult
2476 0 : Database::MigrateV49Up() {
2477 : // Calculate initial frecency stats, which should have been done as part of
2478 : // the v48 migration but wasn't.
2479 0 : nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
2480 0 : NS_ENSURE_STATE(navHistory);
2481 0 : nsresult rv = navHistory->RecalculateFrecencyStats(nullptr);
2482 0 : NS_ENSURE_SUCCESS(rv, rv);
2483 :
2484 : // These hidden preferences were added along with the v48 migration as part of
2485 : // the frecency stats implementation but are now replaced with entries in the
2486 : // moz_meta table.
2487 0 : Unused << Preferences::ClearUser("places.frecency.stats.count");
2488 0 : Unused << Preferences::ClearUser("places.frecency.stats.sum");
2489 0 : Unused << Preferences::ClearUser("places.frecency.stats.sumOfSquares");
2490 :
2491 0 : return NS_OK;
2492 : }
2493 :
2494 : nsresult
2495 0 : Database::MigrateV50Up() {
2496 : // Convert the existing queries. We don't have REGEX available, so the simplest
2497 : // thing to do is to pull the urls out, and process them manually.
2498 0 : nsCOMPtr<mozIStorageStatement> stmt;
2499 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2500 : "SELECT id, url FROM moz_places "
2501 : "WHERE url_hash BETWEEN hash('place', 'prefix_lo') AND "
2502 : "hash('place', 'prefix_hi') "
2503 : "AND url LIKE '%folder=%' "
2504 0 : ), getter_AddRefs(stmt));
2505 0 : if (NS_FAILED(rv)) return rv;
2506 :
2507 0 : AutoTArray<Pair<int64_t, nsCString>, 32> placeURLs;
2508 :
2509 0 : bool hasMore = false;
2510 0 : nsCString url;
2511 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
2512 : int64_t placeId;
2513 0 : rv = stmt->GetInt64(0, &placeId);
2514 0 : if (NS_FAILED(rv)) return rv;
2515 0 : rv = stmt->GetUTF8String(1, url);
2516 0 : if (NS_FAILED(rv)) return rv;
2517 :
2518 0 : if (!placeURLs.AppendElement(MakePair(placeId, url))) {
2519 : return NS_ERROR_OUT_OF_MEMORY;
2520 : }
2521 : }
2522 :
2523 0 : if (placeURLs.IsEmpty()) {
2524 : return NS_OK;
2525 : }
2526 :
2527 : int64_t placeId;
2528 0 : for (uint32_t i = 0; i < placeURLs.Length(); ++i) {
2529 0 : placeId = placeURLs[i].first();
2530 0 : url = placeURLs[i].second();
2531 :
2532 0 : rv = ConvertOldStyleQuery(url);
2533 : // Something bad happened, and we can't convert it, so just continue.
2534 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
2535 0 : continue;
2536 : }
2537 :
2538 0 : nsCOMPtr<mozIStorageStatement> updateStmt;
2539 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2540 : "UPDATE moz_places "
2541 : "SET url = :url, url_hash = hash(:url) "
2542 : "WHERE id = :placeId "
2543 0 : ), getter_AddRefs(updateStmt));
2544 0 : if (NS_FAILED(rv)) return rv;
2545 :
2546 0 : rv = URIBinder::Bind(updateStmt, NS_LITERAL_CSTRING("url"), url);
2547 0 : if (NS_FAILED(rv)) return rv;
2548 0 : rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("placeId"), placeId);
2549 0 : if (NS_FAILED(rv)) return rv;
2550 :
2551 0 : rv = updateStmt->Execute();
2552 0 : if (NS_FAILED(rv)) return rv;
2553 :
2554 : // Update Sync fields for these queries.
2555 0 : nsCOMPtr<mozIStorageStatement> syncStmt;
2556 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2557 : "UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + 1 "
2558 : "WHERE fk = :placeId "
2559 0 : ), getter_AddRefs(syncStmt));
2560 0 : if (NS_FAILED(rv)) return rv;
2561 :
2562 0 : rv = syncStmt->BindInt64ByName(NS_LITERAL_CSTRING("placeId"), placeId);
2563 0 : if (NS_FAILED(rv)) return rv;
2564 :
2565 0 : rv = syncStmt->Execute();
2566 0 : if (NS_FAILED(rv)) return rv;
2567 : }
2568 :
2569 : return NS_OK;
2570 : }
2571 :
2572 :
2573 0 : struct StringWriteFunc : public JSONWriteFunc
2574 : {
2575 : nsCString& mCString;
2576 0 : explicit StringWriteFunc(nsCString& aCString) : mCString(aCString)
2577 : {
2578 : }
2579 0 : void Write(const char* aStr) override { mCString.Append(aStr); }
2580 : };
2581 :
2582 : nsresult
2583 0 : Database::MigrateV51Up()
2584 : {
2585 0 : nsCOMPtr<mozIStorageStatement> stmt;
2586 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2587 : "SELECT b.guid FROM moz_anno_attributes n "
2588 : "JOIN moz_items_annos a ON n.id = a.anno_attribute_id "
2589 : "JOIN moz_bookmarks b ON a.item_id = b.id "
2590 : "WHERE n.name = :anno_name ORDER BY a.content DESC"
2591 0 : ), getter_AddRefs(stmt));
2592 0 : if (NS_FAILED(rv)) {
2593 0 : MOZ_ASSERT(false, "Should succeed unless item annotations table has been removed");
2594 : return NS_OK;
2595 : };
2596 :
2597 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
2598 0 : LAST_USED_ANNO);
2599 0 : NS_ENSURE_SUCCESS(rv, rv);
2600 :
2601 0 : nsAutoCString json;
2602 0 : JSONWriter jw{ MakeUnique<StringWriteFunc>(json) };
2603 0 : jw.StartArrayProperty(nullptr, JSONWriter::SingleLineStyle);
2604 :
2605 0 : bool hasAtLeastOne = false;
2606 0 : bool hasMore = false;
2607 : uint32_t length;
2608 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
2609 0 : hasAtLeastOne = true;
2610 0 : jw.StringElement(stmt->AsSharedUTF8String(0, &length));
2611 : }
2612 0 : jw.EndArray();
2613 :
2614 : // If we don't have any, just abort early and save the extra work.
2615 0 : if (!hasAtLeastOne) {
2616 : return NS_OK;
2617 : }
2618 :
2619 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2620 : "INSERT OR REPLACE INTO moz_meta "
2621 : "VALUES (:key, :value) "
2622 0 : ), getter_AddRefs(stmt));
2623 0 : NS_ENSURE_SUCCESS(rv, rv);
2624 :
2625 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"),
2626 0 : LAST_USED_FOLDERS_META_KEY);
2627 0 : NS_ENSURE_SUCCESS(rv, rv);
2628 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), json);
2629 0 : NS_ENSURE_SUCCESS(rv, rv);
2630 0 : rv = stmt->Execute();
2631 0 : NS_ENSURE_SUCCESS(rv, rv);
2632 :
2633 : // Clean up the now redundant annotations.
2634 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2635 : "DELETE FROM moz_items_annos WHERE anno_attribute_id = "
2636 : "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) "
2637 0 : ), getter_AddRefs(stmt));
2638 0 : NS_ENSURE_SUCCESS(rv, rv);
2639 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), LAST_USED_ANNO);
2640 0 : NS_ENSURE_SUCCESS(rv, rv);
2641 0 : rv = stmt->Execute();
2642 0 : NS_ENSURE_SUCCESS(rv, rv);
2643 :
2644 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2645 : "DELETE FROM moz_anno_attributes WHERE name = :anno_name "
2646 0 : ), getter_AddRefs(stmt));
2647 0 : NS_ENSURE_SUCCESS(rv, rv);
2648 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), LAST_USED_ANNO);
2649 0 : NS_ENSURE_SUCCESS(rv, rv);
2650 0 : rv = stmt->Execute();
2651 0 : NS_ENSURE_SUCCESS(rv, rv);
2652 :
2653 : return NS_OK;
2654 : }
2655 :
2656 :
2657 : nsresult
2658 0 : Database::ConvertOldStyleQuery(nsCString& aURL)
2659 : {
2660 0 : AutoTArray<QueryKeyValuePair, 8> tokens;
2661 0 : nsresult rv = TokenizeQueryString(aURL, &tokens);
2662 0 : NS_ENSURE_SUCCESS(rv, rv);
2663 :
2664 0 : AutoTArray<QueryKeyValuePair, 8> newTokens;
2665 0 : bool invalid = false;
2666 0 : nsAutoCString guid;
2667 :
2668 0 : for (uint32_t j = 0; j < tokens.Length(); ++j) {
2669 0 : const QueryKeyValuePair& kvp = tokens[j];
2670 :
2671 0 : if (!kvp.key.EqualsLiteral("folder")) {
2672 0 : if (!newTokens.AppendElement(kvp)) {
2673 : return NS_ERROR_OUT_OF_MEMORY;
2674 : }
2675 : continue;
2676 : }
2677 :
2678 0 : int64_t itemId = kvp.value.ToInteger(&rv);
2679 0 : if (NS_SUCCEEDED(rv)) {
2680 : // We have the folder's ID, now to find its GUID.
2681 0 : nsCOMPtr<mozIStorageStatement> stmt;
2682 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2683 : "SELECT guid FROM moz_bookmarks "
2684 : "WHERE id = :itemId "
2685 0 : ), getter_AddRefs(stmt));
2686 0 : if (NS_FAILED(rv)) return rv;
2687 :
2688 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("itemId"), itemId);
2689 0 : if (NS_FAILED(rv)) return rv;
2690 :
2691 0 : bool hasMore = false;
2692 0 : if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
2693 0 : rv = stmt->GetUTF8String(0, guid);
2694 0 : if (NS_FAILED(rv)) return rv;
2695 : }
2696 0 : } else if (kvp.value.EqualsLiteral("PLACES_ROOT")) {
2697 0 : guid = NS_LITERAL_CSTRING(ROOT_GUID);
2698 0 : } else if (kvp.value.EqualsLiteral("BOOKMARKS_MENU")) {
2699 0 : guid = NS_LITERAL_CSTRING(MENU_ROOT_GUID);
2700 0 : } else if (kvp.value.EqualsLiteral("TAGS")) {
2701 0 : guid = NS_LITERAL_CSTRING(TAGS_ROOT_GUID);
2702 0 : } else if (kvp.value.EqualsLiteral("UNFILED_BOOKMARKS")) {
2703 0 : guid = NS_LITERAL_CSTRING(UNFILED_ROOT_GUID);
2704 0 : } else if (kvp.value.EqualsLiteral("TOOLBAR")) {
2705 0 : guid = NS_LITERAL_CSTRING(TOOLBAR_ROOT_GUID);
2706 0 : } else if (kvp.value.EqualsLiteral("MOBILE_BOOKMARKS")) {
2707 0 : guid = NS_LITERAL_CSTRING(MOBILE_ROOT_GUID);
2708 : }
2709 :
2710 : QueryKeyValuePair* newPair;
2711 0 : if (guid.IsEmpty()) {
2712 : // This is invalid, so we'll change this key/value pair to something else
2713 : // so that the query remains a valid url.
2714 0 : newPair = new QueryKeyValuePair(NS_LITERAL_CSTRING("invalidOldParentId"), kvp.value);
2715 0 : invalid = true;
2716 : } else {
2717 0 : newPair = new QueryKeyValuePair(NS_LITERAL_CSTRING("parent"), guid);
2718 : }
2719 0 : if (!newTokens.AppendElement(*newPair)) {
2720 : return NS_ERROR_OUT_OF_MEMORY;
2721 : }
2722 0 : delete newPair;
2723 : }
2724 :
2725 0 : if (invalid) {
2726 : // One or more of the folders don't exist, replace with an empty query.
2727 0 : newTokens.AppendElement(QueryKeyValuePair(NS_LITERAL_CSTRING("excludeItems"),
2728 0 : NS_LITERAL_CSTRING("1")));
2729 : }
2730 :
2731 0 : TokensToQueryString(newTokens, aURL);
2732 0 : return NS_OK;
2733 : }
2734 :
2735 : nsresult
2736 0 : Database::GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
2737 : nsTArray<int64_t>& aItemIds)
2738 : {
2739 0 : nsCOMPtr<mozIStorageStatement> stmt;
2740 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2741 : "SELECT b.id FROM moz_items_annos a "
2742 : "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
2743 : "JOIN moz_bookmarks b ON b.id = a.item_id "
2744 : "WHERE n.name = :anno_name AND "
2745 : "b.type = :item_type"
2746 0 : ), getter_AddRefs(stmt));
2747 0 : if (NS_FAILED(rv)) return rv;
2748 :
2749 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aAnnoName);
2750 0 : if (NS_FAILED(rv)) return rv;
2751 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_type"), aItemType);
2752 0 : if (NS_FAILED(rv)) return rv;
2753 :
2754 0 : bool hasMore = false;
2755 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
2756 : int64_t itemId;
2757 0 : rv = stmt->GetInt64(0, &itemId);
2758 0 : if (NS_FAILED(rv)) return rv;
2759 0 : aItemIds.AppendElement(itemId);
2760 : }
2761 :
2762 : return NS_OK;
2763 : }
2764 :
2765 : nsresult
2766 0 : Database::DeleteBookmarkItem(int32_t aItemId)
2767 : {
2768 : // Delete the old bookmark.
2769 0 : nsCOMPtr<mozIStorageStatement> deleteStmt;
2770 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2771 : "DELETE FROM moz_bookmarks WHERE id = :item_id"
2772 0 : ), getter_AddRefs(deleteStmt));
2773 0 : if (NS_FAILED(rv)) return rv;
2774 :
2775 0 : rv = deleteStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
2776 0 : aItemId);
2777 0 : if (NS_FAILED(rv)) return rv;
2778 :
2779 0 : rv = deleteStmt->Execute();
2780 0 : if (NS_FAILED(rv)) return rv;
2781 :
2782 : // Clean up orphan annotations.
2783 0 : nsCOMPtr<mozIStorageStatement> removeAnnosStmt;
2784 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2785 : "DELETE FROM moz_items_annos WHERE item_id = :item_id"
2786 0 : ), getter_AddRefs(removeAnnosStmt));
2787 0 : if (NS_FAILED(rv)) return rv;
2788 :
2789 0 : rv = removeAnnosStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
2790 0 : aItemId);
2791 0 : if (NS_FAILED(rv)) return rv;
2792 :
2793 0 : rv = removeAnnosStmt->Execute();
2794 0 : if (NS_FAILED(rv)) return rv;
2795 :
2796 0 : return NS_OK;
2797 : }
2798 :
2799 : int64_t
2800 0 : Database::CreateMobileRoot()
2801 : {
2802 1 : MOZ_ASSERT(NS_IsMainThread());
2803 :
2804 : // Create the mobile root, ignoring conflicts if one already exists (for
2805 : // example, if the user downgraded to an earlier release channel).
2806 2 : nsCOMPtr<mozIStorageStatement> createStmt;
2807 3 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2808 : "INSERT OR IGNORE INTO moz_bookmarks "
2809 : "(type, title, dateAdded, lastModified, guid, position, parent) "
2810 : "SELECT :item_type, :item_title, :timestamp, :timestamp, :guid, "
2811 : "IFNULL((SELECT MAX(position) + 1 FROM moz_bookmarks p WHERE p.parent = b.id), 0), b.id "
2812 : "FROM moz_bookmarks b WHERE b.parent = 0"
2813 0 : ), getter_AddRefs(createStmt));
2814 1 : if (NS_FAILED(rv)) return -1;
2815 :
2816 0 : rv = createStmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
2817 3 : nsINavBookmarksService::TYPE_FOLDER);
2818 0 : if (NS_FAILED(rv)) return -1;
2819 0 : rv = createStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
2820 0 : NS_LITERAL_CSTRING(MOBILE_ROOT_TITLE));
2821 0 : if (NS_FAILED(rv)) return -1;
2822 0 : rv = createStmt->BindInt64ByName(NS_LITERAL_CSTRING("timestamp"),
2823 3 : RoundedPRNow());
2824 1 : if (NS_FAILED(rv)) return -1;
2825 0 : rv = createStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
2826 6 : NS_LITERAL_CSTRING(MOBILE_ROOT_GUID));
2827 0 : if (NS_FAILED(rv)) return -1;
2828 :
2829 0 : rv = createStmt->Execute();
2830 0 : if (NS_FAILED(rv)) return -1;
2831 :
2832 : // Find the mobile root ID. We can't use the last inserted ID because the
2833 : // root might already exist, and we ignore on conflict.
2834 0 : nsCOMPtr<mozIStorageStatement> findIdStmt;
2835 3 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2836 : "SELECT id FROM moz_bookmarks WHERE guid = :guid"
2837 0 : ), getter_AddRefs(findIdStmt));
2838 0 : if (NS_FAILED(rv)) return -1;
2839 :
2840 0 : rv = findIdStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
2841 6 : NS_LITERAL_CSTRING(MOBILE_ROOT_GUID));
2842 1 : if (NS_FAILED(rv)) return -1;
2843 :
2844 0 : bool hasResult = false;
2845 0 : rv = findIdStmt->ExecuteStep(&hasResult);
2846 1 : if (NS_FAILED(rv) || !hasResult) return -1;
2847 :
2848 : int64_t rootId;
2849 1 : rv = findIdStmt->GetInt64(0, &rootId);
2850 1 : if (NS_FAILED(rv)) return -1;
2851 :
2852 0 : return rootId;
2853 : }
2854 :
2855 : void
2856 0 : Database::Shutdown()
2857 : {
2858 : // As the last step in the shutdown path, finalize the database handle.
2859 0 : MOZ_ASSERT(NS_IsMainThread());
2860 0 : MOZ_ASSERT(!mClosed);
2861 :
2862 : // Break cycles with the shutdown blockers.
2863 0 : mClientsShutdown = nullptr;
2864 0 : nsCOMPtr<mozIStorageCompletionCallback> connectionShutdown = mConnectionShutdown.forget();
2865 :
2866 0 : if (!mMainConn) {
2867 : // The connection has never been initialized. Just mark it as closed.
2868 0 : mClosed = true;
2869 0 : (void)connectionShutdown->Complete(NS_OK, nullptr);
2870 0 : return;
2871 : }
2872 :
2873 : #ifdef DEBUG
2874 : {
2875 : bool hasResult;
2876 0 : nsCOMPtr<mozIStorageStatement> stmt;
2877 :
2878 : // Sanity check for missing guids.
2879 0 : nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2880 : "SELECT 1 "
2881 : "FROM moz_places "
2882 : "WHERE guid IS NULL "
2883 0 : ), getter_AddRefs(stmt));
2884 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2885 0 : rv = stmt->ExecuteStep(&hasResult);
2886 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2887 0 : MOZ_ASSERT(!hasResult, "Found a page without a GUID!");
2888 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2889 : "SELECT 1 "
2890 : "FROM moz_bookmarks "
2891 : "WHERE guid IS NULL "
2892 0 : ), getter_AddRefs(stmt));
2893 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2894 0 : rv = stmt->ExecuteStep(&hasResult);
2895 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2896 0 : MOZ_ASSERT(!hasResult, "Found a bookmark without a GUID!");
2897 :
2898 : // Sanity check for unrounded dateAdded and lastModified values (bug
2899 : // 1107308).
2900 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2901 : "SELECT 1 "
2902 : "FROM moz_bookmarks "
2903 : "WHERE dateAdded % 1000 > 0 OR lastModified % 1000 > 0 LIMIT 1"
2904 0 : ), getter_AddRefs(stmt));
2905 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2906 0 : rv = stmt->ExecuteStep(&hasResult);
2907 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2908 0 : MOZ_ASSERT(!hasResult, "Found unrounded dates!");
2909 :
2910 : // Sanity check url_hash
2911 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2912 : "SELECT 1 FROM moz_places WHERE url_hash = 0"
2913 0 : ), getter_AddRefs(stmt));
2914 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2915 0 : rv = stmt->ExecuteStep(&hasResult);
2916 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2917 0 : MOZ_ASSERT(!hasResult, "Found a place without a hash!");
2918 :
2919 : // Sanity check unique urls
2920 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2921 : "SELECT 1 FROM moz_places GROUP BY url HAVING count(*) > 1 "
2922 0 : ), getter_AddRefs(stmt));
2923 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2924 0 : rv = stmt->ExecuteStep(&hasResult);
2925 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2926 0 : MOZ_ASSERT(!hasResult, "Found a duplicate url!");
2927 :
2928 : // Sanity check NULL urls
2929 0 : rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
2930 : "SELECT 1 FROM moz_places WHERE url ISNULL "
2931 0 : ), getter_AddRefs(stmt));
2932 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2933 0 : rv = stmt->ExecuteStep(&hasResult);
2934 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2935 0 : MOZ_ASSERT(!hasResult, "Found a NULL url!");
2936 : }
2937 : #endif
2938 :
2939 0 : mMainThreadStatements.FinalizeStatements();
2940 0 : mMainThreadAsyncStatements.FinalizeStatements();
2941 :
2942 : RefPtr< FinalizeStatementCacheProxy<mozIStorageStatement> > event =
2943 : new FinalizeStatementCacheProxy<mozIStorageStatement>(
2944 : mAsyncThreadStatements,
2945 : NS_ISUPPORTS_CAST(nsIObserver*, this)
2946 0 : );
2947 0 : DispatchToAsyncThread(event);
2948 :
2949 0 : mClosed = true;
2950 :
2951 : // Execute PRAGMA optimized as last step, this will ensure proper database
2952 : // performance across restarts.
2953 0 : nsCOMPtr<mozIStoragePendingStatement> ps;
2954 : MOZ_ALWAYS_SUCCEEDS(mMainConn->ExecuteSimpleSQLAsync(NS_LITERAL_CSTRING(
2955 : "PRAGMA optimize(0x02)"
2956 : ), nullptr, getter_AddRefs(ps)));
2957 :
2958 : (void)mMainConn->AsyncClose(connectionShutdown);
2959 : mMainConn = nullptr;
2960 : }
2961 :
2962 : ////////////////////////////////////////////////////////////////////////////////
2963 : //// nsIObserver
2964 :
2965 : NS_IMETHODIMP
2966 : Database::Observe(nsISupports *aSubject,
2967 : const char *aTopic,
2968 : const char16_t *aData)
2969 : {
2970 : MOZ_ASSERT(NS_IsMainThread());
2971 : if (strcmp(aTopic, TOPIC_PROFILE_CHANGE_TEARDOWN) == 0) {
2972 : // Tests simulating shutdown may cause multiple notifications.
2973 : if (IsShutdownStarted()) {
2974 : return NS_OK;
2975 : }
2976 :
2977 : nsCOMPtr<nsIObserverService> os = services::GetObserverService();
2978 : NS_ENSURE_STATE(os);
2979 :
2980 : // If shutdown happens in the same mainthread loop as init, observers could
2981 : // handle the places-init-complete notification after xpcom-shutdown, when
2982 : // the connection does not exist anymore. Removing those observers would
2983 : // be less expensive but may cause their RemoveObserver calls to throw.
2984 : // Thus notify the topic now, so they stop listening for it.
2985 : nsCOMPtr<nsISimpleEnumerator> e;
2986 : if (NS_SUCCEEDED(os->EnumerateObservers(TOPIC_PLACES_INIT_COMPLETE,
2987 : getter_AddRefs(e))) && e) {
2988 : bool hasMore = false;
2989 : while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
2990 : nsCOMPtr<nsISupports> supports;
2991 : if (NS_SUCCEEDED(e->GetNext(getter_AddRefs(supports)))) {
2992 : nsCOMPtr<nsIObserver> observer = do_QueryInterface(supports);
2993 : (void)observer->Observe(observer, TOPIC_PLACES_INIT_COMPLETE, nullptr);
2994 : }
2995 : }
2996 : }
2997 :
2998 : // Notify all Places users that we are about to shutdown.
2999 : (void)os->NotifyObservers(nullptr, TOPIC_PLACES_SHUTDOWN, nullptr);
3000 : } else if (strcmp(aTopic, TOPIC_SIMULATE_PLACES_SHUTDOWN) == 0) {
3001 : // This notification is (and must be) only used by tests that are trying
3002 : // to simulate Places shutdown out of the normal shutdown path.
3003 :
3004 : // Tests simulating shutdown may cause re-entrance.
3005 : if (IsShutdownStarted()) {
3006 : return NS_OK;
3007 : }
3008 :
3009 : // We are simulating a shutdown, so invoke the shutdown blockers,
3010 : // wait for them, then proceed with connection shutdown.
3011 : // Since we are already going through shutdown, but it's not the real one,
3012 : // we won't need to block the real one anymore, so we can unblock it.
3013 : {
3014 : nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileChangeTeardownPhase();
3015 : if (shutdownPhase) {
3016 : shutdownPhase->RemoveBlocker(mClientsShutdown.get());
3017 : }
3018 : (void)mClientsShutdown->BlockShutdown(nullptr);
3019 : }
3020 :
3021 : // Spin the events loop until the clients are done.
3022 : // Note, this is just for tests, specifically test_clearHistory_shutdown.js
3023 : SpinEventLoopUntil([&]() {
3024 : return mClientsShutdown->State() == PlacesShutdownBlocker::States::RECEIVED_DONE;
3025 : });
3026 :
3027 : {
3028 : nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileBeforeChangePhase();
3029 : if (shutdownPhase) {
3030 : shutdownPhase->RemoveBlocker(mConnectionShutdown.get());
3031 : }
3032 : (void)mConnectionShutdown->BlockShutdown(nullptr);
3033 : }
3034 : }
3035 : return NS_OK;
3036 : }
3037 :
3038 : uint32_t
3039 : Database::MaxUrlLength() {
3040 : MOZ_ASSERT(NS_IsMainThread());
3041 : if (!mMaxUrlLength) {
3042 : mMaxUrlLength = Preferences::GetInt(PREF_HISTORY_MAXURLLEN,
3043 : PREF_HISTORY_MAXURLLEN_DEFAULT);
3044 : if (mMaxUrlLength < 255 || mMaxUrlLength > INT32_MAX) {
3045 : mMaxUrlLength = PREF_HISTORY_MAXURLLEN_DEFAULT;
3046 : }
3047 : }
3048 : return mMaxUrlLength;
3049 : }
3050 :
3051 :
3052 :
3053 : } // namespace places
3054 : } // namespace mozilla
|