root/webkit/browser/appcache/appcache_database.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. CreateTable
  2. CreateIndex
  3. GetActiveExperimentFlags
  4. was_corruption_detected_
  5. Disable
  6. GetOriginUsage
  7. GetAllOriginUsage
  8. FindOriginsWithGroups
  9. FindLastStorageIds
  10. FindGroup
  11. FindGroupForManifestUrl
  12. FindGroupsForOrigin
  13. FindGroupForCache
  14. UpdateGroupLastAccessTime
  15. InsertGroup
  16. DeleteGroup
  17. FindCache
  18. FindCacheForGroup
  19. FindCachesForOrigin
  20. InsertCache
  21. DeleteCache
  22. FindEntriesForCache
  23. FindEntriesForUrl
  24. FindEntry
  25. InsertEntry
  26. InsertEntryRecords
  27. DeleteEntriesForCache
  28. AddEntryFlags
  29. FindNamespacesForOrigin
  30. FindNamespacesForCache
  31. InsertNamespace
  32. InsertNamespaceRecords
  33. DeleteNamespacesForCache
  34. FindOnlineWhiteListForCache
  35. InsertOnlineWhiteList
  36. InsertOnlineWhiteListRecords
  37. DeleteOnlineWhiteListForCache
  38. GetDeletableResponseIds
  39. InsertDeletableResponseIds
  40. DeleteDeletableResponseIds
  41. RunCachedStatementWithIds
  42. RunUniqueStatementWithInt64Result
  43. FindResponseIdsForCacheHelper
  44. ReadGroupRecord
  45. ReadCacheRecord
  46. ReadEntryRecord
  47. ReadNamespaceRecords
  48. ReadNamespaceRecord
  49. ReadOnlineWhiteListRecord
  50. LazyOpen
  51. EnsureDatabaseVersion
  52. CreateSchema
  53. UpgradeSchema
  54. ResetConnectionAndTables
  55. DeleteExistingAndCreateNewDatabase
  56. OnDatabaseError

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "webkit/browser/appcache/appcache_database.h"

#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "sql/connection.h"
#include "sql/error_delegate_util.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "webkit/browser/appcache/appcache_entry.h"
#include "webkit/browser/appcache/appcache_histograms.h"

