Home | History | Annotate | Download | only in appcache
      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 "webkit/browser/appcache/appcache_database.h"
      6 
      7 #include "base/auto_reset.h"
      8 #include "base/bind.h"
      9 #include "base/command_line.h"
     10 #include "base/file_util.h"
     11 #include "base/logging.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "sql/connection.h"
     14 #include "sql/error_delegate_util.h"
     15 #include "sql/meta_table.h"
     16 #include "sql/statement.h"
     17 #include "sql/transaction.h"
     18 #include "webkit/browser/appcache/appcache_entry.h"
     19 #include "webkit/browser/appcache/appcache_histograms.h"
     20 
     21 namespace appcache {
     22 
     23 // Schema -------------------------------------------------------------------
     24 namespace {
     25 
     26 #if defined(APPCACHE_USE_SIMPLE_CACHE)
     27 const int kCurrentVersion = 6;
     28 const int kCompatibleVersion = 6;
     29 #else
     30 const int kCurrentVersion = 5;
     31 const int kCompatibleVersion = 5;
     32 #endif
     33 
     34 // A mechanism to run experiments that may affect in data being persisted
     35 // in different ways such that when the experiment is toggled on/off via
     36 // cmd line flags, the database gets reset. The active flags are stored at
     37 // the time of database creation and compared when reopening. If different
     38 // the database is reset.
     39 const char kExperimentFlagsKey[] = "ExperimentFlags";
     40 
     41 const char kGroupsTable[] = "Groups";
     42 const char kCachesTable[] = "Caches";
     43 const char kEntriesTable[] = "Entries";
     44 const char kNamespacesTable[] = "Namespaces";
     45 const char kOnlineWhiteListsTable[] = "OnlineWhiteLists";
     46 const char kDeletableResponseIdsTable[] = "DeletableResponseIds";
     47 
     48 struct TableInfo {
     49   const char* table_name;
     50   const char* columns;
     51 };
     52 
     53 struct IndexInfo {
     54   const char* index_name;
     55   const char* table_name;
     56   const char* columns;
     57   bool unique;
     58 };
     59 
     60 const TableInfo kTables[] = {
     61   { kGroupsTable,
     62     "(group_id INTEGER PRIMARY KEY,"
     63     " origin TEXT,"
     64     " manifest_url TEXT,"
     65     " creation_time INTEGER,"
     66     " last_access_time INTEGER)" },
     67 
     68   { kCachesTable,
     69     "(cache_id INTEGER PRIMARY KEY,"
     70     " group_id INTEGER,"
     71     " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
     72     " update_time INTEGER,"
     73     " cache_size INTEGER)" },  // intentionally not normalized
     74 
     75   { kEntriesTable,
     76     "(cache_id INTEGER,"
     77     " url TEXT,"
     78     " flags INTEGER,"
     79     " response_id INTEGER,"
     80     " response_size INTEGER)" },
     81 
     82   { kNamespacesTable,
     83     "(cache_id INTEGER,"
     84     " origin TEXT,"  // intentionally not normalized
     85     " type INTEGER,"
     86     " namespace_url TEXT,"
     87     " target_url TEXT,"
     88     " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
     89 
     90   { kOnlineWhiteListsTable,
     91     "(cache_id INTEGER,"
     92     " namespace_url TEXT,"
     93     " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
     94 
     95   { kDeletableResponseIdsTable,
     96     "(response_id INTEGER NOT NULL)" },
     97 };
     98 
     99 const IndexInfo kIndexes[] = {
    100   { "GroupsOriginIndex",
    101     kGroupsTable,
    102     "(origin)",
    103     false },
    104 
    105   { "GroupsManifestIndex",
    106     kGroupsTable,
    107     "(manifest_url)",
    108     true },
    109 
    110   { "CachesGroupIndex",
    111     kCachesTable,
    112     "(group_id)",
    113     false },
    114 
    115   { "EntriesCacheIndex",
    116     kEntriesTable,
    117     "(cache_id)",
    118     false },
    119 
    120   { "EntriesCacheAndUrlIndex",
    121     kEntriesTable,
    122     "(cache_id, url)",
    123     true },
    124 
    125   { "EntriesResponseIdIndex",
    126     kEntriesTable,
    127     "(response_id)",
    128     true },
    129 
    130   { "NamespacesCacheIndex",
    131     kNamespacesTable,
    132     "(cache_id)",
    133     false },
    134 
    135   { "NamespacesOriginIndex",
    136     kNamespacesTable,
    137     "(origin)",
    138     false },
    139 
    140   { "NamespacesCacheAndUrlIndex",
    141     kNamespacesTable,
    142     "(cache_id, namespace_url)",
    143     true },
    144 
    145   { "OnlineWhiteListCacheIndex",
    146     kOnlineWhiteListsTable,
    147     "(cache_id)",
    148     false },
    149 
    150   { "DeletableResponsesIdIndex",
    151     kDeletableResponseIdsTable,
    152     "(response_id)",
    153     true },
    154 };
    155 
    156 const int kTableCount = ARRAYSIZE_UNSAFE(kTables);
    157 const int kIndexCount = ARRAYSIZE_UNSAFE(kIndexes);
    158 
    159 bool CreateTable(sql::Connection* db, const TableInfo& info) {
    160   std::string sql("CREATE TABLE ");
    161   sql += info.table_name;
    162   sql += info.columns;
    163   return db->Execute(sql.c_str());
    164 }
    165 
    166 bool CreateIndex(sql::Connection* db, const IndexInfo& info) {
    167   std::string sql;
    168   if (info.unique)
    169     sql += "CREATE UNIQUE INDEX ";
    170   else
    171     sql += "CREATE INDEX ";
    172   sql += info.index_name;
    173   sql += " ON ";
    174   sql += info.table_name;
    175   sql += info.columns;
    176   return db->Execute(sql.c_str());
    177 }
    178 
    179 std::string GetActiveExperimentFlags() {
    180   if (CommandLine::ForCurrentProcess()->HasSwitch(kEnableExecutableHandlers))
    181     return std::string("executableHandlersEnabled");
    182   return std::string();
    183 }
    184 
    185 }  // anon namespace
    186 
    187 // AppCacheDatabase ----------------------------------------------------------
    188 
    189 AppCacheDatabase::GroupRecord::GroupRecord()
    190     : group_id(0) {
    191 }
    192 
    193 AppCacheDatabase::GroupRecord::~GroupRecord() {
    194 }
    195 
    196 AppCacheDatabase::NamespaceRecord::NamespaceRecord()
    197     : cache_id(0) {
    198 }
    199 
    200 AppCacheDatabase::NamespaceRecord::~NamespaceRecord() {
    201 }
    202 
    203 
    204 AppCacheDatabase::AppCacheDatabase(const base::FilePath& path)
    205     : db_file_path_(path),
    206       is_disabled_(false),
    207       is_recreating_(false),
    208       was_corruption_detected_(false) {
    209 }
    210 
    211 AppCacheDatabase::~AppCacheDatabase() {
    212 }
    213 
    214 void AppCacheDatabase::Disable() {
    215   VLOG(1) << "Disabling appcache database.";
    216   is_disabled_ = true;
    217   ResetConnectionAndTables();
    218 }
    219 
    220 int64 AppCacheDatabase::GetOriginUsage(const GURL& origin) {
    221   std::vector<CacheRecord> records;
    222   if (!FindCachesForOrigin(origin, &records))
    223     return 0;
    224 
    225   int64 origin_usage = 0;
    226   std::vector<CacheRecord>::const_iterator iter = records.begin();
    227   while (iter != records.end()) {
    228     origin_usage += iter->cache_size;
    229     ++iter;
    230   }
    231   return origin_usage;
    232 }
    233 
    234 bool AppCacheDatabase::GetAllOriginUsage(std::map<GURL, int64>* usage_map) {
    235   std::set<GURL> origins;
    236   if (!FindOriginsWithGroups(&origins))
    237     return false;
    238   for (std::set<GURL>::const_iterator origin = origins.begin();
    239        origin != origins.end(); ++origin) {
    240     (*usage_map)[*origin] = GetOriginUsage(*origin);
    241   }
    242   return true;
    243 }
    244 
    245 bool AppCacheDatabase::FindOriginsWithGroups(std::set<GURL>* origins) {
    246   DCHECK(origins && origins->empty());
    247   if (!LazyOpen(false))
    248     return false;
    249 
    250   const char* kSql =
    251       "SELECT DISTINCT(origin) FROM Groups";
    252 
    253   sql::Statement statement(db_->GetUniqueStatement(kSql));
    254 
    255   while (statement.Step())
    256     origins->insert(GURL(statement.ColumnString(0)));
    257 
    258   return statement.Succeeded();
    259 }
    260 
    261 bool AppCacheDatabase::FindLastStorageIds(
    262     int64* last_group_id, int64* last_cache_id, int64* last_response_id,
    263     int64* last_deletable_response_rowid) {
    264   DCHECK(last_group_id && last_cache_id && last_response_id &&
    265          last_deletable_response_rowid);
    266 
    267   *last_group_id = 0;
    268   *last_cache_id = 0;
    269   *last_response_id = 0;
    270   *last_deletable_response_rowid = 0;
    271 
    272   if (!LazyOpen(false))
    273     return false;
    274 
    275   const char* kMaxGroupIdSql = "SELECT MAX(group_id) FROM Groups";
    276   const char* kMaxCacheIdSql = "SELECT MAX(cache_id) FROM Caches";
    277   const char* kMaxResponseIdFromEntriesSql =
    278       "SELECT MAX(response_id) FROM Entries";
    279   const char* kMaxResponseIdFromDeletablesSql =
    280       "SELECT MAX(response_id) FROM DeletableResponseIds";
    281   const char* kMaxDeletableResponseRowIdSql =
    282       "SELECT MAX(rowid) FROM DeletableResponseIds";
    283   int64 max_group_id;
    284   int64 max_cache_id;
    285   int64 max_response_id_from_entries;
    286   int64 max_response_id_from_deletables;
    287   int64 max_deletable_response_rowid;
    288   if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql, &max_group_id) ||
    289       !RunUniqueStatementWithInt64Result(kMaxCacheIdSql, &max_cache_id) ||
    290       !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql,
    291                                          &max_response_id_from_entries) ||
    292       !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql,
    293                                          &max_response_id_from_deletables) ||
    294       !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql,
    295                                          &max_deletable_response_rowid)) {
    296     return false;
    297   }
    298 
    299   *last_group_id = max_group_id;
    300   *last_cache_id = max_cache_id;
    301   *last_response_id = std::max(max_response_id_from_entries,
    302                                max_response_id_from_deletables);
    303   *last_deletable_response_rowid = max_deletable_response_rowid;
    304   return true;
    305 }
    306 
    307 bool AppCacheDatabase::FindGroup(int64 group_id, GroupRecord* record) {
    308   DCHECK(record);
    309   if (!LazyOpen(false))
    310     return false;
    311 
    312   const char* kSql =
    313       "SELECT group_id, origin, manifest_url,"
    314       "       creation_time, last_access_time"
    315       "  FROM Groups WHERE group_id = ?";
    316 
    317   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    318 
    319   statement.BindInt64(0, group_id);
    320   if (!statement.Step())
    321     return false;
    322 
    323   ReadGroupRecord(statement, record);
    324   DCHECK(record->group_id == group_id);
    325   return true;
    326 }
    327 
    328 bool AppCacheDatabase::FindGroupForManifestUrl(
    329     const GURL& manifest_url, GroupRecord* record) {
    330   DCHECK(record);
    331   if (!LazyOpen(false))
    332     return false;
    333 
    334   const char* kSql =
    335       "SELECT group_id, origin, manifest_url,"
    336       "       creation_time, last_access_time"
    337       "  FROM Groups WHERE manifest_url = ?";
    338 
    339   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    340   statement.BindString(0, manifest_url.spec());
    341 
    342   if (!statement.Step())
    343     return false;
    344 
    345   ReadGroupRecord(statement, record);
    346   DCHECK(record->manifest_url == manifest_url);
    347   return true;
    348 }
    349 
    350 bool AppCacheDatabase::FindGroupsForOrigin(
    351     const GURL& origin, std::vector<GroupRecord>* records) {
    352   DCHECK(records && records->empty());
    353   if (!LazyOpen(false))
    354     return false;
    355 
    356   const char* kSql =
    357       "SELECT group_id, origin, manifest_url,"
    358       "       creation_time, last_access_time"
    359       "   FROM Groups WHERE origin = ?";
    360 
    361   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    362   statement.BindString(0, origin.spec());
    363 
    364   while (statement.Step()) {
    365     records->push_back(GroupRecord());
    366     ReadGroupRecord(statement, &records->back());
    367     DCHECK(records->back().origin == origin);
    368   }
    369 
    370   return statement.Succeeded();
    371 }
    372 
    373 bool AppCacheDatabase::FindGroupForCache(int64 cache_id, GroupRecord* record) {
    374   DCHECK(record);
    375   if (!LazyOpen(false))
    376     return false;
    377 
    378   const char* kSql =
    379       "SELECT g.group_id, g.origin, g.manifest_url,"
    380       "       g.creation_time, g.last_access_time"
    381       "  FROM Groups g, Caches c"
    382       "  WHERE c.cache_id = ? AND c.group_id = g.group_id";
    383 
    384   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    385   statement.BindInt64(0, cache_id);
    386 
    387   if (!statement.Step())
    388     return false;
    389 
    390   ReadGroupRecord(statement, record);
    391   return true;
    392 }
    393 
    394 bool AppCacheDatabase::UpdateGroupLastAccessTime(
    395     int64 group_id, base::Time time) {
    396   if (!LazyOpen(true))
    397     return false;
    398 
    399   const char* kSql =
    400       "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
    401 
    402   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    403   statement.BindInt64(0, time.ToInternalValue());
    404   statement.BindInt64(1, group_id);
    405 
    406   return statement.Run() && db_->GetLastChangeCount();
    407 }
    408 
    409 bool AppCacheDatabase::InsertGroup(const GroupRecord* record) {
    410   if (!LazyOpen(true))
    411     return false;
    412 
    413   const char* kSql =
    414       "INSERT INTO Groups"
    415       "  (group_id, origin, manifest_url, creation_time, last_access_time)"
    416       "  VALUES(?, ?, ?, ?, ?)";
    417 
    418   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    419   statement.BindInt64(0, record->group_id);
    420   statement.BindString(1, record->origin.spec());
    421   statement.BindString(2, record->manifest_url.spec());
    422   statement.BindInt64(3, record->creation_time.ToInternalValue());
    423   statement.BindInt64(4, record->last_access_time.ToInternalValue());
    424 
    425   return statement.Run();
    426 }
    427 
    428 bool AppCacheDatabase::DeleteGroup(int64 group_id) {
    429   if (!LazyOpen(false))
    430     return false;
    431 
    432   const char* kSql =
    433       "DELETE FROM Groups WHERE group_id = ?";
    434 
    435   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    436   statement.BindInt64(0, group_id);
    437 
    438   return statement.Run();
    439 }
    440 
    441 bool AppCacheDatabase::FindCache(int64 cache_id, CacheRecord* record) {
    442   DCHECK(record);
    443   if (!LazyOpen(false))
    444     return false;
    445 
    446   const char* kSql =
    447       "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
    448       " FROM Caches WHERE cache_id = ?";
    449 
    450   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    451   statement.BindInt64(0, cache_id);
    452 
    453   if (!statement.Step())
    454     return false;
    455 
    456   ReadCacheRecord(statement, record);
    457   return true;
    458 }
    459 
    460 bool AppCacheDatabase::FindCacheForGroup(int64 group_id, CacheRecord* record) {
    461   DCHECK(record);
    462   if (!LazyOpen(false))
    463     return false;
    464 
    465   const char* kSql =
    466       "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
    467       "  FROM Caches WHERE group_id = ?";
    468 
    469   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    470   statement.BindInt64(0, group_id);
    471 
    472   if (!statement.Step())
    473     return false;
    474 
    475   ReadCacheRecord(statement, record);
    476   return true;
    477 }
    478 
    479 bool AppCacheDatabase::FindCachesForOrigin(
    480     const GURL& origin, std::vector<CacheRecord>* records) {
    481   DCHECK(records);
    482   std::vector<GroupRecord> group_records;
    483   if (!FindGroupsForOrigin(origin, &group_records))
    484     return false;
    485 
    486   CacheRecord cache_record;
    487   std::vector<GroupRecord>::const_iterator iter = group_records.begin();
    488   while (iter != group_records.end()) {
    489     if (FindCacheForGroup(iter->group_id, &cache_record))
    490       records->push_back(cache_record);
    491     ++iter;
    492   }
    493   return true;
    494 }
    495 
    496 bool AppCacheDatabase::InsertCache(const CacheRecord* record) {
    497   if (!LazyOpen(true))
    498     return false;
    499 
    500   const char* kSql =
    501       "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
    502       "                    update_time, cache_size)"
    503       "  VALUES(?, ?, ?, ?, ?)";
    504 
    505   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    506   statement.BindInt64(0, record->cache_id);
    507   statement.BindInt64(1, record->group_id);
    508   statement.BindBool(2, record->online_wildcard);
    509   statement.BindInt64(3, record->update_time.ToInternalValue());
    510   statement.BindInt64(4, record->cache_size);
    511 
    512   return statement.Run();
    513 }
    514 
    515 bool AppCacheDatabase::DeleteCache(int64 cache_id) {
    516   if (!LazyOpen(false))
    517     return false;
    518 
    519   const char* kSql =
    520       "DELETE FROM Caches WHERE cache_id = ?";
    521 
    522   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    523   statement.BindInt64(0, cache_id);
    524 
    525   return statement.Run();
    526 }
    527 
    528 bool AppCacheDatabase::FindEntriesForCache(
    529     int64 cache_id, std::vector<EntryRecord>* records) {
    530   DCHECK(records && records->empty());
    531   if (!LazyOpen(false))
    532     return false;
    533 
    534   const char* kSql =
    535       "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
    536       "  WHERE cache_id = ?";
    537 
    538   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    539   statement.BindInt64(0, cache_id);
    540 
    541   while (statement.Step()) {
    542     records->push_back(EntryRecord());
    543     ReadEntryRecord(statement, &records->back());
    544     DCHECK(records->back().cache_id == cache_id);
    545   }
    546 
    547   return statement.Succeeded();
    548 }
    549 
    550 bool AppCacheDatabase::FindEntriesForUrl(
    551     const GURL& url, std::vector<EntryRecord>* records) {
    552   DCHECK(records && records->empty());
    553   if (!LazyOpen(false))
    554     return false;
    555 
    556   const char* kSql =
    557       "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
    558       "  WHERE url = ?";
    559 
    560   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    561   statement.BindString(0, url.spec());
    562 
    563   while (statement.Step()) {
    564     records->push_back(EntryRecord());
    565     ReadEntryRecord(statement, &records->back());
    566     DCHECK(records->back().url == url);
    567   }
    568 
    569   return statement.Succeeded();
    570 }
    571 
    572 bool AppCacheDatabase::FindEntry(
    573     int64 cache_id, const GURL& url, EntryRecord* record) {
    574   DCHECK(record);
    575   if (!LazyOpen(false))
    576     return false;
    577 
    578   const char* kSql =
    579       "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
    580       "  WHERE cache_id = ? AND url = ?";
    581 
    582   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    583   statement.BindInt64(0, cache_id);
    584   statement.BindString(1, url.spec());
    585 
    586   if (!statement.Step())
    587     return false;
    588 
    589   ReadEntryRecord(statement, record);
    590   DCHECK(record->cache_id == cache_id);
    591   DCHECK(record->url == url);
    592   return true;
    593 }
    594 
    595 bool AppCacheDatabase::InsertEntry(const EntryRecord* record) {
    596   if (!LazyOpen(true))
    597     return false;
    598 
    599   const char* kSql =
    600       "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
    601       "  VALUES(?, ?, ?, ?, ?)";
    602 
    603   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    604   statement.BindInt64(0, record->cache_id);
    605   statement.BindString(1, record->url.spec());
    606   statement.BindInt(2, record->flags);
    607   statement.BindInt64(3, record->response_id);
    608   statement.BindInt64(4, record->response_size);
    609 
    610   return statement.Run();
    611 }
    612 
    613 bool AppCacheDatabase::InsertEntryRecords(
    614     const std::vector<EntryRecord>& records) {
    615   if (records.empty())
    616     return true;
    617   sql::Transaction transaction(db_.get());
    618   if (!transaction.Begin())
    619     return false;
    620   std::vector<EntryRecord>::const_iterator iter = records.begin();
    621   while (iter != records.end()) {
    622     if (!InsertEntry(&(*iter)))
    623       return false;
    624     ++iter;
    625   }
    626   return transaction.Commit();
    627 }
    628 
    629 bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id) {
    630   if (!LazyOpen(false))
    631     return false;
    632 
    633   const char* kSql =
    634       "DELETE FROM Entries WHERE cache_id = ?";
    635 
    636   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    637   statement.BindInt64(0, cache_id);
    638 
    639   return statement.Run();
    640 }
    641 
    642 bool AppCacheDatabase::AddEntryFlags(
    643     const GURL& entry_url, int64 cache_id, int additional_flags) {
    644   if (!LazyOpen(false))
    645     return false;
    646 
    647   const char* kSql =
    648       "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
    649 
    650   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    651   statement.BindInt(0, additional_flags);
    652   statement.BindInt64(1, cache_id);
    653   statement.BindString(2, entry_url.spec());
    654 
    655   return statement.Run() && db_->GetLastChangeCount();
    656 }
    657 
    658 bool AppCacheDatabase::FindNamespacesForOrigin(
    659     const GURL& origin,
    660     std::vector<NamespaceRecord>* intercepts,
    661     std::vector<NamespaceRecord>* fallbacks) {
    662   DCHECK(intercepts && intercepts->empty());
    663   DCHECK(fallbacks && fallbacks->empty());
    664   if (!LazyOpen(false))
    665     return false;
    666 
    667   const char* kSql =
    668       "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
    669       "  FROM Namespaces WHERE origin = ?";
    670 
    671   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    672   statement.BindString(0, origin.spec());
    673 
    674   ReadNamespaceRecords(&statement, intercepts, fallbacks);
    675 
    676   return statement.Succeeded();
    677 }
    678 
    679 bool AppCacheDatabase::FindNamespacesForCache(
    680     int64 cache_id,
    681     std::vector<NamespaceRecord>* intercepts,
    682     std::vector<NamespaceRecord>* fallbacks) {
    683   DCHECK(intercepts && intercepts->empty());
    684   DCHECK(fallbacks && fallbacks->empty());
    685   if (!LazyOpen(false))
    686     return false;
    687 
    688   const char* kSql =
    689       "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
    690       "  FROM Namespaces WHERE cache_id = ?";
    691 
    692   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    693   statement.BindInt64(0, cache_id);
    694 
    695   ReadNamespaceRecords(&statement, intercepts, fallbacks);
    696 
    697   return statement.Succeeded();
    698 }
    699 
    700 bool AppCacheDatabase::InsertNamespace(
    701     const NamespaceRecord* record) {
    702   if (!LazyOpen(true))
    703     return false;
    704 
    705   const char* kSql =
    706       "INSERT INTO Namespaces"
    707       "  (cache_id, origin, type, namespace_url, target_url, is_pattern)"
    708       "  VALUES (?, ?, ?, ?, ?, ?)";
    709 
    710   // Note: quick and dirty storage for the 'executable' bit w/o changing
    711   // schemas, we use the high bit of 'type' field.
    712   int type_with_executable_bit = record->namespace_.type;
    713   if (record->namespace_.is_executable) {
    714     type_with_executable_bit |= 0x8000000;
    715     DCHECK(CommandLine::ForCurrentProcess()->HasSwitch(
    716         kEnableExecutableHandlers));
    717   }
    718 
    719   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    720   statement.BindInt64(0, record->cache_id);
    721   statement.BindString(1, record->origin.spec());
    722   statement.BindInt(2, type_with_executable_bit);
    723   statement.BindString(3, record->namespace_.namespace_url.spec());
    724   statement.BindString(4, record->namespace_.target_url.spec());
    725   statement.BindBool(5, record->namespace_.is_pattern);
    726   return statement.Run();
    727 }
    728 
    729 bool AppCacheDatabase::InsertNamespaceRecords(
    730     const std::vector<NamespaceRecord>& records) {
    731   if (records.empty())
    732     return true;
    733   sql::Transaction transaction(db_.get());
    734   if (!transaction.Begin())
    735     return false;
    736   std::vector<NamespaceRecord>::const_iterator iter = records.begin();
    737   while (iter != records.end()) {
    738     if (!InsertNamespace(&(*iter)))
    739       return false;
    740     ++iter;
    741   }
    742   return transaction.Commit();
    743 }
    744 
    745 bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id) {
    746   if (!LazyOpen(false))
    747     return false;
    748 
    749   const char* kSql =
    750       "DELETE FROM Namespaces WHERE cache_id = ?";
    751 
    752   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    753   statement.BindInt64(0, cache_id);
    754 
    755   return statement.Run();
    756 }
    757 
    758 bool AppCacheDatabase::FindOnlineWhiteListForCache(
    759     int64 cache_id, std::vector<OnlineWhiteListRecord>* records) {
    760   DCHECK(records && records->empty());
    761   if (!LazyOpen(false))
    762     return false;
    763 
    764   const char* kSql =
    765       "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
    766       "  WHERE cache_id = ?";
    767 
    768   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    769   statement.BindInt64(0, cache_id);
    770 
    771   while (statement.Step()) {
    772     records->push_back(OnlineWhiteListRecord());
    773     this->ReadOnlineWhiteListRecord(statement, &records->back());
    774     DCHECK(records->back().cache_id == cache_id);
    775   }
    776   return statement.Succeeded();
    777 }
    778 
    779 bool AppCacheDatabase::InsertOnlineWhiteList(
    780     const OnlineWhiteListRecord* record) {
    781   if (!LazyOpen(true))
    782     return false;
    783 
    784   const char* kSql =
    785       "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
    786       "  VALUES (?, ?, ?)";
    787 
    788   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    789   statement.BindInt64(0, record->cache_id);
    790   statement.BindString(1, record->namespace_url.spec());
    791   statement.BindBool(2, record->is_pattern);
    792 
    793   return statement.Run();
    794 }
    795 
    796 bool AppCacheDatabase::InsertOnlineWhiteListRecords(
    797     const std::vector<OnlineWhiteListRecord>& records) {
    798   if (records.empty())
    799     return true;
    800   sql::Transaction transaction(db_.get());
    801   if (!transaction.Begin())
    802     return false;
    803   std::vector<OnlineWhiteListRecord>::const_iterator iter = records.begin();
    804   while (iter != records.end()) {
    805     if (!InsertOnlineWhiteList(&(*iter)))
    806       return false;
    807     ++iter;
    808   }
    809   return transaction.Commit();
    810 }
    811 
    812 bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id) {
    813   if (!LazyOpen(false))
    814     return false;
    815 
    816   const char* kSql =
    817       "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
    818 
    819   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    820   statement.BindInt64(0, cache_id);
    821 
    822   return statement.Run();
    823 }
    824 
    825 bool AppCacheDatabase::GetDeletableResponseIds(
    826     std::vector<int64>* response_ids, int64 max_rowid, int limit) {
    827   if (!LazyOpen(false))
    828     return false;
    829 
    830   const char* kSql =
    831       "SELECT response_id FROM DeletableResponseIds "
    832       "  WHERE rowid <= ?"
    833       "  LIMIT ?";
    834 
    835   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    836   statement.BindInt64(0, max_rowid);
    837   statement.BindInt64(1, limit);
    838 
    839   while (statement.Step())
    840     response_ids->push_back(statement.ColumnInt64(0));
    841   return statement.Succeeded();
    842 }
    843 
    844 bool AppCacheDatabase::InsertDeletableResponseIds(
    845     const std::vector<int64>& response_ids) {
    846   const char* kSql =
    847       "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
    848   return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
    849 }
    850 
    851 bool AppCacheDatabase::DeleteDeletableResponseIds(
    852     const std::vector<int64>& response_ids) {
    853   const char* kSql =
    854       "DELETE FROM DeletableResponseIds WHERE response_id = ?";
    855   return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
    856 }
    857 
    858 bool AppCacheDatabase::RunCachedStatementWithIds(
    859     const sql::StatementID& statement_id, const char* sql,
    860     const std::vector<int64>& ids) {
    861   DCHECK(sql);
    862   if (!LazyOpen(true))
    863     return false;
    864 
    865   sql::Transaction transaction(db_.get());
    866   if (!transaction.Begin())
    867     return false;
    868 
    869   sql::Statement statement(db_->GetCachedStatement(statement_id, sql));
    870 
    871   std::vector<int64>::const_iterator iter = ids.begin();
    872   while (iter != ids.end()) {
    873     statement.BindInt64(0, *iter);
    874     if (!statement.Run())
    875       return false;
    876     statement.Reset(true);
    877     ++iter;
    878   }
    879 
    880   return transaction.Commit();
    881 }
    882 
    883 bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
    884     const char* sql, int64* result) {
    885   DCHECK(sql);
    886   sql::Statement statement(db_->GetUniqueStatement(sql));
    887   if (!statement.Step()) {
    888     return false;
    889   }
    890   *result = statement.ColumnInt64(0);
    891   return true;
    892 }
    893 
    894 bool AppCacheDatabase::FindResponseIdsForCacheHelper(
    895     int64 cache_id, std::vector<int64>* ids_vector,
    896     std::set<int64>* ids_set) {
    897   DCHECK(ids_vector || ids_set);
    898   DCHECK(!(ids_vector && ids_set));
    899   if (!LazyOpen(false))
    900     return false;
    901 
    902   const char* kSql =
    903       "SELECT response_id FROM Entries WHERE cache_id = ?";
    904 
    905   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
    906 
    907   statement.BindInt64(0, cache_id);
    908   while (statement.Step()) {
    909     int64 id = statement.ColumnInt64(0);
    910     if (ids_set)
    911       ids_set->insert(id);
    912     else
    913       ids_vector->push_back(id);
    914   }
    915 
    916   return statement.Succeeded();
    917 }
    918 
    919 void AppCacheDatabase::ReadGroupRecord(
    920     const sql::Statement& statement, GroupRecord* record) {
    921   record->group_id = statement.ColumnInt64(0);
    922   record->origin = GURL(statement.ColumnString(1));
    923   record->manifest_url = GURL(statement.ColumnString(2));
    924   record->creation_time =
    925       base::Time::FromInternalValue(statement.ColumnInt64(3));
    926   record->last_access_time =
    927       base::Time::FromInternalValue(statement.ColumnInt64(4));
    928 }
    929 
    930 void AppCacheDatabase::ReadCacheRecord(
    931     const sql::Statement& statement, CacheRecord* record) {
    932   record->cache_id = statement.ColumnInt64(0);
    933   record->group_id = statement.ColumnInt64(1);
    934   record->online_wildcard = statement.ColumnBool(2);
    935   record->update_time =
    936       base::Time::FromInternalValue(statement.ColumnInt64(3));
    937   record->cache_size = statement.ColumnInt64(4);
    938 }
    939 
    940 void AppCacheDatabase::ReadEntryRecord(
    941     const sql::Statement& statement, EntryRecord* record) {
    942   record->cache_id = statement.ColumnInt64(0);
    943   record->url = GURL(statement.ColumnString(1));
    944   record->flags = statement.ColumnInt(2);
    945   record->response_id = statement.ColumnInt64(3);
    946   record->response_size = statement.ColumnInt64(4);
    947 }
    948 
    949 void AppCacheDatabase::ReadNamespaceRecords(
    950       sql::Statement* statement,
    951       NamespaceRecordVector* intercepts,
    952       NamespaceRecordVector* fallbacks) {
    953   while (statement->Step()) {
    954     AppCacheNamespaceType type = static_cast<AppCacheNamespaceType>(
    955         statement->ColumnInt(2));
    956     NamespaceRecordVector* records =
    957         (type == APPCACHE_FALLBACK_NAMESPACE) ? fallbacks : intercepts;
    958     records->push_back(NamespaceRecord());
    959     ReadNamespaceRecord(statement, &records->back());
    960   }
    961 }
    962 
    963 void AppCacheDatabase::ReadNamespaceRecord(
    964     const sql::Statement* statement, NamespaceRecord* record) {
    965   record->cache_id = statement->ColumnInt64(0);
    966   record->origin = GURL(statement->ColumnString(1));
    967   int type_with_executable_bit = statement->ColumnInt(2);
    968   record->namespace_.namespace_url = GURL(statement->ColumnString(3));
    969   record->namespace_.target_url = GURL(statement->ColumnString(4));
    970   record->namespace_.is_pattern = statement->ColumnBool(5);
    971 
    972   // Note: quick and dirty storage for the 'executable' bit w/o changing
    973   // schemas, we use the high bit of 'type' field.
    974   record->namespace_.type = static_cast<AppCacheNamespaceType>
    975       (type_with_executable_bit & 0x7ffffff);
    976   record->namespace_.is_executable =
    977       (type_with_executable_bit & 0x80000000) != 0;
    978   DCHECK(!record->namespace_.is_executable ||
    979       CommandLine::ForCurrentProcess()->HasSwitch(kEnableExecutableHandlers));
    980 }
    981 
    982 void AppCacheDatabase::ReadOnlineWhiteListRecord(
    983     const sql::Statement& statement, OnlineWhiteListRecord* record) {
    984   record->cache_id = statement.ColumnInt64(0);
    985   record->namespace_url = GURL(statement.ColumnString(1));
    986   record->is_pattern = statement.ColumnBool(2);
    987 }
    988 
    989 bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
    990   if (db_)
    991     return true;
    992 
    993   // If we tried and failed once, don't try again in the same session
    994   // to avoid creating an incoherent mess on disk.
    995   if (is_disabled_)
    996     return false;
    997 
    998   // Avoid creating a database at all if we can.
    999   bool use_in_memory_db = db_file_path_.empty();
   1000   if (!create_if_needed &&
   1001       (use_in_memory_db || !base::PathExists(db_file_path_))) {
   1002     return false;
   1003   }
   1004 
   1005   db_.reset(new sql::Connection);
   1006   meta_table_.reset(new sql::MetaTable);
   1007 
   1008   db_->set_histogram_tag("AppCache");
   1009 
   1010   bool opened = false;
   1011   if (use_in_memory_db) {
   1012     opened = db_->OpenInMemory();
   1013   } else if (!base::CreateDirectory(db_file_path_.DirName())) {
   1014     LOG(ERROR) << "Failed to create appcache directory.";
   1015   } else {
   1016     opened = db_->Open(db_file_path_);
   1017     if (opened)
   1018       db_->Preload();
   1019   }
   1020 
   1021   if (!opened || !db_->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
   1022     LOG(ERROR) << "Failed to open the appcache database.";
   1023     AppCacheHistograms::CountInitResult(
   1024         AppCacheHistograms::SQL_DATABASE_ERROR);
   1025 
   1026     // We're unable to open the database. This is a fatal error
   1027     // which we can't recover from. We try to handle it by deleting
   1028     // the existing appcache data and starting with a clean slate in
   1029     // this browser session.
   1030     if (!use_in_memory_db && DeleteExistingAndCreateNewDatabase())
   1031       return true;
   1032 
   1033     Disable();
   1034     return false;
   1035   }
   1036 
   1037   AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK);
   1038   was_corruption_detected_ = false;
   1039   db_->set_error_callback(
   1040       base::Bind(&AppCacheDatabase::OnDatabaseError, base::Unretained(this)));
   1041   return true;
   1042 }
   1043 
   1044 bool AppCacheDatabase::EnsureDatabaseVersion() {
   1045   if (!sql::MetaTable::DoesTableExist(db_.get()))
   1046     return CreateSchema();
   1047 
   1048   if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
   1049     return false;
   1050 
   1051   if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
   1052     LOG(WARNING) << "AppCache database is too new.";
   1053     return false;
   1054   }
   1055 
   1056   std::string stored_flags;
   1057   meta_table_->GetValue(kExperimentFlagsKey, &stored_flags);
   1058   if (stored_flags != GetActiveExperimentFlags())
   1059     return false;
   1060 
   1061   if (meta_table_->GetVersionNumber() < kCurrentVersion)
   1062     return UpgradeSchema();
   1063 
   1064 #ifndef NDEBUG
   1065   DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
   1066   for (int i = 0; i < kTableCount; ++i) {
   1067     DCHECK(db_->DoesTableExist(kTables[i].table_name));
   1068   }
   1069   for (int i = 0; i < kIndexCount; ++i) {
   1070     DCHECK(db_->DoesIndexExist(kIndexes[i].index_name));
   1071   }
   1072 #endif
   1073 
   1074   return true;
   1075 }
   1076 
   1077 bool AppCacheDatabase::CreateSchema() {
   1078   sql::Transaction transaction(db_.get());
   1079   if (!transaction.Begin())
   1080     return false;
   1081 
   1082   if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
   1083     return false;
   1084 
   1085   if (!meta_table_->SetValue(kExperimentFlagsKey,
   1086                              GetActiveExperimentFlags())) {
   1087     return false;
   1088   }
   1089 
   1090   for (int i = 0; i < kTableCount; ++i) {
   1091     if (!CreateTable(db_.get(), kTables[i]))
   1092       return false;
   1093   }
   1094 
   1095   for (int i = 0; i < kIndexCount; ++i) {
   1096     if (!CreateIndex(db_.get(), kIndexes[i]))
   1097       return false;
   1098   }
   1099 
   1100   return transaction.Commit();
   1101 }
   1102 
   1103 bool AppCacheDatabase::UpgradeSchema() {
   1104 #if defined(APPCACHE_USE_SIMPLE_CACHE)
   1105   return DeleteExistingAndCreateNewDatabase();
   1106 #else
   1107   if (meta_table_->GetVersionNumber() == 3) {
   1108     // version 3 was pre 12/17/2011
   1109     DCHECK_EQ(strcmp(kNamespacesTable, kTables[3].table_name), 0);
   1110     DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[6].table_name), 0);
   1111     DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[7].table_name), 0);
   1112     DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[8].table_name), 0);
   1113 
   1114     const TableInfo kNamespaceTable_v4 = {
   1115         kNamespacesTable,
   1116         "(cache_id INTEGER,"
   1117         " origin TEXT,"  // intentionally not normalized
   1118         " type INTEGER,"
   1119         " namespace_url TEXT,"
   1120         " target_url TEXT)"
   1121     };
   1122 
   1123     // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
   1124     // but without the is_pattern column added in v5.
   1125     sql::Transaction transaction(db_.get());
   1126     if (!transaction.Begin() ||
   1127         !CreateTable(db_.get(), kNamespaceTable_v4)) {
   1128       return false;
   1129     }
   1130 
   1131     // Move data from the old table to the new table, setting the
   1132     // 'type' for all current records to the value for
   1133     // APPCACHE_FALLBACK_NAMESPACE.
   1134     DCHECK_EQ(0, static_cast<int>(APPCACHE_FALLBACK_NAMESPACE));
   1135     if (!db_->Execute(
   1136             "INSERT INTO Namespaces"
   1137             "  SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
   1138             "  FROM FallbackNameSpaces")) {
   1139       return false;
   1140     }
   1141 
   1142     // Drop the old table, indexes on that table are also removed by this.
   1143     if (!db_->Execute("DROP TABLE FallbackNameSpaces"))
   1144       return false;
   1145 
   1146     // Create new indexes.
   1147     if (!CreateIndex(db_.get(), kIndexes[6]) ||
   1148         !CreateIndex(db_.get(), kIndexes[7]) ||
   1149         !CreateIndex(db_.get(), kIndexes[8])) {
   1150       return false;
   1151     }
   1152 
   1153     meta_table_->SetVersionNumber(4);
   1154     meta_table_->SetCompatibleVersionNumber(4);
   1155     if (!transaction.Commit())
   1156       return false;
   1157   }
   1158 
   1159   if (meta_table_->GetVersionNumber() == 4) {
   1160     // version 4 pre 3/30/2013
   1161     // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
   1162     DCHECK_EQ(strcmp(kNamespacesTable, "Namespaces"), 0);
   1163     sql::Transaction transaction(db_.get());
   1164     if (!transaction.Begin())
   1165       return false;
   1166     if (!db_->Execute(
   1167             "ALTER TABLE Namespaces ADD COLUMN"
   1168             "  is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
   1169       return false;
   1170     }
   1171     if (!db_->Execute(
   1172             "ALTER TABLE OnlineWhitelists ADD COLUMN"
   1173             "  is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
   1174       return false;
   1175     }
   1176     meta_table_->SetVersionNumber(5);
   1177     meta_table_->SetCompatibleVersionNumber(5);
   1178     return transaction.Commit();
   1179   }
   1180 
   1181   // If there is no upgrade path for the version on disk to the current
   1182   // version, nuke everything and start over.
   1183   return DeleteExistingAndCreateNewDatabase();
   1184 #endif
   1185 }
   1186 
   1187 void AppCacheDatabase::ResetConnectionAndTables() {
   1188   meta_table_.reset();
   1189   db_.reset();
   1190 }
   1191 
   1192 bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
   1193   DCHECK(!db_file_path_.empty());
   1194   DCHECK(base::PathExists(db_file_path_));
   1195   VLOG(1) << "Deleting existing appcache data and starting over.";
   1196 
   1197   ResetConnectionAndTables();
   1198 
   1199   // This also deletes the disk cache data.
   1200   base::FilePath directory = db_file_path_.DirName();
   1201   if (!base::DeleteFile(directory, true))
   1202     return false;
   1203 
   1204   // Make sure the steps above actually deleted things.
   1205   if (base::PathExists(directory))
   1206     return false;
   1207 
   1208   if (!base::CreateDirectory(directory))
   1209     return false;
   1210 
   1211   // So we can't go recursive.
   1212   if (is_recreating_)
   1213     return false;
   1214 
   1215   base::AutoReset<bool> auto_reset(&is_recreating_, true);
   1216   return LazyOpen(true);
   1217 }
   1218 
   1219 void AppCacheDatabase::OnDatabaseError(int err, sql::Statement* stmt) {
   1220   was_corruption_detected_ |= sql::IsErrorCatastrophic(err);
   1221   if (!db_->ShouldIgnoreSqliteError(err))
   1222     DLOG(ERROR) << db_->GetErrorMessage();
   1223   // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
   1224 }
   1225 
   1226 }  // namespace appcache
   1227