When Retrier succeeds, record errors it encountered.
[chromium-blink-merge.git] / webkit / appcache / appcache_database.cc
blob2f1442158014d9e48ded080bd329ea46d6f757b3
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 "webkit/appcache/appcache_database.h"
7 #include "base/auto_reset.h"
8 #include "base/command_line.h"
9 #include "base/file_util.h"
10 #include "base/logging.h"
11 #include "base/utf_string_conversions.h"
12 #include "sql/connection.h"
13 #include "sql/meta_table.h"
14 #include "sql/statement.h"
15 #include "sql/transaction.h"
16 #include "webkit/appcache/appcache_entry.h"
17 #include "webkit/appcache/appcache_histograms.h"
19 namespace appcache {
21 // Schema -------------------------------------------------------------------
22 namespace {
24 const int kCurrentVersion = 5;
25 const int kCompatibleVersion = 5;
27 // A mechanism to run experiments that may affect in data being persisted
28 // in different ways such that when the experiment is toggled on/off via
29 // cmd line flags, the database gets reset. The active flags are stored at
30 // the time of database creation and compared when reopening. If different
31 // the database is reset.
32 const char kExperimentFlagsKey[] = "ExperimentFlags";
34 const char kGroupsTable[] = "Groups";
35 const char kCachesTable[] = "Caches";
36 const char kEntriesTable[] = "Entries";
37 const char kNamespacesTable[] = "Namespaces";
38 const char kOnlineWhiteListsTable[] = "OnlineWhiteLists";
39 const char kDeletableResponseIdsTable[] = "DeletableResponseIds";
41 struct TableInfo {
42 const char* table_name;
43 const char* columns;
46 struct IndexInfo {
47 const char* index_name;
48 const char* table_name;
49 const char* columns;
50 bool unique;
53 const TableInfo kTables[] = {
54 { kGroupsTable,
55 "(group_id INTEGER PRIMARY KEY,"
56 " origin TEXT,"
57 " manifest_url TEXT,"
58 " creation_time INTEGER,"
59 " last_access_time INTEGER)" },
61 { kCachesTable,
62 "(cache_id INTEGER PRIMARY KEY,"
63 " group_id INTEGER,"
64 " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
65 " update_time INTEGER,"
66 " cache_size INTEGER)" }, // intentionally not normalized
68 { kEntriesTable,
69 "(cache_id INTEGER,"
70 " url TEXT,"
71 " flags INTEGER,"
72 " response_id INTEGER,"
73 " response_size INTEGER)" },
75 { kNamespacesTable,
76 "(cache_id INTEGER,"
77 " origin TEXT," // intentionally not normalized
78 " type INTEGER,"
79 " namespace_url TEXT,"
80 " target_url TEXT,"
81 " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
83 { kOnlineWhiteListsTable,
84 "(cache_id INTEGER,"
85 " namespace_url TEXT,"
86 " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
88 { kDeletableResponseIdsTable,
89 "(response_id INTEGER NOT NULL)" },
92 const IndexInfo kIndexes[] = {
93 { "GroupsOriginIndex",
94 kGroupsTable,
95 "(origin)",
96 false },
98 { "GroupsManifestIndex",
99 kGroupsTable,
100 "(manifest_url)",
101 true },
103 { "CachesGroupIndex",
104 kCachesTable,
105 "(group_id)",
106 false },
108 { "EntriesCacheIndex",
109 kEntriesTable,
110 "(cache_id)",
111 false },
113 { "EntriesCacheAndUrlIndex",
114 kEntriesTable,
115 "(cache_id, url)",
116 true },
118 { "EntriesResponseIdIndex",
119 kEntriesTable,
120 "(response_id)",
121 true },
123 { "NamespacesCacheIndex",
124 kNamespacesTable,
125 "(cache_id)",
126 false },
128 { "NamespacesOriginIndex",
129 kNamespacesTable,
130 "(origin)",
131 false },
133 { "NamespacesCacheAndUrlIndex",
134 kNamespacesTable,
135 "(cache_id, namespace_url)",
136 true },
138 { "OnlineWhiteListCacheIndex",
139 kOnlineWhiteListsTable,
140 "(cache_id)",
141 false },
143 { "DeletableResponsesIdIndex",
144 kDeletableResponseIdsTable,
145 "(response_id)",
146 true },
149 const int kTableCount = ARRAYSIZE_UNSAFE(kTables);
150 const int kIndexCount = ARRAYSIZE_UNSAFE(kIndexes);
152 bool CreateTable(sql::Connection* db, const TableInfo& info) {
153 std::string sql("CREATE TABLE ");
154 sql += info.table_name;
155 sql += info.columns;
156 return db->Execute(sql.c_str());
159 bool CreateIndex(sql::Connection* db, const IndexInfo& info) {
160 std::string sql;
161 if (info.unique)
162 sql += "CREATE UNIQUE INDEX ";
163 else
164 sql += "CREATE INDEX ";
165 sql += info.index_name;
166 sql += " ON ";
167 sql += info.table_name;
168 sql += info.columns;
169 return db->Execute(sql.c_str());
172 std::string GetActiveExperimentFlags() {
173 if (CommandLine::ForCurrentProcess()->HasSwitch(kEnableExecutableHandlers))
174 return std::string("executableHandlersEnabled");
175 return std::string();
178 } // anon namespace
180 // AppCacheDatabase ----------------------------------------------------------
182 AppCacheDatabase::GroupRecord::GroupRecord()
183 : group_id(0) {
186 AppCacheDatabase::GroupRecord::~GroupRecord() {
189 AppCacheDatabase::NamespaceRecord::NamespaceRecord()
190 : cache_id(0) {
193 AppCacheDatabase::NamespaceRecord::~NamespaceRecord() {
197 AppCacheDatabase::AppCacheDatabase(const base::FilePath& path)
198 : db_file_path_(path), is_disabled_(false), is_recreating_(false) {
201 AppCacheDatabase::~AppCacheDatabase() {
204 void AppCacheDatabase::CloseConnection() {
205 // We can't close the connection for an in-memory database w/o
206 // losing all of the data, so we don't do that.
207 if (!db_file_path_.empty())
208 ResetConnectionAndTables();
211 void AppCacheDatabase::Disable() {
212 VLOG(1) << "Disabling appcache database.";
213 is_disabled_ = true;
214 ResetConnectionAndTables();
217 int64 AppCacheDatabase::GetOriginUsage(const GURL& origin) {
218 std::vector<CacheRecord> records;
219 if (!FindCachesForOrigin(origin, &records))
220 return 0;
222 int64 origin_usage = 0;
223 std::vector<CacheRecord>::const_iterator iter = records.begin();
224 while (iter != records.end()) {
225 origin_usage += iter->cache_size;
226 ++iter;
228 return origin_usage;
231 bool AppCacheDatabase::GetAllOriginUsage(std::map<GURL, int64>* usage_map) {
232 std::set<GURL> origins;
233 if (!FindOriginsWithGroups(&origins))
234 return false;
235 for (std::set<GURL>::const_iterator origin = origins.begin();
236 origin != origins.end(); ++origin) {
237 (*usage_map)[*origin] = GetOriginUsage(*origin);
239 return true;
242 bool AppCacheDatabase::FindOriginsWithGroups(std::set<GURL>* origins) {
243 DCHECK(origins && origins->empty());
244 if (!LazyOpen(false))
245 return false;
247 const char* kSql =
248 "SELECT DISTINCT(origin) FROM Groups";
250 sql::Statement statement(db_->GetUniqueStatement(kSql));
252 while (statement.Step())
253 origins->insert(GURL(statement.ColumnString(0)));
255 return statement.Succeeded();
258 bool AppCacheDatabase::FindLastStorageIds(
259 int64* last_group_id, int64* last_cache_id, int64* last_response_id,
260 int64* last_deletable_response_rowid) {
261 DCHECK(last_group_id && last_cache_id && last_response_id &&
262 last_deletable_response_rowid);
264 *last_group_id = 0;
265 *last_cache_id = 0;
266 *last_response_id = 0;
267 *last_deletable_response_rowid = 0;
269 if (!LazyOpen(false))
270 return false;
272 const char* kMaxGroupIdSql = "SELECT MAX(group_id) FROM Groups";
273 const char* kMaxCacheIdSql = "SELECT MAX(cache_id) FROM Caches";
274 const char* kMaxResponseIdFromEntriesSql =
275 "SELECT MAX(response_id) FROM Entries";
276 const char* kMaxResponseIdFromDeletablesSql =
277 "SELECT MAX(response_id) FROM DeletableResponseIds";
278 const char* kMaxDeletableResponseRowIdSql =
279 "SELECT MAX(rowid) FROM DeletableResponseIds";
280 int64 max_group_id;
281 int64 max_cache_id;
282 int64 max_response_id_from_entries;
283 int64 max_response_id_from_deletables;
284 int64 max_deletable_response_rowid;
285 if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql, &max_group_id) ||
286 !RunUniqueStatementWithInt64Result(kMaxCacheIdSql, &max_cache_id) ||
287 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql,
288 &max_response_id_from_entries) ||
289 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql,
290 &max_response_id_from_deletables) ||
291 !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql,
292 &max_deletable_response_rowid)) {
293 return false;
296 *last_group_id = max_group_id;
297 *last_cache_id = max_cache_id;
298 *last_response_id = std::max(max_response_id_from_entries,
299 max_response_id_from_deletables);
300 *last_deletable_response_rowid = max_deletable_response_rowid;
301 return true;
304 bool AppCacheDatabase::FindGroup(int64 group_id, GroupRecord* record) {
305 DCHECK(record);
306 if (!LazyOpen(false))
307 return false;
309 const char* kSql =
310 "SELECT group_id, origin, manifest_url,"
311 " creation_time, last_access_time"
312 " FROM Groups WHERE group_id = ?";
314 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
316 statement.BindInt64(0, group_id);
317 if (!statement.Step())
318 return false;
320 ReadGroupRecord(statement, record);
321 DCHECK(record->group_id == group_id);
322 return true;
325 bool AppCacheDatabase::FindGroupForManifestUrl(
326 const GURL& manifest_url, GroupRecord* record) {
327 DCHECK(record);
328 if (!LazyOpen(false))
329 return false;
331 const char* kSql =
332 "SELECT group_id, origin, manifest_url,"
333 " creation_time, last_access_time"
334 " FROM Groups WHERE manifest_url = ?";
336 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
337 statement.BindString(0, manifest_url.spec());
339 if (!statement.Step())
340 return false;
342 ReadGroupRecord(statement, record);
343 DCHECK(record->manifest_url == manifest_url);
344 return true;
347 bool AppCacheDatabase::FindGroupsForOrigin(
348 const GURL& origin, std::vector<GroupRecord>* records) {
349 DCHECK(records && records->empty());
350 if (!LazyOpen(false))
351 return false;
353 const char* kSql =
354 "SELECT group_id, origin, manifest_url,"
355 " creation_time, last_access_time"
356 " FROM Groups WHERE origin = ?";
358 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
359 statement.BindString(0, origin.spec());
361 while (statement.Step()) {
362 records->push_back(GroupRecord());
363 ReadGroupRecord(statement, &records->back());
364 DCHECK(records->back().origin == origin);
367 return statement.Succeeded();
370 bool AppCacheDatabase::FindGroupForCache(int64 cache_id, GroupRecord* record) {
371 DCHECK(record);
372 if (!LazyOpen(false))
373 return false;
375 const char* kSql =
376 "SELECT g.group_id, g.origin, g.manifest_url,"
377 " g.creation_time, g.last_access_time"
378 " FROM Groups g, Caches c"
379 " WHERE c.cache_id = ? AND c.group_id = g.group_id";
381 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
382 statement.BindInt64(0, cache_id);
384 if (!statement.Step())
385 return false;
387 ReadGroupRecord(statement, record);
388 return true;
391 bool AppCacheDatabase::UpdateGroupLastAccessTime(
392 int64 group_id, base::Time time) {
393 if (!LazyOpen(true))
394 return false;
396 const char* kSql =
397 "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
399 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
400 statement.BindInt64(0, time.ToInternalValue());
401 statement.BindInt64(1, group_id);
403 return statement.Run() && db_->GetLastChangeCount();
406 bool AppCacheDatabase::InsertGroup(const GroupRecord* record) {
407 if (!LazyOpen(true))
408 return false;
410 const char* kSql =
411 "INSERT INTO Groups"
412 " (group_id, origin, manifest_url, creation_time, last_access_time)"
413 " VALUES(?, ?, ?, ?, ?)";
415 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
416 statement.BindInt64(0, record->group_id);
417 statement.BindString(1, record->origin.spec());
418 statement.BindString(2, record->manifest_url.spec());
419 statement.BindInt64(3, record->creation_time.ToInternalValue());
420 statement.BindInt64(4, record->last_access_time.ToInternalValue());
422 return statement.Run();
425 bool AppCacheDatabase::DeleteGroup(int64 group_id) {
426 if (!LazyOpen(false))
427 return false;
429 const char* kSql =
430 "DELETE FROM Groups WHERE group_id = ?";
432 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
433 statement.BindInt64(0, group_id);
435 return statement.Run();
438 bool AppCacheDatabase::FindCache(int64 cache_id, CacheRecord* record) {
439 DCHECK(record);
440 if (!LazyOpen(false))
441 return false;
443 const char* kSql =
444 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
445 " FROM Caches WHERE cache_id = ?";
447 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
448 statement.BindInt64(0, cache_id);
450 if (!statement.Step())
451 return false;
453 ReadCacheRecord(statement, record);
454 return true;
457 bool AppCacheDatabase::FindCacheForGroup(int64 group_id, CacheRecord* record) {
458 DCHECK(record);
459 if (!LazyOpen(false))
460 return false;
462 const char* kSql =
463 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
464 " FROM Caches WHERE group_id = ?";
466 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
467 statement.BindInt64(0, group_id);
469 if (!statement.Step())
470 return false;
472 ReadCacheRecord(statement, record);
473 return true;
476 bool AppCacheDatabase::FindCachesForOrigin(
477 const GURL& origin, std::vector<CacheRecord>* records) {
478 DCHECK(records);
479 std::vector<GroupRecord> group_records;
480 if (!FindGroupsForOrigin(origin, &group_records))
481 return false;
483 CacheRecord cache_record;
484 std::vector<GroupRecord>::const_iterator iter = group_records.begin();
485 while (iter != group_records.end()) {
486 if (FindCacheForGroup(iter->group_id, &cache_record))
487 records->push_back(cache_record);
488 ++iter;
490 return true;
493 bool AppCacheDatabase::InsertCache(const CacheRecord* record) {
494 if (!LazyOpen(true))
495 return false;
497 const char* kSql =
498 "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
499 " update_time, cache_size)"
500 " VALUES(?, ?, ?, ?, ?)";
502 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
503 statement.BindInt64(0, record->cache_id);
504 statement.BindInt64(1, record->group_id);
505 statement.BindBool(2, record->online_wildcard);
506 statement.BindInt64(3, record->update_time.ToInternalValue());
507 statement.BindInt64(4, record->cache_size);
509 return statement.Run();
512 bool AppCacheDatabase::DeleteCache(int64 cache_id) {
513 if (!LazyOpen(false))
514 return false;
516 const char* kSql =
517 "DELETE FROM Caches WHERE cache_id = ?";
519 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
520 statement.BindInt64(0, cache_id);
522 return statement.Run();
525 bool AppCacheDatabase::FindEntriesForCache(
526 int64 cache_id, std::vector<EntryRecord>* records) {
527 DCHECK(records && records->empty());
528 if (!LazyOpen(false))
529 return false;
531 const char* kSql =
532 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
533 " WHERE cache_id = ?";
535 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
536 statement.BindInt64(0, cache_id);
538 while (statement.Step()) {
539 records->push_back(EntryRecord());
540 ReadEntryRecord(statement, &records->back());
541 DCHECK(records->back().cache_id == cache_id);
544 return statement.Succeeded();
547 bool AppCacheDatabase::FindEntriesForUrl(
548 const GURL& url, std::vector<EntryRecord>* records) {
549 DCHECK(records && records->empty());
550 if (!LazyOpen(false))
551 return false;
553 const char* kSql =
554 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
555 " WHERE url = ?";
557 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
558 statement.BindString(0, url.spec());
560 while (statement.Step()) {
561 records->push_back(EntryRecord());
562 ReadEntryRecord(statement, &records->back());
563 DCHECK(records->back().url == url);
566 return statement.Succeeded();
569 bool AppCacheDatabase::FindEntry(
570 int64 cache_id, const GURL& url, EntryRecord* record) {
571 DCHECK(record);
572 if (!LazyOpen(false))
573 return false;
575 const char* kSql =
576 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
577 " WHERE cache_id = ? AND url = ?";
579 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
580 statement.BindInt64(0, cache_id);
581 statement.BindString(1, url.spec());
583 if (!statement.Step())
584 return false;
586 ReadEntryRecord(statement, record);
587 DCHECK(record->cache_id == cache_id);
588 DCHECK(record->url == url);
589 return true;
592 bool AppCacheDatabase::InsertEntry(const EntryRecord* record) {
593 if (!LazyOpen(true))
594 return false;
596 const char* kSql =
597 "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
598 " VALUES(?, ?, ?, ?, ?)";
600 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
601 statement.BindInt64(0, record->cache_id);
602 statement.BindString(1, record->url.spec());
603 statement.BindInt(2, record->flags);
604 statement.BindInt64(3, record->response_id);
605 statement.BindInt64(4, record->response_size);
607 return statement.Run();
610 bool AppCacheDatabase::InsertEntryRecords(
611 const std::vector<EntryRecord>& records) {
612 if (records.empty())
613 return true;
614 sql::Transaction transaction(db_.get());
615 if (!transaction.Begin())
616 return false;
617 std::vector<EntryRecord>::const_iterator iter = records.begin();
618 while (iter != records.end()) {
619 if (!InsertEntry(&(*iter)))
620 return false;
621 ++iter;
623 return transaction.Commit();
626 bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id) {
627 if (!LazyOpen(false))
628 return false;
630 const char* kSql =
631 "DELETE FROM Entries WHERE cache_id = ?";
633 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
634 statement.BindInt64(0, cache_id);
636 return statement.Run();
639 bool AppCacheDatabase::AddEntryFlags(
640 const GURL& entry_url, int64 cache_id, int additional_flags) {
641 if (!LazyOpen(false))
642 return false;
644 const char* kSql =
645 "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
647 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
648 statement.BindInt(0, additional_flags);
649 statement.BindInt64(1, cache_id);
650 statement.BindString(2, entry_url.spec());
652 return statement.Run() && db_->GetLastChangeCount();
655 bool AppCacheDatabase::FindNamespacesForOrigin(
656 const GURL& origin,
657 std::vector<NamespaceRecord>* intercepts,
658 std::vector<NamespaceRecord>* fallbacks) {
659 DCHECK(intercepts && intercepts->empty());
660 DCHECK(fallbacks && fallbacks->empty());
661 if (!LazyOpen(false))
662 return false;
664 const char* kSql =
665 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
666 " FROM Namespaces WHERE origin = ?";
668 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
669 statement.BindString(0, origin.spec());
671 ReadNamespaceRecords(&statement, intercepts, fallbacks);
673 return statement.Succeeded();
676 bool AppCacheDatabase::FindNamespacesForCache(
677 int64 cache_id,
678 std::vector<NamespaceRecord>* intercepts,
679 std::vector<NamespaceRecord>* fallbacks) {
680 DCHECK(intercepts && intercepts->empty());
681 DCHECK(fallbacks && fallbacks->empty());
682 if (!LazyOpen(false))
683 return false;
685 const char* kSql =
686 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
687 " FROM Namespaces WHERE cache_id = ?";
689 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
690 statement.BindInt64(0, cache_id);
692 ReadNamespaceRecords(&statement, intercepts, fallbacks);
694 return statement.Succeeded();
697 bool AppCacheDatabase::InsertNamespace(
698 const NamespaceRecord* record) {
699 if (!LazyOpen(true))
700 return false;
702 const char* kSql =
703 "INSERT INTO Namespaces"
704 " (cache_id, origin, type, namespace_url, target_url, is_pattern)"
705 " VALUES (?, ?, ?, ?, ?, ?)";
707 // Note: quick and dirty storage for the 'executable' bit w/o changing
708 // schemas, we use the high bit of 'type' field.
709 int type_with_executable_bit = record->namespace_.type;
710 if (record->namespace_.is_executable) {
711 type_with_executable_bit |= 0x8000000;
712 DCHECK(CommandLine::ForCurrentProcess()->HasSwitch(
713 kEnableExecutableHandlers));
716 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
717 statement.BindInt64(0, record->cache_id);
718 statement.BindString(1, record->origin.spec());
719 statement.BindInt(2, type_with_executable_bit);
720 statement.BindString(3, record->namespace_.namespace_url.spec());
721 statement.BindString(4, record->namespace_.target_url.spec());
722 statement.BindBool(5, record->namespace_.is_pattern);
723 return statement.Run();
726 bool AppCacheDatabase::InsertNamespaceRecords(
727 const std::vector<NamespaceRecord>& records) {
728 if (records.empty())
729 return true;
730 sql::Transaction transaction(db_.get());
731 if (!transaction.Begin())
732 return false;
733 std::vector<NamespaceRecord>::const_iterator iter = records.begin();
734 while (iter != records.end()) {
735 if (!InsertNamespace(&(*iter)))
736 return false;
737 ++iter;
739 return transaction.Commit();
742 bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id) {
743 if (!LazyOpen(false))
744 return false;
746 const char* kSql =
747 "DELETE FROM Namespaces WHERE cache_id = ?";
749 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
750 statement.BindInt64(0, cache_id);
752 return statement.Run();
755 bool AppCacheDatabase::FindOnlineWhiteListForCache(
756 int64 cache_id, std::vector<OnlineWhiteListRecord>* records) {
757 DCHECK(records && records->empty());
758 if (!LazyOpen(false))
759 return false;
761 const char* kSql =
762 "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
763 " WHERE cache_id = ?";
765 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
766 statement.BindInt64(0, cache_id);
768 while (statement.Step()) {
769 records->push_back(OnlineWhiteListRecord());
770 this->ReadOnlineWhiteListRecord(statement, &records->back());
771 DCHECK(records->back().cache_id == cache_id);
773 return statement.Succeeded();
776 bool AppCacheDatabase::InsertOnlineWhiteList(
777 const OnlineWhiteListRecord* record) {
778 if (!LazyOpen(true))
779 return false;
781 const char* kSql =
782 "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
783 " VALUES (?, ?, ?)";
785 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
786 statement.BindInt64(0, record->cache_id);
787 statement.BindString(1, record->namespace_url.spec());
788 statement.BindBool(2, record->is_pattern);
790 return statement.Run();
793 bool AppCacheDatabase::InsertOnlineWhiteListRecords(
794 const std::vector<OnlineWhiteListRecord>& records) {
795 if (records.empty())
796 return true;
797 sql::Transaction transaction(db_.get());
798 if (!transaction.Begin())
799 return false;
800 std::vector<OnlineWhiteListRecord>::const_iterator iter = records.begin();
801 while (iter != records.end()) {
802 if (!InsertOnlineWhiteList(&(*iter)))
803 return false;
804 ++iter;
806 return transaction.Commit();
809 bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id) {
810 if (!LazyOpen(false))
811 return false;
813 const char* kSql =
814 "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
816 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
817 statement.BindInt64(0, cache_id);
819 return statement.Run();
822 bool AppCacheDatabase::GetDeletableResponseIds(
823 std::vector<int64>* response_ids, int64 max_rowid, int limit) {
824 if (!LazyOpen(false))
825 return false;
827 const char* kSql =
828 "SELECT response_id FROM DeletableResponseIds "
829 " WHERE rowid <= ?"
830 " LIMIT ?";
832 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
833 statement.BindInt64(0, max_rowid);
834 statement.BindInt64(1, limit);
836 while (statement.Step())
837 response_ids->push_back(statement.ColumnInt64(0));
838 return statement.Succeeded();
841 bool AppCacheDatabase::InsertDeletableResponseIds(
842 const std::vector<int64>& response_ids) {
843 const char* kSql =
844 "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
845 return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
848 bool AppCacheDatabase::DeleteDeletableResponseIds(
849 const std::vector<int64>& response_ids) {
850 const char* kSql =
851 "DELETE FROM DeletableResponseIds WHERE response_id = ?";
852 return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
855 bool AppCacheDatabase::RunCachedStatementWithIds(
856 const sql::StatementID& statement_id, const char* sql,
857 const std::vector<int64>& ids) {
858 DCHECK(sql);
859 if (!LazyOpen(true))
860 return false;
862 sql::Transaction transaction(db_.get());
863 if (!transaction.Begin())
864 return false;
866 sql::Statement statement(db_->GetCachedStatement(statement_id, sql));
868 std::vector<int64>::const_iterator iter = ids.begin();
869 while (iter != ids.end()) {
870 statement.BindInt64(0, *iter);
871 if (!statement.Run())
872 return false;
873 statement.Reset(true);
874 ++iter;
877 return transaction.Commit();
880 bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
881 const char* sql, int64* result) {
882 DCHECK(sql);
883 sql::Statement statement(db_->GetUniqueStatement(sql));
884 if (!statement.Step()) {
885 return false;
887 *result = statement.ColumnInt64(0);
888 return true;
891 bool AppCacheDatabase::FindResponseIdsForCacheHelper(
892 int64 cache_id, std::vector<int64>* ids_vector,
893 std::set<int64>* ids_set) {
894 DCHECK(ids_vector || ids_set);
895 DCHECK(!(ids_vector && ids_set));
896 if (!LazyOpen(false))
897 return false;
899 const char* kSql =
900 "SELECT response_id FROM Entries WHERE cache_id = ?";
902 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
904 statement.BindInt64(0, cache_id);
905 while (statement.Step()) {
906 int64 id = statement.ColumnInt64(0);
907 if (ids_set)
908 ids_set->insert(id);
909 else
910 ids_vector->push_back(id);
913 return statement.Succeeded();
916 void AppCacheDatabase::ReadGroupRecord(
917 const sql::Statement& statement, GroupRecord* record) {
918 record->group_id = statement.ColumnInt64(0);
919 record->origin = GURL(statement.ColumnString(1));
920 record->manifest_url = GURL(statement.ColumnString(2));
921 record->creation_time =
922 base::Time::FromInternalValue(statement.ColumnInt64(3));
923 record->last_access_time =
924 base::Time::FromInternalValue(statement.ColumnInt64(4));
927 void AppCacheDatabase::ReadCacheRecord(
928 const sql::Statement& statement, CacheRecord* record) {
929 record->cache_id = statement.ColumnInt64(0);
930 record->group_id = statement.ColumnInt64(1);
931 record->online_wildcard = statement.ColumnBool(2);
932 record->update_time =
933 base::Time::FromInternalValue(statement.ColumnInt64(3));
934 record->cache_size = statement.ColumnInt64(4);
937 void AppCacheDatabase::ReadEntryRecord(
938 const sql::Statement& statement, EntryRecord* record) {
939 record->cache_id = statement.ColumnInt64(0);
940 record->url = GURL(statement.ColumnString(1));
941 record->flags = statement.ColumnInt(2);
942 record->response_id = statement.ColumnInt64(3);
943 record->response_size = statement.ColumnInt64(4);
946 void AppCacheDatabase::ReadNamespaceRecords(
947 sql::Statement* statement,
948 NamespaceRecordVector* intercepts,
949 NamespaceRecordVector* fallbacks) {
950 while (statement->Step()) {
951 NamespaceType type = static_cast<NamespaceType>(statement->ColumnInt(2));
952 NamespaceRecordVector* records =
953 (type == FALLBACK_NAMESPACE) ? fallbacks : intercepts;
954 records->push_back(NamespaceRecord());
955 ReadNamespaceRecord(statement, &records->back());
959 void AppCacheDatabase::ReadNamespaceRecord(
960 const sql::Statement* statement, NamespaceRecord* record) {
961 record->cache_id = statement->ColumnInt64(0);
962 record->origin = GURL(statement->ColumnString(1));
963 int type_with_executable_bit = statement->ColumnInt(2);
964 record->namespace_.namespace_url = GURL(statement->ColumnString(3));
965 record->namespace_.target_url = GURL(statement->ColumnString(4));
966 record->namespace_.is_pattern = statement->ColumnBool(5);
968 // Note: quick and dirty storage for the 'executable' bit w/o changing
969 // schemas, we use the high bit of 'type' field.
970 record->namespace_.type = static_cast<NamespaceType>
971 (type_with_executable_bit & 0x7ffffff);
972 record->namespace_.is_executable =
973 (type_with_executable_bit & 0x80000000) != 0;
974 DCHECK(!record->namespace_.is_executable ||
975 CommandLine::ForCurrentProcess()->HasSwitch(kEnableExecutableHandlers));
978 void AppCacheDatabase::ReadOnlineWhiteListRecord(
979 const sql::Statement& statement, OnlineWhiteListRecord* record) {
980 record->cache_id = statement.ColumnInt64(0);
981 record->namespace_url = GURL(statement.ColumnString(1));
982 record->is_pattern = statement.ColumnBool(2);
985 bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
986 if (db_)
987 return true;
989 // If we tried and failed once, don't try again in the same session
990 // to avoid creating an incoherent mess on disk.
991 if (is_disabled_)
992 return false;
994 // Avoid creating a database at all if we can.
995 bool use_in_memory_db = db_file_path_.empty();
996 if (!create_if_needed &&
997 (use_in_memory_db || !file_util::PathExists(db_file_path_))) {
998 return false;
1001 db_.reset(new sql::Connection);
1002 meta_table_.reset(new sql::MetaTable);
1004 db_->set_histogram_tag("AppCache");
1006 bool opened = false;
1007 if (use_in_memory_db) {
1008 opened = db_->OpenInMemory();
1009 } else if (!file_util::CreateDirectory(db_file_path_.DirName())) {
1010 LOG(ERROR) << "Failed to create appcache directory.";
1011 } else {
1012 opened = db_->Open(db_file_path_);
1013 if (opened)
1014 db_->Preload();
1017 if (!opened || !EnsureDatabaseVersion()) {
1018 LOG(ERROR) << "Failed to open the appcache database.";
1019 AppCacheHistograms::CountInitResult(
1020 AppCacheHistograms::SQL_DATABASE_ERROR);
1022 // We're unable to open the database. This is a fatal error
1023 // which we can't recover from. We try to handle it by deleting
1024 // the existing appcache data and starting with a clean slate in
1025 // this browser session.
1026 if (!use_in_memory_db && DeleteExistingAndCreateNewDatabase())
1027 return true;
1029 Disable();
1030 return false;
1033 AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK);
1034 return true;
1037 bool AppCacheDatabase::EnsureDatabaseVersion() {
1038 if (!sql::MetaTable::DoesTableExist(db_.get()))
1039 return CreateSchema();
1041 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1042 return false;
1044 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
1045 LOG(WARNING) << "AppCache database is too new.";
1046 return false;
1049 std::string stored_flags;
1050 meta_table_->GetValue(kExperimentFlagsKey, &stored_flags);
1051 if (stored_flags != GetActiveExperimentFlags())
1052 return false;
1054 if (meta_table_->GetVersionNumber() < kCurrentVersion)
1055 return UpgradeSchema();
1057 #ifndef NDEBUG
1058 DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
1059 for (int i = 0; i < kTableCount; ++i) {
1060 DCHECK(db_->DoesTableExist(kTables[i].table_name));
1062 for (int i = 0; i < kIndexCount; ++i) {
1063 DCHECK(db_->DoesIndexExist(kIndexes[i].index_name));
1065 #endif
1067 return true;
1070 bool AppCacheDatabase::CreateSchema() {
1071 sql::Transaction transaction(db_.get());
1072 if (!transaction.Begin())
1073 return false;
1075 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1076 return false;
1078 if (!meta_table_->SetValue(kExperimentFlagsKey,
1079 GetActiveExperimentFlags())) {
1080 return false;
1083 for (int i = 0; i < kTableCount; ++i) {
1084 if (!CreateTable(db_.get(), kTables[i]))
1085 return false;
1088 for (int i = 0; i < kIndexCount; ++i) {
1089 if (!CreateIndex(db_.get(), kIndexes[i]))
1090 return false;
1093 return transaction.Commit();
1096 bool AppCacheDatabase::UpgradeSchema() {
1097 if (meta_table_->GetVersionNumber() == 3) {
1098 // version 3 was pre 12/17/2011
1099 DCHECK_EQ(strcmp(kNamespacesTable, kTables[3].table_name), 0);
1100 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[6].table_name), 0);
1101 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[7].table_name), 0);
1102 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[8].table_name), 0);
1104 const TableInfo kNamespaceTable_v4 = {
1105 kNamespacesTable,
1106 "(cache_id INTEGER,"
1107 " origin TEXT," // intentionally not normalized
1108 " type INTEGER,"
1109 " namespace_url TEXT,"
1110 " target_url TEXT)"
1113 // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
1114 // but without the is_pattern column added in v5.
1115 sql::Transaction transaction(db_.get());
1116 if (!transaction.Begin() ||
1117 !CreateTable(db_.get(), kNamespaceTable_v4)) {
1118 return false;
1121 // Move data from the old table to the new table, setting the
1122 // 'type' for all current records to the value for FALLBACK_NAMESPACE.
1123 DCHECK_EQ(0, static_cast<int>(FALLBACK_NAMESPACE));
1124 if (!db_->Execute(
1125 "INSERT INTO Namespaces"
1126 " SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
1127 " FROM FallbackNameSpaces")) {
1128 return false;
1131 // Drop the old table, indexes on that table are also removed by this.
1132 if (!db_->Execute("DROP TABLE FallbackNameSpaces"))
1133 return false;
1135 // Create new indexes.
1136 if (!CreateIndex(db_.get(), kIndexes[6]) ||
1137 !CreateIndex(db_.get(), kIndexes[7]) ||
1138 !CreateIndex(db_.get(), kIndexes[8])) {
1139 return false;
1142 meta_table_->SetVersionNumber(4);
1143 meta_table_->SetCompatibleVersionNumber(4);
1144 if (!transaction.Commit())
1145 return false;
1148 if (meta_table_->GetVersionNumber() == 4) {
1149 // version 4 pre 3/30/2013
1150 // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
1151 DCHECK_EQ(strcmp(kNamespacesTable, "Namespaces"), 0);
1152 sql::Transaction transaction(db_.get());
1153 if (!transaction.Begin())
1154 return false;
1155 if (!db_->Execute(
1156 "ALTER TABLE Namespaces ADD COLUMN"
1157 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1158 return false;
1160 if (!db_->Execute(
1161 "ALTER TABLE OnlineWhitelists ADD COLUMN"
1162 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1163 return false;
1165 meta_table_->SetVersionNumber(5);
1166 meta_table_->SetCompatibleVersionNumber(5);
1167 return transaction.Commit();
1170 // If there is no upgrade path for the version on disk to the current
1171 // version, nuke everything and start over.
1172 return DeleteExistingAndCreateNewDatabase();
1175 void AppCacheDatabase::ResetConnectionAndTables() {
1176 meta_table_.reset();
1177 db_.reset();
1180 bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
1181 DCHECK(!db_file_path_.empty());
1182 DCHECK(file_util::PathExists(db_file_path_));
1183 VLOG(1) << "Deleting existing appcache data and starting over.";
1185 ResetConnectionAndTables();
1187 // This also deletes the disk cache data.
1188 base::FilePath directory = db_file_path_.DirName();
1189 if (!file_util::Delete(directory, true) ||
1190 !file_util::CreateDirectory(directory)) {
1191 return false;
1194 // Make sure the steps above actually deleted things.
1195 if (file_util::PathExists(db_file_path_))
1196 return false;
1198 // So we can't go recursive.
1199 if (is_recreating_)
1200 return false;
1202 base::AutoReset<bool> auto_reset(&is_recreating_, true);
1203 return LazyOpen(true);
1206 } // namespace appcache