namespace appcache {

// Schema -------------------------------------------------------------------
namespace {

#if defined(APPCACHE_USE_SIMPLE_CACHE)
const int kCurrentVersion = 6;
const int kCompatibleVersion = 6;
#else
const int kCurrentVersion = 5;
const int kCompatibleVersion = 5;
#endif

// A mechanism to run experiments that may affect in data being persisted
// in different ways such that when the experiment is toggled on/off via
// cmd line flags, the database gets reset. The active flags are stored at
// the time of database creation and compared when reopening. If different
// the database is reset.
const char kExperimentFlagsKey[] = "ExperimentFlags";

const char kGroupsTable[] = "Groups";
const char kCachesTable[] = "Caches";
const char kEntriesTable[] = "Entries";
const char kNamespacesTable[] = "Namespaces";
const char kOnlineWhiteListsTable[] = "OnlineWhiteLists";
const char kDeletableResponseIdsTable[] = "DeletableResponseIds";

struct TableInfo {
  const char* table_name;
  const char* columns;
};

struct IndexInfo {
  const char* index_name;
  const char* table_name;
  const char* columns;
  bool unique;
};

const TableInfo kTables[] = {
  { kGroupsTable,
    "(group_id INTEGER PRIMARY KEY,"
    " origin TEXT,"
    " manifest_url TEXT,"
    " creation_time INTEGER,"
    " last_access_time INTEGER)" },

  { kCachesTable,
    "(cache_id INTEGER PRIMARY KEY,"
    " group_id INTEGER,"
    " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
    " update_time INTEGER,"
    " cache_size INTEGER)" },  // intentionally not normalized

  { kEntriesTable,
    "(cache_id INTEGER,"
    " url TEXT,"
    " flags INTEGER,"
    " response_id INTEGER,"
    " response_size INTEGER)" },

  { kNamespacesTable,
    "(cache_id INTEGER,"
    " origin TEXT,"  // intentionally not normalized
    " type INTEGER,"
    " namespace_url TEXT,"
    " target_url TEXT,"
    " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },

  { kOnlineWhiteListsTable,
    "(cache_id INTEGER,"
    " namespace_url TEXT,"
    " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },

  { kDeletableResponseIdsTable,
    "(response_id INTEGER NOT NULL)" },
};

const IndexInfo kIndexes[] = {
  { "GroupsOriginIndex",
    kGroupsTable,
    "(origin)",
    false },

  { "GroupsManifestIndex",
    kGroupsTable,
    "(manifest_url)",
    true },

  { "CachesGroupIndex",
    kCachesTable,
    "(group_id)",
    false },

  { "EntriesCacheIndex",
    kEntriesTable,
    "(cache_id)",
    false },

  { "EntriesCacheAndUrlIndex",
    kEntriesTable,
    "(cache_id, url)",
    true },

  { "EntriesResponseIdIndex",
    kEntriesTable,
    "(response_id)",
    true },

  { "NamespacesCacheIndex",
    kNamespacesTable,
    "(cache_id)",
    false },

  { "NamespacesOriginIndex",
    kNamespacesTable,
    "(origin)",
    false },

  { "NamespacesCacheAndUrlIndex",
    kNamespacesTable,
    "(cache_id, namespace_url)",
    true },

  { "OnlineWhiteListCacheIndex",
    kOnlineWhiteListsTable,
    "(cache_id)",
    false },

  { "DeletableResponsesIdIndex",
    kDeletableResponseIdsTable,
    "(response_id)",
    true },
};

const int kTableCount = ARRAYSIZE_UNSAFE(kTables);
const int kIndexCount = ARRAYSIZE_UNSAFE(kIndexes);

bool CreateTable(sql::Connection* db, const TableInfo& info) {
  std::string sql("CREATE TABLE ");
  sql += info.table_name;
  sql += info.columns;
  return db->Execute(sql.c_str());
}

bool CreateIndex(sql::Connection* db, const IndexInfo& info) {
  std::string sql;
  if (info.unique)
    sql += "CREATE UNIQUE INDEX ";
  else
    sql += "CREATE INDEX ";
  sql += info.index_name;
  sql += " ON ";
  sql += info.table_name;
  sql += info.columns;
  return db->Execute(sql.c_str());
}

std::string GetActiveExperimentFlags() {
  if (CommandLine::ForCurrentProcess()->HasSwitch(kEnableExecutableHandlers))
    return std::string("executableHandlersEnabled");
  return std::string();
}

}  // anon namespace

// AppCacheDatabase ----------------------------------------------------------

AppCacheDatabase::GroupRecord::GroupRecord()
    : group_id(0) {
}

AppCacheDatabase::GroupRecord::~GroupRecord() {
}

AppCacheDatabase::NamespaceRecord::NamespaceRecord()
    : cache_id(0) {
}

AppCacheDatabase::NamespaceRecord::~NamespaceRecord() {
}


AppCacheDatabase::AppCacheDatabase(const base::FilePath& path)
    : db_file_path_(path),
      is_disabled_(false),
      is_recreating_(false),
      was_corruption_detected_(false) {
}

AppCacheDatabase::~AppCacheDatabase() {
}

void AppCacheDatabase::Disable() {
  VLOG(1) << "Disabling appcache database.";
  is_disabled_ = true;
  ResetConnectionAndTables();
}

int64 AppCacheDatabase::GetOriginUsage(const GURL& origin) {
  std::vector<CacheRecord> records;
  if (!FindCachesForOrigin(origin, &records))
    return 0;

  int64 origin_usage = 0;
  std::vector<CacheRecord>::const_iterator iter = records.begin();
  while (iter != records.end()) {
    origin_usage += iter->cache_size;
    ++iter;
  }
  return origin_usage;
}

bool AppCacheDatabase::GetAllOriginUsage(std::map<GURL, int64>* usage_map) {
  std::set<GURL> origins;
  if (!FindOriginsWithGroups(&origins))
    return false;
  for (std::set<GURL>::const_iterator origin = origins.begin();
       origin != origins.end(); ++origin) {
    (*usage_map)[*origin] = GetOriginUsage(*origin);
  }
  return true;
}

bool AppCacheDatabase::FindOriginsWithGroups(std::set<GURL>* origins) {
  DCHECK(origins && origins->empty());
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT DISTINCT(origin) FROM Groups";

  sql::Statement statement(db_->GetUniqueStatement(kSql));

  while (statement.Step())
    origins->insert(GURL(statement.ColumnString(0)));

  return statement.Succeeded();
}

bool AppCacheDatabase::FindLastStorageIds(
    int64* last_group_id, int64* last_cache_id, int64* last_response_id,
    int64* last_deletable_response_rowid) {
  DCHECK(last_group_id && last_cache_id && last_response_id &&
         last_deletable_response_rowid);

  *last_group_id = 0;
  *last_cache_id = 0;
  *last_response_id = 0;
  *last_deletable_response_rowid = 0;

  if (!LazyOpen(false))
    return false;

  const char* kMaxGroupIdSql = "SELECT MAX(group_id) FROM Groups";
  const char* kMaxCacheIdSql = "SELECT MAX(cache_id) FROM Caches";
  const char* kMaxResponseIdFromEntriesSql =
      "SELECT MAX(response_id) FROM Entries";
  const char* kMaxResponseIdFromDeletablesSql =
      "SELECT MAX(response_id) FROM DeletableResponseIds";
  const char* kMaxDeletableResponseRowIdSql =
      "SELECT MAX(rowid) FROM DeletableResponseIds";
  int64 max_group_id;
  int64 max_cache_id;
  int64 max_response_id_from_entries;
  int64 max_response_id_from_deletables;
  int64 max_deletable_response_rowid;
  if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql, &max_group_id) ||
      !RunUniqueStatementWithInt64Result(kMaxCacheIdSql, &max_cache_id) ||
      !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql,
                                         &max_response_id_from_entries) ||
      !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql,
                                         &max_response_id_from_deletables) ||
      !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql,
                                         &max_deletable_response_rowid)) {
    return false;
  }

  *last_group_id = max_group_id;
  *last_cache_id = max_cache_id;
  *last_response_id = std::max(max_response_id_from_entries,
                               max_response_id_from_deletables);
  *last_deletable_response_rowid = max_deletable_response_rowid;
  return true;
}

