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