Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "mozilla/ArrayUtils.h"
6
#include "mozilla/Attributes.h"
7
#include "mozilla/DebugOnly.h"
8
#include "mozilla/ScopeExit.h"
9
#include "mozilla/JSONWriter.h"
10
11
#include "Database.h"
12
13
#include "nsIAnnotationService.h"
14
#include "nsIInterfaceRequestorUtils.h"
15
#include "nsIFile.h"
16
#include "nsIWritablePropertyBag2.h"
17
18
#include "nsNavBookmarks.h"
19
#include "nsNavHistory.h"
20
#include "nsPlacesTables.h"
21
#include "nsPlacesIndexes.h"
22
#include "nsPlacesTriggers.h"
23
#include "nsPlacesMacros.h"
24
#include "nsVariant.h"
25
#include "SQLFunctions.h"
26
#include "Helpers.h"
27
#include "nsFaviconService.h"
28
29
#include "nsAppDirectoryServiceDefs.h"
30
#include "nsDirectoryServiceUtils.h"
31
#include "prenv.h"
32
#include "prsystem.h"
33
#include "nsPrintfCString.h"
34
#include "mozilla/Preferences.h"
35
#include "mozilla/Services.h"
36
#include "mozilla/Unused.h"
37
#include "prtime.h"
38
39
#include "nsXULAppAPI.h"
40
41
// Time between corrupt database backups.
42
#define RECENT_BACKUP_TIME_MICROSEC (int64_t)86400 * PR_USEC_PER_SEC // 24H
43
44
// Filename of the database.
45
#define DATABASE_FILENAME NS_LITERAL_STRING("places.sqlite")
46
// Filename of the icons database.
47
#define DATABASE_FAVICONS_FILENAME NS_LITERAL_STRING("favicons.sqlite")
48
49
// Set to the database file name when it was found corrupt by a previous
50
// maintenance run.
51
#define PREF_FORCE_DATABASE_REPLACEMENT \
52
"places.database.replaceDatabaseOnStartup"
53
54
// Whether on corruption we should try to fix the database by cloning it.
55
#define PREF_DATABASE_CLONEONCORRUPTION "places.database.cloneOnCorruption"
56
57
// Set to specify the size of the places database growth increments in kibibytes
58
#define PREF_GROWTH_INCREMENT_KIB "places.database.growthIncrementKiB"
59
60
// Set to disable the default robust storage and use volatile, in-memory
61
// storage without robust transaction flushing guarantees. This makes
62
// SQLite use much less I/O at the cost of losing data when things crash.
63
// The pref is only honored if an environment variable is set. The env
64
// variable is intentionally named something scary to help prevent someone
65
// from thinking it is a useful performance optimization they should enable.
66
#define PREF_DISABLE_DURABILITY "places.database.disableDurability"
67
#define ENV_ALLOW_CORRUPTION \
68
"ALLOW_PLACES_DATABASE_TO_LOSE_DATA_AND_BECOME_CORRUPT"
69
70
// The maximum url length we can store in history.
71
// We do not add to history URLs longer than this value.
72
#define PREF_HISTORY_MAXURLLEN "places.history.maxUrlLength"
73
// This number is mostly a guess based on various facts:
74
// * IE didn't support urls longer than 2083 chars
75
// * Sitemaps protocol used to support a maximum of 2048 chars
76
// * Various SEO guides suggest to not go over 2000 chars
77
// * Various apps/services are known to have issues over 2000 chars
78
// * RFC 2616 - HTTP/1.1 suggests being cautious about depending
79
// on URI lengths above 255 bytes
80
#define PREF_HISTORY_MAXURLLEN_DEFAULT 2000
81
82
#define PREF_MIGRATE_V52_ORIGIN_FRECENCIES \
83
"places.database.migrateV52OriginFrecencies"
84
85
// Maximum size for the WAL file.
86
// For performance reasons this should be as large as possible, so that more
87
// transactions can fit into it, and the checkpoint cost is paid less often.
88
// At the same time, since we use synchronous = NORMAL, an fsync happens only
89
// at checkpoint time, so we don't want the WAL to grow too much and risk to
90
// lose all the contained transactions on a crash.
91
#define DATABASE_MAX_WAL_BYTES 2048000
92
93
// Since exceeding the journal limit will cause a truncate, we allow a slightly
94
// larger limit than DATABASE_MAX_WAL_BYTES to reduce the number of truncates.
95
// This is the number of bytes the journal can grow over the maximum wal size
96
// before being truncated.
97
#define DATABASE_JOURNAL_OVERHEAD_BYTES 2048000
98
99
#define BYTES_PER_KIBIBYTE 1024
100
101
// How much time Sqlite can wait before returning a SQLITE_BUSY error.
102
#define DATABASE_BUSY_TIMEOUT_MS 100
103
104
// This annotation is no longer used & is obsolete, but here for migration.
105
#define LAST_USED_ANNO \
106
NS_LITERAL_CSTRING("bookmarkPropertiesDialog/folderLastUsed")
107
// This is key in the meta table that the LAST_USED_ANNO is migrated to.
108
#define LAST_USED_FOLDERS_META_KEY \
109
NS_LITERAL_CSTRING("places/bookmarks/edit/lastusedfolder")
110
111
// We use a fixed title for the mobile root to avoid marking the database as
112
// corrupt if we can't look up the localized title in the string bundle. Sync
113
// sets the title to the localized version when it creates the left pane query.
114
#define MOBILE_ROOT_TITLE "mobile"
115
116
// Legacy item annotation used by the old Sync engine.
117
#define SYNC_PARENT_ANNO "sync/parent"
118
119
using namespace mozilla;
120
121
namespace mozilla {
122
namespace places {
123
124
namespace {
125
126
////////////////////////////////////////////////////////////////////////////////
127
//// Helpers
128
129
/**
130
* Get the filename for a corrupt database.
131
*/
132
nsString getCorruptFilename(const nsString& aDbFilename) {
133
return aDbFilename + NS_LITERAL_STRING(".corrupt");
134
}
135
/**
136
* Get the filename for a recover database.
137
*/
138
nsString getRecoverFilename(const nsString& aDbFilename) {
139
return aDbFilename + NS_LITERAL_STRING(".recover");
140
}
141
142
/**
143
* Checks whether exists a corrupt database file created not longer than
144
* RECENT_BACKUP_TIME_MICROSEC ago.
145
*/
146
bool isRecentCorruptFile(const nsCOMPtr<nsIFile>& aCorruptFile) {
147
MOZ_ASSERT(NS_IsMainThread());
148
bool fileExists = false;
149
if (NS_FAILED(aCorruptFile->Exists(&fileExists)) || !fileExists) {
150
return false;
151
}
152
PRTime lastMod = 0;
153
if (NS_FAILED(aCorruptFile->GetLastModifiedTime(&lastMod)) || lastMod <= 0 ||
154
(PR_Now() - lastMod) > RECENT_BACKUP_TIME_MICROSEC) {
155
return false;
156
}
157
return true;
158
}
159
160
/**
161
* Sets the connection journal mode to one of the JOURNAL_* types.
162
*
163
* @param aDBConn
164
* The database connection.
165
* @param aJournalMode
166
* One of the JOURNAL_* types.
167
* @returns the current journal mode.
168
* @note this may return a different journal mode than the required one, since
169
* setting it may fail.
170
*/
171
enum JournalMode SetJournalMode(nsCOMPtr<mozIStorageConnection>& aDBConn,
172
enum JournalMode aJournalMode) {
173
MOZ_ASSERT(NS_IsMainThread());
174
nsAutoCString journalMode;
175
switch (aJournalMode) {
176
default:
177
MOZ_FALLTHROUGH_ASSERT("Trying to set an unknown journal mode.");
178
// Fall through to the default DELETE journal.
179
case JOURNAL_DELETE:
180
journalMode.AssignLiteral("delete");
181
break;
182
case JOURNAL_TRUNCATE:
183
journalMode.AssignLiteral("truncate");
184
break;
185
case JOURNAL_MEMORY:
186
journalMode.AssignLiteral("memory");
187
break;
188
case JOURNAL_WAL:
189
journalMode.AssignLiteral("wal");
190
break;
191
}
192
193
nsCOMPtr<mozIStorageStatement> statement;
194
nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
195
query.Append(journalMode);
196
aDBConn->CreateStatement(query, getter_AddRefs(statement));
197
NS_ENSURE_TRUE(statement, JOURNAL_DELETE);
198
199
bool hasResult = false;
200
if (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && hasResult &&
201
NS_SUCCEEDED(statement->GetUTF8String(0, journalMode))) {
202
if (journalMode.EqualsLiteral("delete")) {
203
return JOURNAL_DELETE;
204
}
205
if (journalMode.EqualsLiteral("truncate")) {
206
return JOURNAL_TRUNCATE;
207
}
208
if (journalMode.EqualsLiteral("memory")) {
209
return JOURNAL_MEMORY;
210
}
211
if (journalMode.EqualsLiteral("wal")) {
212
return JOURNAL_WAL;
213
}
214
MOZ_ASSERT(false, "Got an unknown journal mode.");
215
}
216
217
return JOURNAL_DELETE;
218
}
219
220
nsresult CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
221
const nsCString& aRootName, const nsCString& aGuid,
222
const nsCString& titleString, const int32_t position,
223
int64_t& newId) {
224
MOZ_ASSERT(NS_IsMainThread());
225
226
// A single creation timestamp for all roots so that the root folder's
227
// last modification time isn't earlier than its childrens' creation time.
228
static PRTime timestamp = 0;
229
if (!timestamp) timestamp = RoundedPRNow();
230
231
// Create a new bookmark folder for the root.
232
nsCOMPtr<mozIStorageStatement> stmt;
233
nsresult rv = aDBConn->CreateStatement(
234
NS_LITERAL_CSTRING(
235
"INSERT INTO moz_bookmarks "
236
"(type, position, title, dateAdded, lastModified, guid, parent, "
237
"syncChangeCounter, syncStatus) "
238
"VALUES (:item_type, :item_position, :item_title,"
239
":date_added, :last_modified, :guid, "
240
"IFNULL((SELECT id FROM moz_bookmarks WHERE parent = 0), 0), "
241
"1, :sync_status)"),
242
getter_AddRefs(stmt));
243
if (NS_FAILED(rv)) return rv;
244
245
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
246
nsINavBookmarksService::TYPE_FOLDER);
247
if (NS_FAILED(rv)) return rv;
248
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"), position);
249
if (NS_FAILED(rv)) return rv;
250
rv =
251
stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"), titleString);
252
if (NS_FAILED(rv)) return rv;
253
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), timestamp);
254
if (NS_FAILED(rv)) return rv;
255
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), timestamp);
256
if (NS_FAILED(rv)) return rv;
257
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGuid);
258
if (NS_FAILED(rv)) return rv;
259
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("sync_status"),
260
nsINavBookmarksService::SYNC_STATUS_NEW);
261
if (NS_FAILED(rv)) return rv;
262
rv = stmt->Execute();
263
if (NS_FAILED(rv)) return rv;
264
265
newId = nsNavBookmarks::sLastInsertedItemId;
266
return NS_OK;
267
}
268
269
nsresult SetupDurability(nsCOMPtr<mozIStorageConnection>& aDBConn,
270
int32_t aDBPageSize) {
271
nsresult rv;
272
if (PR_GetEnv(ENV_ALLOW_CORRUPTION) &&
273
Preferences::GetBool(PREF_DISABLE_DURABILITY, false)) {
274
// Volatile storage was requested. Use the in-memory journal (no
275
// filesystem I/O) and don't sync the filesystem after writing.
276
SetJournalMode(aDBConn, JOURNAL_MEMORY);
277
rv = aDBConn->ExecuteSimpleSQL(
278
NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
279
NS_ENSURE_SUCCESS(rv, rv);
280
} else {
281
// Be sure to set journal mode after page_size. WAL would prevent the
282
// change otherwise.
283
if (JOURNAL_WAL == SetJournalMode(aDBConn, JOURNAL_WAL)) {
284
// Set the WAL journal size limit.
285
int32_t checkpointPages =
286
static_cast<int32_t>(DATABASE_MAX_WAL_BYTES / aDBPageSize);
287
nsAutoCString checkpointPragma("PRAGMA wal_autocheckpoint = ");
288
checkpointPragma.AppendInt(checkpointPages);
289
rv = aDBConn->ExecuteSimpleSQL(checkpointPragma);
290
NS_ENSURE_SUCCESS(rv, rv);
291
} else {
292
// Ignore errors, if we fail here the database could be considered corrupt
293
// and we won't be able to go on, even if it's just matter of a bogus file
294
// system. The default mode (DELETE) will be fine in such a case.
295
(void)SetJournalMode(aDBConn, JOURNAL_TRUNCATE);
296
297
// Set synchronous to FULL to ensure maximum data integrity, even in
298
// case of crashes or unclean shutdowns.
299
rv = aDBConn->ExecuteSimpleSQL(
300
NS_LITERAL_CSTRING("PRAGMA synchronous = FULL"));
301
NS_ENSURE_SUCCESS(rv, rv);
302
}
303
}
304
305
// The journal is usually free to grow for performance reasons, but it never
306
// shrinks back. Since the space taken may be problematic, limit its size.
307
nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
308
journalSizePragma.AppendInt(DATABASE_MAX_WAL_BYTES +
309
DATABASE_JOURNAL_OVERHEAD_BYTES);
310
(void)aDBConn->ExecuteSimpleSQL(journalSizePragma);
311
312
// Grow places in |growthIncrementKiB| increments to limit fragmentation on
313
// disk. By default, it's 5 MB.
314
int32_t growthIncrementKiB =
315
Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 5 * BYTES_PER_KIBIBYTE);
316
if (growthIncrementKiB > 0) {
317
(void)aDBConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE,
318
EmptyCString());
319
}
320
return NS_OK;
321
}
322
323
nsresult AttachDatabase(nsCOMPtr<mozIStorageConnection>& aDBConn,
324
const nsACString& aPath, const nsACString& aName) {
325
nsCOMPtr<mozIStorageStatement> stmt;
326
nsresult rv = aDBConn->CreateStatement(
327
NS_LITERAL_CSTRING("ATTACH DATABASE :path AS ") + aName,
328
getter_AddRefs(stmt));
329
NS_ENSURE_SUCCESS(rv, rv);
330
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), aPath);
331
NS_ENSURE_SUCCESS(rv, rv);
332
rv = stmt->Execute();
333
NS_ENSURE_SUCCESS(rv, rv);
334
335
// The journal limit must be set apart for each database.
336
nsAutoCString journalSizePragma("PRAGMA favicons.journal_size_limit = ");
337
journalSizePragma.AppendInt(DATABASE_MAX_WAL_BYTES +
338
DATABASE_JOURNAL_OVERHEAD_BYTES);
339
Unused << aDBConn->ExecuteSimpleSQL(journalSizePragma);
340
341
return NS_OK;
342
}
343
344
} // namespace
345
346
////////////////////////////////////////////////////////////////////////////////
347
//// Database
348
349
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase)
350
351
NS_IMPL_ISUPPORTS(Database, nsIObserver, nsISupportsWeakReference)
352
353
Database::Database()
354
: mMainThreadStatements(mMainConn),
355
mMainThreadAsyncStatements(mMainConn),
356
mAsyncThreadStatements(mMainConn),
357
mDBPageSize(0),
358
mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK),
359
mClosed(false),
360
mShouldConvertIconPayloads(false),
361
mShouldVacuumIcons(false),
362
mClientsShutdown(new ClientsShutdownBlocker()),
363
mConnectionShutdown(new ConnectionShutdownBlocker(this)),
364
mMaxUrlLength(0),
365
mCacheObservers(TOPIC_PLACES_INIT_COMPLETE),
366
mRootId(-1),
367
mMenuRootId(-1),
368
mTagsRootId(-1),
369
mUnfiledRootId(-1),
370
mToolbarRootId(-1),
371
mMobileRootId(-1) {
372
MOZ_ASSERT(!XRE_IsContentProcess(),
373
"Cannot instantiate Places in the content process");
374
// Attempting to create two instances of the service?
375
MOZ_ASSERT(!gDatabase);
376
gDatabase = this;
377
}
378
379
already_AddRefed<nsIAsyncShutdownClient>
380
Database::GetProfileChangeTeardownPhase() {
381
nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc =
382
services::GetAsyncShutdown();
383
MOZ_ASSERT(asyncShutdownSvc);
384
if (NS_WARN_IF(!asyncShutdownSvc)) {
385
return nullptr;
386
}
387
388
// Consumers of Places should shutdown before us, at profile-change-teardown.
389
nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
390
DebugOnly<nsresult> rv =
391
asyncShutdownSvc->GetProfileChangeTeardown(getter_AddRefs(shutdownPhase));
392
MOZ_ASSERT(NS_SUCCEEDED(rv));
393
return shutdownPhase.forget();
394
}
395
396
already_AddRefed<nsIAsyncShutdownClient>
397
Database::GetProfileBeforeChangePhase() {
398
nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc =
399
services::GetAsyncShutdown();
400
MOZ_ASSERT(asyncShutdownSvc);
401
if (NS_WARN_IF(!asyncShutdownSvc)) {
402
return nullptr;
403
}
404
405
// Consumers of Places should shutdown before us, at profile-change-teardown.
406
nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
407
DebugOnly<nsresult> rv =
408
asyncShutdownSvc->GetProfileBeforeChange(getter_AddRefs(shutdownPhase));
409
MOZ_ASSERT(NS_SUCCEEDED(rv));
410
return shutdownPhase.forget();
411
}
412
413
Database::~Database() {}
414
415
bool Database::IsShutdownStarted() const {
416
if (!mConnectionShutdown) {
417
// We have already broken the cycle between `this` and
418
// `mConnectionShutdown`.
419
return true;
420
}
421
return mConnectionShutdown->IsStarted();
422
}
423
424
already_AddRefed<mozIStorageAsyncStatement> Database::GetAsyncStatement(
425
const nsACString& aQuery) {
426
if (IsShutdownStarted() || NS_FAILED(EnsureConnection())) {
427
return nullptr;
428
}
429
430
MOZ_ASSERT(NS_IsMainThread());
431
return mMainThreadAsyncStatements.GetCachedStatement(aQuery);
432
}
433
434
already_AddRefed<mozIStorageStatement> Database::GetStatement(
435
const nsACString& aQuery) {
436
if (IsShutdownStarted()) {
437
return nullptr;
438
}
439
if (NS_IsMainThread()) {
440
if (NS_FAILED(EnsureConnection())) {
441
return nullptr;
442
}
443
return mMainThreadStatements.GetCachedStatement(aQuery);
444
}
445
// In the async case, the connection must have been started on the main-thread
446
// already.
447
MOZ_ASSERT(mMainConn);
448
return mAsyncThreadStatements.GetCachedStatement(aQuery);
449
}
450
451
already_AddRefed<nsIAsyncShutdownClient> Database::GetClientsShutdown() {
452
if (mClientsShutdown) return mClientsShutdown->GetClient();
453
return nullptr;
454
}
455
456
already_AddRefed<nsIAsyncShutdownClient> Database::GetConnectionShutdown() {
457
if (mConnectionShutdown) return mConnectionShutdown->GetClient();
458
return nullptr;
459
}
460
461
// static
462
already_AddRefed<Database> Database::GetDatabase() {
463
if (PlacesShutdownBlocker::IsStarted()) {
464
return nullptr;
465
}
466
return GetSingleton();
467
}
468
469
nsresult Database::Init() {
470
MOZ_ASSERT(NS_IsMainThread());
471
472
// DO NOT FAIL HERE, otherwise we would never break the cycle between this
473
// object and the shutdown blockers, causing unexpected leaks.
474
475
{
476
// First of all Places clients should block profile-change-teardown.
477
nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase =
478
GetProfileChangeTeardownPhase();
479
MOZ_ASSERT(shutdownPhase);
480
if (shutdownPhase) {
481
DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
482
static_cast<nsIAsyncShutdownBlocker*>(mClientsShutdown.get()),
483
NS_LITERAL_STRING(__FILE__), __LINE__, NS_LITERAL_STRING(""));
484
MOZ_ASSERT(NS_SUCCEEDED(rv));
485
}
486
}
487
488
{
489
// Then connection closing should block profile-before-change.
490
nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase =
491
GetProfileBeforeChangePhase();
492
MOZ_ASSERT(shutdownPhase);
493
if (shutdownPhase) {
494
DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
495
static_cast<nsIAsyncShutdownBlocker*>(mConnectionShutdown.get()),
496
NS_LITERAL_STRING(__FILE__), __LINE__, NS_LITERAL_STRING(""));
497
MOZ_ASSERT(NS_SUCCEEDED(rv));
498
}
499
}
500
501
// Finally observe profile shutdown notifications.
502
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
503
if (os) {
504
(void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
505
}
506
return NS_OK;
507
}
508
509
nsresult Database::EnsureConnection() {
510
// Run this only once.
511
if (mMainConn ||
512
mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_LOCKED) {
513
return NS_OK;
514
}
515
// Don't try to create a database too late.
516
if (IsShutdownStarted()) {
517
return NS_ERROR_FAILURE;
518
}
519
520
MOZ_ASSERT(NS_IsMainThread(),
521
"Database initialization must happen on the main-thread");
522
523
{
524
bool initSucceeded = false;
525
auto notify = MakeScopeExit([&]() {
526
// If the database connection cannot be opened, it may just be locked
527
// by third parties. Set a locked state.
528
if (!initSucceeded) {
529
mMainConn = nullptr;
530
mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_LOCKED;
531
}
532
// Notify at the next tick, to avoid re-entrancy problems.
533
NS_DispatchToMainThread(
534
NewRunnableMethod("places::Database::EnsureConnection()", this,
535
&Database::NotifyConnectionInitalized));
536
});
537
538
nsCOMPtr<mozIStorageService> storage =
539
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
540
NS_ENSURE_STATE(storage);
541
542
nsCOMPtr<nsIFile> profileDir;
543
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
544
getter_AddRefs(profileDir));
545
NS_ENSURE_SUCCESS(rv, rv);
546
547
nsCOMPtr<nsIFile> databaseFile;
548
rv = profileDir->Clone(getter_AddRefs(databaseFile));
549
NS_ENSURE_SUCCESS(rv, rv);
550
rv = databaseFile->Append(DATABASE_FILENAME);
551
NS_ENSURE_SUCCESS(rv, rv);
552
bool databaseExisted = false;
553
rv = databaseFile->Exists(&databaseExisted);
554
NS_ENSURE_SUCCESS(rv, rv);
555
556
nsAutoString corruptDbName;
557
if (NS_SUCCEEDED(Preferences::GetString(PREF_FORCE_DATABASE_REPLACEMENT,
558
corruptDbName)) &&
559
!corruptDbName.IsEmpty()) {
560
// If this pref is set, maintenance required a database replacement, due
561
// to integrity corruption. Be sure to clear the pref to avoid handling it
562
// more than once.
563
(void)Preferences::ClearUser(PREF_FORCE_DATABASE_REPLACEMENT);
564
565
// The database is corrupt, backup and replace it with a new one.
566
nsCOMPtr<nsIFile> fileToBeReplaced;
567
bool fileExists = false;
568
if (NS_SUCCEEDED(profileDir->Clone(getter_AddRefs(fileToBeReplaced))) &&
569
NS_SUCCEEDED(fileToBeReplaced->Exists(&fileExists)) && fileExists) {
570
rv = BackupAndReplaceDatabaseFile(storage, corruptDbName, true, false);
571
NS_ENSURE_SUCCESS(rv, rv);
572
}
573
}
574
575
// Open the database file. If it does not exist a new one will be created.
576
// Use an unshared connection, it will consume more memory but avoid shared
577
// cache contentions across threads.
578
rv = storage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
579
if (NS_SUCCEEDED(rv) && !databaseExisted) {
580
mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE;
581
} else if (rv == NS_ERROR_FILE_CORRUPTED) {
582
// The database is corrupt, backup and replace it with a new one.
583
rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FILENAME, true, true);
584
// Fallback to catch-all handler.
585
}
586
NS_ENSURE_SUCCESS(rv, rv);
587
588
// Initialize the database schema. In case of failure the existing schema
589
// is is corrupt or incoherent, thus the database should be replaced.
590
bool databaseMigrated = false;
591
rv = SetupDatabaseConnection(storage);
592
bool shouldTryToCloneDb = true;
593
if (NS_SUCCEEDED(rv)) {
594
// Failing to initialize the schema may indicate a corruption.
595
rv = InitSchema(&databaseMigrated);
596
if (NS_FAILED(rv)) {
597
// Cloning the db on a schema migration may not be a good idea, since we
598
// may end up cloning the schema problems.
599
shouldTryToCloneDb = false;
600
if (rv == NS_ERROR_STORAGE_BUSY || rv == NS_ERROR_FILE_IS_LOCKED ||
601
rv == NS_ERROR_FILE_NO_DEVICE_SPACE ||
602
rv == NS_ERROR_OUT_OF_MEMORY) {
603
// The database is not corrupt, though some migration step failed.
604
// This may be caused by concurrent use of sync and async Storage APIs
605
// or by a system issue.
606
// The best we can do is trying again. If it should still fail, Places
607
// won't work properly and will be handled as LOCKED.
608
rv = InitSchema(&databaseMigrated);
609
if (NS_FAILED(rv)) {
610
rv = NS_ERROR_FILE_IS_LOCKED;
611
}
612
} else {
613
rv = NS_ERROR_FILE_CORRUPTED;
614
}
615
}
616
}
617
if (NS_WARN_IF(NS_FAILED(rv))) {
618
if (rv != NS_ERROR_FILE_IS_LOCKED) {
619
mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
620
}
621
// Some errors may not indicate a database corruption, for those cases we
622
// just bail out without throwing away a possibly valid places.sqlite.
623
if (rv == NS_ERROR_FILE_CORRUPTED) {
624
// Since we don't know which database is corrupt, we must replace both.
625
rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FAVICONS_FILENAME,
626
false, false);
627
NS_ENSURE_SUCCESS(rv, rv);
628
rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FILENAME,
629
shouldTryToCloneDb, true);
630
NS_ENSURE_SUCCESS(rv, rv);
631
// Try to initialize the new database again.
632
rv = SetupDatabaseConnection(storage);
633
NS_ENSURE_SUCCESS(rv, rv);
634
rv = InitSchema(&databaseMigrated);
635
}
636
// Bail out if we couldn't fix the database.
637
NS_ENSURE_SUCCESS(rv, rv);
638
}
639
640
if (databaseMigrated) {
641
mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
642
}
643
644
// Initialize here all the items that are not part of the on-disk database,
645
// like views, temp triggers or temp tables. The database should not be
646
// considered corrupt if any of the following fails.
647
648
rv = InitTempEntities();
649
NS_ENSURE_SUCCESS(rv, rv);
650
651
rv = CheckRoots();
652
NS_ENSURE_SUCCESS(rv, rv);
653
654
initSucceeded = true;
655
}
656
return NS_OK;
657
}
658
659
nsresult Database::NotifyConnectionInitalized() {
660
MOZ_ASSERT(NS_IsMainThread());
661
// Notify about Places initialization.
662
nsCOMArray<nsIObserver> entries;
663
mCacheObservers.GetEntries(entries);
664
for (int32_t idx = 0; idx < entries.Count(); ++idx) {
665
MOZ_ALWAYS_SUCCEEDS(
666
entries[idx]->Observe(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
667
}
668
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
669
if (obs) {
670
MOZ_ALWAYS_SUCCEEDS(
671
obs->NotifyObservers(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
672
}
673
return NS_OK;
674
}
675
676
nsresult Database::EnsureFaviconsDatabaseAttached(
677
const nsCOMPtr<mozIStorageService>& aStorage) {
678
MOZ_ASSERT(NS_IsMainThread());
679
680
nsCOMPtr<nsIFile> databaseFile;
681
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
682
getter_AddRefs(databaseFile));
683
NS_ENSURE_STATE(databaseFile);
684
nsresult rv = databaseFile->Append(DATABASE_FAVICONS_FILENAME);
685
NS_ENSURE_SUCCESS(rv, rv);
686
nsString iconsPath;
687
rv = databaseFile->GetPath(iconsPath);
688
NS_ENSURE_SUCCESS(rv, rv);
689
690
bool fileExists = false;
691
if (NS_SUCCEEDED(databaseFile->Exists(&fileExists)) && fileExists) {
692
return AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
693
NS_LITERAL_CSTRING("favicons"));
694
}
695
696
// Open the database file, this will also create it.
697
nsCOMPtr<mozIStorageConnection> conn;
698
rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(conn));
699
NS_ENSURE_SUCCESS(rv, rv);
700
701
{
702
// Ensure we'll close the connection when done.
703
auto cleanup = MakeScopeExit([&]() {
704
// We cannot use AsyncClose() here, because by the time we try to ATTACH
705
// this database, its transaction could be still be running and that would
706
// cause the ATTACH query to fail.
707
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(conn->Close()));
708
});
709
710
// Enable incremental vacuum for this database. Since it will contain even
711
// large blobs and can be cleared with history, it's worth to have it.
712
// Note that it will be necessary to manually use PRAGMA incremental_vacuum.
713
rv = conn->ExecuteSimpleSQL(
714
NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL"));
715
NS_ENSURE_SUCCESS(rv, rv);
716
717
#if !defined(HAVE_64BIT_BUILD)
718
// Ensure that temp tables are held in memory, not on disk, on 32 bit
719
// platforms.
720
rv = conn->ExecuteSimpleSQL(
721
NS_LITERAL_CSTRING("PRAGMA temp_store = MEMORY"));
722
NS_ENSURE_SUCCESS(rv, rv);
723
#endif
724
725
int32_t defaultPageSize;
726
rv = conn->GetDefaultPageSize(&defaultPageSize);
727
NS_ENSURE_SUCCESS(rv, rv);
728
rv = SetupDurability(conn, defaultPageSize);
729
NS_ENSURE_SUCCESS(rv, rv);
730
731
// We are going to update the database, so everything from now on should be
732
// in a transaction for performances.
733
mozStorageTransaction transaction(conn, false);
734
rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS);
735
NS_ENSURE_SUCCESS(rv, rv);
736
rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ICONS_ICONURLHASH);
737
NS_ENSURE_SUCCESS(rv, rv);
738
rv = conn->ExecuteSimpleSQL(CREATE_MOZ_PAGES_W_ICONS);
739
NS_ENSURE_SUCCESS(rv, rv);
740
rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PAGES_W_ICONS_ICONURLHASH);
741
NS_ENSURE_SUCCESS(rv, rv);
742
rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS_TO_PAGES);
743
NS_ENSURE_SUCCESS(rv, rv);
744
rv = transaction.Commit();
745
NS_ENSURE_SUCCESS(rv, rv);
746
747
// The scope exit will take care of closing the connection.
748
}
749
750
rv = AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
751
NS_LITERAL_CSTRING("favicons"));
752
NS_ENSURE_SUCCESS(rv, rv);
753
754
return NS_OK;
755
}
756
757
nsresult Database::BackupAndReplaceDatabaseFile(
758
nsCOMPtr<mozIStorageService>& aStorage, const nsString& aDbFilename,
759
bool aTryToClone, bool aReopenConnection) {
760
MOZ_ASSERT(NS_IsMainThread());
761
762
if (aDbFilename.Equals(DATABASE_FILENAME)) {
763
mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
764
} else {
765
// Due to OS file lockings, attached databases can't be cloned properly,
766
// otherwise trying to reattach them later would fail.
767
aTryToClone = false;
768
}
769
770
nsCOMPtr<nsIFile> profDir;
771
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
772
getter_AddRefs(profDir));
773
NS_ENSURE_SUCCESS(rv, rv);
774
nsCOMPtr<nsIFile> databaseFile;
775
rv = profDir->Clone(getter_AddRefs(databaseFile));
776
NS_ENSURE_SUCCESS(rv, rv);
777
rv = databaseFile->Append(aDbFilename);
778
NS_ENSURE_SUCCESS(rv, rv);
779
780
// If we already failed in the last 24 hours avoid to create another corrupt
781
// file, since doing so, in some situation, could cause us to create a new
782
// corrupt file at every try to access any Places service. That is bad
783
// because it would quickly fill the user's disk space without any notice.
784
nsCOMPtr<nsIFile> corruptFile;
785
rv = profDir->Clone(getter_AddRefs(corruptFile));
786
NS_ENSURE_SUCCESS(rv, rv);
787
nsString corruptFilename = getCorruptFilename(aDbFilename);
788
rv = corruptFile->Append(corruptFilename);
789
NS_ENSURE_SUCCESS(rv, rv);
790
if (!isRecentCorruptFile(corruptFile)) {
791
// Ensure we never create more than one corrupt file.
792
nsCOMPtr<nsIFile> corruptFile;
793
rv = profDir->Clone(getter_AddRefs(corruptFile));
794
NS_ENSURE_SUCCESS(rv, rv);
795
nsString corruptFilename = getCorruptFilename(aDbFilename);
796
rv = corruptFile->Append(corruptFilename);
797
NS_ENSURE_SUCCESS(rv, rv);
798
rv = corruptFile->Remove(false);
799
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
800
rv != NS_ERROR_FILE_NOT_FOUND) {
801
return rv;
802
}
803
804
nsCOMPtr<nsIFile> backup;
805
Unused << aStorage->BackupDatabaseFile(databaseFile, corruptFilename,
806
profDir, getter_AddRefs(backup));
807
}
808
809
// If anything fails from this point on, we have a stale connection or
810
// database file, and there's not much more we can do.
811
// The only thing we can try to do is to replace the database on the next
812
// startup, and report the problem through telemetry.
813
{
814
enum eCorruptDBReplaceStage : int8_t {
815
stage_closing = 0,
816
stage_removing,
817
stage_reopening,
818
stage_replaced,
819
stage_cloning,
820
stage_cloned
821
};
822
eCorruptDBReplaceStage stage = stage_closing;
823
auto guard = MakeScopeExit([&]() {
824
if (stage != stage_replaced) {
825
// Reaching this point means the database is corrupt and we failed to
826
// replace it. For this session part of the application related to
827
// bookmarks and history will misbehave. The frontend may show a
828
// "locked" notification to the user though.
829
// Set up a pref to try replacing the database at the next startup.
830
Preferences::SetString(PREF_FORCE_DATABASE_REPLACEMENT, aDbFilename);
831
}
832
// Report the corruption through telemetry.
833
Telemetry::Accumulate(
834
Telemetry::PLACES_DATABASE_CORRUPTION_HANDLING_STAGE,
835
static_cast<int8_t>(stage));
836
});
837
838
// Close database connection if open.
839
if (mMainConn) {
840
rv = mMainConn->SpinningSynchronousClose();
841
NS_ENSURE_SUCCESS(rv, rv);
842
mMainConn = nullptr;
843
}
844
845
// Remove the broken database.
846
stage = stage_removing;
847
rv = databaseFile->Remove(false);
848
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
849
rv != NS_ERROR_FILE_NOT_FOUND) {
850
return rv;
851
}
852
853
// Create a new database file and try to clone tables from the corrupt one.
854
bool cloned = false;
855
if (aTryToClone &&
856
Preferences::GetBool(PREF_DATABASE_CLONEONCORRUPTION, true)) {
857
stage = stage_cloning;
858
rv = TryToCloneTablesFromCorruptDatabase(aStorage, databaseFile);
859
if (NS_SUCCEEDED(rv)) {
860
// If we cloned successfully, we should not consider the database
861
// corrupt anymore, otherwise we could reimport default bookmarks.
862
mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_OK;
863
cloned = true;
864
}
865
}
866
867
if (aReopenConnection) {
868
// Use an unshared connection, it will consume more memory but avoid
869
// shared cache contentions across threads.
870
stage = stage_reopening;
871
rv = aStorage->OpenUnsharedDatabase(databaseFile,
872
getter_AddRefs(mMainConn));
873
NS_ENSURE_SUCCESS(rv, rv);
874
}
875
876
stage = cloned ? stage_cloned : stage_replaced;
877
}
878
879
return NS_OK;
880
}
881
882
nsresult Database::TryToCloneTablesFromCorruptDatabase(
883
const nsCOMPtr<mozIStorageService>& aStorage,
884
const nsCOMPtr<nsIFile>& aDatabaseFile) {
885
MOZ_ASSERT(NS_IsMainThread());
886
887
nsAutoString filename;
888
nsresult rv = aDatabaseFile->GetLeafName(filename);
889
890
nsCOMPtr<nsIFile> corruptFile;
891
rv = aDatabaseFile->Clone(getter_AddRefs(corruptFile));
892
NS_ENSURE_SUCCESS(rv, rv);
893
rv = corruptFile->SetLeafName(getCorruptFilename(filename));
894
NS_ENSURE_SUCCESS(rv, rv);
895
nsAutoString path;
896
rv = corruptFile->GetPath(path);
897
NS_ENSURE_SUCCESS(rv, rv);
898
899
nsCOMPtr<nsIFile> recoverFile;
900
rv = aDatabaseFile->Clone(getter_AddRefs(recoverFile));
901
NS_ENSURE_SUCCESS(rv, rv);
902
rv = recoverFile->SetLeafName(getRecoverFilename(filename));
903
NS_ENSURE_SUCCESS(rv, rv);
904
// Ensure there's no previous recover file.
905
rv = recoverFile->Remove(false);
906
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
907
rv != NS_ERROR_FILE_NOT_FOUND) {
908
return rv;
909
}
910
911
nsCOMPtr<mozIStorageConnection> conn;
912
auto guard = MakeScopeExit([&]() {
913
if (conn) {
914
Unused << conn->Close();
915
}
916
Unused << recoverFile->Remove(false);
917
});
918
919
rv = aStorage->OpenUnsharedDatabase(recoverFile, getter_AddRefs(conn));
920
NS_ENSURE_SUCCESS(rv, rv);
921
rv = AttachDatabase(conn, NS_ConvertUTF16toUTF8(path),
922
NS_LITERAL_CSTRING("corrupt"));
923
NS_ENSURE_SUCCESS(rv, rv);
924
925
mozStorageTransaction transaction(conn, false);
926
927
// Copy the schema version.
928
nsCOMPtr<mozIStorageStatement> stmt;
929
(void)conn->CreateStatement(NS_LITERAL_CSTRING("PRAGMA corrupt.user_version"),
930
getter_AddRefs(stmt));
931
NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY);
932
bool hasResult;
933
rv = stmt->ExecuteStep(&hasResult);
934
NS_ENSURE_SUCCESS(rv, rv);
935
int32_t schemaVersion = stmt->AsInt32(0);
936
rv = conn->SetSchemaVersion(schemaVersion);
937
NS_ENSURE_SUCCESS(rv, rv);
938
939
// Recreate the tables.
940
rv = conn->CreateStatement(
941
NS_LITERAL_CSTRING(
942
"SELECT name, sql FROM corrupt.sqlite_master "
943
"WHERE type = 'table' AND name BETWEEN 'moz_' AND 'moza'"),
944
getter_AddRefs(stmt));
945
NS_ENSURE_SUCCESS(rv, rv);
946
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
947
nsAutoCString name;
948
rv = stmt->GetUTF8String(0, name);
949
NS_ENSURE_SUCCESS(rv, rv);
950
nsAutoCString query;
951
rv = stmt->GetUTF8String(1, query);
952
NS_ENSURE_SUCCESS(rv, rv);
953
rv = conn->ExecuteSimpleSQL(query);
954
NS_ENSURE_SUCCESS(rv, rv);
955
// Copy the table contents.
956
rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("INSERT INTO main.") + name +
957
NS_LITERAL_CSTRING(" SELECT * FROM corrupt.") +
958
name);
959
if (NS_FAILED(rv)) {
960
rv = conn->ExecuteSimpleSQL(
961
NS_LITERAL_CSTRING("INSERT INTO main.") + name +
962
NS_LITERAL_CSTRING(" SELECT * FROM corrupt.") + name +
963
NS_LITERAL_CSTRING(" ORDER BY rowid DESC"));
964
}
965
NS_ENSURE_SUCCESS(rv, rv);
966
}
967
968
// Recreate the indices. Doing this after data addition is faster.
969
rv = conn->CreateStatement(
970
NS_LITERAL_CSTRING(
971
"SELECT sql FROM corrupt.sqlite_master "
972
"WHERE type <> 'table' AND name BETWEEN 'moz_' AND 'moza'"),
973
getter_AddRefs(stmt));
974
NS_ENSURE_SUCCESS(rv, rv);
975
hasResult = false;
976
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
977
nsAutoCString query;
978
rv = stmt->GetUTF8String(0, query);
979
NS_ENSURE_SUCCESS(rv, rv);
980
rv = conn->ExecuteSimpleSQL(query);
981
NS_ENSURE_SUCCESS(rv, rv);
982
}
983
rv = stmt->Finalize();
984
NS_ENSURE_SUCCESS(rv, rv);
985
986
rv = transaction.Commit();
987
NS_ENSURE_SUCCESS(rv, rv);
988
989
Unused << conn->Close();
990
conn = nullptr;
991
rv = recoverFile->RenameTo(nullptr, filename);
992
NS_ENSURE_SUCCESS(rv, rv);
993
Unused << corruptFile->Remove(false);
994
995
guard.release();
996
return NS_OK;
997
}
998
999
nsresult Database::SetupDatabaseConnection(
1000
nsCOMPtr<mozIStorageService>& aStorage) {
1001
MOZ_ASSERT(NS_IsMainThread());
1002
1003
// Using immediate transactions allows the main connection to retry writes
1004
// that fail with `SQLITE_BUSY` because a cloned connection has locked the
1005
// database for writing.
1006
nsresult rv = mMainConn->SetDefaultTransactionType(
1007
mozIStorageConnection::TRANSACTION_IMMEDIATE);
1008
NS_ENSURE_SUCCESS(rv, rv);
1009
1010
// WARNING: any statement executed before setting the journal mode must be
1011
// finalized, since SQLite doesn't allow changing the journal mode if there
1012
// is any outstanding statement.
1013
1014
{
1015
// Get the page size. This may be different than the default if the
1016
// database file already existed with a different page size.
1017
nsCOMPtr<mozIStorageStatement> statement;
1018
rv = mMainConn->CreateStatement(
1019
NS_LITERAL_CSTRING(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"),
1020
getter_AddRefs(statement));
1021
NS_ENSURE_SUCCESS(rv, rv);
1022
bool hasResult = false;
1023
rv = statement->ExecuteStep(&hasResult);
1024
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FILE_CORRUPTED);
1025
rv = statement->GetInt32(0, &mDBPageSize);
1026
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0,
1027
NS_ERROR_FILE_CORRUPTED);
1028
}
1029
1030
#if !defined(HAVE_64BIT_BUILD)
1031
// Ensure that temp tables are held in memory, not on disk, on 32 bit
1032
// platforms.
1033
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1034
MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY"));
1035
NS_ENSURE_SUCCESS(rv, rv);
1036
#endif
1037
1038
rv = SetupDurability(mMainConn, mDBPageSize);
1039
NS_ENSURE_SUCCESS(rv, rv);
1040
1041
nsAutoCString busyTimeoutPragma("PRAGMA busy_timeout = ");
1042
busyTimeoutPragma.AppendInt(DATABASE_BUSY_TIMEOUT_MS);
1043
(void)mMainConn->ExecuteSimpleSQL(busyTimeoutPragma);
1044
1045
// Enable FOREIGN KEY support. This is a strict requirement.
1046
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1047
MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA foreign_keys = ON"));
1048
NS_ENSURE_SUCCESS(rv, NS_ERROR_FILE_CORRUPTED);
1049
#ifdef DEBUG
1050
{
1051
// There are a few cases where setting foreign_keys doesn't work:
1052
// * in the middle of a multi-statement transaction
1053
// * if the SQLite library in use doesn't support them
1054
// Since we need foreign_keys, let's at least assert in debug mode.
1055
nsCOMPtr<mozIStorageStatement> stmt;
1056
mMainConn->CreateStatement(NS_LITERAL_CSTRING("PRAGMA foreign_keys"),
1057
getter_AddRefs(stmt));
1058
bool hasResult = false;
1059
if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1060
int32_t fkState = stmt->AsInt32(0);
1061
MOZ_ASSERT(fkState, "Foreign keys should be enabled");
1062
}
1063
}
1064
#endif
1065
1066
// Attach the favicons database to the main connection.
1067
rv = EnsureFaviconsDatabaseAttached(aStorage);
1068
if (NS_FAILED(rv)) {
1069
// The favicons database may be corrupt. Try to replace and reattach it.
1070
nsCOMPtr<nsIFile> iconsFile;
1071
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1072
getter_AddRefs(iconsFile));
1073
NS_ENSURE_SUCCESS(rv, rv);
1074
rv = iconsFile->Append(DATABASE_FAVICONS_FILENAME);
1075
NS_ENSURE_SUCCESS(rv, rv);
1076
rv = iconsFile->Remove(false);
1077
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
1078
rv != NS_ERROR_FILE_NOT_FOUND) {
1079
return rv;
1080
}
1081
rv = EnsureFaviconsDatabaseAttached(aStorage);
1082
NS_ENSURE_SUCCESS(rv, rv);
1083
}
1084
1085
// Create favicons temp entities.
1086
rv = mMainConn->ExecuteSimpleSQL(CREATE_ICONS_AFTERINSERT_TRIGGER);
1087
NS_ENSURE_SUCCESS(rv, rv);
1088
1089
// We use our functions during migration, so initialize them now.
1090
rv = InitFunctions();
1091
NS_ENSURE_SUCCESS(rv, rv);
1092
1093
return NS_OK;
1094
}
1095
1096
nsresult Database::InitSchema(bool* aDatabaseMigrated) {
1097
MOZ_ASSERT(NS_IsMainThread());
1098
*aDatabaseMigrated = false;
1099
1100
// Get the database schema version.
1101
int32_t currentSchemaVersion;
1102
nsresult rv = mMainConn->GetSchemaVersion(&currentSchemaVersion);
1103
NS_ENSURE_SUCCESS(rv, rv);
1104
bool databaseInitialized = currentSchemaVersion > 0;
1105
1106
if (databaseInitialized && currentSchemaVersion == DATABASE_SCHEMA_VERSION) {
1107
// The database is up to date and ready to go.
1108
return NS_OK;
1109
}
1110
1111
auto guard = MakeScopeExit([&]() {
1112
// This runs at the end of the migration, out of the transaction,
1113
// regardless of its success.
1114
if (mShouldVacuumIcons) {
1115
mShouldVacuumIcons = false;
1116
MOZ_ALWAYS_SUCCEEDS(
1117
mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM favicons")));
1118
}
1119
if (mShouldConvertIconPayloads) {
1120
mShouldConvertIconPayloads = false;
1121
nsFaviconService::ConvertUnsupportedPayloads(mMainConn);
1122
}
1123
MigrateV52OriginFrecencies();
1124
});
1125
1126
// We are going to update the database, so everything from now on should be in
1127
// a transaction for performances.
1128
mozStorageTransaction transaction(mMainConn, false);
1129
1130
if (databaseInitialized) {
1131
// Migration How-to:
1132
//
1133
// 1. increment PLACES_SCHEMA_VERSION.
1134
// 2. implement a method that performs upgrade to your version from the
1135
// previous one.
1136
//
1137
// NOTE: The downgrade process is pretty much complicated by the fact old
1138
// versions cannot know what a new version is going to implement.
1139
// The only thing we will do for downgrades is setting back the schema
1140
// version, so that next upgrades will run again the migration step.
1141
1142
if (currentSchemaVersion < DATABASE_SCHEMA_VERSION) {
1143
*aDatabaseMigrated = true;
1144
1145
if (currentSchemaVersion < 35) {
1146
// These are versions older than Firefox 52 ESR that are not supported
1147
// anymore. In this case it's safer to just replace the database.
1148
return NS_ERROR_FILE_CORRUPTED;
1149
}
1150
1151
// Firefox 52 ESR uses schema version 35.
1152
1153
if (currentSchemaVersion < 36) {
1154
rv = MigrateV36Up();
1155
NS_ENSURE_SUCCESS(rv, rv);
1156
}
1157
1158
if (currentSchemaVersion < 37) {
1159
rv = MigrateV37Up();
1160
NS_ENSURE_SUCCESS(rv, rv);
1161
}
1162
1163
// Firefox 55 uses schema version 37.
1164
1165
if (currentSchemaVersion < 38) {
1166
rv = MigrateV38Up();
1167
NS_ENSURE_SUCCESS(rv, rv);
1168
}
1169
1170
// Firefox 56 uses schema version 38.
1171
1172
if (currentSchemaVersion < 39) {
1173
rv = MigrateV39Up();
1174
NS_ENSURE_SUCCESS(rv, rv);
1175
}
1176
1177
// Firefox 57 uses schema version 39. - This is a watershed version.
1178
1179
if (currentSchemaVersion < 40) {
1180
rv = MigrateV40Up();
1181
NS_ENSURE_SUCCESS(rv, rv);
1182
}
1183
1184
if (currentSchemaVersion < 41) {
1185
rv = MigrateV41Up();
1186
NS_ENSURE_SUCCESS(rv, rv);
1187
}
1188
1189
// Firefox 58 uses schema version 41.
1190
1191
if (currentSchemaVersion < 42) {
1192
rv = MigrateV42Up();
1193
NS_ENSURE_SUCCESS(rv, rv);
1194
}
1195
1196
if (currentSchemaVersion < 43) {
1197
rv = MigrateV43Up();
1198
NS_ENSURE_SUCCESS(rv, rv);
1199
}
1200
1201
// Firefox 60 uses schema version 43. - This is an ESR.
1202
1203
if (currentSchemaVersion < 44) {
1204
rv = MigrateV44Up();
1205
NS_ENSURE_SUCCESS(rv, rv);
1206
}
1207
1208
if (currentSchemaVersion < 45) {
1209
rv = MigrateV45Up();
1210
NS_ENSURE_SUCCESS(rv, rv);
1211
}
1212
1213
if (currentSchemaVersion < 46) {
1214
rv = MigrateV46Up();
1215
NS_ENSURE_SUCCESS(rv, rv);
1216
}
1217
1218
if (currentSchemaVersion < 47) {
1219
rv = MigrateV47Up();
1220
NS_ENSURE_SUCCESS(rv, rv);
1221
}
1222
1223
// Firefox 61 uses schema version 47.
1224
1225
if (currentSchemaVersion < 48) {
1226
rv = MigrateV48Up();
1227
NS_ENSURE_SUCCESS(rv, rv);
1228
}
1229
1230
if (currentSchemaVersion < 49) {
1231
rv = MigrateV49Up();
1232
NS_ENSURE_SUCCESS(rv, rv);
1233
}
1234
1235
if (currentSchemaVersion < 50) {
1236
rv = MigrateV50Up();
1237
NS_ENSURE_SUCCESS(rv, rv);
1238
}
1239
1240
if (currentSchemaVersion < 51) {
1241
rv = MigrateV51Up();
1242
NS_ENSURE_SUCCESS(rv, rv);
1243
}
1244
1245
if (currentSchemaVersion < 52) {
1246
rv = MigrateV52Up();
1247
NS_ENSURE_SUCCESS(rv, rv);
1248
}
1249
1250
// Firefox 62 uses schema version 52.
1251
1252
if (currentSchemaVersion < 53) {
1253
rv = MigrateV53Up();
1254
NS_ENSURE_SUCCESS(rv, rv);
1255
}
1256
1257
// Firefox 69 uses schema version 53
1258
1259
// Schema Upgrades must add migration code here.
1260
// >>> IMPORTANT! <<<
1261
// NEVER MIX UP SYNC AND ASYNC EXECUTION IN MIGRATORS, YOU MAY LOCK THE
1262
// CONNECTION AND CAUSE FURTHER STEPS TO FAIL.
1263
// In case, set a bool and do the async work in the ScopeExit guard just
1264
// before the migration steps.
1265
}
1266
} else {
1267
// This is a new database, so we have to create all the tables and indices.
1268
1269
// moz_origins.
1270
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ORIGINS);
1271
NS_ENSURE_SUCCESS(rv, rv);
1272
1273
// moz_places.
1274
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES);
1275
NS_ENSURE_SUCCESS(rv, rv);
1276
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
1277
NS_ENSURE_SUCCESS(rv, rv);
1278
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_REVHOST);
1279
NS_ENSURE_SUCCESS(rv, rv);
1280
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_VISITCOUNT);
1281
NS_ENSURE_SUCCESS(rv, rv);
1282
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
1283
NS_ENSURE_SUCCESS(rv, rv);
1284
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
1285
NS_ENSURE_SUCCESS(rv, rv);
1286
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
1287
NS_ENSURE_SUCCESS(rv, rv);
1288
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_ORIGIN_ID);
1289
NS_ENSURE_SUCCESS(rv, rv);
1290
1291
// moz_historyvisits.
1292
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS);
1293
NS_ENSURE_SUCCESS(rv, rv);
1294
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
1295
NS_ENSURE_SUCCESS(rv, rv);
1296
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT);
1297
NS_ENSURE_SUCCESS(rv, rv);
1298
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE);
1299
NS_ENSURE_SUCCESS(rv, rv);
1300
1301
// moz_inputhistory.
1302
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
1303
NS_ENSURE_SUCCESS(rv, rv);
1304
1305
// moz_bookmarks.
1306
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS);
1307
NS_ENSURE_SUCCESS(rv, rv);
1308
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_DELETED);
1309
NS_ENSURE_SUCCESS(rv, rv);
1310
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE);
1311
NS_ENSURE_SUCCESS(rv, rv);
1312
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION);
1313
NS_ENSURE_SUCCESS(rv, rv);
1314
rv =
1315
mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
1316
NS_ENSURE_SUCCESS(rv, rv);
1317
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_DATEADDED);
1318
NS_ENSURE_SUCCESS(rv, rv);
1319
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
1320
NS_ENSURE_SUCCESS(rv, rv);
1321
1322
// moz_keywords.
1323
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
1324
NS_ENSURE_SUCCESS(rv, rv);
1325
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
1326
NS_ENSURE_SUCCESS(rv, rv);
1327
1328
// moz_anno_attributes.
1329
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNO_ATTRIBUTES);
1330
NS_ENSURE_SUCCESS(rv, rv);
1331
1332
// moz_annos.
1333
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNOS);
1334
NS_ENSURE_SUCCESS(rv, rv);
1335
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
1336
NS_ENSURE_SUCCESS(rv, rv);
1337
1338
// moz_items_annos.
1339
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ITEMS_ANNOS);
1340
NS_ENSURE_SUCCESS(rv, rv);
1341
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
1342
NS_ENSURE_SUCCESS(rv, rv);
1343
1344
// moz_meta.
1345
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_META);
1346
NS_ENSURE_SUCCESS(rv, rv);
1347
1348
// The bookmarks roots get initialized in CheckRoots().
1349
}
1350
1351
// Set the schema version to the current one.
1352
rv = mMainConn->SetSchemaVersion(DATABASE_SCHEMA_VERSION);
1353
NS_ENSURE_SUCCESS(rv, rv);
1354
1355
rv = transaction.Commit();
1356
NS_ENSURE_SUCCESS(rv, rv);
1357
1358
// ANY FAILURE IN THIS METHOD WILL CAUSE US TO MARK THE DATABASE AS CORRUPT
1359
// AND TRY TO REPLACE IT.
1360
// DO NOT PUT HERE ANYTHING THAT IS NOT RELATED TO INITIALIZATION OR MODIFYING
1361
// THE DISK DATABASE.
1362
1363
return NS_OK;
1364
}
1365
1366
nsresult Database::CheckRoots() {
1367
MOZ_ASSERT(NS_IsMainThread());
1368
1369
// If the database has just been created, skip straight to the part where
1370
// we create the roots.
1371
if (mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_CREATE) {
1372
return EnsureBookmarkRoots(0, /* shouldReparentRoots */ false);
1373
}
1374
1375
nsCOMPtr<mozIStorageStatement> stmt;
1376
nsresult rv = mMainConn->CreateStatement(
1377
NS_LITERAL_CSTRING("SELECT guid, id, position, parent FROM moz_bookmarks "
1378
"WHERE guid IN ( "
1379
"'" ROOT_GUID "', '" MENU_ROOT_GUID
1380
"', '" TOOLBAR_ROOT_GUID "', "
1381
"'" TAGS_ROOT_GUID "', '" UNFILED_ROOT_GUID
1382
"', '" MOBILE_ROOT_GUID "' )"),
1383
getter_AddRefs(stmt));
1384
NS_ENSURE_SUCCESS(rv, rv);
1385
1386
bool hasResult;
1387
nsAutoCString guid;
1388
int32_t maxPosition = 0;
1389
bool shouldReparentRoots = false;
1390
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1391
rv = stmt->GetUTF8String(0, guid);
1392
NS_ENSURE_SUCCESS(rv, rv);
1393
1394
int64_t parentId = stmt->AsInt64(3);
1395
1396
if (guid.EqualsLiteral(ROOT_GUID)) {
1397
mRootId = stmt->AsInt64(1);
1398
shouldReparentRoots |= parentId != 0;
1399
} else {
1400
maxPosition = std::max(stmt->AsInt32(2), maxPosition);
1401
1402
if (guid.EqualsLiteral(MENU_ROOT_GUID)) {
1403
mMenuRootId = stmt->AsInt64(1);
1404
} else if (guid.EqualsLiteral(TOOLBAR_ROOT_GUID)) {
1405
mToolbarRootId = stmt->AsInt64(1);
1406
} else if (guid.EqualsLiteral(TAGS_ROOT_GUID)) {
1407
mTagsRootId = stmt->AsInt64(1);
1408
} else if (guid.EqualsLiteral(UNFILED_ROOT_GUID)) {
1409
mUnfiledRootId = stmt->AsInt64(1);
1410
} else if (guid.EqualsLiteral(MOBILE_ROOT_GUID)) {
1411
mMobileRootId = stmt->AsInt64(1);
1412
}
1413
shouldReparentRoots |= parentId != mRootId;
1414
}
1415
}
1416
1417
rv = EnsureBookmarkRoots(maxPosition + 1, shouldReparentRoots);
1418
NS_ENSURE_SUCCESS(rv, rv);
1419
1420
return NS_OK;
1421
}
1422
1423
nsresult Database::EnsureBookmarkRoots(const int32_t startPosition,
1424
bool shouldReparentRoots) {
1425
MOZ_ASSERT(NS_IsMainThread());
1426
1427
nsresult rv;
1428
1429
if (mRootId < 1) {
1430
// The first root's title is an empty string.
1431
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"),
1432
NS_LITERAL_CSTRING("root________"), EmptyCString(), 0,
1433
mRootId);
1434
1435
if (NS_FAILED(rv)) return rv;
1436
}
1437
1438
int32_t position = startPosition;
1439
1440
// For the other roots, the UI doesn't rely on the value in the database, so
1441
// just set it to something simple to make it easier for humans to read.
1442
if (mMenuRootId < 1) {
1443
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"),
1444
NS_LITERAL_CSTRING("menu________"),
1445
NS_LITERAL_CSTRING("menu"), position, mMenuRootId);
1446
if (NS_FAILED(rv)) return rv;
1447
position++;
1448
}
1449
1450
if (mToolbarRootId < 1) {
1451
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"),
1452
NS_LITERAL_CSTRING("toolbar_____"),
1453
NS_LITERAL_CSTRING("toolbar"), position, mToolbarRootId);
1454
if (NS_FAILED(rv)) return rv;
1455
position++;
1456
}
1457
1458
if (mTagsRootId < 1) {
1459
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"),
1460
NS_LITERAL_CSTRING("tags________"),
1461
NS_LITERAL_CSTRING("tags"), position, mTagsRootId);
1462
if (NS_FAILED(rv)) return rv;
1463
position++;
1464
}
1465
1466
if (mUnfiledRootId < 1) {
1467
if (NS_FAILED(rv)) return rv;
1468
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"),
1469
NS_LITERAL_CSTRING("unfiled_____"),
1470
NS_LITERAL_CSTRING("unfiled"), position, mUnfiledRootId);
1471
if (NS_FAILED(rv)) return rv;
1472
position++;
1473
}
1474
1475
if (mMobileRootId < 1) {
1476
int64_t mobileRootId = CreateMobileRoot();
1477
if (mobileRootId <= 0) return NS_ERROR_FAILURE;
1478
{
1479
nsCOMPtr<mozIStorageStatement> mobileRootSyncStatusStmt;
1480
rv = mMainConn->CreateStatement(
1481
NS_LITERAL_CSTRING("UPDATE moz_bookmarks SET syncStatus = "
1482
":sync_status WHERE id = :id"),
1483
getter_AddRefs(mobileRootSyncStatusStmt));
1484
if (NS_FAILED(rv)) return rv;
1485
1486
rv = mobileRootSyncStatusStmt->BindInt32ByName(
1487
NS_LITERAL_CSTRING("sync_status"),
1488
nsINavBookmarksService::SYNC_STATUS_NEW);
1489
if (NS_FAILED(rv)) return rv;
1490
rv = mobileRootSyncStatusStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"),
1491
mobileRootId);
1492
if (NS_FAILED(rv)) return rv;
1493
1494
rv = mobileRootSyncStatusStmt->Execute();
1495
if (NS_FAILED(rv)) return rv;
1496
1497
mMobileRootId = mobileRootId;
1498
}
1499
}
1500
1501
if (!shouldReparentRoots) {
1502
return NS_OK;
1503
}
1504
1505
// At least one root had the wrong parent, so we need to ensure that
1506
// all roots are parented correctly, fix their positions, and bump the
1507
// Sync change counter.
1508
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1509
"CREATE TEMP TRIGGER moz_ensure_bookmark_roots_trigger "
1510
"AFTER UPDATE OF parent ON moz_bookmarks FOR EACH ROW "
1511
"WHEN OLD.parent <> NEW.parent "
1512
"BEGIN "
1513
"UPDATE moz_bookmarks SET "
1514
"syncChangeCounter = syncChangeCounter + 1 "
1515
"WHERE id IN (OLD.parent, NEW.parent, NEW.id); "
1516
1517
"UPDATE moz_bookmarks SET "
1518
"position = position - 1 "
1519
"WHERE parent = OLD.parent AND position >= OLD.position; "
1520
1521
// Fix the positions of the root's old siblings. Since we've already
1522
// moved the root, we need to exclude it from the subquery.
1523
"UPDATE moz_bookmarks SET "
1524
"position = IFNULL((SELECT MAX(position) + 1 FROM moz_bookmarks "
1525
"WHERE parent = NEW.parent AND "
1526
"id <> NEW.id), 0)"
1527
"WHERE id = NEW.id; "
1528
"END"));
1529
if (NS_FAILED(rv)) return rv;
1530
auto guard = MakeScopeExit([&]() {
1531
Unused << mMainConn->ExecuteSimpleSQL(
1532
NS_LITERAL_CSTRING("DROP TRIGGER moz_ensure_bookmark_roots_trigger"));
1533
});
1534
1535
nsCOMPtr<mozIStorageStatement> reparentStmt;
1536
rv = mMainConn->CreateStatement(
1537
NS_LITERAL_CSTRING(
1538
"UPDATE moz_bookmarks SET "
1539
"parent = CASE id WHEN :root_id THEN 0 ELSE :root_id END "
1540
"WHERE id IN (:root_id, :menu_root_id, :toolbar_root_id, "
1541
":tags_root_id, "
1542
":unfiled_root_id, :mobile_root_id)"),
1543
getter_AddRefs(reparentStmt));
1544
if (NS_FAILED(rv)) return rv;
1545
1546
rv = reparentStmt->BindInt64ByName(NS_LITERAL_CSTRING("root_id"), mRootId);
1547
if (NS_FAILED(rv)) return rv;
1548
rv = reparentStmt->BindInt64ByName(NS_LITERAL_CSTRING("menu_root_id"),
1549
mMenuRootId);
1550
if (NS_FAILED(rv)) return rv;
1551
rv = reparentStmt->BindInt64ByName(NS_LITERAL_CSTRING("toolbar_root_id"),
1552
mToolbarRootId);
1553
if (NS_FAILED(rv)) return rv;
1554
rv = reparentStmt->BindInt64ByName(NS_LITERAL_CSTRING("tags_root_id"),
1555
mTagsRootId);
1556
if (NS_FAILED(rv)) return rv;
1557
rv = reparentStmt->BindInt64ByName(NS_LITERAL_CSTRING("unfiled_root_id"),
1558
mUnfiledRootId);
1559
if (NS_FAILED(rv)) return rv;
1560
rv = reparentStmt->BindInt64ByName(NS_LITERAL_CSTRING("mobile_root_id"),
1561
mMobileRootId);
1562
if (NS_FAILED(rv)) return rv;
1563
1564
rv = reparentStmt->Execute();
1565
if (NS_FAILED(rv)) return rv;
1566
1567
return NS_OK;
1568
}
1569
1570
nsresult Database::InitFunctions() {
1571
MOZ_ASSERT(NS_IsMainThread());
1572
1573
nsresult rv = GetUnreversedHostFunction::create(mMainConn);
1574
NS_ENSURE_SUCCESS(rv, rv);
1575
rv = MatchAutoCompleteFunction::create(mMainConn);
1576
NS_ENSURE_SUCCESS(rv, rv);
1577
rv = CalculateFrecencyFunction::create(mMainConn);
1578
NS_ENSURE_SUCCESS(rv, rv);
1579
rv = GenerateGUIDFunction::create(mMainConn);
1580
NS_ENSURE_SUCCESS(rv, rv);
1581
rv = IsValidGUIDFunction::create(mMainConn);
1582
NS_ENSURE_SUCCESS(rv, rv);
1583
rv = FixupURLFunction::create(mMainConn);
1584
NS_ENSURE_SUCCESS(rv, rv);
1585
rv = FrecencyNotificationFunction::create(mMainConn);
1586
NS_ENSURE_SUCCESS(rv, rv);
1587
rv = StoreLastInsertedIdFunction::create(mMainConn);
1588
NS_ENSURE_SUCCESS(rv, rv);
1589
rv = HashFunction::create(mMainConn);
1590
NS_ENSURE_SUCCESS(rv, rv);
1591
rv = GetQueryParamFunction::create(mMainConn);
1592
NS_ENSURE_SUCCESS(rv, rv);
1593
rv = GetPrefixFunction::create(mMainConn);
1594
NS_ENSURE_SUCCESS(rv, rv);
1595
rv = GetHostAndPortFunction::create(mMainConn);
1596
NS_ENSURE_SUCCESS(rv, rv);
1597
rv = StripPrefixAndUserinfoFunction::create(mMainConn);
1598
NS_ENSURE_SUCCESS(rv, rv);
1599
rv = IsFrecencyDecayingFunction::create(mMainConn);
1600
NS_ENSURE_SUCCESS(rv, rv);
1601
rv = SqrtFunction::create(mMainConn);
1602
NS_ENSURE_SUCCESS(rv, rv);
1603
rv = NoteSyncChangeFunction::create(mMainConn);
1604
NS_ENSURE_SUCCESS(rv, rv);
1605
1606
return NS_OK;
1607
}
1608
1609
nsresult Database::InitTempEntities() {
1610
MOZ_ASSERT(NS_IsMainThread());
1611
1612
nsresult rv =
1613
mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
1614
NS_ENSURE_SUCCESS(rv, rv);
1615
rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
1616
NS_ENSURE_SUCCESS(rv, rv);
1617
1618
// Add the triggers that update the moz_origins table as necessary.
1619
rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSINSERT_TEMP);
1620
NS_ENSURE_SUCCESS(rv, rv);
1621
rv = mMainConn->ExecuteSimpleSQL(
1622
CREATE_UPDATEORIGINSINSERT_AFTERDELETE_TRIGGER);
1623
NS_ENSURE_SUCCESS(rv, rv);
1624
rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER);
1625
NS_ENSURE_SUCCESS(rv, rv);
1626
rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSDELETE_TEMP);
1627
NS_ENSURE_SUCCESS(rv, rv);
1628
rv = mMainConn->ExecuteSimpleSQL(
1629
CREATE_UPDATEORIGINSDELETE_AFTERDELETE_TRIGGER);
1630
NS_ENSURE_SUCCESS(rv, rv);
1631
rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER);
1632
NS_ENSURE_SUCCESS(rv, rv);
1633
rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSUPDATE_TEMP);
1634
NS_ENSURE_SUCCESS(rv, rv);
1635
rv = mMainConn->ExecuteSimpleSQL(
1636
CREATE_UPDATEORIGINSUPDATE_AFTERDELETE_TRIGGER);
1637
NS_ENSURE_SUCCESS(rv, rv);
1638
rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER);
1639
NS_ENSURE_SUCCESS(rv, rv);
1640
1641
rv = mMainConn->ExecuteSimpleSQL(
1642
CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
1643
NS_ENSURE_SUCCESS(rv, rv);
1644
rv = mMainConn->ExecuteSimpleSQL(
1645
CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
1646
NS_ENSURE_SUCCESS(rv, rv);
1647
rv = mMainConn->ExecuteSimpleSQL(
1648
CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
1649
NS_ENSURE_SUCCESS(rv, rv);
1650
1651
rv = mMainConn->ExecuteSimpleSQL(
1652
CREATE_KEYWORDS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
1653
NS_ENSURE_SUCCESS(rv, rv);
1654
rv = mMainConn->ExecuteSimpleSQL(
1655
CREATE_KEYWORDS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
1656
NS_ENSURE_SUCCESS(rv, rv);
1657
rv = mMainConn->ExecuteSimpleSQL(
1658
CREATE_KEYWORDS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
1659
NS_ENSURE_SUCCESS(rv, rv);
1660
rv =
1661
mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_DELETED_AFTERINSERT_TRIGGER);
1662
NS_ENSURE_SUCCESS(rv, rv);
1663
rv =
1664
mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_DELETED_AFTERDELETE_TRIGGER);
1665
NS_ENSURE_SUCCESS(rv, rv);
1666
1667
return NS_OK;
1668
}
1669
1670
nsresult Database::MigrateV36Up() {
1671
// Add sync status and change counter tracking columns for bookmarks.
1672
nsCOMPtr<mozIStorageStatement> syncStatusStmt;
1673
nsresult rv = mMainConn->CreateStatement(
1674
NS_LITERAL_CSTRING("SELECT syncStatus FROM moz_bookmarks"),
1675
getter_AddRefs(syncStatusStmt));
1676
if (NS_FAILED(rv)) {
1677
// We default to SYNC_STATUS_UNKNOWN = 0 for existing bookmarks, matching
1678
// the bookmark restore behavior. If Sync is set up, we'll update the status
1679
// to SYNC_STATUS_NORMAL = 2 before the first post-migration sync.
1680
rv = mMainConn->ExecuteSimpleSQL(
1681
NS_LITERAL_CSTRING("ALTER TABLE moz_bookmarks "
1682
"ADD COLUMN syncStatus INTEGER DEFAULT 0 NOT NULL"));
1683
NS_ENSURE_SUCCESS(rv, rv);
1684
}
1685
1686
nsCOMPtr<mozIStorageStatement> syncChangeCounterStmt;
1687
rv = mMainConn->CreateStatement(
1688
NS_LITERAL_CSTRING("SELECT syncChangeCounter FROM moz_bookmarks"),
1689
getter_AddRefs(syncChangeCounterStmt));
1690
if (NS_FAILED(rv)) {
1691
// The change counter starts at 1 for all local bookmarks. It's incremented
1692
// for each modification that should trigger a sync, and decremented after
1693
// the modified bookmark is uploaded to the server.
1694
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1695
"ALTER TABLE moz_bookmarks "
1696
"ADD COLUMN syncChangeCounter INTEGER DEFAULT 1 NOT NULL"));
1697
NS_ENSURE_SUCCESS(rv, rv);
1698
}
1699
1700
nsCOMPtr<mozIStorageStatement> tombstoneTableStmt;
1701
rv = mMainConn->CreateStatement(
1702
NS_LITERAL_CSTRING("SELECT 1 FROM moz_bookmarks_deleted"),
1703
getter_AddRefs(tombstoneTableStmt));
1704
if (NS_FAILED(rv)) {
1705
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_DELETED);
1706
NS_ENSURE_SUCCESS(rv, rv);
1707
}
1708
1709
return NS_OK;
1710
}
1711
1712
nsresult Database::MigrateV37Up() {
1713
// Move favicons to the new database.
1714
// For now we retain the old moz_favicons table, but we empty it.
1715
// This allows for a "safer" downgrade, even if icons will be lost in the
1716
// process. In a couple versions we shall drop moz_favicons completely.
1717
1718
// First, check if the old favicons table still exists.
1719
nsCOMPtr<mozIStorageStatement> stmt;
1720
nsresult rv = mMainConn->CreateStatement(
1721
NS_LITERAL_CSTRING("SELECT url FROM moz_favicons"), getter_AddRefs(stmt));
1722
if (NS_FAILED(rv)) {
1723
// The table has already been removed, nothing to do.
1724
return NS_OK;
1725
}
1726
1727
// The new table accepts only png or svg payloads, so we set a valid width
1728
// only for them, the mime-type for the others. Later we will asynchronously
1729
// try to convert the unsupported payloads, or remove them.
1730
1731
// Add pages.
1732
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1733
"INSERT INTO moz_pages_w_icons (page_url, page_url_hash) "
1734
"SELECT h.url, hash(h.url) "
1735
"FROM moz_places h "
1736
"JOIN moz_favicons f ON f.id = h.favicon_id"));
1737
NS_ENSURE_SUCCESS(rv, rv);
1738
// Set icons as expired, so we will replace them with proper versions at the
1739
// first load.
1740
// Note: we use a peculiarity of Sqlite here, where the column affinity
1741
// is not enforced, thanks to that we can store a string in an integer column.
1742
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1743
"INSERT INTO moz_icons (icon_url, fixed_icon_url_hash, width, data) "
1744
"SELECT url, hash(fixup_url(url)), "
1745
"(CASE WHEN mime_type = 'image/png' THEN 16 "
1746
"WHEN mime_type = 'image/svg+xml' THEN 65535 "
1747
"ELSE mime_type END), "
1748
"data FROM moz_favicons "
1749
"WHERE LENGTH(data) > 0 "));
1750
NS_ENSURE_SUCCESS(rv, rv);
1751
// Create relations.
1752
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1753
"INSERT OR IGNORE INTO moz_icons_to_pages (page_id, icon_id) "
1754
"SELECT (SELECT id FROM moz_pages_w_icons "
1755
"WHERE page_url_hash = h.url_hash "
1756
"AND page_url = h.url), "
1757
"(SELECT id FROM moz_icons "
1758
"WHERE fixed_icon_url_hash = hash(fixup_url(f.url)) "
1759
"AND icon_url = f.url) "
1760
"FROM moz_favicons f "
1761
"JOIN moz_places h on f.id = h.favicon_id"));
1762
NS_ENSURE_SUCCESS(rv, rv);
1763
// Remove old favicons and relations.
1764
rv = mMainConn->ExecuteSimpleSQL(
1765
NS_LITERAL_CSTRING("DELETE FROM moz_favicons"));
1766
NS_ENSURE_SUCCESS(rv, rv);
1767
1768
rv = mMainConn->ExecuteSimpleSQL(
1769
NS_LITERAL_CSTRING("UPDATE moz_places SET favicon_id = NULL"));
1770
NS_ENSURE_SUCCESS(rv, rv);
1771
1772
// The async favicons conversion will happen at the end of the normal schema
1773
// migration.
1774
mShouldConvertIconPayloads = true;
1775
1776
return NS_OK;
1777
}
1778
1779
nsresult Database::MigrateV38Up() {
1780
nsCOMPtr<mozIStorageStatement> stmt;
1781
nsresult rv = mMainConn->CreateStatement(
1782
NS_LITERAL_CSTRING(
1783
"SELECT description, preview_image_url FROM moz_places"),
1784
getter_AddRefs(stmt));
1785
if (NS_FAILED(rv)) {
1786
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1787
"ALTER TABLE moz_places ADD COLUMN description TEXT"));
1788
NS_ENSURE_SUCCESS(rv, rv);
1789
1790
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1791
"ALTER TABLE moz_places ADD COLUMN preview_image_url TEXT"));
1792
NS_ENSURE_SUCCESS(rv, rv);
1793
}
1794
1795
return NS_OK;
1796
}
1797
1798
nsresult Database::MigrateV39Up() {
1799
// Create an index on dateAdded.
1800
nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_DATEADDED);
1801
NS_ENSURE_SUCCESS(rv, rv);
1802
1803
return NS_OK;
1804
}
1805
1806
nsresult Database::MigrateV40Up() {
1807
// We are changing the hashing function to crop the hashed text to a maximum
1808
// length, thus we must recalculate the hashes.
1809
// Due to this, on downgrade some of these may not match, it should be limited
1810
// to unicode and very long urls though.
1811
nsresult rv = mMainConn->ExecuteSimpleSQL(
1812
NS_LITERAL_CSTRING("UPDATE moz_places "
1813
"SET url_hash = hash(url) "
1814
"WHERE url_hash <> hash(url)"));
1815
NS_ENSURE_SUCCESS(rv, rv);
1816
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1817
"UPDATE moz_icons "
1818
"SET fixed_icon_url_hash = hash(fixup_url(icon_url)) "
1819
"WHERE fixed_icon_url_hash <> hash(fixup_url(icon_url))"));
1820
NS_ENSURE_SUCCESS(rv, rv);
1821
rv = mMainConn->ExecuteSimpleSQL(
1822
NS_LITERAL_CSTRING("UPDATE moz_pages_w_icons "
1823
"SET page_url_hash = hash(page_url) "
1824
"WHERE page_url_hash <> hash(page_url)"));
1825
NS_ENSURE_SUCCESS(rv, rv);
1826
return NS_OK;
1827
}
1828
1829
nsresult Database::MigrateV41Up() {
1830
// Remove old favicons entities.
1831
nsresult rv = mMainConn->ExecuteSimpleSQL(
1832
NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_places_faviconindex"));
1833
NS_ENSURE_SUCCESS(rv, rv);
1834
rv = mMainConn->ExecuteSimpleSQL(
1835
NS_LITERAL_CSTRING("DROP TABLE IF EXISTS moz_favicons"));
1836
NS_ENSURE_SUCCESS(rv, rv);
1837
return NS_OK;
1838
}