bool AppCacheDatabase::FindGroup(int64 group_id, GroupRecord* record) {
  DCHECK(record);
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT group_id, origin, manifest_url,"
      "       creation_time, last_access_time"
      "  FROM Groups WHERE group_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));

  statement.BindInt64(0, group_id);
  if (!statement.Step())
    return false;

  ReadGroupRecord(statement, record);
  DCHECK(record->group_id == group_id);
  return true;
}

bool AppCacheDatabase::FindGroupForManifestUrl(
    const GURL& manifest_url, GroupRecord* record) {
  DCHECK(record);
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT group_id, origin, manifest_url,"
      "       creation_time, last_access_time"
      "  FROM Groups WHERE manifest_url = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindString(0, manifest_url.spec());

  if (!statement.Step())
    return false;

  ReadGroupRecord(statement, record);
  DCHECK(record->manifest_url == manifest_url);
  return true;
}

bool AppCacheDatabase::FindGroupsForOrigin(
    const GURL& origin, std::vector<GroupRecord>* records) {
  DCHECK(records && records->empty());
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT group_id, origin, manifest_url,"
      "       creation_time, last_access_time"
      "   FROM Groups WHERE origin = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindString(0, origin.spec());

  while (statement.Step()) {
    records->push_back(GroupRecord());
    ReadGroupRecord(statement, &records->back());
    DCHECK(records->back().origin == origin);
  }

  return statement.Succeeded();
}

bool AppCacheDatabase::FindGroupForCache(int64 cache_id, GroupRecord* record) {
  DCHECK(record);
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT g.group_id, g.origin, g.manifest_url,"
      "       g.creation_time, g.last_access_time"
      "  FROM Groups g, Caches c"
      "  WHERE c.cache_id = ? AND c.group_id = g.group_id";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);

  if (!statement.Step())
    return false;

  ReadGroupRecord(statement, record);
  return true;
}

bool AppCacheDatabase::UpdateGroupLastAccessTime(
    int64 group_id, base::Time time) {
  if (!LazyOpen(true))
    return false;

  const char* kSql =
      "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, time.ToInternalValue());
  statement.BindInt64(1, group_id);

  return statement.Run() && db_->GetLastChangeCount();
}

