1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/history/thumbnail_database.h"
6
7#include <algorithm>
8#include <string>
9
10#include "base/bind.h"
11#include "base/debug/alias.h"
12#include "base/debug/dump_without_crashing.h"
13#include "base/files/file_util.h"
14#include "base/format_macros.h"
15#include "base/memory/ref_counted_memory.h"
16#include "base/metrics/histogram.h"
17#include "base/rand_util.h"
18#include "base/strings/string_util.h"
19#include "base/strings/stringprintf.h"
20#include "base/time/time.h"
21#include "components/history/core/browser/history_client.h"
22#include "components/history/core/browser/url_database.h"
23#include "sql/recovery.h"
24#include "sql/statement.h"
25#include "sql/transaction.h"
26#include "third_party/sqlite/sqlite3.h"
27
28#if defined(OS_MACOSX)
29#include "base/mac/mac_util.h"
30#endif
31
32// Description of database tables:
33//
34// icon_mapping
35//   id               Unique ID.
36//   page_url         Page URL which has one or more associated favicons.
37//   icon_id          The ID of favicon that this mapping maps to.
38//
39// favicons           This table associates a row to each favicon for a
40//                    |page_url| in the |icon_mapping| table. This is the
41//                    default favicon |page_url|/favicon.ico plus any favicons
42//                    associated via <link rel="icon_type" href="url">.
43//                    The |id| matches the |icon_id| field in the appropriate
44//                    row in the icon_mapping table.
45//
46//   id               Unique ID.
47//   url              The URL at which the favicon file is located.
48//   icon_type        The type of the favicon specified in the rel attribute of
49//                    the link tag. The FAVICON type is used for the default
50//                    favicon.ico favicon.
51//
52// favicon_bitmaps    This table contains the PNG encoded bitmap data of the
53//                    favicons. There is a separate row for every size in a
54//                    multi resolution bitmap. The bitmap data is associated
55//                    to the favicon via the |icon_id| field which matches
56//                    the |id| field in the appropriate row in the |favicons|
57//                    table.
58//
59//  id                Unique ID.
60//  icon_id           The ID of the favicon that the bitmap is associated to.
61//  last_updated      The time at which this favicon was inserted into the
62//                    table. This is used to determine if it needs to be
63//                    redownloaded from the web.
64//  image_data        PNG encoded data of the favicon.
65//  width             Pixel width of |image_data|.
66//  height            Pixel height of |image_data|.
67
68namespace {
69
70// For this database, schema migrations are deprecated after two
71// years.  This means that the oldest non-deprecated version should be
72// two years old or greater (thus the migrations to get there are
73// older).  Databases containing deprecated versions will be cleared
74// at startup.  Since this database is a cache, losing old data is not
75// fatal (in fact, very old data may be expired immediately at startup
76// anyhow).
77
78// Version 7: 911a634d/r209424 by qsr@chromium.org on 2013-07-01
79// Version 6: 610f923b/r152367 by pkotwicz@chromium.org on 2012-08-20
80// Version 5: e2ee8ae9/r105004 by groby@chromium.org on 2011-10-12
81// Version 4: 5f104d76/r77288 by sky@chromium.org on 2011-03-08 (deprecated)
82// Version 3: 09911bf3/r15 by initial.commit on 2008-07-26 (deprecated)
83
84// Version number of the database.
85// NOTE(shess): When changing the version, add a new golden file for
86// the new version and a test to verify that Init() works with it.
87const int kCurrentVersionNumber = 7;
88const int kCompatibleVersionNumber = 7;
89const int kDeprecatedVersionNumber = 4;  // and earlier.
90
91void FillIconMapping(const sql::Statement& statement,
92                     const GURL& page_url,
93                     history::IconMapping* icon_mapping) {
94  icon_mapping->mapping_id = statement.ColumnInt64(0);
95  icon_mapping->icon_id = statement.ColumnInt64(1);
96  icon_mapping->icon_type =
97      static_cast<favicon_base::IconType>(statement.ColumnInt(2));
98  icon_mapping->icon_url = GURL(statement.ColumnString(3));
99  icon_mapping->page_url = page_url;
100}
101
102enum InvalidStructureType {
103  // NOTE(shess): Intentionally skip bucket 0 to account for
104  // conversion from a boolean histogram.
105  STRUCTURE_EVENT_FAVICON = 1,
106  STRUCTURE_EVENT_VERSION4,
107  STRUCTURE_EVENT_VERSION5,
108
109  // Always keep this at the end.
110  STRUCTURE_EVENT_MAX,
111};
112
113void RecordInvalidStructure(InvalidStructureType invalid_type) {
114  UMA_HISTOGRAM_ENUMERATION("History.InvalidFaviconsDBStructure",
115                            invalid_type, STRUCTURE_EVENT_MAX);
116}
117
118// Attempt to pass 2000 bytes of |debug_info| into a crash dump.
119void DumpWithoutCrashing2000(const std::string& debug_info) {
120  char debug_buf[2000];
121  base::strlcpy(debug_buf, debug_info.c_str(), arraysize(debug_buf));
122  base::debug::Alias(&debug_buf);
123
124  base::debug::DumpWithoutCrashing();
125}
126
127void ReportCorrupt(sql::Connection* db, size_t startup_kb) {
128  // Buffer for accumulating debugging info about the error.  Place
129  // more-relevant information earlier, in case things overflow the
130  // fixed-size buffer.
131  std::string debug_info;
132
133  base::StringAppendF(&debug_info, "SQLITE_CORRUPT, integrity_check:\n");
134
135  // Check files up to 8M to keep things from blocking too long.
136  const size_t kMaxIntegrityCheckSize = 8192;
137  if (startup_kb > kMaxIntegrityCheckSize) {
138    base::StringAppendF(&debug_info, "too big %" PRIuS "\n", startup_kb);
139  } else {
140    std::vector<std::string> messages;
141
142    const base::TimeTicks before = base::TimeTicks::Now();
143    db->FullIntegrityCheck(&messages);
144    base::StringAppendF(&debug_info, "# %" PRIx64 " ms, %" PRIuS " records\n",
145                        (base::TimeTicks::Now() - before).InMilliseconds(),
146                        messages.size());
147
148    // SQLite returns up to 100 messages by default, trim deeper to
149    // keep close to the 2000-character size limit for dumping.
150    //
151    // TODO(shess): If the first 20 tend to be actionable, test if
152    // passing the count to integrity_check makes it exit earlier.  In
153    // that case it may be possible to greatly ease the size
154    // restriction.
155    const size_t kMaxMessages = 20;
156    for (size_t i = 0; i < kMaxMessages && i < messages.size(); ++i) {
157      base::StringAppendF(&debug_info, "%s\n", messages[i].c_str());
158    }
159  }
160
161  DumpWithoutCrashing2000(debug_info);
162}
163
164void ReportError(sql::Connection* db, int error) {
165  // Buffer for accumulating debugging info about the error.  Place
166  // more-relevant information earlier, in case things overflow the
167  // fixed-size buffer.
168  std::string debug_info;
169
170  // The error message from the failed operation.
171  base::StringAppendF(&debug_info, "db error: %d/%s\n",
172                      db->GetErrorCode(), db->GetErrorMessage());
173
174  // System errno information.
175  base::StringAppendF(&debug_info, "errno: %d\n", db->GetLastErrno());
176
177  // SQLITE_ERROR reports seem to be attempts to upgrade invalid
178  // schema, try to log that info.
179  if (error == SQLITE_ERROR) {
180    const char* kVersionSql = "SELECT value FROM meta WHERE key = 'version'";
181    if (db->IsSQLValid(kVersionSql)) {
182      sql::Statement statement(db->GetUniqueStatement(kVersionSql));
183      if (statement.Step()) {
184        debug_info += "version: ";
185        debug_info += statement.ColumnString(0);
186        debug_info += '\n';
187      } else if (statement.Succeeded()) {
188        debug_info += "version: none\n";
189      } else {
190        debug_info += "version: error\n";
191      }
192    } else {
193      debug_info += "version: invalid\n";
194    }
195
196    debug_info += "schema:\n";
197
198    // sqlite_master has columns:
199    //   type - "index" or "table".
200    //   name - name of created element.
201    //   tbl_name - name of element, or target table in case of index.
202    //   rootpage - root page of the element in database file.
203    //   sql - SQL to create the element.
204    // In general, the |sql| column is sufficient to derive the other
205    // columns.  |rootpage| is not interesting for debugging, without
206    // the contents of the database.  The COALESCE is because certain
207    // automatic elements will have a |name| but no |sql|,
208    const char* kSchemaSql = "SELECT COALESCE(sql, name) FROM sqlite_master";
209    sql::Statement statement(db->GetUniqueStatement(kSchemaSql));
210    while (statement.Step()) {
211      debug_info += statement.ColumnString(0);
212      debug_info += '\n';
213    }
214    if (!statement.Succeeded())
215      debug_info += "error\n";
216  }
217
218  // TODO(shess): Think of other things to log.  Not logging the
219  // statement text because the backtrace should suffice in most
220  // cases.  The database schema is a possibility, but the
221  // likelihood of recursive error callbacks makes that risky (same
222  // reasoning applies to other data fetched from the database).
223
224  DumpWithoutCrashing2000(debug_info);
225}
226
227// TODO(shess): If this proves out, perhaps lift the code out to
228// chrome/browser/diagnostics/sqlite_diagnostics.{h,cc}.
229void GenerateDiagnostics(sql::Connection* db,
230                         size_t startup_kb,
231                         int extended_error) {
232  int error = (extended_error & 0xFF);
233
234  // Infrequently report information about the error up to the crash
235  // server.
236  static const uint64 kReportsPerMillion = 50000;
237
238  // Since some/most errors will not resolve themselves, only report
239  // once per Chrome run.
240  static bool reported = false;
241  if (reported)
242    return;
243
244  uint64 rand = base::RandGenerator(1000000);
245  if (error == SQLITE_CORRUPT) {
246    // Once the database is known to be corrupt, it will generate a
247    // stream of errors until someone fixes it, so give one chance.
248    // Set first in case of errors in generating the report.
249    reported = true;
250
251    // Corrupt cases currently dominate, report them very infrequently.
252    static const uint64 kCorruptReportsPerMillion = 10000;
253    if (rand < kCorruptReportsPerMillion)
254      ReportCorrupt(db, startup_kb);
255  } else if (error == SQLITE_READONLY) {
256    // SQLITE_READONLY appears similar to SQLITE_CORRUPT - once it
257    // is seen, it is almost guaranteed to be seen again.
258    reported = true;
259
260    if (rand < kReportsPerMillion)
261      ReportError(db, extended_error);
262  } else {
263    // Only set the flag when making a report.  This should allow
264    // later (potentially different) errors in a stream of errors to
265    // be reported.
266    //
267    // TODO(shess): Would it be worthwile to audit for which cases
268    // want once-only handling?  Sqlite.Error.Thumbnail shows
269    // CORRUPT and READONLY as almost 95% of all reports on these
270    // channels, so probably easier to just harvest from the field.
271    if (rand < kReportsPerMillion) {
272      reported = true;
273      ReportError(db, extended_error);
274    }
275  }
276}
277
278// NOTE(shess): Schema modifications must consider initial creation in
279// |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in
280// |RetainDataForPageUrls()|.
281bool InitTables(sql::Connection* db) {
282  const char kIconMappingSql[] =
283      "CREATE TABLE IF NOT EXISTS icon_mapping"
284      "("
285      "id INTEGER PRIMARY KEY,"
286      "page_url LONGVARCHAR NOT NULL,"
287      "icon_id INTEGER"
288      ")";
289  if (!db->Execute(kIconMappingSql))
290    return false;
291
292  const char kFaviconsSql[] =
293      "CREATE TABLE IF NOT EXISTS favicons"
294      "("
295      "id INTEGER PRIMARY KEY,"
296      "url LONGVARCHAR NOT NULL,"
297      // default icon_type FAVICON to be consistent with past migration.
298      "icon_type INTEGER DEFAULT 1"
299      ")";
300  if (!db->Execute(kFaviconsSql))
301    return false;
302
303  const char kFaviconBitmapsSql[] =
304      "CREATE TABLE IF NOT EXISTS favicon_bitmaps"
305      "("
306      "id INTEGER PRIMARY KEY,"
307      "icon_id INTEGER NOT NULL,"
308      "last_updated INTEGER DEFAULT 0,"
309      "image_data BLOB,"
310      "width INTEGER DEFAULT 0,"
311      "height INTEGER DEFAULT 0"
312      ")";
313  if (!db->Execute(kFaviconBitmapsSql))
314    return false;
315
316  return true;
317}
318
319// NOTE(shess): Schema modifications must consider initial creation in
320// |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in
321// |RetainDataForPageUrls()|.
322bool InitIndices(sql::Connection* db) {
323  const char kIconMappingUrlIndexSql[] =
324      "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx"
325      " ON icon_mapping(page_url)";
326  const char kIconMappingIdIndexSql[] =
327      "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx"
328      " ON icon_mapping(icon_id)";
329  if (!db->Execute(kIconMappingUrlIndexSql) ||
330      !db->Execute(kIconMappingIdIndexSql)) {
331    return false;
332  }
333
334  const char kFaviconsIndexSql[] =
335      "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)";
336  if (!db->Execute(kFaviconsIndexSql))
337    return false;
338
339  const char kFaviconBitmapsIndexSql[] =
340      "CREATE INDEX IF NOT EXISTS favicon_bitmaps_icon_id ON "
341      "favicon_bitmaps(icon_id)";
342  if (!db->Execute(kFaviconBitmapsIndexSql))
343    return false;
344
345  return true;
346}
347
348enum RecoveryEventType {
349  RECOVERY_EVENT_RECOVERED = 0,
350  RECOVERY_EVENT_FAILED_SCOPER,
351  RECOVERY_EVENT_FAILED_META_VERSION_ERROR,  // obsolete
352  RECOVERY_EVENT_FAILED_META_VERSION_NONE,  // obsolete
353  RECOVERY_EVENT_FAILED_META_WRONG_VERSION6,  // obsolete
354  RECOVERY_EVENT_FAILED_META_WRONG_VERSION5,  // obsolete
355  RECOVERY_EVENT_FAILED_META_WRONG_VERSION,
356  RECOVERY_EVENT_FAILED_RECOVER_META,  // obsolete
357  RECOVERY_EVENT_FAILED_META_INSERT,  // obsolete
358  RECOVERY_EVENT_FAILED_INIT,
359  RECOVERY_EVENT_FAILED_RECOVER_FAVICONS,  // obsolete
360  RECOVERY_EVENT_FAILED_FAVICONS_INSERT,  // obsolete
361  RECOVERY_EVENT_FAILED_RECOVER_FAVICON_BITMAPS,  // obsolete
362  RECOVERY_EVENT_FAILED_FAVICON_BITMAPS_INSERT,  // obsolete
363  RECOVERY_EVENT_FAILED_RECOVER_ICON_MAPPING,  // obsolete
364  RECOVERY_EVENT_FAILED_ICON_MAPPING_INSERT,  // obsolete
365  RECOVERY_EVENT_RECOVERED_VERSION6,  // obsolete
366  RECOVERY_EVENT_FAILED_META_INIT,
367  RECOVERY_EVENT_FAILED_META_VERSION,
368  RECOVERY_EVENT_DEPRECATED,
369  RECOVERY_EVENT_FAILED_V5_INITSCHEMA,  // obsolete
370  RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS,  // obsolete
371  RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING,  // obsolete
372  RECOVERY_EVENT_RECOVERED_VERSION5,  // obsolete
373  RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS,
374  RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS,
375  RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING,
376  RECOVERY_EVENT_FAILED_COMMIT,
377
378  // Always keep this at the end.
379  RECOVERY_EVENT_MAX,
380};
381
382void RecordRecoveryEvent(RecoveryEventType recovery_event) {
383  UMA_HISTOGRAM_ENUMERATION("History.FaviconsRecovery",
384                            recovery_event, RECOVERY_EVENT_MAX);
385}
386
387// Recover the database to the extent possible, razing it if recovery
388// is not possible.
389// TODO(shess): This is mostly just a safe proof of concept.  In the
390// real world, this database is probably not worthwhile recovering, as
391// opposed to just razing it and starting over whenever corruption is
392// detected.  So this database is a good test subject.
393void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) {
394  // NOTE(shess): This code is currently specific to the version
395  // number.  I am working on simplifying things to loosen the
396  // dependency, meanwhile contact me if you need to bump the version.
397  DCHECK_EQ(7, kCurrentVersionNumber);
398
399  // TODO(shess): Reset back after?
400  db->reset_error_callback();
401
402  // For histogram purposes.
403  size_t favicons_rows_recovered = 0;
404  size_t favicon_bitmaps_rows_recovered = 0;
405  size_t icon_mapping_rows_recovered = 0;
406  int64 original_size = 0;
407  base::GetFileSize(db_path, &original_size);
408
409  scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
410  if (!recovery) {
411    // TODO(shess): Unable to create recovery connection.  This
412    // implies something substantial is wrong.  At this point |db| has
413    // been poisoned so there is nothing really to do.
414    //
415    // Possible responses are unclear.  If the failure relates to a
416    // problem somehow specific to the temporary file used to back the
417    // database, then an in-memory database could possibly be used.
418    // This could potentially allow recovering the main database, and
419    // might be simple to implement w/in Begin().
420    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCOPER);
421    return;
422  }
423
424  // Setup the meta recovery table and fetch the version number from
425  // the corrupt database.
426  int version = 0;
427  if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) {
428    // TODO(shess): Prior histograms indicate all failures are in
429    // creating the recover virtual table for corrupt.meta.  The table
430    // may not exist, or the database may be too far gone.  Either
431    // way, unclear how to resolve.
432    sql::Recovery::Rollback(recovery.Pass());
433    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION);
434    return;
435  }
436
437  // This code may be able to fetch version information that the regular
438  // deprecation path cannot.
439  // NOTE(shess): v5 and v6 are currently not deprecated in the normal Init()
440  // path, but are deprecated in the recovery path in the interest of keeping
441  // the code simple.  http://crbug.com/327485 for numbers.
442  DCHECK_LE(kDeprecatedVersionNumber, 6);
443  if (version <= 6) {
444    sql::Recovery::Unrecoverable(recovery.Pass());
445    RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED);
446    return;
447  }
448
449  // Earlier versions have been handled or deprecated, later versions should be
450  // impossible.
451  if (version != 7) {
452    sql::Recovery::Unrecoverable(recovery.Pass());
453    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION);
454    return;
455  }
456
457  // Recover to current schema version.
458  sql::MetaTable recover_meta_table;
459  if (!recover_meta_table.Init(recovery->db(), kCurrentVersionNumber,
460                               kCompatibleVersionNumber)) {
461    sql::Recovery::Rollback(recovery.Pass());
462    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT);
463    return;
464  }
465
466  // Create a fresh version of the database.  The recovery code uses
467  // conflict-resolution to handle duplicates, so the indices are
468  // necessary.
469  if (!InitTables(recovery->db()) || !InitIndices(recovery->db())) {
470    // TODO(shess): Unable to create the new schema in the new
471    // database.  The new database should be a temporary file, so
472    // being unable to work with it is pretty unclear.
473    //
474    // What are the potential responses, even?  The recovery database
475    // could be opened as in-memory.  If the temp database had a
476    // filesystem problem and the temp filesystem differs from the
477    // main database, then that could fix it.
478    sql::Recovery::Rollback(recovery.Pass());
479    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_INIT);
480    return;
481  }
482
483  if (!recovery->AutoRecoverTable("favicons", 0, &favicons_rows_recovered)) {
484    sql::Recovery::Rollback(recovery.Pass());
485    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS);
486    return;
487  }
488  if (!recovery->AutoRecoverTable("favicon_bitmaps", 0,
489                                  &favicon_bitmaps_rows_recovered)) {
490    sql::Recovery::Rollback(recovery.Pass());
491    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS);
492    return;
493  }
494  if (!recovery->AutoRecoverTable("icon_mapping", 0,
495                                  &icon_mapping_rows_recovered)) {
496    sql::Recovery::Rollback(recovery.Pass());
497    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING);
498    return;
499  }
500
501  // TODO(shess): Is it possible/likely to have broken foreign-key
502  // issues with the tables?
503  // - icon_mapping.icon_id maps to no favicons.id
504  // - favicon_bitmaps.icon_id maps to no favicons.id
505  // - favicons.id is referenced by no icon_mapping.icon_id
506  // - favicons.id is referenced by no favicon_bitmaps.icon_id
507  // This step is possibly not worth the effort necessary to develop
508  // and sequence the statements, as it is basically a form of garbage
509  // collection.
510
511  if (!sql::Recovery::Recovered(recovery.Pass())) {
512    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_COMMIT);
513    return;
514  }
515
516  // Track the size of the recovered database relative to the size of
517  // the input database.  The size should almost always be smaller,
518  // unless the input database was empty to start with.  If the
519  // percentage results are very low, something is awry.
520  int64 final_size = 0;
521  if (original_size > 0 &&
522      base::GetFileSize(db_path, &final_size) &&
523      final_size > 0) {
524    int percentage = static_cast<int>(original_size * 100 / final_size);
525    UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage",
526                             std::max(100, percentage));
527  }
528
529  // Using 10,000 because these cases mostly care about "none
530  // recovered" and "lots recovered".  More than 10,000 rows recovered
531  // probably means there's something wrong with the profile.
532  UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons",
533                             favicons_rows_recovered);
534  UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFaviconBitmaps",
535                             favicon_bitmaps_rows_recovered);
536  UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping",
537                             icon_mapping_rows_recovered);
538
539  RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED);
540}
541
542void DatabaseErrorCallback(sql::Connection* db,
543                           const base::FilePath& db_path,
544                           size_t startup_kb,
545                           history::HistoryClient* history_client,
546                           int extended_error,
547                           sql::Statement* stmt) {
548  // TODO(shess): Assert that this is running on a safe thread.
549  // AFAICT, should be the history thread, but at this level I can't
550  // see how to reach that.
551
552  if (history_client && history_client->ShouldReportDatabaseError()) {
553    GenerateDiagnostics(db, startup_kb, extended_error);
554  }
555
556  // Attempt to recover corrupt databases.
557  int error = (extended_error & 0xFF);
558  if (error == SQLITE_CORRUPT ||
559      error == SQLITE_CANTOPEN ||
560      error == SQLITE_NOTADB) {
561    RecoverDatabaseOrRaze(db, db_path);
562  }
563
564  // The default handling is to assert on debug and to ignore on release.
565  if (!sql::Connection::ShouldIgnoreSqliteError(extended_error))
566    DLOG(FATAL) << db->GetErrorMessage();
567}
568
569}  // namespace
570
571namespace history {
572
573ThumbnailDatabase::IconMappingEnumerator::IconMappingEnumerator() {
574}
575
576ThumbnailDatabase::IconMappingEnumerator::~IconMappingEnumerator() {
577}
578
579bool ThumbnailDatabase::IconMappingEnumerator::GetNextIconMapping(
580    IconMapping* icon_mapping) {
581  if (!statement_.Step())
582    return false;
583  FillIconMapping(statement_, GURL(statement_.ColumnString(4)), icon_mapping);
584  return true;
585}
586
587ThumbnailDatabase::ThumbnailDatabase(HistoryClient* history_client)
588    : history_client_(history_client) {
589}
590
591ThumbnailDatabase::~ThumbnailDatabase() {
592  // The DBCloseScoper will delete the DB and the cache.
593}
594
595sql::InitStatus ThumbnailDatabase::Init(const base::FilePath& db_name) {
596  // TODO(shess): Consider separating database open from schema setup.
597  // With that change, this code could Raze() from outside the
598  // transaction, rather than needing RazeAndClose() in InitImpl().
599
600  // Retry failed setup in case the recovery system fixed things.
601  const size_t kAttempts = 2;
602
603  sql::InitStatus status = sql::INIT_FAILURE;
604  for (size_t i = 0; i < kAttempts; ++i) {
605    status = InitImpl(db_name);
606    if (status == sql::INIT_OK)
607      return status;
608
609    meta_table_.Reset();
610    db_.Close();
611  }
612  return status;
613}
614
615void ThumbnailDatabase::ComputeDatabaseMetrics() {
616  sql::Statement favicon_count(
617      db_.GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM favicons"));
618  UMA_HISTOGRAM_COUNTS_10000(
619      "History.NumFaviconsInDB",
620      favicon_count.Step() ? favicon_count.ColumnInt(0) : 0);
621}
622
623void ThumbnailDatabase::BeginTransaction() {
624  db_.BeginTransaction();
625}
626
627void ThumbnailDatabase::CommitTransaction() {
628  db_.CommitTransaction();
629}
630
631void ThumbnailDatabase::RollbackTransaction() {
632  db_.RollbackTransaction();
633}
634
635void ThumbnailDatabase::Vacuum() {
636  DCHECK(db_.transaction_nesting() == 0) <<
637      "Can not have a transaction when vacuuming.";
638  ignore_result(db_.Execute("VACUUM"));
639}
640
641void ThumbnailDatabase::TrimMemory(bool aggressively) {
642  db_.TrimMemory(aggressively);
643}
644
645bool ThumbnailDatabase::GetFaviconBitmapIDSizes(
646    favicon_base::FaviconID icon_id,
647    std::vector<FaviconBitmapIDSize>* bitmap_id_sizes) {
648  DCHECK(icon_id);
649  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
650      "SELECT id, width, height FROM favicon_bitmaps WHERE icon_id=?"));
651  statement.BindInt64(0, icon_id);
652
653  bool result = false;
654  while (statement.Step()) {
655    result = true;
656    if (!bitmap_id_sizes)
657      return result;
658
659    FaviconBitmapIDSize bitmap_id_size;
660    bitmap_id_size.bitmap_id = statement.ColumnInt64(0);
661    bitmap_id_size.pixel_size = gfx::Size(statement.ColumnInt(1),
662                                          statement.ColumnInt(2));
663    bitmap_id_sizes->push_back(bitmap_id_size);
664  }
665  return result;
666}
667
668bool ThumbnailDatabase::GetFaviconBitmaps(
669    favicon_base::FaviconID icon_id,
670    std::vector<FaviconBitmap>* favicon_bitmaps) {
671  DCHECK(icon_id);
672  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
673      "SELECT id, last_updated, image_data, width, height FROM favicon_bitmaps "
674      "WHERE icon_id=?"));
675  statement.BindInt64(0, icon_id);
676
677  bool result = false;
678  while (statement.Step()) {
679    result = true;
680    if (!favicon_bitmaps)
681      return result;
682
683    FaviconBitmap favicon_bitmap;
684    favicon_bitmap.bitmap_id = statement.ColumnInt64(0);
685    favicon_bitmap.icon_id = icon_id;
686    favicon_bitmap.last_updated =
687        base::Time::FromInternalValue(statement.ColumnInt64(1));
688    if (statement.ColumnByteLength(2) > 0) {
689      scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes());
690      statement.ColumnBlobAsVector(2, &data->data());
691      favicon_bitmap.bitmap_data = data;
692    }
693    favicon_bitmap.pixel_size = gfx::Size(statement.ColumnInt(3),
694                                          statement.ColumnInt(4));
695    favicon_bitmaps->push_back(favicon_bitmap);
696  }
697  return result;
698}
699
700bool ThumbnailDatabase::GetFaviconBitmap(
701    FaviconBitmapID bitmap_id,
702    base::Time* last_updated,
703    scoped_refptr<base::RefCountedMemory>* png_icon_data,
704    gfx::Size* pixel_size) {
705  DCHECK(bitmap_id);
706  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
707      "SELECT last_updated, image_data, width, height FROM favicon_bitmaps "
708      "WHERE id=?"));
709  statement.BindInt64(0, bitmap_id);
710
711  if (!statement.Step())
712    return false;
713
714  if (last_updated)
715    *last_updated = base::Time::FromInternalValue(statement.ColumnInt64(0));
716
717  if (png_icon_data && statement.ColumnByteLength(1) > 0) {
718    scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes());
719    statement.ColumnBlobAsVector(1, &data->data());
720    *png_icon_data = data;
721  }
722
723  if (pixel_size) {
724    *pixel_size = gfx::Size(statement.ColumnInt(2),
725                            statement.ColumnInt(3));
726  }
727  return true;
728}
729
730FaviconBitmapID ThumbnailDatabase::AddFaviconBitmap(
731    favicon_base::FaviconID icon_id,
732    const scoped_refptr<base::RefCountedMemory>& icon_data,
733    base::Time time,
734    const gfx::Size& pixel_size) {
735  DCHECK(icon_id);
736  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
737      "INSERT INTO favicon_bitmaps (icon_id, image_data, last_updated, width, "
738      "height) VALUES (?, ?, ?, ?, ?)"));
739  statement.BindInt64(0, icon_id);
740  if (icon_data.get() && icon_data->size()) {
741    statement.BindBlob(1, icon_data->front(),
742                       static_cast<int>(icon_data->size()));
743  } else {
744    statement.BindNull(1);
745  }
746  statement.BindInt64(2, time.ToInternalValue());
747  statement.BindInt(3, pixel_size.width());
748  statement.BindInt(4, pixel_size.height());
749
750  if (!statement.Run())
751    return 0;
752  return db_.GetLastInsertRowId();
753}
754
755bool ThumbnailDatabase::SetFaviconBitmap(
756    FaviconBitmapID bitmap_id,
757    scoped_refptr<base::RefCountedMemory> bitmap_data,
758    base::Time time) {
759  DCHECK(bitmap_id);
760  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
761      "UPDATE favicon_bitmaps SET image_data=?, last_updated=? WHERE id=?"));
762  if (bitmap_data.get() && bitmap_data->size()) {
763    statement.BindBlob(0, bitmap_data->front(),
764                       static_cast<int>(bitmap_data->size()));
765  } else {
766    statement.BindNull(0);
767  }
768  statement.BindInt64(1, time.ToInternalValue());
769  statement.BindInt64(2, bitmap_id);
770
771  return statement.Run();
772}
773
774bool ThumbnailDatabase::SetFaviconBitmapLastUpdateTime(
775    FaviconBitmapID bitmap_id,
776    base::Time time) {
777  DCHECK(bitmap_id);
778  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
779      "UPDATE favicon_bitmaps SET last_updated=? WHERE id=?"));
780  statement.BindInt64(0, time.ToInternalValue());
781  statement.BindInt64(1, bitmap_id);
782  return statement.Run();
783}
784
785bool ThumbnailDatabase::DeleteFaviconBitmap(FaviconBitmapID bitmap_id) {
786  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
787      "DELETE FROM favicon_bitmaps WHERE id=?"));
788  statement.BindInt64(0, bitmap_id);
789  return statement.Run();
790}
791
792bool ThumbnailDatabase::SetFaviconOutOfDate(favicon_base::FaviconID icon_id) {
793  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
794      "UPDATE favicon_bitmaps SET last_updated=? WHERE icon_id=?"));
795  statement.BindInt64(0, 0);
796  statement.BindInt64(1, icon_id);
797
798  return statement.Run();
799}
800
801favicon_base::FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL(
802    const GURL& icon_url,
803    int required_icon_type,
804    favicon_base::IconType* icon_type) {
805  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
806      "SELECT id, icon_type FROM favicons WHERE url=? AND (icon_type & ? > 0) "
807      "ORDER BY icon_type DESC"));
808  statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url));
809  statement.BindInt(1, required_icon_type);
810
811  if (!statement.Step())
812    return 0;  // not cached
813
814  if (icon_type)
815    *icon_type = static_cast<favicon_base::IconType>(statement.ColumnInt(1));
816  return statement.ColumnInt64(0);
817}
818
819bool ThumbnailDatabase::GetFaviconHeader(favicon_base::FaviconID icon_id,
820                                         GURL* icon_url,
821                                         favicon_base::IconType* icon_type) {
822  DCHECK(icon_id);
823
824  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
825      "SELECT url, icon_type FROM favicons WHERE id=?"));
826  statement.BindInt64(0, icon_id);
827
828  if (!statement.Step())
829    return false;  // No entry for the id.
830
831  if (icon_url)
832    *icon_url = GURL(statement.ColumnString(0));
833  if (icon_type)
834    *icon_type = static_cast<favicon_base::IconType>(statement.ColumnInt(1));
835
836  return true;
837}
838
839favicon_base::FaviconID ThumbnailDatabase::AddFavicon(
840    const GURL& icon_url,
841    favicon_base::IconType icon_type) {
842
843  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
844      "INSERT INTO favicons (url, icon_type) VALUES (?, ?)"));
845  statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url));
846  statement.BindInt(1, icon_type);
847
848  if (!statement.Run())
849    return 0;
850  return db_.GetLastInsertRowId();
851}
852
853favicon_base::FaviconID ThumbnailDatabase::AddFavicon(
854    const GURL& icon_url,
855    favicon_base::IconType icon_type,
856    const scoped_refptr<base::RefCountedMemory>& icon_data,
857    base::Time time,
858    const gfx::Size& pixel_size) {
859  favicon_base::FaviconID icon_id = AddFavicon(icon_url, icon_type);
860  if (!icon_id || !AddFaviconBitmap(icon_id, icon_data, time, pixel_size))
861    return 0;
862
863  return icon_id;
864}
865
866bool ThumbnailDatabase::DeleteFavicon(favicon_base::FaviconID id) {
867  sql::Statement statement;
868  statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE,
869      "DELETE FROM favicons WHERE id = ?"));
870  statement.BindInt64(0, id);
871  if (!statement.Run())
872    return false;
873
874  statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE,
875      "DELETE FROM favicon_bitmaps WHERE icon_id = ?"));
876  statement.BindInt64(0, id);
877  return statement.Run();
878}
879
880bool ThumbnailDatabase::GetIconMappingsForPageURL(
881    const GURL& page_url,
882    int required_icon_types,
883    std::vector<IconMapping>* filtered_mapping_data) {
884  std::vector<IconMapping> mapping_data;
885  if (!GetIconMappingsForPageURL(page_url, &mapping_data))
886    return false;
887
888  bool result = false;
889  for (std::vector<IconMapping>::iterator m = mapping_data.begin();
890       m != mapping_data.end(); ++m) {
891    if (m->icon_type & required_icon_types) {
892      result = true;
893      if (!filtered_mapping_data)
894        return result;
895
896      // Restrict icon type of subsequent matches to |m->icon_type|.
897      // |m->icon_type| is the largest IconType in |mapping_data| because
898      // |mapping_data| is sorted in descending order of IconType.
899      required_icon_types = m->icon_type;
900
901      filtered_mapping_data->push_back(*m);
902    }
903  }
904  return result;
905}
906
907bool ThumbnailDatabase::GetIconMappingsForPageURL(
908    const GURL& page_url,
909    std::vector<IconMapping>* mapping_data) {
910  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
911      "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, "
912      "favicons.url "
913      "FROM icon_mapping "
914      "INNER JOIN favicons "
915      "ON icon_mapping.icon_id = favicons.id "
916      "WHERE icon_mapping.page_url=? "
917      "ORDER BY favicons.icon_type DESC"));
918  statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));
919
920  bool result = false;
921  while (statement.Step()) {
922    result = true;
923    if (!mapping_data)
924      return result;
925
926    IconMapping icon_mapping;
927    FillIconMapping(statement, page_url, &icon_mapping);
928    mapping_data->push_back(icon_mapping);
929  }
930  return result;
931}
932
933IconMappingID ThumbnailDatabase::AddIconMapping(
934    const GURL& page_url,
935    favicon_base::FaviconID icon_id) {
936  const char kSql[] =
937      "INSERT INTO icon_mapping (page_url, icon_id) VALUES (?, ?)";
938  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql));
939  statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));
940  statement.BindInt64(1, icon_id);
941
942  if (!statement.Run())
943    return 0;
944
945  return db_.GetLastInsertRowId();
946}
947
948bool ThumbnailDatabase::UpdateIconMapping(IconMappingID mapping_id,
949                                          favicon_base::FaviconID icon_id) {
950  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
951      "UPDATE icon_mapping SET icon_id=? WHERE id=?"));
952  statement.BindInt64(0, icon_id);
953  statement.BindInt64(1, mapping_id);
954
955  return statement.Run();
956}
957
958bool ThumbnailDatabase::DeleteIconMappings(const GURL& page_url) {
959  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
960      "DELETE FROM icon_mapping WHERE page_url = ?"));
961  statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));
962
963  return statement.Run();
964}
965
966bool ThumbnailDatabase::DeleteIconMapping(IconMappingID mapping_id) {
967  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
968      "DELETE FROM icon_mapping WHERE id=?"));
969  statement.BindInt64(0, mapping_id);
970
971  return statement.Run();
972}
973
974bool ThumbnailDatabase::HasMappingFor(favicon_base::FaviconID id) {
975  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
976      "SELECT id FROM icon_mapping "
977      "WHERE icon_id=?"));
978  statement.BindInt64(0, id);
979
980  return statement.Step();
981}
982
983bool ThumbnailDatabase::CloneIconMappings(const GURL& old_page_url,
984                                          const GURL& new_page_url) {
985  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
986      "SELECT icon_id FROM icon_mapping "
987      "WHERE page_url=?"));
988  if (!statement.is_valid())
989    return false;
990
991  // Do nothing if there are existing bindings
992  statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url));
993  if (statement.Step())
994    return true;
995
996  statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE,
997      "INSERT INTO icon_mapping (page_url, icon_id) "
998        "SELECT ?, icon_id FROM icon_mapping "
999        "WHERE page_url = ?"));
1000
1001  statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url));
1002  statement.BindString(1, URLDatabase::GURLToDatabaseURL(old_page_url));
1003  return statement.Run();
1004}
1005
1006bool ThumbnailDatabase::InitIconMappingEnumerator(
1007    favicon_base::IconType type,
1008    IconMappingEnumerator* enumerator) {
1009  DCHECK(!enumerator->statement_.is_valid());
1010  enumerator->statement_.Assign(db_.GetCachedStatement(
1011      SQL_FROM_HERE,
1012      "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, "
1013             "favicons.url, icon_mapping.page_url "
1014         "FROM icon_mapping JOIN favicons ON ("
1015              "icon_mapping.icon_id = favicons.id) "
1016         "WHERE favicons.icon_type = ?"));
1017  enumerator->statement_.BindInt(0, type);
1018  return enumerator->statement_.is_valid();
1019}
1020
1021bool ThumbnailDatabase::RetainDataForPageUrls(
1022    const std::vector<GURL>& urls_to_keep) {
1023  sql::Transaction transaction(&db_);
1024  if (!transaction.Begin())
1025    return false;
1026
1027  // temp.icon_id_mapping generates new icon ids as consecutive
1028  // integers starting from 1, and maps them to the old icon ids.
1029  {
1030    const char kIconMappingCreate[] =
1031        "CREATE TEMP TABLE icon_id_mapping "
1032        "("
1033        "new_icon_id INTEGER PRIMARY KEY,"
1034        "old_icon_id INTEGER NOT NULL UNIQUE"
1035        ")";
1036    if (!db_.Execute(kIconMappingCreate))
1037      return false;
1038
1039    // Insert the icon ids for retained urls, skipping duplicates.
1040    const char kIconMappingSql[] =
1041        "INSERT OR IGNORE INTO temp.icon_id_mapping (old_icon_id) "
1042        "SELECT icon_id FROM icon_mapping WHERE page_url = ?";
1043    sql::Statement statement(db_.GetUniqueStatement(kIconMappingSql));
1044    for (std::vector<GURL>::const_iterator
1045             i = urls_to_keep.begin(); i != urls_to_keep.end(); ++i) {
1046      statement.BindString(0, URLDatabase::GURLToDatabaseURL(*i));
1047      if (!statement.Run())
1048        return false;
1049      statement.Reset(true);
1050    }
1051  }
1052
1053  const char kRenameIconMappingTable[] =
1054      "ALTER TABLE icon_mapping RENAME TO old_icon_mapping";
1055  const char kCopyIconMapping[] =
1056      "INSERT INTO icon_mapping (page_url, icon_id) "
1057      "SELECT old.page_url, mapping.new_icon_id "
1058      "FROM old_icon_mapping AS old "
1059      "JOIN temp.icon_id_mapping AS mapping "
1060      "ON (old.icon_id = mapping.old_icon_id)";
1061  const char kDropOldIconMappingTable[] = "DROP TABLE old_icon_mapping";
1062
1063  const char kRenameFaviconsTable[] =
1064      "ALTER TABLE favicons RENAME TO old_favicons";
1065  const char kCopyFavicons[] =
1066      "INSERT INTO favicons (id, url, icon_type) "
1067      "SELECT mapping.new_icon_id, old.url, old.icon_type "
1068      "FROM old_favicons AS old "
1069      "JOIN temp.icon_id_mapping AS mapping "
1070      "ON (old.id = mapping.old_icon_id)";
1071  const char kDropOldFaviconsTable[] = "DROP TABLE old_favicons";
1072
1073  const char kRenameFaviconBitmapsTable[] =
1074      "ALTER TABLE favicon_bitmaps RENAME TO old_favicon_bitmaps";
1075  const char kCopyFaviconBitmaps[] =
1076      "INSERT INTO favicon_bitmaps "
1077      "  (icon_id, last_updated, image_data, width, height) "
1078      "SELECT mapping.new_icon_id, old.last_updated, "
1079      "    old.image_data, old.width, old.height "
1080      "FROM old_favicon_bitmaps AS old "
1081      "JOIN temp.icon_id_mapping AS mapping "
1082      "ON (old.icon_id = mapping.old_icon_id)";
1083  const char kDropOldFaviconBitmapsTable[] =
1084      "DROP TABLE old_favicon_bitmaps";
1085
1086  // Rename existing tables to new location.
1087  if (!db_.Execute(kRenameIconMappingTable) ||
1088      !db_.Execute(kRenameFaviconsTable) ||
1089      !db_.Execute(kRenameFaviconBitmapsTable)) {
1090    return false;
1091  }
1092
1093  // Initialize the replacement tables.  At this point the old indices
1094  // still exist (pointing to the old_* tables), so do not initialize
1095  // the indices.
1096  if (!InitTables(&db_))
1097    return false;
1098
1099  // Copy all of the data over.
1100  if (!db_.Execute(kCopyIconMapping) ||
1101      !db_.Execute(kCopyFavicons) ||
1102      !db_.Execute(kCopyFaviconBitmaps)) {
1103    return false;
1104  }
1105
1106  // Drop the old_* tables, which also drops the indices.
1107  if (!db_.Execute(kDropOldIconMappingTable) ||
1108      !db_.Execute(kDropOldFaviconsTable) ||
1109      !db_.Execute(kDropOldFaviconBitmapsTable)) {
1110    return false;
1111  }
1112
1113  // Recreate the indices.
1114  // TODO(shess): UNIQUE indices could fail due to duplication.  This
1115  // could happen in case of corruption.
1116  if (!InitIndices(&db_))
1117    return false;
1118
1119  const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping";
1120  if (!db_.Execute(kIconMappingDrop))
1121    return false;
1122
1123  return transaction.Commit();
1124}
1125
1126sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db,
1127                                                const base::FilePath& db_name) {
1128  size_t startup_kb = 0;
1129  int64 size_64;
1130  if (base::GetFileSize(db_name, &size_64))
1131    startup_kb = static_cast<size_t>(size_64 / 1024);
1132
1133  db->set_histogram_tag("Thumbnail");
1134  db->set_error_callback(base::Bind(&DatabaseErrorCallback,
1135                                    db, db_name, startup_kb, history_client_));
1136
1137  // Thumbnails db now only stores favicons, so we don't need that big a page
1138  // size or cache.
1139  db->set_page_size(2048);
1140  db->set_cache_size(32);
1141
1142  // Run the database in exclusive mode. Nobody else should be accessing the
1143  // database while we're running, and this will give somewhat improved perf.
1144  db->set_exclusive_locking();
1145
1146  if (!db->Open(db_name))
1147    return sql::INIT_FAILURE;
1148
1149  return sql::INIT_OK;
1150}
1151
1152sql::InitStatus ThumbnailDatabase::InitImpl(const base::FilePath& db_name) {
1153  sql::InitStatus status = OpenDatabase(&db_, db_name);
1154  if (status != sql::INIT_OK)
1155    return status;
1156
1157  // Clear databases which are too old to process.
1158  DCHECK_LT(kDeprecatedVersionNumber, kCurrentVersionNumber);
1159  sql::MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersionNumber);
1160
1161  // TODO(shess): Sqlite.Version.Thumbnail shows versions 22, 23, and
1162  // 25.  Future versions are not destroyed because that could lead to
1163  // data loss if the profile is opened by a later channel, but
1164  // perhaps a heuristic like >kCurrentVersionNumber+3 could be used.
1165
1166  // Scope initialization in a transaction so we can't be partially initialized.
1167  sql::Transaction transaction(&db_);
1168  if (!transaction.Begin())
1169    return sql::INIT_FAILURE;
1170
1171  // TODO(shess): Failing Begin() implies that something serious is
1172  // wrong with the database.  Raze() may be in order.
1173
1174#if defined(OS_MACOSX)
1175  // Exclude the thumbnails file from backups.
1176  base::mac::SetFileBackupExclusion(db_name);
1177#endif
1178
1179  // thumbnails table has been obsolete for a long time, remove any
1180  // detrious.
1181  ignore_result(db_.Execute("DROP TABLE IF EXISTS thumbnails"));
1182
1183  // At some point, operations involving temporary tables weren't done
1184  // atomically and users have been stranded.  Drop those tables and
1185  // move on.
1186  // TODO(shess): Prove it?  Audit all cases and see if it's possible
1187  // that this implies non-atomic update, and should thus be handled
1188  // via the corruption handler.
1189  ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicons"));
1190  ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicon_bitmaps"));
1191  ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_icon_mapping"));
1192
1193  // Create the tables.
1194  if (!meta_table_.Init(&db_, kCurrentVersionNumber,
1195                        kCompatibleVersionNumber) ||
1196      !InitTables(&db_) ||
1197      !InitIndices(&db_)) {
1198    return sql::INIT_FAILURE;
1199  }
1200
1201  // Version check. We should not encounter a database too old for us to handle
1202  // in the wild, so we try to continue in that case.
1203  if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
1204    LOG(WARNING) << "Thumbnail database is too new.";
1205    return sql::INIT_TOO_NEW;
1206  }
1207
1208  int cur_version = meta_table_.GetVersionNumber();
1209
1210  if (!db_.DoesColumnExist("favicons", "icon_type")) {
1211    LOG(ERROR) << "Raze because of missing favicon.icon_type";
1212    RecordInvalidStructure(STRUCTURE_EVENT_VERSION4);
1213
1214    db_.RazeAndClose();
1215    return sql::INIT_FAILURE;
1216  }
1217
1218  if (cur_version < 7 && !db_.DoesColumnExist("favicons", "sizes")) {
1219    LOG(ERROR) << "Raze because of missing favicon.sizes";
1220    RecordInvalidStructure(STRUCTURE_EVENT_VERSION5);
1221
1222    db_.RazeAndClose();
1223    return sql::INIT_FAILURE;
1224  }
1225
1226  if (cur_version == 5) {
1227    ++cur_version;
1228    if (!UpgradeToVersion6())
1229      return CantUpgradeToVersion(cur_version);
1230  }
1231
1232  if (cur_version == 6) {
1233    ++cur_version;
1234    if (!UpgradeToVersion7())
1235      return CantUpgradeToVersion(cur_version);
1236  }
1237
1238  LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
1239      "Thumbnail database version " << cur_version << " is too old to handle.";
1240
1241  // Initialization is complete.
1242  if (!transaction.Commit())
1243    return sql::INIT_FAILURE;
1244
1245  // Raze the database if the structure of the favicons database is not what
1246  // it should be. This error cannot be detected via the SQL error code because
1247  // the error code for running SQL statements against a database with missing
1248  // columns is SQLITE_ERROR which is not unique enough to act upon.
1249  // TODO(pkotwicz): Revisit this in M27 and see if the razing can be removed.
1250  // (crbug.com/166453)
1251  if (IsFaviconDBStructureIncorrect()) {
1252    LOG(ERROR) << "Raze because of invalid favicon db structure.";
1253    RecordInvalidStructure(STRUCTURE_EVENT_FAVICON);
1254
1255    db_.RazeAndClose();
1256    return sql::INIT_FAILURE;
1257  }
1258
1259  return sql::INIT_OK;
1260}
1261
1262sql::InitStatus ThumbnailDatabase::CantUpgradeToVersion(int cur_version) {
1263  LOG(WARNING) << "Unable to update to thumbnail database to version " <<
1264               cur_version << ".";
1265  db_.Close();
1266  return sql::INIT_FAILURE;
1267}
1268
1269bool ThumbnailDatabase::UpgradeToVersion6() {
1270  // Move bitmap data from favicons to favicon_bitmaps.
1271  bool success =
1272      db_.Execute("INSERT INTO favicon_bitmaps (icon_id, last_updated, "
1273                  "image_data, width, height)"
1274                  "SELECT id, last_updated, image_data, 0, 0 FROM favicons") &&
1275      db_.Execute("CREATE TABLE temp_favicons ("
1276                  "id INTEGER PRIMARY KEY,"
1277                  "url LONGVARCHAR NOT NULL,"
1278                  "icon_type INTEGER DEFAULT 1,"
1279                  // default icon_type FAVICON to be consistent with
1280                  // past migration.
1281                  "sizes LONGVARCHAR)") &&
1282      db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) "
1283                  "SELECT id, url, icon_type FROM favicons") &&
1284      db_.Execute("DROP TABLE favicons") &&
1285      db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons");
1286  // NOTE(shess): v7 will re-create the index.
1287  if (!success)
1288    return false;
1289
1290  meta_table_.SetVersionNumber(6);
1291  meta_table_.SetCompatibleVersionNumber(std::min(6, kCompatibleVersionNumber));
1292  return true;
1293}
1294
1295bool ThumbnailDatabase::UpgradeToVersion7() {
1296  // Sizes column was never used, remove it.
1297  bool success =
1298      db_.Execute("CREATE TABLE temp_favicons ("
1299                  "id INTEGER PRIMARY KEY,"
1300                  "url LONGVARCHAR NOT NULL,"
1301                  // default icon_type FAVICON to be consistent with
1302                  // past migration.
1303                  "icon_type INTEGER DEFAULT 1)") &&
1304      db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) "
1305                  "SELECT id, url, icon_type FROM favicons") &&
1306      db_.Execute("DROP TABLE favicons") &&
1307      db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons") &&
1308      db_.Execute("CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)");
1309
1310  if (!success)
1311    return false;
1312
1313  meta_table_.SetVersionNumber(7);
1314  meta_table_.SetCompatibleVersionNumber(std::min(7, kCompatibleVersionNumber));
1315  return true;
1316}
1317
1318bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() {
1319  return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons");
1320}
1321
1322}  // namespace history
1323