// 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/appcache/appcache_database.h" #include "base/auto_reset.h" #include "base/command_line.h" #include "base/file_util.h" #include "base/logging.h" #include "base/utf_string_conversions.h" #include "sql/connection.h" #include "sql/meta_table.h" #include "sql/statement.h" #include "sql/transaction.h" #include "webkit/appcache/appcache_entry.h" #include "webkit/appcache/appcache_histograms.h" namespace appcache { // Schema ------------------------------------------------------------------- namespace { const int kCurrentVersion = 5; const int kCompatibleVersion = 5; // 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) { } AppCacheDatabase::~AppCacheDatabase() { } void AppCacheDatabase::CloseConnection() { // We can't close the connection for an in-memory database w/o // losing all of the data, so we don't do that. if (!db_file_path_.empty()) ResetConnectionAndTables(); } void AppCacheDatabase::Disable() { VLOG(1) << "Disabling appcache database."; is_disabled_ = true; ResetConnectionAndTables(); } int64 AppCacheDatabase::GetOriginUsage(const GURL& origin) { std::vector records; if (!FindCachesForOrigin(origin, &records)) return 0; int64 origin_usage = 0; std::vector::const_iterator iter = records.begin(); while (iter != records.end()) { origin_usage += iter->cache_size; ++iter; } return origin_usage; } bool AppCacheDatabase::GetAllOriginUsage(std::map* usage_map) { std::set origins; if (!FindOriginsWithGroups(&origins)) return false; for (std::set::const_iterator origin = origins.begin(); origin != origins.end(); ++origin) { (*usage_map)[*origin] = GetOriginUsage(*origin); } return true; } bool AppCacheDatabase::FindOriginsWithGroups(std::set* 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* 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* records) { DCHECK(records); std::vector group_records; if (!FindGroupsForOrigin(origin, &group_records)) return false; CacheRecord cache_record; std::vector::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* 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* 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& records) { if (records.empty()) return true; sql::Transaction transaction(db_.get()); if (!transaction.Begin()) return false; std::vector::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* intercepts, std::vector* 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* intercepts, std::vector* 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& records) { if (records.empty()) return true; sql::Transaction transaction(db_.get()); if (!transaction.Begin()) return false; std::vector::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* 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& records) { if (records.empty()) return true; sql::Transaction transaction(db_.get()); if (!transaction.Begin()) return false; std::vector::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* 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& 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& 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& 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::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* ids_vector, std::set* 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(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 (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 || !file_util::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 (!file_util::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 || !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); 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 (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(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(); } void AppCacheDatabase::ResetConnectionAndTables() { meta_table_.reset(); db_.reset(); } bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() { DCHECK(!db_file_path_.empty()); DCHECK(file_util::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 (!file_util::Delete(directory, true) || !file_util::CreateDirectory(directory)) { return false; } // Make sure the steps above actually deleted things. if (file_util::PathExists(db_file_path_)) return false; // So we can't go recursive. if (is_recreating_) return false; base::AutoReset auto_reset(&is_recreating_, true); return LazyOpen(true); } } // namespace appcache