bool AppCacheDatabase::InsertGroup(const GroupRecord* record) {
  if (!LazyOpen(true))
    return false;

  const char* kSql =
      "INSERT INTO Groups"
      "  (group_id, origin, manifest_url, creation_time, last_access_time)"
      "  VALUES(?, ?, ?, ?, ?)";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, record->group_id);
  statement.BindString(1, record->origin.spec());
  statement.BindString(2, record->manifest_url.spec());
  statement.BindInt64(3, record->creation_time.ToInternalValue());
  statement.BindInt64(4, record->last_access_time.ToInternalValue());

  return statement.Run();
}

bool AppCacheDatabase::DeleteGroup(int64 group_id) {
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "DELETE FROM Groups WHERE group_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, group_id);

  return statement.Run();
}

bool AppCacheDatabase::FindCache(int64 cache_id, CacheRecord* record) {
  DCHECK(record);
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
      " FROM Caches WHERE cache_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);

  if (!statement.Step())
    return false;

  ReadCacheRecord(statement, record);
  return true;
}

bool AppCacheDatabase::FindCacheForGroup(int64 group_id, CacheRecord* record) {
  DCHECK(record);
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
      "  FROM Caches WHERE group_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, group_id);

  if (!statement.Step())
    return false;

  ReadCacheRecord(statement, record);
  return true;
}

bool AppCacheDatabase::FindCachesForOrigin(
    const GURL& origin, std::vector<CacheRecord>* records) {
  DCHECK(records);
  std::vector<GroupRecord> group_records;
  if (!FindGroupsForOrigin(origin, &group_records))
    return false;

  CacheRecord cache_record;
  std::vector<GroupRecord>::const_iterator iter = group_records.begin();
  while (iter != group_records.end()) {
    if (FindCacheForGroup(iter->group_id, &cache_record))
      records->push_back(cache_record);
    ++iter;
  }
  return true;
}

bool AppCacheDatabase::InsertCache(const CacheRecord* record) {
  if (!LazyOpen(true))
    return false;

  const char* kSql =
      "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
      "                    update_time, cache_size)"
      "  VALUES(?, ?, ?, ?, ?)";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, record->cache_id);
  statement.BindInt64(1, record->group_id);
  statement.BindBool(2, record->online_wildcard);
  statement.BindInt64(3, record->update_time.ToInternalValue());
  statement.BindInt64(4, record->cache_size);

  return statement.Run();
}

bool AppCacheDatabase::DeleteCache(int64 cache_id) {
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "DELETE FROM Caches WHERE cache_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);

  return statement.Run();
}

bool AppCacheDatabase::FindEntriesForCache(
    int64 cache_id, std::vector<EntryRecord>* records) {
  DCHECK(records && records->empty());
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
      "  WHERE cache_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);

  while (statement.Step()) {
    records->push_back(EntryRecord());
    ReadEntryRecord(statement, &records->back());
    DCHECK(records->back().cache_id == cache_id);
  }

  return statement.Succeeded();
}

bool AppCacheDatabase::FindEntriesForUrl(
    const GURL& url, std::vector<EntryRecord>* records) {
  DCHECK(records && records->empty());
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
      "  WHERE url = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindString(0, url.spec());

  while (statement.Step()) {
    records->push_back(EntryRecord());
    ReadEntryRecord(statement, &records->back());
    DCHECK(records->back().url == url);
  }

  return statement.Succeeded();
}

bool AppCacheDatabase::FindEntry(
    int64 cache_id, const GURL& url, EntryRecord* record) {
  DCHECK(record);
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
      "  WHERE cache_id = ? AND url = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);
  statement.BindString(1, url.spec());

  if (!statement.Step())
    return false;

  ReadEntryRecord(statement, record);
  DCHECK(record->cache_id == cache_id);
  DCHECK(record->url == url);
  return true;
}

bool AppCacheDatabase::InsertEntry(const EntryRecord* record) {
  if (!LazyOpen(true))
    return false;

  const char* kSql =
      "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
      "  VALUES(?, ?, ?, ?, ?)";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, record->cache_id);
  statement.BindString(1, record->url.spec());
  statement.BindInt(2, record->flags);
  statement.BindInt64(3, record->response_id);
  statement.BindInt64(4, record->response_size);

  return statement.Run();
}

bool AppCacheDatabase::InsertEntryRecords(
    const std::vector<EntryRecord>& records) {
  if (records.empty())
    return true;
  sql::Transaction transaction(db_.get());
  if (!transaction.Begin())
    return false;
  std::vector<EntryRecord>::const_iterator iter = records.begin();
  while (iter != records.end()) {
    if (!InsertEntry(&(*iter)))
      return false;
    ++iter;
  }
  return transaction.Commit();
}

bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id) {
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "DELETE FROM Entries WHERE cache_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);

  return statement.Run();
}

bool AppCacheDatabase::AddEntryFlags(
    const GURL& entry_url, int64 cache_id, int additional_flags) {
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt(0, additional_flags);
  statement.BindInt64(1, cache_id);
  statement.BindString(2, entry_url.spec());

  return statement.Run() && db_->GetLastChangeCount();
}

bool AppCacheDatabase::FindNamespacesForOrigin(
    const GURL& origin,
    std::vector<NamespaceRecord>* intercepts,
    std::vector<NamespaceRecord>* fallbacks) {
  DCHECK(intercepts && intercepts->empty());
  DCHECK(fallbacks && fallbacks->empty());
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
      "  FROM Namespaces WHERE origin = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindString(0, origin.spec());

  ReadNamespaceRecords(&statement, intercepts, fallbacks);

  return statement.Succeeded();
}

bool AppCacheDatabase::FindNamespacesForCache(
    int64 cache_id,
    std::vector<NamespaceRecord>* intercepts,
    std::vector<NamespaceRecord>* fallbacks) {
  DCHECK(intercepts && intercepts->empty());
  DCHECK(fallbacks && fallbacks->empty());
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
      "  FROM Namespaces WHERE cache_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);

  ReadNamespaceRecords(&statement, intercepts, fallbacks);

  return statement.Succeeded();
}

bool AppCacheDatabase::InsertNamespace(
    const NamespaceRecord* record) {
  if (!LazyOpen(true))
    return false;

  const char* kSql =
      "INSERT INTO Namespaces"
      "  (cache_id, origin, type, namespace_url, target_url, is_pattern)"
      "  VALUES (?, ?, ?, ?, ?, ?)";

  // Note: quick and dirty storage for the 'executable' bit w/o changing
  // schemas, we use the high bit of 'type' field.
  int type_with_executable_bit = record->namespace_.type;
  if (record->namespace_.is_executable) {
    type_with_executable_bit |= 0x8000000;
    DCHECK(CommandLine::ForCurrentProcess()->HasSwitch(
        kEnableExecutableHandlers));
  }

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, record->cache_id);
  statement.BindString(1, record->origin.spec());
  statement.BindInt(2, type_with_executable_bit);
  statement.BindString(3, record->namespace_.namespace_url.spec());
  statement.BindString(4, record->namespace_.target_url.spec());
  statement.BindBool(5, record->namespace_.is_pattern);
  return statement.Run();
}

bool AppCacheDatabase::InsertNamespaceRecords(
    const std::vector<NamespaceRecord>& records) {
  if (records.empty())
    return true;
  sql::Transaction transaction(db_.get());
  if (!transaction.Begin())
    return false;
  std::vector<NamespaceRecord>::const_iterator iter = records.begin();
  while (iter != records.end()) {
    if (!InsertNamespace(&(*iter)))
      return false;
    ++iter;
  }
  return transaction.Commit();
}

bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id) {
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "DELETE FROM Namespaces WHERE cache_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);

  return statement.Run();
}

bool AppCacheDatabase::FindOnlineWhiteListForCache(
    int64 cache_id, std::vector<OnlineWhiteListRecord>* records) {
  DCHECK(records && records->empty());
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
      "  WHERE cache_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);

  while (statement.Step()) {
    records->push_back(OnlineWhiteListRecord());
    this->ReadOnlineWhiteListRecord(statement, &records->back());
    DCHECK(records->back().cache_id == cache_id);
  }
  return statement.Succeeded();
}

bool AppCacheDatabase::InsertOnlineWhiteList(
    const OnlineWhiteListRecord* record) {
  if (!LazyOpen(true))
    return false;

  const char* kSql =
      "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
      "  VALUES (?, ?, ?)";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, record->cache_id);
  statement.BindString(1, record->namespace_url.spec());
  statement.BindBool(2, record->is_pattern);

  return statement.Run();
}

