Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / appcache / appcache_database.cc
blob8298bfb58bb6fff89a8c230800b653cc3ae4f350
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.
5 #include "content/browser/appcache/appcache_database.h"
7 #include "base/auto_reset.h"
8 #include "base/bind.h"
9 #include "base/command_line.h"
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "content/browser/appcache/appcache_entry.h"
14 #include "content/browser/appcache/appcache_histograms.h"
15 #include "sql/connection.h"
16 #include "sql/error_delegate_util.h"
17 #include "sql/meta_table.h"
18 #include "sql/statement.h"
19 #include "sql/transaction.h"
21 namespace content {
23 // Schema -------------------------------------------------------------------
24 namespace {
26 const int kCurrentVersion = 7;
27 const int kCompatibleVersion = 7;
28 const bool kCreateIfNeeded = true;
29 const bool kDontCreate = false;
31 // A mechanism to run experiments that may affect in data being persisted
32 // in different ways such that when the experiment is toggled on/off via
33 // cmd line flags, the database gets reset. The active flags are stored at
34 // the time of database creation and compared when reopening. If different
35 // the database is reset.
36 const char kExperimentFlagsKey[] = "ExperimentFlags";
38 const char kGroupsTable[] = "Groups";
39 const char kCachesTable[] = "Caches";
40 const char kEntriesTable[] = "Entries";
41 const char kNamespacesTable[] = "Namespaces";
42 const char kOnlineWhiteListsTable[] = "OnlineWhiteLists";
43 const char kDeletableResponseIdsTable[] = "DeletableResponseIds";
45 struct TableInfo {
46 const char* table_name;
47 const char* columns;
50 struct IndexInfo {
51 const char* index_name;
52 const char* table_name;
53 const char* columns;
54 bool unique;
57 const TableInfo kTables[] = {
58 { kGroupsTable,
59 "(group_id INTEGER PRIMARY KEY,"
60 " origin TEXT,"
61 " manifest_url TEXT,"
62 " creation_time INTEGER,"
63 " last_access_time INTEGER,"
64 " last_full_update_check_time INTEGER,"
65 " first_evictable_error_time INTEGER)" },
67 { kCachesTable,
68 "(cache_id INTEGER PRIMARY KEY,"
69 " group_id INTEGER,"
70 " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
71 " update_time INTEGER,"
72 " cache_size INTEGER)" }, // intentionally not normalized
74 { kEntriesTable,
75 "(cache_id INTEGER,"
76 " url TEXT,"
77 " flags INTEGER,"
78 " response_id INTEGER,"
79 " response_size INTEGER)" },
81 { kNamespacesTable,
82 "(cache_id INTEGER,"
83 " origin TEXT," // intentionally not normalized
84 " type INTEGER,"
85 " namespace_url TEXT,"
86 " target_url TEXT,"
87 " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
89 { kOnlineWhiteListsTable,
90 "(cache_id INTEGER,"
91 " namespace_url TEXT,"
92 " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
94 { kDeletableResponseIdsTable,
95 "(response_id INTEGER NOT NULL)" },
98 const IndexInfo kIndexes[] = {
99 { "GroupsOriginIndex",
100 kGroupsTable,
101 "(origin)",
102 false },
104 { "GroupsManifestIndex",
105 kGroupsTable,
106 "(manifest_url)",
107 true },
109 { "CachesGroupIndex",
110 kCachesTable,
111 "(group_id)",
112 false },
114 { "EntriesCacheIndex",
115 kEntriesTable,
116 "(cache_id)",
117 false },
119 { "EntriesCacheAndUrlIndex",
120 kEntriesTable,
121 "(cache_id, url)",
122 true },
124 { "EntriesResponseIdIndex",
125 kEntriesTable,
126 "(response_id)",
127 true },
129 { "NamespacesCacheIndex",
130 kNamespacesTable,
131 "(cache_id)",
132 false },
134 { "NamespacesOriginIndex",
135 kNamespacesTable,
136 "(origin)",
137 false },
139 { "NamespacesCacheAndUrlIndex",
140 kNamespacesTable,
141 "(cache_id, namespace_url)",
142 true },
144 { "OnlineWhiteListCacheIndex",
145 kOnlineWhiteListsTable,
146 "(cache_id)",
147 false },
149 { "DeletableResponsesIdIndex",
150 kDeletableResponseIdsTable,
151 "(response_id)",
152 true },
155 const int kTableCount = arraysize(kTables);
156 const int kIndexCount = arraysize(kIndexes);
158 bool CreateTable(sql::Connection* db, const TableInfo& info) {
159 std::string sql("CREATE TABLE ");
160 sql += info.table_name;
161 sql += info.columns;
162 return db->Execute(sql.c_str());
165 bool CreateIndex(sql::Connection* db, const IndexInfo& info) {
166 std::string sql;
167 if (info.unique)
168 sql += "CREATE UNIQUE INDEX ";
169 else
170 sql += "CREATE INDEX ";
171 sql += info.index_name;
172 sql += " ON ";
173 sql += info.table_name;
174 sql += info.columns;
175 return db->Execute(sql.c_str());
178 std::string GetActiveExperimentFlags() {
179 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
180 kEnableExecutableHandlers))
181 return std::string("executableHandlersEnabled");
182 return std::string();
185 } // anon namespace
187 // AppCacheDatabase ----------------------------------------------------------
189 AppCacheDatabase::GroupRecord::GroupRecord()
190 : group_id(0) {
193 AppCacheDatabase::GroupRecord::~GroupRecord() {
196 AppCacheDatabase::NamespaceRecord::NamespaceRecord()
197 : cache_id(0) {
200 AppCacheDatabase::NamespaceRecord::~NamespaceRecord() {
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) {
211 AppCacheDatabase::~AppCacheDatabase() {
212 CommitLazyLastAccessTimes();
215 void AppCacheDatabase::Disable() {
216 VLOG(1) << "Disabling appcache database.";
217 is_disabled_ = true;
218 ResetConnectionAndTables();
221 int64 AppCacheDatabase::GetOriginUsage(const GURL& origin) {
222 std::vector<CacheRecord> records;
223 if (!FindCachesForOrigin(origin, &records))
224 return 0;
226 int64 origin_usage = 0;
227 std::vector<CacheRecord>::const_iterator iter = records.begin();
228 while (iter != records.end()) {
229 origin_usage += iter->cache_size;
230 ++iter;
232 return origin_usage;
235 bool AppCacheDatabase::GetAllOriginUsage(std::map<GURL, int64>* usage_map) {
236 std::set<GURL> origins;
237 if (!FindOriginsWithGroups(&origins))
238 return false;
239 for (std::set<GURL>::const_iterator origin = origins.begin();
240 origin != origins.end(); ++origin) {
241 (*usage_map)[*origin] = GetOriginUsage(*origin);
243 return true;
246 bool AppCacheDatabase::FindOriginsWithGroups(std::set<GURL>* origins) {
247 DCHECK(origins && origins->empty());
248 if (!LazyOpen(kDontCreate))
249 return false;
251 const char kSql[] =
252 "SELECT DISTINCT(origin) FROM Groups";
254 sql::Statement statement(db_->GetUniqueStatement(kSql));
256 while (statement.Step())
257 origins->insert(GURL(statement.ColumnString(0)));
259 return statement.Succeeded();
262 bool AppCacheDatabase::FindLastStorageIds(
263 int64* last_group_id, int64* last_cache_id, int64* last_response_id,
264 int64* last_deletable_response_rowid) {
265 DCHECK(last_group_id && last_cache_id && last_response_id &&
266 last_deletable_response_rowid);
268 *last_group_id = 0;
269 *last_cache_id = 0;
270 *last_response_id = 0;
271 *last_deletable_response_rowid = 0;
273 if (!LazyOpen(kDontCreate))
274 return false;
276 const char* kMaxGroupIdSql = "SELECT MAX(group_id) FROM Groups";
277 const char* kMaxCacheIdSql = "SELECT MAX(cache_id) FROM Caches";
278 const char* kMaxResponseIdFromEntriesSql =
279 "SELECT MAX(response_id) FROM Entries";
280 const char* kMaxResponseIdFromDeletablesSql =
281 "SELECT MAX(response_id) FROM DeletableResponseIds";
282 const char* kMaxDeletableResponseRowIdSql =
283 "SELECT MAX(rowid) FROM DeletableResponseIds";
284 int64 max_group_id;
285 int64 max_cache_id;
286 int64 max_response_id_from_entries;
287 int64 max_response_id_from_deletables;
288 int64 max_deletable_response_rowid;
289 if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql, &max_group_id) ||
290 !RunUniqueStatementWithInt64Result(kMaxCacheIdSql, &max_cache_id) ||
291 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql,
292 &max_response_id_from_entries) ||
293 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql,
294 &max_response_id_from_deletables) ||
295 !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql,
296 &max_deletable_response_rowid)) {
297 return false;
300 *last_group_id = max_group_id;
301 *last_cache_id = max_cache_id;
302 *last_response_id = std::max(max_response_id_from_entries,
303 max_response_id_from_deletables);
304 *last_deletable_response_rowid = max_deletable_response_rowid;
305 return true;
308 bool AppCacheDatabase::FindGroup(int64 group_id, GroupRecord* record) {
309 DCHECK(record);
310 if (!LazyOpen(kDontCreate))
311 return false;
313 const char kSql[] =
314 "SELECT group_id, origin, manifest_url,"
315 " creation_time, last_access_time,"
316 " last_full_update_check_time,"
317 " first_evictable_error_time"
318 " FROM Groups WHERE group_id = ?";
320 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
322 statement.BindInt64(0, group_id);
323 if (!statement.Step())
324 return false;
326 ReadGroupRecord(statement, record);
327 DCHECK(record->group_id == group_id);
328 return true;
331 bool AppCacheDatabase::FindGroupForManifestUrl(
332 const GURL& manifest_url, GroupRecord* record) {
333 DCHECK(record);
334 if (!LazyOpen(kDontCreate))
335 return false;
337 const char kSql[] =
338 "SELECT group_id, origin, manifest_url,"
339 " creation_time, last_access_time,"
340 " last_full_update_check_time,"
341 " first_evictable_error_time"
342 " FROM Groups WHERE manifest_url = ?";
344 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
345 statement.BindString(0, manifest_url.spec());
347 if (!statement.Step())
348 return false;
350 ReadGroupRecord(statement, record);
351 DCHECK(record->manifest_url == manifest_url);
352 return true;
355 bool AppCacheDatabase::FindGroupsForOrigin(
356 const GURL& origin, std::vector<GroupRecord>* records) {
357 DCHECK(records && records->empty());
358 if (!LazyOpen(kDontCreate))
359 return false;
361 const char kSql[] =
362 "SELECT group_id, origin, manifest_url,"
363 " creation_time, last_access_time,"
364 " last_full_update_check_time,"
365 " first_evictable_error_time"
366 " FROM Groups WHERE origin = ?";
368 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
369 statement.BindString(0, origin.spec());
371 while (statement.Step()) {
372 records->push_back(GroupRecord());
373 ReadGroupRecord(statement, &records->back());
374 DCHECK(records->back().origin == origin);
377 return statement.Succeeded();
380 bool AppCacheDatabase::FindGroupForCache(int64 cache_id, GroupRecord* record) {
381 DCHECK(record);
382 if (!LazyOpen(kDontCreate))
383 return false;
385 const char kSql[] =
386 "SELECT g.group_id, g.origin, g.manifest_url,"
387 " g.creation_time, g.last_access_time,"
388 " g.last_full_update_check_time,"
389 " g.first_evictable_error_time"
390 " FROM Groups g, Caches c"
391 " WHERE c.cache_id = ? AND c.group_id = g.group_id";
393 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
394 statement.BindInt64(0, cache_id);
396 if (!statement.Step())
397 return false;
399 ReadGroupRecord(statement, record);
400 return true;
403 bool AppCacheDatabase::InsertGroup(const GroupRecord* record) {
404 if (!LazyOpen(kCreateIfNeeded))
405 return false;
407 const char kSql[] =
408 "INSERT INTO Groups"
409 " (group_id, origin, manifest_url, creation_time, last_access_time,"
410 " last_full_update_check_time, first_evictable_error_time)"
411 " VALUES(?, ?, ?, ?, ?, ?, ?)";
412 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
413 statement.BindInt64(0, record->group_id);
414 statement.BindString(1, record->origin.spec());
415 statement.BindString(2, record->manifest_url.spec());
416 statement.BindInt64(3, record->creation_time.ToInternalValue());
417 statement.BindInt64(4, record->last_access_time.ToInternalValue());
418 statement.BindInt64(5, record->last_full_update_check_time.ToInternalValue());
419 statement.BindInt64(6, record->first_evictable_error_time.ToInternalValue());
420 return statement.Run();
423 bool AppCacheDatabase::DeleteGroup(int64 group_id) {
424 if (!LazyOpen(kDontCreate))
425 return false;
427 const char kSql[] =
428 "DELETE FROM Groups WHERE group_id = ?";
429 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
430 statement.BindInt64(0, group_id);
431 return statement.Run();
434 bool AppCacheDatabase::UpdateLastAccessTime(
435 int64 group_id, base::Time time) {
436 if (!LazyUpdateLastAccessTime(group_id, time))
437 return false;
438 return CommitLazyLastAccessTimes();
441 bool AppCacheDatabase::LazyUpdateLastAccessTime(
442 int64 group_id, base::Time time) {
443 if (!LazyOpen(kCreateIfNeeded))
444 return false;
445 lazy_last_access_times_[group_id] = time;
446 return true;
449 bool AppCacheDatabase::CommitLazyLastAccessTimes() {
450 if (lazy_last_access_times_.empty())
451 return true;
452 if (!LazyOpen(kDontCreate))
453 return false;
455 sql::Transaction transaction(db_.get());
456 if (!transaction.Begin())
457 return false;
458 for (const auto& pair : lazy_last_access_times_) {
459 const char kSql[] =
460 "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
461 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
462 statement.BindInt64(0, pair.second.ToInternalValue()); // time
463 statement.BindInt64(1, pair.first); // group_id
464 statement.Run();
466 lazy_last_access_times_.clear();
467 return transaction.Commit();
470 bool AppCacheDatabase::UpdateEvictionTimes(
471 int64 group_id,
472 base::Time last_full_update_check_time,
473 base::Time first_evictable_error_time) {
474 if (!LazyOpen(kCreateIfNeeded))
475 return false;
477 const char kSql[] =
478 "UPDATE Groups"
479 " SET last_full_update_check_time = ?, first_evictable_error_time = ?"
480 " WHERE group_id = ?";
481 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
482 statement.BindInt64(0, last_full_update_check_time.ToInternalValue());
483 statement.BindInt64(1, first_evictable_error_time.ToInternalValue());
484 statement.BindInt64(2, group_id);
485 return statement.Run(); // Will succeed even if group_id is invalid.
488 bool AppCacheDatabase::FindCache(int64 cache_id, CacheRecord* record) {
489 DCHECK(record);
490 if (!LazyOpen(kDontCreate))
491 return false;
493 const char kSql[] =
494 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
495 " FROM Caches WHERE cache_id = ?";
497 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
498 statement.BindInt64(0, cache_id);
500 if (!statement.Step())
501 return false;
503 ReadCacheRecord(statement, record);
504 return true;
507 bool AppCacheDatabase::FindCacheForGroup(int64 group_id, CacheRecord* record) {
508 DCHECK(record);
509 if (!LazyOpen(kDontCreate))
510 return false;
512 const char kSql[] =
513 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
514 " FROM Caches WHERE group_id = ?";
516 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
517 statement.BindInt64(0, group_id);
519 if (!statement.Step())
520 return false;
522 ReadCacheRecord(statement, record);
523 return true;
526 bool AppCacheDatabase::FindCachesForOrigin(
527 const GURL& origin, std::vector<CacheRecord>* records) {
528 DCHECK(records);
529 std::vector<GroupRecord> group_records;
530 if (!FindGroupsForOrigin(origin, &group_records))
531 return false;
533 CacheRecord cache_record;
534 std::vector<GroupRecord>::const_iterator iter = group_records.begin();
535 while (iter != group_records.end()) {
536 if (FindCacheForGroup(iter->group_id, &cache_record))
537 records->push_back(cache_record);
538 ++iter;
540 return true;
543 bool AppCacheDatabase::InsertCache(const CacheRecord* record) {
544 if (!LazyOpen(kCreateIfNeeded))
545 return false;
547 const char kSql[] =
548 "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
549 " update_time, cache_size)"
550 " VALUES(?, ?, ?, ?, ?)";
552 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
553 statement.BindInt64(0, record->cache_id);
554 statement.BindInt64(1, record->group_id);
555 statement.BindBool(2, record->online_wildcard);
556 statement.BindInt64(3, record->update_time.ToInternalValue());
557 statement.BindInt64(4, record->cache_size);
559 return statement.Run();
562 bool AppCacheDatabase::DeleteCache(int64 cache_id) {
563 if (!LazyOpen(kDontCreate))
564 return false;
566 const char kSql[] =
567 "DELETE FROM Caches WHERE cache_id = ?";
569 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
570 statement.BindInt64(0, cache_id);
572 return statement.Run();
575 bool AppCacheDatabase::FindEntriesForCache(
576 int64 cache_id, std::vector<EntryRecord>* records) {
577 DCHECK(records && records->empty());
578 if (!LazyOpen(kDontCreate))
579 return false;
581 const char kSql[] =
582 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
583 " WHERE cache_id = ?";
585 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
586 statement.BindInt64(0, cache_id);
588 while (statement.Step()) {
589 records->push_back(EntryRecord());
590 ReadEntryRecord(statement, &records->back());
591 DCHECK(records->back().cache_id == cache_id);
594 return statement.Succeeded();
597 bool AppCacheDatabase::FindEntriesForUrl(
598 const GURL& url, std::vector<EntryRecord>* records) {
599 DCHECK(records && records->empty());
600 if (!LazyOpen(kDontCreate))
601 return false;
603 const char kSql[] =
604 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
605 " WHERE url = ?";
607 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
608 statement.BindString(0, url.spec());
610 while (statement.Step()) {
611 records->push_back(EntryRecord());
612 ReadEntryRecord(statement, &records->back());
613 DCHECK(records->back().url == url);
616 return statement.Succeeded();
619 bool AppCacheDatabase::FindEntry(
620 int64 cache_id, const GURL& url, EntryRecord* record) {
621 DCHECK(record);
622 if (!LazyOpen(kDontCreate))
623 return false;
625 const char kSql[] =
626 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
627 " WHERE cache_id = ? AND url = ?";
629 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
630 statement.BindInt64(0, cache_id);
631 statement.BindString(1, url.spec());
633 if (!statement.Step())
634 return false;
636 ReadEntryRecord(statement, record);
637 DCHECK(record->cache_id == cache_id);
638 DCHECK(record->url == url);
639 return true;
642 bool AppCacheDatabase::InsertEntry(const EntryRecord* record) {
643 if (!LazyOpen(kCreateIfNeeded))
644 return false;
646 const char kSql[] =
647 "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
648 " VALUES(?, ?, ?, ?, ?)";
650 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
651 statement.BindInt64(0, record->cache_id);
652 statement.BindString(1, record->url.spec());
653 statement.BindInt(2, record->flags);
654 statement.BindInt64(3, record->response_id);
655 statement.BindInt64(4, record->response_size);
657 return statement.Run();
660 bool AppCacheDatabase::InsertEntryRecords(
661 const std::vector<EntryRecord>& records) {
662 if (records.empty())
663 return true;
664 sql::Transaction transaction(db_.get());
665 if (!transaction.Begin())
666 return false;
667 std::vector<EntryRecord>::const_iterator iter = records.begin();
668 while (iter != records.end()) {
669 if (!InsertEntry(&(*iter)))
670 return false;
671 ++iter;
673 return transaction.Commit();
676 bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id) {
677 if (!LazyOpen(kDontCreate))
678 return false;
680 const char kSql[] =
681 "DELETE FROM Entries WHERE cache_id = ?";
683 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
684 statement.BindInt64(0, cache_id);
686 return statement.Run();
689 bool AppCacheDatabase::AddEntryFlags(
690 const GURL& entry_url, int64 cache_id, int additional_flags) {
691 if (!LazyOpen(kDontCreate))
692 return false;
694 const char kSql[] =
695 "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
697 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
698 statement.BindInt(0, additional_flags);
699 statement.BindInt64(1, cache_id);
700 statement.BindString(2, entry_url.spec());
702 return statement.Run() && db_->GetLastChangeCount();
705 bool AppCacheDatabase::FindNamespacesForOrigin(
706 const GURL& origin,
707 std::vector<NamespaceRecord>* intercepts,
708 std::vector<NamespaceRecord>* fallbacks) {
709 DCHECK(intercepts && intercepts->empty());
710 DCHECK(fallbacks && fallbacks->empty());
711 if (!LazyOpen(kDontCreate))
712 return false;
714 const char kSql[] =
715 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
716 " FROM Namespaces WHERE origin = ?";
718 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
719 statement.BindString(0, origin.spec());
721 ReadNamespaceRecords(&statement, intercepts, fallbacks);
723 return statement.Succeeded();
726 bool AppCacheDatabase::FindNamespacesForCache(
727 int64 cache_id,
728 std::vector<NamespaceRecord>* intercepts,
729 std::vector<NamespaceRecord>* fallbacks) {
730 DCHECK(intercepts && intercepts->empty());
731 DCHECK(fallbacks && fallbacks->empty());
732 if (!LazyOpen(kDontCreate))
733 return false;
735 const char kSql[] =
736 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
737 " FROM Namespaces WHERE cache_id = ?";
739 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
740 statement.BindInt64(0, cache_id);
742 ReadNamespaceRecords(&statement, intercepts, fallbacks);
744 return statement.Succeeded();
747 bool AppCacheDatabase::InsertNamespace(
748 const NamespaceRecord* record) {
749 if (!LazyOpen(kCreateIfNeeded))
750 return false;
752 const char kSql[] =
753 "INSERT INTO Namespaces"
754 " (cache_id, origin, type, namespace_url, target_url, is_pattern)"
755 " VALUES (?, ?, ?, ?, ?, ?)";
757 // Note: quick and dirty storage for the 'executable' bit w/o changing
758 // schemas, we use the high bit of 'type' field.
759 int type_with_executable_bit = record->namespace_.type;
760 if (record->namespace_.is_executable) {
761 type_with_executable_bit |= 0x8000000;
762 DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
763 kEnableExecutableHandlers));
766 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
767 statement.BindInt64(0, record->cache_id);
768 statement.BindString(1, record->origin.spec());
769 statement.BindInt(2, type_with_executable_bit);
770 statement.BindString(3, record->namespace_.namespace_url.spec());
771 statement.BindString(4, record->namespace_.target_url.spec());
772 statement.BindBool(5, record->namespace_.is_pattern);
773 return statement.Run();
776 bool AppCacheDatabase::InsertNamespaceRecords(
777 const std::vector<NamespaceRecord>& records) {
778 if (records.empty())
779 return true;
780 sql::Transaction transaction(db_.get());
781 if (!transaction.Begin())
782 return false;
783 std::vector<NamespaceRecord>::const_iterator iter = records.begin();
784 while (iter != records.end()) {
785 if (!InsertNamespace(&(*iter)))
786 return false;
787 ++iter;
789 return transaction.Commit();
792 bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id) {
793 if (!LazyOpen(kDontCreate))
794 return false;
796 const char kSql[] =
797 "DELETE FROM Namespaces WHERE cache_id = ?";
799 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
800 statement.BindInt64(0, cache_id);
802 return statement.Run();
805 bool AppCacheDatabase::FindOnlineWhiteListForCache(
806 int64 cache_id, std::vector<OnlineWhiteListRecord>* records) {
807 DCHECK(records && records->empty());
808 if (!LazyOpen(kDontCreate))
809 return false;
811 const char kSql[] =
812 "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
813 " WHERE cache_id = ?";
815 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
816 statement.BindInt64(0, cache_id);
818 while (statement.Step()) {
819 records->push_back(OnlineWhiteListRecord());
820 this->ReadOnlineWhiteListRecord(statement, &records->back());
821 DCHECK(records->back().cache_id == cache_id);
823 return statement.Succeeded();
826 bool AppCacheDatabase::InsertOnlineWhiteList(
827 const OnlineWhiteListRecord* record) {
828 if (!LazyOpen(kCreateIfNeeded))
829 return false;
831 const char kSql[] =
832 "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
833 " VALUES (?, ?, ?)";
835 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
836 statement.BindInt64(0, record->cache_id);
837 statement.BindString(1, record->namespace_url.spec());
838 statement.BindBool(2, record->is_pattern);
840 return statement.Run();
843 bool AppCacheDatabase::InsertOnlineWhiteListRecords(
844 const std::vector<OnlineWhiteListRecord>& records) {
845 if (records.empty())
846 return true;
847 sql::Transaction transaction(db_.get());
848 if (!transaction.Begin())
849 return false;
850 std::vector<OnlineWhiteListRecord>::const_iterator iter = records.begin();
851 while (iter != records.end()) {
852 if (!InsertOnlineWhiteList(&(*iter)))
853 return false;
854 ++iter;
856 return transaction.Commit();
859 bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id) {
860 if (!LazyOpen(kDontCreate))
861 return false;
863 const char kSql[] =
864 "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
866 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
867 statement.BindInt64(0, cache_id);
869 return statement.Run();
872 bool AppCacheDatabase::GetDeletableResponseIds(
873 std::vector<int64>* response_ids, int64 max_rowid, int limit) {
874 if (!LazyOpen(kDontCreate))
875 return false;
877 const char kSql[] =
878 "SELECT response_id FROM DeletableResponseIds "
879 " WHERE rowid <= ?"
880 " LIMIT ?";
882 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
883 statement.BindInt64(0, max_rowid);
884 statement.BindInt64(1, limit);
886 while (statement.Step())
887 response_ids->push_back(statement.ColumnInt64(0));
888 return statement.Succeeded();
891 bool AppCacheDatabase::InsertDeletableResponseIds(
892 const std::vector<int64>& response_ids) {
893 const char kSql[] =
894 "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
895 return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
898 bool AppCacheDatabase::DeleteDeletableResponseIds(
899 const std::vector<int64>& response_ids) {
900 const char kSql[] =
901 "DELETE FROM DeletableResponseIds WHERE response_id = ?";
902 return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
905 bool AppCacheDatabase::RunCachedStatementWithIds(
906 const sql::StatementID& statement_id, const char* sql,
907 const std::vector<int64>& ids) {
908 DCHECK(sql);
909 if (!LazyOpen(kCreateIfNeeded))
910 return false;
912 sql::Transaction transaction(db_.get());
913 if (!transaction.Begin())
914 return false;
916 sql::Statement statement(db_->GetCachedStatement(statement_id, sql));
918 std::vector<int64>::const_iterator iter = ids.begin();
919 while (iter != ids.end()) {
920 statement.BindInt64(0, *iter);
921 if (!statement.Run())
922 return false;
923 statement.Reset(true);
924 ++iter;
927 return transaction.Commit();
930 bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
931 const char* sql, int64* result) {
932 DCHECK(sql);
933 sql::Statement statement(db_->GetUniqueStatement(sql));
934 if (!statement.Step()) {
935 return false;
937 *result = statement.ColumnInt64(0);
938 return true;
941 bool AppCacheDatabase::FindResponseIdsForCacheHelper(
942 int64 cache_id, std::vector<int64>* ids_vector,
943 std::set<int64>* ids_set) {
944 DCHECK(ids_vector || ids_set);
945 DCHECK(!(ids_vector && ids_set));
946 if (!LazyOpen(kDontCreate))
947 return false;
949 const char kSql[] =
950 "SELECT response_id FROM Entries WHERE cache_id = ?";
952 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
954 statement.BindInt64(0, cache_id);
955 while (statement.Step()) {
956 int64 id = statement.ColumnInt64(0);
957 if (ids_set)
958 ids_set->insert(id);
959 else
960 ids_vector->push_back(id);
963 return statement.Succeeded();
966 void AppCacheDatabase::ReadGroupRecord(
967 const sql::Statement& statement, GroupRecord* record) {
968 record->group_id = statement.ColumnInt64(0);
969 record->origin = GURL(statement.ColumnString(1));
970 record->manifest_url = GURL(statement.ColumnString(2));
971 record->creation_time =
972 base::Time::FromInternalValue(statement.ColumnInt64(3));
974 const auto found = lazy_last_access_times_.find(record->group_id);
975 if (found != lazy_last_access_times_.end()) {
976 record->last_access_time = found->second;
977 } else {
978 record->last_access_time =
979 base::Time::FromInternalValue(statement.ColumnInt64(4));
982 record->last_full_update_check_time =
983 base::Time::FromInternalValue(statement.ColumnInt64(5));
984 record->first_evictable_error_time =
985 base::Time::FromInternalValue(statement.ColumnInt64(6));
988 void AppCacheDatabase::ReadCacheRecord(
989 const sql::Statement& statement, CacheRecord* record) {
990 record->cache_id = statement.ColumnInt64(0);
991 record->group_id = statement.ColumnInt64(1);
992 record->online_wildcard = statement.ColumnBool(2);
993 record->update_time =
994 base::Time::FromInternalValue(statement.ColumnInt64(3));
995 record->cache_size = statement.ColumnInt64(4);
998 void AppCacheDatabase::ReadEntryRecord(
999 const sql::Statement& statement, EntryRecord* record) {
1000 record->cache_id = statement.ColumnInt64(0);
1001 record->url = GURL(statement.ColumnString(1));
1002 record->flags = statement.ColumnInt(2);
1003 record->response_id = statement.ColumnInt64(3);
1004 record->response_size = statement.ColumnInt64(4);
1007 void AppCacheDatabase::ReadNamespaceRecords(
1008 sql::Statement* statement,
1009 NamespaceRecordVector* intercepts,
1010 NamespaceRecordVector* fallbacks) {
1011 while (statement->Step()) {
1012 AppCacheNamespaceType type = static_cast<AppCacheNamespaceType>(
1013 statement->ColumnInt(2));
1014 NamespaceRecordVector* records =
1015 (type == APPCACHE_FALLBACK_NAMESPACE) ? fallbacks : intercepts;
1016 records->push_back(NamespaceRecord());
1017 ReadNamespaceRecord(statement, &records->back());
1021 void AppCacheDatabase::ReadNamespaceRecord(
1022 const sql::Statement* statement, NamespaceRecord* record) {
1023 record->cache_id = statement->ColumnInt64(0);
1024 record->origin = GURL(statement->ColumnString(1));
1025 int type_with_executable_bit = statement->ColumnInt(2);
1026 record->namespace_.namespace_url = GURL(statement->ColumnString(3));
1027 record->namespace_.target_url = GURL(statement->ColumnString(4));
1028 record->namespace_.is_pattern = statement->ColumnBool(5);
1030 // Note: quick and dirty storage for the 'executable' bit w/o changing
1031 // schemas, we use the high bit of 'type' field.
1032 record->namespace_.type = static_cast<AppCacheNamespaceType>
1033 (type_with_executable_bit & 0x7ffffff);
1034 record->namespace_.is_executable =
1035 (type_with_executable_bit & 0x80000000) != 0;
1036 DCHECK(!record->namespace_.is_executable ||
1037 base::CommandLine::ForCurrentProcess()->HasSwitch(
1038 kEnableExecutableHandlers));
1041 void AppCacheDatabase::ReadOnlineWhiteListRecord(
1042 const sql::Statement& statement, OnlineWhiteListRecord* record) {
1043 record->cache_id = statement.ColumnInt64(0);
1044 record->namespace_url = GURL(statement.ColumnString(1));
1045 record->is_pattern = statement.ColumnBool(2);
1048 bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
1049 if (db_)
1050 return true;
1052 // If we tried and failed once, don't try again in the same session
1053 // to avoid creating an incoherent mess on disk.
1054 if (is_disabled_)
1055 return false;
1057 // Avoid creating a database at all if we can.
1058 bool use_in_memory_db = db_file_path_.empty();
1059 if (!create_if_needed &&
1060 (use_in_memory_db || !base::PathExists(db_file_path_))) {
1061 return false;
1064 db_.reset(new sql::Connection);
1065 meta_table_.reset(new sql::MetaTable);
1067 db_->set_histogram_tag("AppCache");
1069 bool opened = false;
1070 if (use_in_memory_db) {
1071 opened = db_->OpenInMemory();
1072 } else if (!base::CreateDirectory(db_file_path_.DirName())) {
1073 LOG(ERROR) << "Failed to create appcache directory.";
1074 } else {
1075 opened = db_->Open(db_file_path_);
1076 if (opened)
1077 db_->Preload();
1080 if (!opened || !db_->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
1081 LOG(ERROR) << "Failed to open the appcache database.";
1082 AppCacheHistograms::CountInitResult(
1083 AppCacheHistograms::SQL_DATABASE_ERROR);
1085 // We're unable to open the database. This is a fatal error
1086 // which we can't recover from. We try to handle it by deleting
1087 // the existing appcache data and starting with a clean slate in
1088 // this browser session.
1089 if (!use_in_memory_db && DeleteExistingAndCreateNewDatabase())
1090 return true;
1092 Disable();
1093 return false;
1096 AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK);
1097 was_corruption_detected_ = false;
1098 db_->set_error_callback(
1099 base::Bind(&AppCacheDatabase::OnDatabaseError, base::Unretained(this)));
1100 return true;
1103 bool AppCacheDatabase::EnsureDatabaseVersion() {
1104 if (!sql::MetaTable::DoesTableExist(db_.get()))
1105 return CreateSchema();
1107 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1108 return false;
1110 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
1111 LOG(WARNING) << "AppCache database is too new.";
1112 return false;
1115 std::string stored_flags;
1116 meta_table_->GetValue(kExperimentFlagsKey, &stored_flags);
1117 if (stored_flags != GetActiveExperimentFlags())
1118 return false;
1120 if (meta_table_->GetVersionNumber() < kCurrentVersion)
1121 return UpgradeSchema();
1123 #ifndef NDEBUG
1124 DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
1125 for (int i = 0; i < kTableCount; ++i) {
1126 DCHECK(db_->DoesTableExist(kTables[i].table_name));
1128 for (int i = 0; i < kIndexCount; ++i) {
1129 DCHECK(db_->DoesIndexExist(kIndexes[i].index_name));
1131 #endif
1133 return true;
1136 bool AppCacheDatabase::CreateSchema() {
1137 sql::Transaction transaction(db_.get());
1138 if (!transaction.Begin())
1139 return false;
1141 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1142 return false;
1144 if (!meta_table_->SetValue(kExperimentFlagsKey,
1145 GetActiveExperimentFlags())) {
1146 return false;
1149 for (int i = 0; i < kTableCount; ++i) {
1150 if (!CreateTable(db_.get(), kTables[i]))
1151 return false;
1154 for (int i = 0; i < kIndexCount; ++i) {
1155 if (!CreateIndex(db_.get(), kIndexes[i]))
1156 return false;
1159 return transaction.Commit();
1162 bool AppCacheDatabase::UpgradeSchema() {
1163 #if defined(APPCACHE_USE_SIMPLE_CACHE)
1164 if (meta_table_->GetVersionNumber() < 6)
1165 return DeleteExistingAndCreateNewDatabase();
1166 #endif
1167 if (meta_table_->GetVersionNumber() == 3) {
1168 // version 3 was pre 12/17/2011
1169 DCHECK_EQ(strcmp(kNamespacesTable, kTables[3].table_name), 0);
1170 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[6].table_name), 0);
1171 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[7].table_name), 0);
1172 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[8].table_name), 0);
1174 const TableInfo kNamespaceTable_v4 = {
1175 kNamespacesTable,
1176 "(cache_id INTEGER,"
1177 " origin TEXT," // intentionally not normalized
1178 " type INTEGER,"
1179 " namespace_url TEXT,"
1180 " target_url TEXT)"
1183 // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
1184 // but without the is_pattern column added in v5.
1185 sql::Transaction transaction(db_.get());
1186 if (!transaction.Begin() ||
1187 !CreateTable(db_.get(), kNamespaceTable_v4)) {
1188 return false;
1191 // Move data from the old table to the new table, setting the
1192 // 'type' for all current records to the value for
1193 // APPCACHE_FALLBACK_NAMESPACE.
1194 DCHECK_EQ(0, static_cast<int>(APPCACHE_FALLBACK_NAMESPACE));
1195 if (!db_->Execute(
1196 "INSERT INTO Namespaces"
1197 " SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
1198 " FROM FallbackNameSpaces")) {
1199 return false;
1202 // Drop the old table, indexes on that table are also removed by this.
1203 if (!db_->Execute("DROP TABLE FallbackNameSpaces"))
1204 return false;
1206 // Create new indexes.
1207 if (!CreateIndex(db_.get(), kIndexes[6]) ||
1208 !CreateIndex(db_.get(), kIndexes[7]) ||
1209 !CreateIndex(db_.get(), kIndexes[8])) {
1210 return false;
1213 meta_table_->SetVersionNumber(4);
1214 meta_table_->SetCompatibleVersionNumber(4);
1215 if (!transaction.Commit())
1216 return false;
1219 if (meta_table_->GetVersionNumber() == 4) {
1220 // version 4 pre 3/30/2013
1221 // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
1222 DCHECK_EQ(strcmp(kNamespacesTable, "Namespaces"), 0);
1223 sql::Transaction transaction(db_.get());
1224 if (!transaction.Begin())
1225 return false;
1226 if (!db_->Execute(
1227 "ALTER TABLE Namespaces ADD COLUMN"
1228 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1229 return false;
1231 if (!db_->Execute(
1232 "ALTER TABLE OnlineWhitelists ADD COLUMN"
1233 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1234 return false;
1236 meta_table_->SetVersionNumber(5);
1237 meta_table_->SetCompatibleVersionNumber(5);
1238 if (!transaction.Commit())
1239 return false;
1242 #if defined(APPCACHE_USE_SIMPLE_CACHE)
1243 // The schema version number was increased to 6 when we switched to the
1244 // SimpleCache for Android, but the SQL part of the schema is identical
1245 // to v5 on desktop chrome.
1246 if (meta_table_->GetVersionNumber() == 6) {
1247 #else
1248 if (meta_table_->GetVersionNumber() == 5) {
1249 #endif
1250 // Versions 5 and 6 were pre-July 2015.
1251 // Version 7 adds support for expiring caches that are failing to update.
1252 sql::Transaction transaction(db_.get());
1253 if (!transaction.Begin() ||
1254 !db_->Execute(
1255 "ALTER TABLE Groups ADD COLUMN"
1256 " last_full_update_check_time INTEGER") ||
1257 !db_->Execute(
1258 "ALTER TABLE Groups ADD COLUMN"
1259 " first_evictable_error_time INTEGER") ||
1260 !db_->Execute(
1261 "UPDATE Groups"
1262 " SET last_full_update_check_time ="
1263 " (SELECT update_time FROM Caches"
1264 " WHERE Caches.group_id = Groups.group_id)")) {
1265 return false;
1267 meta_table_->SetVersionNumber(7);
1268 meta_table_->SetCompatibleVersionNumber(7);
1269 return transaction.Commit();
1272 // If there is no upgrade path for the version on disk to the current
1273 // version, nuke everything and start over.
1274 return DeleteExistingAndCreateNewDatabase();
1277 void AppCacheDatabase::ResetConnectionAndTables() {
1278 meta_table_.reset();
1279 db_.reset();
1282 bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
1283 DCHECK(!db_file_path_.empty());
1284 DCHECK(base::PathExists(db_file_path_));
1285 VLOG(1) << "Deleting existing appcache data and starting over.";
1287 ResetConnectionAndTables();
1289 // This also deletes the disk cache data.
1290 base::FilePath directory = db_file_path_.DirName();
1291 if (!base::DeleteFile(directory, true))
1292 return false;
1294 // Make sure the steps above actually deleted things.
1295 if (base::PathExists(directory))
1296 return false;
1298 if (!base::CreateDirectory(directory))
1299 return false;
1301 // So we can't go recursive.
1302 if (is_recreating_)
1303 return false;
1305 base::AutoReset<bool> auto_reset(&is_recreating_, true);
1306 return LazyOpen(kCreateIfNeeded);
1309 void AppCacheDatabase::OnDatabaseError(int err, sql::Statement* stmt) {
1310 was_corruption_detected_ |= sql::IsErrorCatastrophic(err);
1311 if (!db_->ShouldIgnoreSqliteError(err))
1312 DLOG(ERROR) << db_->GetErrorMessage();
1313 // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
1316 } // namespace content