Use multiline attribute to check for IA2_STATE_MULTILINE.
[chromium-blink-merge.git] / content / browser / appcache / appcache_database.cc
blob985173be166b49be5017b5ad8e6478488b5307bf
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 #if defined(APPCACHE_USE_SIMPLE_CACHE)
27 const int kCurrentVersion = 6;
28 const int kCompatibleVersion = 6;
29 #else
30 const int kCurrentVersion = 5;
31 const int kCompatibleVersion = 5;
32 #endif
34 // A mechanism to run experiments that may affect in data being persisted
35 // in different ways such that when the experiment is toggled on/off via
36 // cmd line flags, the database gets reset. The active flags are stored at
37 // the time of database creation and compared when reopening. If different
38 // the database is reset.
39 const char kExperimentFlagsKey[] = "ExperimentFlags";
41 const char kGroupsTable[] = "Groups";
42 const char kCachesTable[] = "Caches";
43 const char kEntriesTable[] = "Entries";
44 const char kNamespacesTable[] = "Namespaces";
45 const char kOnlineWhiteListsTable[] = "OnlineWhiteLists";
46 const char kDeletableResponseIdsTable[] = "DeletableResponseIds";
48 struct TableInfo {
49 const char* table_name;
50 const char* columns;
53 struct IndexInfo {
54 const char* index_name;
55 const char* table_name;
56 const char* columns;
57 bool unique;
60 const TableInfo kTables[] = {
61 { kGroupsTable,
62 "(group_id INTEGER PRIMARY KEY,"
63 " origin TEXT,"
64 " manifest_url TEXT,"
65 " creation_time INTEGER,"
66 " last_access_time INTEGER)" },
68 { kCachesTable,
69 "(cache_id INTEGER PRIMARY KEY,"
70 " group_id INTEGER,"
71 " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
72 " update_time INTEGER,"
73 " cache_size INTEGER)" }, // intentionally not normalized
75 { kEntriesTable,
76 "(cache_id INTEGER,"
77 " url TEXT,"
78 " flags INTEGER,"
79 " response_id INTEGER,"
80 " response_size INTEGER)" },
82 { kNamespacesTable,
83 "(cache_id INTEGER,"
84 " origin TEXT," // intentionally not normalized
85 " type INTEGER,"
86 " namespace_url TEXT,"
87 " target_url TEXT,"
88 " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
90 { kOnlineWhiteListsTable,
91 "(cache_id INTEGER,"
92 " namespace_url TEXT,"
93 " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
95 { kDeletableResponseIdsTable,
96 "(response_id INTEGER NOT NULL)" },
99 const IndexInfo kIndexes[] = {
100 { "GroupsOriginIndex",
101 kGroupsTable,
102 "(origin)",
103 false },
105 { "GroupsManifestIndex",
106 kGroupsTable,
107 "(manifest_url)",
108 true },
110 { "CachesGroupIndex",
111 kCachesTable,
112 "(group_id)",
113 false },
115 { "EntriesCacheIndex",
116 kEntriesTable,
117 "(cache_id)",
118 false },
120 { "EntriesCacheAndUrlIndex",
121 kEntriesTable,
122 "(cache_id, url)",
123 true },
125 { "EntriesResponseIdIndex",
126 kEntriesTable,
127 "(response_id)",
128 true },
130 { "NamespacesCacheIndex",
131 kNamespacesTable,
132 "(cache_id)",
133 false },
135 { "NamespacesOriginIndex",
136 kNamespacesTable,
137 "(origin)",
138 false },
140 { "NamespacesCacheAndUrlIndex",
141 kNamespacesTable,
142 "(cache_id, namespace_url)",
143 true },
145 { "OnlineWhiteListCacheIndex",
146 kOnlineWhiteListsTable,
147 "(cache_id)",
148 false },
150 { "DeletableResponsesIdIndex",
151 kDeletableResponseIdsTable,
152 "(response_id)",
153 true },
156 const int kTableCount = arraysize(kTables);
157 const int kIndexCount = arraysize(kIndexes);
159 bool CreateTable(sql::Connection* db, const TableInfo& info) {
160 std::string sql("CREATE TABLE ");
161 sql += info.table_name;
162 sql += info.columns;
163 return db->Execute(sql.c_str());
166 bool CreateIndex(sql::Connection* db, const IndexInfo& info) {
167 std::string sql;
168 if (info.unique)
169 sql += "CREATE UNIQUE INDEX ";
170 else
171 sql += "CREATE INDEX ";
172 sql += info.index_name;
173 sql += " ON ";
174 sql += info.table_name;
175 sql += info.columns;
176 return db->Execute(sql.c_str());
179 std::string GetActiveExperimentFlags() {
180 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
181 kEnableExecutableHandlers))
182 return std::string("executableHandlersEnabled");
183 return std::string();
186 } // anon namespace
188 // AppCacheDatabase ----------------------------------------------------------
190 AppCacheDatabase::GroupRecord::GroupRecord()
191 : group_id(0) {
194 AppCacheDatabase::GroupRecord::~GroupRecord() {
197 AppCacheDatabase::NamespaceRecord::NamespaceRecord()
198 : cache_id(0) {
201 AppCacheDatabase::NamespaceRecord::~NamespaceRecord() {
205 AppCacheDatabase::AppCacheDatabase(const base::FilePath& path)
206 : db_file_path_(path),
207 is_disabled_(false),
208 is_recreating_(false),
209 was_corruption_detected_(false) {
212 AppCacheDatabase::~AppCacheDatabase() {
213 CommitLazyLastAccessTimes();
216 void AppCacheDatabase::Disable() {
217 VLOG(1) << "Disabling appcache database.";
218 is_disabled_ = true;
219 ResetConnectionAndTables();
222 int64 AppCacheDatabase::GetOriginUsage(const GURL& origin) {
223 std::vector<CacheRecord> records;
224 if (!FindCachesForOrigin(origin, &records))
225 return 0;
227 int64 origin_usage = 0;
228 std::vector<CacheRecord>::const_iterator iter = records.begin();
229 while (iter != records.end()) {
230 origin_usage += iter->cache_size;
231 ++iter;
233 return origin_usage;
236 bool AppCacheDatabase::GetAllOriginUsage(std::map<GURL, int64>* usage_map) {
237 std::set<GURL> origins;
238 if (!FindOriginsWithGroups(&origins))
239 return false;
240 for (std::set<GURL>::const_iterator origin = origins.begin();
241 origin != origins.end(); ++origin) {
242 (*usage_map)[*origin] = GetOriginUsage(*origin);
244 return true;
247 bool AppCacheDatabase::FindOriginsWithGroups(std::set<GURL>* origins) {
248 DCHECK(origins && origins->empty());
249 if (!LazyOpen(false))
250 return false;
252 const char* kSql =
253 "SELECT DISTINCT(origin) FROM Groups";
255 sql::Statement statement(db_->GetUniqueStatement(kSql));
257 while (statement.Step())
258 origins->insert(GURL(statement.ColumnString(0)));
260 return statement.Succeeded();
263 bool AppCacheDatabase::FindLastStorageIds(
264 int64* last_group_id, int64* last_cache_id, int64* last_response_id,
265 int64* last_deletable_response_rowid) {
266 DCHECK(last_group_id && last_cache_id && last_response_id &&
267 last_deletable_response_rowid);
269 *last_group_id = 0;
270 *last_cache_id = 0;
271 *last_response_id = 0;
272 *last_deletable_response_rowid = 0;
274 if (!LazyOpen(false))
275 return false;
277 const char* kMaxGroupIdSql = "SELECT MAX(group_id) FROM Groups";
278 const char* kMaxCacheIdSql = "SELECT MAX(cache_id) FROM Caches";
279 const char* kMaxResponseIdFromEntriesSql =
280 "SELECT MAX(response_id) FROM Entries";
281 const char* kMaxResponseIdFromDeletablesSql =
282 "SELECT MAX(response_id) FROM DeletableResponseIds";
283 const char* kMaxDeletableResponseRowIdSql =
284 "SELECT MAX(rowid) FROM DeletableResponseIds";
285 int64 max_group_id;
286 int64 max_cache_id;
287 int64 max_response_id_from_entries;
288 int64 max_response_id_from_deletables;
289 int64 max_deletable_response_rowid;
290 if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql, &max_group_id) ||
291 !RunUniqueStatementWithInt64Result(kMaxCacheIdSql, &max_cache_id) ||
292 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql,
293 &max_response_id_from_entries) ||
294 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql,
295 &max_response_id_from_deletables) ||
296 !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql,
297 &max_deletable_response_rowid)) {
298 return false;
301 *last_group_id = max_group_id;
302 *last_cache_id = max_cache_id;
303 *last_response_id = std::max(max_response_id_from_entries,
304 max_response_id_from_deletables);
305 *last_deletable_response_rowid = max_deletable_response_rowid;
306 return true;
309 bool AppCacheDatabase::FindGroup(int64 group_id, GroupRecord* record) {
310 DCHECK(record);
311 if (!LazyOpen(false))
312 return false;
314 const char* kSql =
315 "SELECT group_id, origin, manifest_url,"
316 " creation_time, last_access_time"
317 " FROM Groups WHERE group_id = ?";
319 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
321 statement.BindInt64(0, group_id);
322 if (!statement.Step())
323 return false;
325 ReadGroupRecord(statement, record);
326 DCHECK(record->group_id == group_id);
327 return true;
330 bool AppCacheDatabase::FindGroupForManifestUrl(
331 const GURL& manifest_url, GroupRecord* record) {
332 DCHECK(record);
333 if (!LazyOpen(false))
334 return false;
336 const char* kSql =
337 "SELECT group_id, origin, manifest_url,"
338 " creation_time, last_access_time"
339 " FROM Groups WHERE manifest_url = ?";
341 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
342 statement.BindString(0, manifest_url.spec());
344 if (!statement.Step())
345 return false;
347 ReadGroupRecord(statement, record);
348 DCHECK(record->manifest_url == manifest_url);
349 return true;
352 bool AppCacheDatabase::FindGroupsForOrigin(
353 const GURL& origin, std::vector<GroupRecord>* records) {
354 DCHECK(records && records->empty());
355 if (!LazyOpen(false))
356 return false;
358 const char* kSql =
359 "SELECT group_id, origin, manifest_url,"
360 " creation_time, last_access_time"
361 " FROM Groups WHERE origin = ?";
363 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
364 statement.BindString(0, origin.spec());
366 while (statement.Step()) {
367 records->push_back(GroupRecord());
368 ReadGroupRecord(statement, &records->back());
369 DCHECK(records->back().origin == origin);
372 return statement.Succeeded();
375 bool AppCacheDatabase::FindGroupForCache(int64 cache_id, GroupRecord* record) {
376 DCHECK(record);
377 if (!LazyOpen(false))
378 return false;
380 const char* kSql =
381 "SELECT g.group_id, g.origin, g.manifest_url,"
382 " g.creation_time, g.last_access_time"
383 " FROM Groups g, Caches c"
384 " WHERE c.cache_id = ? AND c.group_id = g.group_id";
386 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
387 statement.BindInt64(0, cache_id);
389 if (!statement.Step())
390 return false;
392 ReadGroupRecord(statement, record);
393 return true;
396 bool AppCacheDatabase::UpdateLastAccessTime(
397 int64 group_id, base::Time time) {
398 if (!LazyOpen(true))
399 return false;
401 const char* kSql =
402 "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
404 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
405 statement.BindInt64(0, time.ToInternalValue());
406 statement.BindInt64(1, group_id);
408 return statement.Run();
411 bool AppCacheDatabase::LazyUpdateLastAccessTime(
412 int64 group_id, base::Time time) {
413 if (!LazyOpen(true))
414 return false;
415 lazy_last_access_times_[group_id] = time;
416 return true;
419 bool AppCacheDatabase::CommitLazyLastAccessTimes() {
420 if (lazy_last_access_times_.empty())
421 return true;
422 if (!LazyOpen(false))
423 return false;
425 sql::Transaction transaction(db_.get());
426 if (!transaction.Begin())
427 return false;
428 for (const auto& pair : lazy_last_access_times_)
429 UpdateLastAccessTime(pair.first, pair.second);
430 lazy_last_access_times_.clear();
431 return transaction.Commit();
434 bool AppCacheDatabase::InsertGroup(const GroupRecord* record) {
435 if (!LazyOpen(true))
436 return false;
438 const char* kSql =
439 "INSERT INTO Groups"
440 " (group_id, origin, manifest_url, creation_time, last_access_time)"
441 " VALUES(?, ?, ?, ?, ?)";
443 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
444 statement.BindInt64(0, record->group_id);
445 statement.BindString(1, record->origin.spec());
446 statement.BindString(2, record->manifest_url.spec());
447 statement.BindInt64(3, record->creation_time.ToInternalValue());
448 statement.BindInt64(4, record->last_access_time.ToInternalValue());
450 return statement.Run();
453 bool AppCacheDatabase::DeleteGroup(int64 group_id) {
454 if (!LazyOpen(false))
455 return false;
457 const char* kSql =
458 "DELETE FROM Groups WHERE group_id = ?";
460 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
461 statement.BindInt64(0, group_id);
463 return statement.Run();
466 bool AppCacheDatabase::FindCache(int64 cache_id, CacheRecord* record) {
467 DCHECK(record);
468 if (!LazyOpen(false))
469 return false;
471 const char* kSql =
472 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
473 " FROM Caches WHERE cache_id = ?";
475 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
476 statement.BindInt64(0, cache_id);
478 if (!statement.Step())
479 return false;
481 ReadCacheRecord(statement, record);
482 return true;
485 bool AppCacheDatabase::FindCacheForGroup(int64 group_id, CacheRecord* record) {
486 DCHECK(record);
487 if (!LazyOpen(false))
488 return false;
490 const char* kSql =
491 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
492 " FROM Caches WHERE group_id = ?";
494 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
495 statement.BindInt64(0, group_id);
497 if (!statement.Step())
498 return false;
500 ReadCacheRecord(statement, record);
501 return true;
504 bool AppCacheDatabase::FindCachesForOrigin(
505 const GURL& origin, std::vector<CacheRecord>* records) {
506 DCHECK(records);
507 std::vector<GroupRecord> group_records;
508 if (!FindGroupsForOrigin(origin, &group_records))
509 return false;
511 CacheRecord cache_record;
512 std::vector<GroupRecord>::const_iterator iter = group_records.begin();
513 while (iter != group_records.end()) {
514 if (FindCacheForGroup(iter->group_id, &cache_record))
515 records->push_back(cache_record);
516 ++iter;
518 return true;
521 bool AppCacheDatabase::InsertCache(const CacheRecord* record) {
522 if (!LazyOpen(true))
523 return false;
525 const char* kSql =
526 "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
527 " update_time, cache_size)"
528 " VALUES(?, ?, ?, ?, ?)";
530 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
531 statement.BindInt64(0, record->cache_id);
532 statement.BindInt64(1, record->group_id);
533 statement.BindBool(2, record->online_wildcard);
534 statement.BindInt64(3, record->update_time.ToInternalValue());
535 statement.BindInt64(4, record->cache_size);
537 return statement.Run();
540 bool AppCacheDatabase::DeleteCache(int64 cache_id) {
541 if (!LazyOpen(false))
542 return false;
544 const char* kSql =
545 "DELETE FROM Caches WHERE cache_id = ?";
547 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
548 statement.BindInt64(0, cache_id);
550 return statement.Run();
553 bool AppCacheDatabase::FindEntriesForCache(
554 int64 cache_id, std::vector<EntryRecord>* records) {
555 DCHECK(records && records->empty());
556 if (!LazyOpen(false))
557 return false;
559 const char* kSql =
560 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
561 " WHERE cache_id = ?";
563 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
564 statement.BindInt64(0, cache_id);
566 while (statement.Step()) {
567 records->push_back(EntryRecord());
568 ReadEntryRecord(statement, &records->back());
569 DCHECK(records->back().cache_id == cache_id);
572 return statement.Succeeded();
575 bool AppCacheDatabase::FindEntriesForUrl(
576 const GURL& url, std::vector<EntryRecord>* records) {
577 DCHECK(records && records->empty());
578 if (!LazyOpen(false))
579 return false;
581 const char* kSql =
582 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
583 " WHERE url = ?";
585 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
586 statement.BindString(0, url.spec());
588 while (statement.Step()) {
589 records->push_back(EntryRecord());
590 ReadEntryRecord(statement, &records->back());
591 DCHECK(records->back().url == url);
594 return statement.Succeeded();
597 bool AppCacheDatabase::FindEntry(
598 int64 cache_id, const GURL& url, EntryRecord* record) {
599 DCHECK(record);
600 if (!LazyOpen(false))
601 return false;
603 const char* kSql =
604 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
605 " WHERE cache_id = ? AND url = ?";
607 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
608 statement.BindInt64(0, cache_id);
609 statement.BindString(1, url.spec());
611 if (!statement.Step())
612 return false;
614 ReadEntryRecord(statement, record);
615 DCHECK(record->cache_id == cache_id);
616 DCHECK(record->url == url);
617 return true;
620 bool AppCacheDatabase::InsertEntry(const EntryRecord* record) {
621 if (!LazyOpen(true))
622 return false;
624 const char* kSql =
625 "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
626 " VALUES(?, ?, ?, ?, ?)";
628 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
629 statement.BindInt64(0, record->cache_id);
630 statement.BindString(1, record->url.spec());
631 statement.BindInt(2, record->flags);
632 statement.BindInt64(3, record->response_id);
633 statement.BindInt64(4, record->response_size);
635 return statement.Run();
638 bool AppCacheDatabase::InsertEntryRecords(
639 const std::vector<EntryRecord>& records) {
640 if (records.empty())
641 return true;
642 sql::Transaction transaction(db_.get());
643 if (!transaction.Begin())
644 return false;
645 std::vector<EntryRecord>::const_iterator iter = records.begin();
646 while (iter != records.end()) {
647 if (!InsertEntry(&(*iter)))
648 return false;
649 ++iter;
651 return transaction.Commit();
654 bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id) {
655 if (!LazyOpen(false))
656 return false;
658 const char* kSql =
659 "DELETE FROM Entries WHERE cache_id = ?";
661 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
662 statement.BindInt64(0, cache_id);
664 return statement.Run();
667 bool AppCacheDatabase::AddEntryFlags(
668 const GURL& entry_url, int64 cache_id, int additional_flags) {
669 if (!LazyOpen(false))
670 return false;
672 const char* kSql =
673 "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
675 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
676 statement.BindInt(0, additional_flags);
677 statement.BindInt64(1, cache_id);
678 statement.BindString(2, entry_url.spec());
680 return statement.Run() && db_->GetLastChangeCount();
683 bool AppCacheDatabase::FindNamespacesForOrigin(
684 const GURL& origin,
685 std::vector<NamespaceRecord>* intercepts,
686 std::vector<NamespaceRecord>* fallbacks) {
687 DCHECK(intercepts && intercepts->empty());
688 DCHECK(fallbacks && fallbacks->empty());
689 if (!LazyOpen(false))
690 return false;
692 const char* kSql =
693 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
694 " FROM Namespaces WHERE origin = ?";
696 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
697 statement.BindString(0, origin.spec());
699 ReadNamespaceRecords(&statement, intercepts, fallbacks);
701 return statement.Succeeded();
704 bool AppCacheDatabase::FindNamespacesForCache(
705 int64 cache_id,
706 std::vector<NamespaceRecord>* intercepts,
707 std::vector<NamespaceRecord>* fallbacks) {
708 DCHECK(intercepts && intercepts->empty());
709 DCHECK(fallbacks && fallbacks->empty());
710 if (!LazyOpen(false))
711 return false;
713 const char* kSql =
714 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
715 " FROM Namespaces WHERE cache_id = ?";
717 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
718 statement.BindInt64(0, cache_id);
720 ReadNamespaceRecords(&statement, intercepts, fallbacks);
722 return statement.Succeeded();
725 bool AppCacheDatabase::InsertNamespace(
726 const NamespaceRecord* record) {
727 if (!LazyOpen(true))
728 return false;
730 const char* kSql =
731 "INSERT INTO Namespaces"
732 " (cache_id, origin, type, namespace_url, target_url, is_pattern)"
733 " VALUES (?, ?, ?, ?, ?, ?)";
735 // Note: quick and dirty storage for the 'executable' bit w/o changing
736 // schemas, we use the high bit of 'type' field.
737 int type_with_executable_bit = record->namespace_.type;
738 if (record->namespace_.is_executable) {
739 type_with_executable_bit |= 0x8000000;
740 DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
741 kEnableExecutableHandlers));
744 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
745 statement.BindInt64(0, record->cache_id);
746 statement.BindString(1, record->origin.spec());
747 statement.BindInt(2, type_with_executable_bit);
748 statement.BindString(3, record->namespace_.namespace_url.spec());
749 statement.BindString(4, record->namespace_.target_url.spec());
750 statement.BindBool(5, record->namespace_.is_pattern);
751 return statement.Run();
754 bool AppCacheDatabase::InsertNamespaceRecords(
755 const std::vector<NamespaceRecord>& records) {
756 if (records.empty())
757 return true;
758 sql::Transaction transaction(db_.get());
759 if (!transaction.Begin())
760 return false;
761 std::vector<NamespaceRecord>::const_iterator iter = records.begin();
762 while (iter != records.end()) {
763 if (!InsertNamespace(&(*iter)))
764 return false;
765 ++iter;
767 return transaction.Commit();
770 bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id) {
771 if (!LazyOpen(false))
772 return false;
774 const char* kSql =
775 "DELETE FROM Namespaces WHERE cache_id = ?";
777 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
778 statement.BindInt64(0, cache_id);
780 return statement.Run();
783 bool AppCacheDatabase::FindOnlineWhiteListForCache(
784 int64 cache_id, std::vector<OnlineWhiteListRecord>* records) {
785 DCHECK(records && records->empty());
786 if (!LazyOpen(false))
787 return false;
789 const char* kSql =
790 "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
791 " WHERE cache_id = ?";
793 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
794 statement.BindInt64(0, cache_id);
796 while (statement.Step()) {
797 records->push_back(OnlineWhiteListRecord());
798 this->ReadOnlineWhiteListRecord(statement, &records->back());
799 DCHECK(records->back().cache_id == cache_id);
801 return statement.Succeeded();
804 bool AppCacheDatabase::InsertOnlineWhiteList(
805 const OnlineWhiteListRecord* record) {
806 if (!LazyOpen(true))
807 return false;
809 const char* kSql =
810 "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
811 " VALUES (?, ?, ?)";
813 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
814 statement.BindInt64(0, record->cache_id);
815 statement.BindString(1, record->namespace_url.spec());
816 statement.BindBool(2, record->is_pattern);
818 return statement.Run();
821 bool AppCacheDatabase::InsertOnlineWhiteListRecords(
822 const std::vector<OnlineWhiteListRecord>& records) {
823 if (records.empty())
824 return true;
825 sql::Transaction transaction(db_.get());
826 if (!transaction.Begin())
827 return false;
828 std::vector<OnlineWhiteListRecord>::const_iterator iter = records.begin();
829 while (iter != records.end()) {
830 if (!InsertOnlineWhiteList(&(*iter)))
831 return false;
832 ++iter;
834 return transaction.Commit();
837 bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id) {
838 if (!LazyOpen(false))
839 return false;
841 const char* kSql =
842 "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
844 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
845 statement.BindInt64(0, cache_id);
847 return statement.Run();
850 bool AppCacheDatabase::GetDeletableResponseIds(
851 std::vector<int64>* response_ids, int64 max_rowid, int limit) {
852 if (!LazyOpen(false))
853 return false;
855 const char* kSql =
856 "SELECT response_id FROM DeletableResponseIds "
857 " WHERE rowid <= ?"
858 " LIMIT ?";
860 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
861 statement.BindInt64(0, max_rowid);
862 statement.BindInt64(1, limit);
864 while (statement.Step())
865 response_ids->push_back(statement.ColumnInt64(0));
866 return statement.Succeeded();
869 bool AppCacheDatabase::InsertDeletableResponseIds(
870 const std::vector<int64>& response_ids) {
871 const char* kSql =
872 "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
873 return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
876 bool AppCacheDatabase::DeleteDeletableResponseIds(
877 const std::vector<int64>& response_ids) {
878 const char* kSql =
879 "DELETE FROM DeletableResponseIds WHERE response_id = ?";
880 return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
883 bool AppCacheDatabase::RunCachedStatementWithIds(
884 const sql::StatementID& statement_id, const char* sql,
885 const std::vector<int64>& ids) {
886 DCHECK(sql);
887 if (!LazyOpen(true))
888 return false;
890 sql::Transaction transaction(db_.get());
891 if (!transaction.Begin())
892 return false;
894 sql::Statement statement(db_->GetCachedStatement(statement_id, sql));
896 std::vector<int64>::const_iterator iter = ids.begin();
897 while (iter != ids.end()) {
898 statement.BindInt64(0, *iter);
899 if (!statement.Run())
900 return false;
901 statement.Reset(true);
902 ++iter;
905 return transaction.Commit();
908 bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
909 const char* sql, int64* result) {
910 DCHECK(sql);
911 sql::Statement statement(db_->GetUniqueStatement(sql));
912 if (!statement.Step()) {
913 return false;
915 *result = statement.ColumnInt64(0);
916 return true;
919 bool AppCacheDatabase::FindResponseIdsForCacheHelper(
920 int64 cache_id, std::vector<int64>* ids_vector,
921 std::set<int64>* ids_set) {
922 DCHECK(ids_vector || ids_set);
923 DCHECK(!(ids_vector && ids_set));
924 if (!LazyOpen(false))
925 return false;
927 const char* kSql =
928 "SELECT response_id FROM Entries WHERE cache_id = ?";
930 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
932 statement.BindInt64(0, cache_id);
933 while (statement.Step()) {
934 int64 id = statement.ColumnInt64(0);
935 if (ids_set)
936 ids_set->insert(id);
937 else
938 ids_vector->push_back(id);
941 return statement.Succeeded();
944 void AppCacheDatabase::ReadGroupRecord(
945 const sql::Statement& statement, GroupRecord* record) {
946 record->group_id = statement.ColumnInt64(0);
947 record->origin = GURL(statement.ColumnString(1));
948 record->manifest_url = GURL(statement.ColumnString(2));
949 record->creation_time =
950 base::Time::FromInternalValue(statement.ColumnInt64(3));
952 const auto found = lazy_last_access_times_.find(record->group_id);
953 if (found != lazy_last_access_times_.end()) {
954 record->last_access_time = found->second;
955 } else {
956 record->last_access_time =
957 base::Time::FromInternalValue(statement.ColumnInt64(4));
961 void AppCacheDatabase::ReadCacheRecord(
962 const sql::Statement& statement, CacheRecord* record) {
963 record->cache_id = statement.ColumnInt64(0);
964 record->group_id = statement.ColumnInt64(1);
965 record->online_wildcard = statement.ColumnBool(2);
966 record->update_time =
967 base::Time::FromInternalValue(statement.ColumnInt64(3));
968 record->cache_size = statement.ColumnInt64(4);
971 void AppCacheDatabase::ReadEntryRecord(
972 const sql::Statement& statement, EntryRecord* record) {
973 record->cache_id = statement.ColumnInt64(0);
974 record->url = GURL(statement.ColumnString(1));
975 record->flags = statement.ColumnInt(2);
976 record->response_id = statement.ColumnInt64(3);
977 record->response_size = statement.ColumnInt64(4);
980 void AppCacheDatabase::ReadNamespaceRecords(
981 sql::Statement* statement,
982 NamespaceRecordVector* intercepts,
983 NamespaceRecordVector* fallbacks) {
984 while (statement->Step()) {
985 AppCacheNamespaceType type = static_cast<AppCacheNamespaceType>(
986 statement->ColumnInt(2));
987 NamespaceRecordVector* records =
988 (type == APPCACHE_FALLBACK_NAMESPACE) ? fallbacks : intercepts;
989 records->push_back(NamespaceRecord());
990 ReadNamespaceRecord(statement, &records->back());
994 void AppCacheDatabase::ReadNamespaceRecord(
995 const sql::Statement* statement, NamespaceRecord* record) {
996 record->cache_id = statement->ColumnInt64(0);
997 record->origin = GURL(statement->ColumnString(1));
998 int type_with_executable_bit = statement->ColumnInt(2);
999 record->namespace_.namespace_url = GURL(statement->ColumnString(3));
1000 record->namespace_.target_url = GURL(statement->ColumnString(4));
1001 record->namespace_.is_pattern = statement->ColumnBool(5);
1003 // Note: quick and dirty storage for the 'executable' bit w/o changing
1004 // schemas, we use the high bit of 'type' field.
1005 record->namespace_.type = static_cast<AppCacheNamespaceType>
1006 (type_with_executable_bit & 0x7ffffff);
1007 record->namespace_.is_executable =
1008 (type_with_executable_bit & 0x80000000) != 0;
1009 DCHECK(!record->namespace_.is_executable ||
1010 base::CommandLine::ForCurrentProcess()->HasSwitch(
1011 kEnableExecutableHandlers));
1014 void AppCacheDatabase::ReadOnlineWhiteListRecord(
1015 const sql::Statement& statement, OnlineWhiteListRecord* record) {
1016 record->cache_id = statement.ColumnInt64(0);
1017 record->namespace_url = GURL(statement.ColumnString(1));
1018 record->is_pattern = statement.ColumnBool(2);
1021 bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
1022 if (db_)
1023 return true;
1025 // If we tried and failed once, don't try again in the same session
1026 // to avoid creating an incoherent mess on disk.
1027 if (is_disabled_)
1028 return false;
1030 // Avoid creating a database at all if we can.
1031 bool use_in_memory_db = db_file_path_.empty();
1032 if (!create_if_needed &&
1033 (use_in_memory_db || !base::PathExists(db_file_path_))) {
1034 return false;
1037 db_.reset(new sql::Connection);
1038 meta_table_.reset(new sql::MetaTable);
1040 db_->set_histogram_tag("AppCache");
1042 bool opened = false;
1043 if (use_in_memory_db) {
1044 opened = db_->OpenInMemory();
1045 } else if (!base::CreateDirectory(db_file_path_.DirName())) {
1046 LOG(ERROR) << "Failed to create appcache directory.";
1047 } else {
1048 opened = db_->Open(db_file_path_);
1049 if (opened)
1050 db_->Preload();
1053 if (!opened || !db_->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
1054 LOG(ERROR) << "Failed to open the appcache database.";
1055 AppCacheHistograms::CountInitResult(
1056 AppCacheHistograms::SQL_DATABASE_ERROR);
1058 // We're unable to open the database. This is a fatal error
1059 // which we can't recover from. We try to handle it by deleting
1060 // the existing appcache data and starting with a clean slate in
1061 // this browser session.
1062 if (!use_in_memory_db && DeleteExistingAndCreateNewDatabase())
1063 return true;
1065 Disable();
1066 return false;
1069 AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK);
1070 was_corruption_detected_ = false;
1071 db_->set_error_callback(
1072 base::Bind(&AppCacheDatabase::OnDatabaseError, base::Unretained(this)));
1073 return true;
1076 bool AppCacheDatabase::EnsureDatabaseVersion() {
1077 if (!sql::MetaTable::DoesTableExist(db_.get()))
1078 return CreateSchema();
1080 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1081 return false;
1083 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
1084 LOG(WARNING) << "AppCache database is too new.";
1085 return false;
1088 std::string stored_flags;
1089 meta_table_->GetValue(kExperimentFlagsKey, &stored_flags);
1090 if (stored_flags != GetActiveExperimentFlags())
1091 return false;
1093 if (meta_table_->GetVersionNumber() < kCurrentVersion)
1094 return UpgradeSchema();
1096 #ifndef NDEBUG
1097 DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
1098 for (int i = 0; i < kTableCount; ++i) {
1099 DCHECK(db_->DoesTableExist(kTables[i].table_name));
1101 for (int i = 0; i < kIndexCount; ++i) {
1102 DCHECK(db_->DoesIndexExist(kIndexes[i].index_name));
1104 #endif
1106 return true;
1109 bool AppCacheDatabase::CreateSchema() {
1110 sql::Transaction transaction(db_.get());
1111 if (!transaction.Begin())
1112 return false;
1114 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1115 return false;
1117 if (!meta_table_->SetValue(kExperimentFlagsKey,
1118 GetActiveExperimentFlags())) {
1119 return false;
1122 for (int i = 0; i < kTableCount; ++i) {
1123 if (!CreateTable(db_.get(), kTables[i]))
1124 return false;
1127 for (int i = 0; i < kIndexCount; ++i) {
1128 if (!CreateIndex(db_.get(), kIndexes[i]))
1129 return false;
1132 return transaction.Commit();
1135 bool AppCacheDatabase::UpgradeSchema() {
1136 #if defined(APPCACHE_USE_SIMPLE_CACHE)
1137 return DeleteExistingAndCreateNewDatabase();
1138 #else
1139 if (meta_table_->GetVersionNumber() == 3) {
1140 // version 3 was pre 12/17/2011
1141 DCHECK_EQ(strcmp(kNamespacesTable, kTables[3].table_name), 0);
1142 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[6].table_name), 0);
1143 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[7].table_name), 0);
1144 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[8].table_name), 0);
1146 const TableInfo kNamespaceTable_v4 = {
1147 kNamespacesTable,
1148 "(cache_id INTEGER,"
1149 " origin TEXT," // intentionally not normalized
1150 " type INTEGER,"
1151 " namespace_url TEXT,"
1152 " target_url TEXT)"
1155 // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
1156 // but without the is_pattern column added in v5.
1157 sql::Transaction transaction(db_.get());
1158 if (!transaction.Begin() ||
1159 !CreateTable(db_.get(), kNamespaceTable_v4)) {
1160 return false;
1163 // Move data from the old table to the new table, setting the
1164 // 'type' for all current records to the value for
1165 // APPCACHE_FALLBACK_NAMESPACE.
1166 DCHECK_EQ(0, static_cast<int>(APPCACHE_FALLBACK_NAMESPACE));
1167 if (!db_->Execute(
1168 "INSERT INTO Namespaces"
1169 " SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
1170 " FROM FallbackNameSpaces")) {
1171 return false;
1174 // Drop the old table, indexes on that table are also removed by this.
1175 if (!db_->Execute("DROP TABLE FallbackNameSpaces"))
1176 return false;
1178 // Create new indexes.
1179 if (!CreateIndex(db_.get(), kIndexes[6]) ||
1180 !CreateIndex(db_.get(), kIndexes[7]) ||
1181 !CreateIndex(db_.get(), kIndexes[8])) {
1182 return false;
1185 meta_table_->SetVersionNumber(4);
1186 meta_table_->SetCompatibleVersionNumber(4);
1187 if (!transaction.Commit())
1188 return false;
1191 if (meta_table_->GetVersionNumber() == 4) {
1192 // version 4 pre 3/30/2013
1193 // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
1194 DCHECK_EQ(strcmp(kNamespacesTable, "Namespaces"), 0);
1195 sql::Transaction transaction(db_.get());
1196 if (!transaction.Begin())
1197 return false;
1198 if (!db_->Execute(
1199 "ALTER TABLE Namespaces ADD COLUMN"
1200 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1201 return false;
1203 if (!db_->Execute(
1204 "ALTER TABLE OnlineWhitelists ADD COLUMN"
1205 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1206 return false;
1208 meta_table_->SetVersionNumber(5);
1209 meta_table_->SetCompatibleVersionNumber(5);
1210 return transaction.Commit();
1213 // If there is no upgrade path for the version on disk to the current
1214 // version, nuke everything and start over.
1215 return DeleteExistingAndCreateNewDatabase();
1216 #endif
1219 void AppCacheDatabase::ResetConnectionAndTables() {
1220 meta_table_.reset();
1221 db_.reset();
1224 bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
1225 DCHECK(!db_file_path_.empty());
1226 DCHECK(base::PathExists(db_file_path_));
1227 VLOG(1) << "Deleting existing appcache data and starting over.";
1229 ResetConnectionAndTables();
1231 // This also deletes the disk cache data.
1232 base::FilePath directory = db_file_path_.DirName();
1233 if (!base::DeleteFile(directory, true))
1234 return false;
1236 // Make sure the steps above actually deleted things.
1237 if (base::PathExists(directory))
1238 return false;
1240 if (!base::CreateDirectory(directory))
1241 return false;
1243 // So we can't go recursive.
1244 if (is_recreating_)
1245 return false;
1247 base::AutoReset<bool> auto_reset(&is_recreating_, true);
1248 return LazyOpen(true);
1251 void AppCacheDatabase::OnDatabaseError(int err, sql::Statement* stmt) {
1252 was_corruption_detected_ |= sql::IsErrorCatastrophic(err);
1253 if (!db_->ShouldIgnoreSqliteError(err))
1254 DLOG(ERROR) << db_->GetErrorMessage();
1255 // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
1258 } // namespace content