bool AppCacheDatabase::InsertOnlineWhiteListRecords(
    const std::vector<OnlineWhiteListRecord>& records) {
  if (records.empty())
    return true;
  sql::Transaction transaction(db_.get());
  if (!transaction.Begin())
    return false;
  std::vector<OnlineWhiteListRecord>::const_iterator iter = records.begin();
  while (iter != records.end()) {
    if (!InsertOnlineWhiteList(&(*iter)))
      return false;
    ++iter;
  }
  return transaction.Commit();
}

bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id) {
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, cache_id);

  return statement.Run();
}

bool AppCacheDatabase::GetDeletableResponseIds(
    std::vector<int64>* response_ids, int64 max_rowid, int limit) {
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT response_id FROM DeletableResponseIds "
      "  WHERE rowid <= ?"
      "  LIMIT ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindInt64(0, max_rowid);
  statement.BindInt64(1, limit);

  while (statement.Step())
    response_ids->push_back(statement.ColumnInt64(0));
  return statement.Succeeded();
}

bool AppCacheDatabase::InsertDeletableResponseIds(
    const std::vector<int64>& response_ids) {
  const char* kSql =
      "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
  return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
}

bool AppCacheDatabase::DeleteDeletableResponseIds(
    const std::vector<int64>& response_ids) {
  const char* kSql =
      "DELETE FROM DeletableResponseIds WHERE response_id = ?";
  return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
}

bool AppCacheDatabase::RunCachedStatementWithIds(
    const sql::StatementID& statement_id, const char* sql,
    const std::vector<int64>& ids) {
  DCHECK(sql);
  if (!LazyOpen(true))
    return false;

  sql::Transaction transaction(db_.get());
  if (!transaction.Begin())
    return false;

  sql::Statement statement(db_->GetCachedStatement(statement_id, sql));

  std::vector<int64>::const_iterator iter = ids.begin();
  while (iter != ids.end()) {
    statement.BindInt64(0, *iter);
    if (!statement.Run())
      return false;
    statement.Reset(true);
    ++iter;
  }

  return transaction.Commit();
}

bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
    const char* sql, int64* result) {
  DCHECK(sql);
  sql::Statement statement(db_->GetUniqueStatement(sql));
  if (!statement.Step()) {
    return false;
  }
  *result = statement.ColumnInt64(0);
  return true;
}

bool AppCacheDatabase::FindResponseIdsForCacheHelper(
    int64 cache_id, std::vector<int64>* ids_vector,
    std::set<int64>* ids_set) {
  DCHECK(ids_vector || ids_set);
  DCHECK(!(ids_vector && ids_set));
  if (!LazyOpen(false))
    return false;

  const char* kSql =
      "SELECT response_id FROM Entries WHERE cache_id = ?";

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));

  statement.BindInt64(0, cache_id);
  while (statement.Step()) {
    int64 id = statement.ColumnInt64(0);
    if (ids_set)
      ids_set->insert(id);
    else
      ids_vector->push_back(id);
  }

  return statement.Succeeded();
}

void AppCacheDatabase::ReadGroupRecord(
    const sql::Statement& statement, GroupRecord* record) {
  record->group_id = statement.ColumnInt64(0);
  record->origin = GURL(statement.ColumnString(1));
  record->manifest_url = GURL(statement.ColumnString(2));
  record->creation_time =
      base::Time::FromInternalValue(statement.ColumnInt64(3));
  record->last_access_time =
      base::Time::FromInternalValue(statement.ColumnInt64(4));
}

void AppCacheDatabase::ReadCacheRecord(
    const sql::Statement& statement, CacheRecord* record) {
  record->cache_id = statement.ColumnInt64(0);
  record->group_id = statement.ColumnInt64(1);
  record->online_wildcard = statement.ColumnBool(2);
  record->update_time =
      base::Time::FromInternalValue(statement.ColumnInt64(3));
  record->cache_size = statement.ColumnInt64(4);
}

void AppCacheDatabase::ReadEntryRecord(
    const sql::Statement& statement, EntryRecord* record) {
  record->cache_id = statement.ColumnInt64(0);
  record->url = GURL(statement.ColumnString(1));
  record->flags = statement.ColumnInt(2);
  record->response_id = statement.ColumnInt64(3);
  record->response_size = statement.ColumnInt64(4);
}

void AppCacheDatabase::ReadNamespaceRecords(
      sql::Statement* statement,
      NamespaceRecordVector* intercepts,
      NamespaceRecordVector* fallbacks) {
  while (statement->Step()) {
    NamespaceType type = static_cast<NamespaceType>(statement->ColumnInt(2));
    NamespaceRecordVector* records =
        (type == FALLBACK_NAMESPACE) ? fallbacks : intercepts;
    records->push_back(NamespaceRecord());
    ReadNamespaceRecord(statement, &records->back());
  }
}

