Coverage Report

Created: 2018-09-25 14:53

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