void AppCacheDatabase::ReadNamespaceRecord(
    const sql::Statement* statement, NamespaceRecord* record) {
  record->cache_id = statement->ColumnInt64(0);
  record->origin = GURL(statement->ColumnString(1));
  int type_with_executable_bit = statement->ColumnInt(2);
  record->namespace_.namespace_url = GURL(statement->ColumnString(3));
  record->namespace_.target_url = GURL(statement->ColumnString(4));
  record->namespace_.is_pattern = statement->ColumnBool(5);

  // Note: quick and dirty storage for the 'executable' bit w/o changing
  // schemas, we use the high bit of 'type' field.
  record->namespace_.type = static_cast<NamespaceType>
      (type_with_executable_bit & 0x7ffffff);
  record->namespace_.is_executable =
      (type_with_executable_bit & 0x80000000) != 0;
  DCHECK(!record->namespace_.is_executable ||
      CommandLine::ForCurrentProcess()->HasSwitch(kEnableExecutableHandlers));
}

void AppCacheDatabase::ReadOnlineWhiteListRecord(
    const sql::Statement& statement, OnlineWhiteListRecord* record) {
  record->cache_id = statement.ColumnInt64(0);
  record->namespace_url = GURL(statement.ColumnString(1));
  record->is_pattern = statement.ColumnBool(2);
}

bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
  if (db_)
    return true;

  // If we tried and failed once, don't try again in the same session
  // to avoid creating an incoherent mess on disk.
  if (is_disabled_)
    return false;

  // Avoid creating a database at all if we can.
  bool use_in_memory_db = db_file_path_.empty();
  if (!create_if_needed &&
      (use_in_memory_db || !base::PathExists(db_file_path_))) {
    return false;
  }

  db_.reset(new sql::Connection);
  meta_table_.reset(new sql::MetaTable);

  db_->set_histogram_tag("AppCache");

  bool opened = false;
  if (use_in_memory_db) {
    opened = db_->OpenInMemory();
  } else if (!base::CreateDirectory(db_file_path_.DirName())) {
    LOG(ERROR) << "Failed to create appcache directory.";
  } else {
    opened = db_->Open(db_file_path_);
    if (opened)
      db_->Preload();
  }

  if (!opened || !db_->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
    LOG(ERROR) << "Failed to open the appcache database.";
    AppCacheHistograms::CountInitResult(
        AppCacheHistograms::SQL_DATABASE_ERROR);

    // We're unable to open the database. This is a fatal error
    // which we can't recover from. We try to handle it by deleting
    // the existing appcache data and starting with a clean slate in
    // this browser session.
    if (!use_in_memory_db && DeleteExistingAndCreateNewDatabase())
      return true;

    Disable();
    return false;
  }

  AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK);
  was_corruption_detected_ = false;
  db_->set_error_callback(
      base::Bind(&AppCacheDatabase::OnDatabaseError, base::Unretained(this)));
  return true;
}

bool AppCacheDatabase::EnsureDatabaseVersion() {
  if (!sql::MetaTable::DoesTableExist(db_.get()))
    return CreateSchema();

  if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
    return false;

  if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
    LOG(WARNING) << "AppCache database is too new.";
    return false;
  }

  std::string stored_flags;
  meta_table_->GetValue(kExperimentFlagsKey, &stored_flags);
  if (stored_flags != GetActiveExperimentFlags())
    return false;

  if (meta_table_->GetVersionNumber() < kCurrentVersion)
    return UpgradeSchema();

#ifndef NDEBUG
  DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
  for (int i = 0; i < kTableCount; ++i) {
    DCHECK(db_->DoesTableExist(kTables[i].table_name));
  }
  for (int i = 0; i < kIndexCount; ++i) {
    DCHECK(db_->DoesIndexExist(kIndexes[i].index_name));
  }
#endif

  return true;
}

bool AppCacheDatabase::CreateSchema() {
  sql::Transaction transaction(db_.get());
  if (!transaction.Begin())
    return false;

  if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
    return false;

  if (!meta_table_->SetValue(kExperimentFlagsKey,
                             GetActiveExperimentFlags())) {
    return false;
  }

  for (int i = 0; i < kTableCount; ++i) {
    if (!CreateTable(db_.get(), kTables[i]))
      return false;
  }

  for (int i = 0; i < kIndexCount; ++i) {
    if (!CreateIndex(db_.get(), kIndexes[i]))
      return false;
  }

  return transaction.Commit();
}

bool AppCacheDatabase::UpgradeSchema() {
#if defined(APPCACHE_USE_SIMPLE_CACHE)
  return DeleteExistingAndCreateNewDatabase();
#else
  if (meta_table_->GetVersionNumber() == 3) {
    // version 3 was pre 12/17/2011
    DCHECK_EQ(strcmp(kNamespacesTable, kTables[3].table_name), 0);
    DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[6].table_name), 0);
    DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[7].table_name), 0);
    DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[8].table_name), 0);

    const TableInfo kNamespaceTable_v4 = {
        kNamespacesTable,
        "(cache_id INTEGER,"
        " origin TEXT,"  // intentionally not normalized
        " type INTEGER,"
        " namespace_url TEXT,"
        " target_url TEXT)"
    };

    // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
    // but without the is_pattern column added in v5.
    sql::Transaction transaction(db_.get());
    if (!transaction.Begin() ||
        !CreateTable(db_.get(), kNamespaceTable_v4)) {
      return false;
    }

    // Move data from the old table to the new table, setting the
    // 'type' for all current records to the value for FALLBACK_NAMESPACE.
    DCHECK_EQ(0, static_cast<int>(FALLBACK_NAMESPACE));
    if (!db_->Execute(
            "INSERT INTO Namespaces"
            "  SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
            "  FROM FallbackNameSpaces")) {
      return false;
    }

    // Drop the old table, indexes on that table are also removed by this.
    if (!db_->Execute("DROP TABLE FallbackNameSpaces"))
      return false;

    // Create new indexes.
    if (!CreateIndex(db_.get(), kIndexes[6]) ||
        !CreateIndex(db_.get(), kIndexes[7]) ||
        !CreateIndex(db_.get(), kIndexes[8])) {
      return false;
    }

    meta_table_->SetVersionNumber(4);
    meta_table_->SetCompatibleVersionNumber(4);
    if (!transaction.Commit())
      return false;
  }

  if (meta_table_->GetVersionNumber() == 4) {
    // version 4 pre 3/30/2013
    // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
    DCHECK_EQ(strcmp(kNamespacesTable, "Namespaces"), 0);
    sql::Transaction transaction(db_.get());
    if (!transaction.Begin())
      return false;
    if (!db_->Execute(
            "ALTER TABLE Namespaces ADD COLUMN"
            "  is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
      return false;
    }
    if (!db_->Execute(
            "ALTER TABLE OnlineWhitelists ADD COLUMN"
            "  is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
      return false;
    }
    meta_table_->SetVersionNumber(5);
    meta_table_->SetCompatibleVersionNumber(5);
    return transaction.Commit();
  }

  // If there is no upgrade path for the version on disk to the current
  // version, nuke everything and start over.
  return DeleteExistingAndCreateNewDatabase();
#endif
}

void AppCacheDatabase::ResetConnectionAndTables() {
  meta_table_.reset();
  db_.reset();
}

bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
  DCHECK(!db_file_path_.empty());
  DCHECK(base::PathExists(db_file_path_));
  VLOG(1) << "Deleting existing appcache data and starting over.";

  ResetConnectionAndTables();

  // This also deletes the disk cache data.
  base::FilePath directory = db_file_path_.DirName();
  if (!base::DeleteFile(directory, true))
    return false;

  // Make sure the steps above actually deleted things.
  if (base::PathExists(directory))
    return false;

  if (!base::CreateDirectory(directory))
    return false;

  // So we can't go recursive.
  if (is_recreating_)
    return false;

  base::AutoReset<bool> auto_reset(&is_recreating_, true);
  return LazyOpen(true);
}

void AppCacheDatabase::OnDatabaseError(int err, sql::Statement* stmt) {
  was_corruption_detected_ |= sql::IsErrorCatastrophic(err);
  if (!db_->ShouldIgnoreSqliteError(err))
    DLOG(ERROR) << db_->GetErrorMessage();
  // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
}

}  // namespace appcache

/* [<][>][^][v][top][bottom][index][help] */