[Storage] Blob Storage Refactoring pt 1:
[chromium-blink-merge.git] / content / browser / appcache / appcache_database.cc
blob3e502813a1b73c32ce795cde8e23b0c37fc7be7f
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() {
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(false))
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(false))
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(false))
311 return false;
313 const char* kSql =
314 "SELECT group_id, origin, manifest_url,"
315 " creation_time, last_access_time"
316 " FROM Groups WHERE group_id = ?";
318 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
320 statement.BindInt64(0, group_id);
321 if (!statement.Step())
322 return false;
324 ReadGroupRecord(statement, record);
325 DCHECK(record->group_id == group_id);
326 return true;
329 bool AppCacheDatabase::FindGroupForManifestUrl(
330 const GURL& manifest_url, GroupRecord* record) {
331 DCHECK(record);
332 if (!LazyOpen(false))
333 return false;
335 const char* kSql =
336 "SELECT group_id, origin, manifest_url,"
337 " creation_time, last_access_time"
338 " FROM Groups WHERE manifest_url = ?";
340 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
341 statement.BindString(0, manifest_url.spec());
343 if (!statement.Step())
344 return false;
346 ReadGroupRecord(statement, record);
347 DCHECK(record->manifest_url == manifest_url);
348 return true;
351 bool AppCacheDatabase::FindGroupsForOrigin(
352 const GURL& origin, std::vector<GroupRecord>* records) {
353 DCHECK(records && records->empty());
354 if (!LazyOpen(false))
355 return false;
357 const char* kSql =
358 "SELECT group_id, origin, manifest_url,"
359 " creation_time, last_access_time"
360 " FROM Groups WHERE origin = ?";
362 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
363 statement.BindString(0, origin.spec());
365 while (statement.Step()) {
366 records->push_back(GroupRecord());
367 ReadGroupRecord(statement, &records->back());
368 DCHECK(records->back().origin == origin);
371 return statement.Succeeded();
374 bool AppCacheDatabase::FindGroupForCache(int64 cache_id, GroupRecord* record) {
375 DCHECK(record);
376 if (!LazyOpen(false))
377 return false;
379 const char* kSql =
380 "SELECT g.group_id, g.origin, g.manifest_url,"
381 " g.creation_time, g.last_access_time"
382 " FROM Groups g, Caches c"
383 " WHERE c.cache_id = ? AND c.group_id = g.group_id";
385 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
386 statement.BindInt64(0, cache_id);
388 if (!statement.Step())
389 return false;
391 ReadGroupRecord(statement, record);
392 return true;
395 bool AppCacheDatabase::UpdateGroupLastAccessTime(
396 int64 group_id, base::Time time) {
397 if (!LazyOpen(true))
398 return false;
400 const char* kSql =
401 "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
403 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
404 statement.BindInt64(0, time.ToInternalValue());
405 statement.BindInt64(1, group_id);
407 return statement.Run() && db_->GetLastChangeCount();
410 bool AppCacheDatabase::InsertGroup(const GroupRecord* record) {
411 if (!LazyOpen(true))
412 return false;
414 const char* kSql =
415 "INSERT INTO Groups"
416 " (group_id, origin, manifest_url, creation_time, last_access_time)"
417 " VALUES(?, ?, ?, ?, ?)";
419 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
420 statement.BindInt64(0, record->group_id);
421 statement.BindString(1, record->origin.spec());
422 statement.BindString(2, record->manifest_url.spec());
423 statement.BindInt64(3, record->creation_time.ToInternalValue());
424 statement.BindInt64(4, record->last_access_time.ToInternalValue());
426 return statement.Run();
429 bool AppCacheDatabase::DeleteGroup(int64 group_id) {
430 if (!LazyOpen(false))
431 return false;
433 const char* kSql =
434 "DELETE FROM Groups WHERE group_id = ?";
436 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
437 statement.BindInt64(0, group_id);
439 return statement.Run();
442 bool AppCacheDatabase::FindCache(int64 cache_id, CacheRecord* record) {
443 DCHECK(record);
444 if (!LazyOpen(false))
445 return false;
447 const char* kSql =
448 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
449 " FROM Caches WHERE cache_id = ?";
451 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
452 statement.BindInt64(0, cache_id);
454 if (!statement.Step())
455 return false;
457 ReadCacheRecord(statement, record);
458 return true;
461 bool AppCacheDatabase::FindCacheForGroup(int64 group_id, CacheRecord* record) {
462 DCHECK(record);
463 if (!LazyOpen(false))
464 return false;
466 const char* kSql =
467 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
468 " FROM Caches WHERE group_id = ?";
470 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
471 statement.BindInt64(0, group_id);
473 if (!statement.Step())
474 return false;
476 ReadCacheRecord(statement, record);
477 return true;
480 bool AppCacheDatabase::FindCachesForOrigin(
481 const GURL& origin, std::vector<CacheRecord>* records) {
482 DCHECK(records);
483 std::vector<GroupRecord> group_records;
484 if (!FindGroupsForOrigin(origin, &group_records))
485 return false;
487 CacheRecord cache_record;
488 std::vector<GroupRecord>::const_iterator iter = group_records.begin();
489 while (iter != group_records.end()) {
490 if (FindCacheForGroup(iter->group_id, &cache_record))
491 records->push_back(cache_record);
492 ++iter;
494 return true;
497 bool AppCacheDatabase::InsertCache(const CacheRecord* record) {
498 if (!LazyOpen(true))
499 return false;
501 const char* kSql =
502 "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
503 " update_time, cache_size)"
504 " VALUES(?, ?, ?, ?, ?)";
506 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
507 statement.BindInt64(0, record->cache_id);
508 statement.BindInt64(1, record->group_id);
509 statement.BindBool(2, record->online_wildcard);
510 statement.BindInt64(3, record->update_time.ToInternalValue());
511 statement.BindInt64(4, record->cache_size);
513 return statement.Run();
516 bool AppCacheDatabase::DeleteCache(int64 cache_id) {
517 if (!LazyOpen(false))
518 return false;
520 const char* kSql =
521 "DELETE FROM Caches WHERE cache_id = ?";
523 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
524 statement.BindInt64(0, cache_id);
526 return statement.Run();
529 bool AppCacheDatabase::FindEntriesForCache(
530 int64 cache_id, std::vector<EntryRecord>* records) {
531 DCHECK(records && records->empty());
532 if (!LazyOpen(false))
533 return false;
535 const char* kSql =
536 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
537 " WHERE cache_id = ?";
539 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
540 statement.BindInt64(0, cache_id);
542 while (statement.Step()) {
543 records->push_back(EntryRecord());
544 ReadEntryRecord(statement, &records->back());
545 DCHECK(records->back().cache_id == cache_id);
548 return statement.Succeeded();
551 bool AppCacheDatabase::FindEntriesForUrl(
552 const GURL& url, std::vector<EntryRecord>* records) {
553 DCHECK(records && records->empty());
554 if (!LazyOpen(false))
555 return false;
557 const char* kSql =
558 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
559 " WHERE url = ?";
561 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
562 statement.BindString(0, url.spec());
564 while (statement.Step()) {
565 records->push_back(EntryRecord());
566 ReadEntryRecord(statement, &records->back());
567 DCHECK(records->back().url == url);
570 return statement.Succeeded();
573 bool AppCacheDatabase::FindEntry(
574 int64 cache_id, const GURL& url, EntryRecord* record) {
575 DCHECK(record);
576 if (!LazyOpen(false))
577 return false;
579 const char* kSql =
580 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
581 " WHERE cache_id = ? AND url = ?";
583 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
584 statement.BindInt64(0, cache_id);
585 statement.BindString(1, url.spec());
587 if (!statement.Step())
588 return false;
590 ReadEntryRecord(statement, record);
591 DCHECK(record->cache_id == cache_id);
592 DCHECK(record->url == url);
593 return true;
596 bool AppCacheDatabase::InsertEntry(const EntryRecord* record) {
597 if (!LazyOpen(true))
598 return false;
600 const char* kSql =
601 "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
602 " VALUES(?, ?, ?, ?, ?)";
604 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
605 statement.BindInt64(0, record->cache_id);
606 statement.BindString(1, record->url.spec());
607 statement.BindInt(2, record->flags);
608 statement.BindInt64(3, record->response_id);
609 statement.BindInt64(4, record->response_size);
611 return statement.Run();
614 bool AppCacheDatabase::InsertEntryRecords(
615 const std::vector<EntryRecord>& records) {
616 if (records.empty())
617 return true;
618 sql::Transaction transaction(db_.get());
619 if (!transaction.Begin())
620 return false;
621 std::vector<EntryRecord>::const_iterator iter = records.begin();
622 while (iter != records.end()) {
623 if (!InsertEntry(&(*iter)))
624 return false;
625 ++iter;
627 return transaction.Commit();
630 bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id) {
631 if (!LazyOpen(false))
632 return false;
634 const char* kSql =
635 "DELETE FROM Entries WHERE cache_id = ?";
637 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
638 statement.BindInt64(0, cache_id);
640 return statement.Run();
643 bool AppCacheDatabase::AddEntryFlags(
644 const GURL& entry_url, int64 cache_id, int additional_flags) {
645 if (!LazyOpen(false))
646 return false;
648 const char* kSql =
649 "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
651 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
652 statement.BindInt(0, additional_flags);
653 statement.BindInt64(1, cache_id);
654 statement.BindString(2, entry_url.spec());
656 return statement.Run() && db_->GetLastChangeCount();
659 bool AppCacheDatabase::FindNamespacesForOrigin(
660 const GURL& origin,
661 std::vector<NamespaceRecord>* intercepts,
662 std::vector<NamespaceRecord>* fallbacks) {
663 DCHECK(intercepts && intercepts->empty());
664 DCHECK(fallbacks && fallbacks->empty());
665 if (!LazyOpen(false))
666 return false;
668 const char* kSql =
669 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
670 " FROM Namespaces WHERE origin = ?";
672 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
673 statement.BindString(0, origin.spec());
675 ReadNamespaceRecords(&statement, intercepts, fallbacks);
677 return statement.Succeeded();
680 bool AppCacheDatabase::FindNamespacesForCache(
681 int64 cache_id,
682 std::vector<NamespaceRecord>* intercepts,
683 std::vector<NamespaceRecord>* fallbacks) {
684 DCHECK(intercepts && intercepts->empty());
685 DCHECK(fallbacks && fallbacks->empty());
686 if (!LazyOpen(false))
687 return false;
689 const char* kSql =
690 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
691 " FROM Namespaces WHERE cache_id = ?";
693 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
694 statement.BindInt64(0, cache_id);
696 ReadNamespaceRecords(&statement, intercepts, fallbacks);
698 return statement.Succeeded();
701 bool AppCacheDatabase::InsertNamespace(
702 const NamespaceRecord* record) {
703 if (!LazyOpen(true))
704 return false;
706 const char* kSql =
707 "INSERT INTO Namespaces"
708 " (cache_id, origin, type, namespace_url, target_url, is_pattern)"
709 " VALUES (?, ?, ?, ?, ?, ?)";
711 // Note: quick and dirty storage for the 'executable' bit w/o changing
712 // schemas, we use the high bit of 'type' field.
713 int type_with_executable_bit = record->namespace_.type;
714 if (record->namespace_.is_executable) {
715 type_with_executable_bit |= 0x8000000;
716 DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
717 kEnableExecutableHandlers));
720 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
721 statement.BindInt64(0, record->cache_id);
722 statement.BindString(1, record->origin.spec());
723 statement.BindInt(2, type_with_executable_bit);
724 statement.BindString(3, record->namespace_.namespace_url.spec());
725 statement.BindString(4, record->namespace_.target_url.spec());
726 statement.BindBool(5, record->namespace_.is_pattern);
727 return statement.Run();
730 bool AppCacheDatabase::InsertNamespaceRecords(
731 const std::vector<NamespaceRecord>& records) {
732 if (records.empty())
733 return true;
734 sql::Transaction transaction(db_.get());
735 if (!transaction.Begin())
736 return false;
737 std::vector<NamespaceRecord>::const_iterator iter = records.begin();
738 while (iter != records.end()) {
739 if (!InsertNamespace(&(*iter)))
740 return false;
741 ++iter;
743 return transaction.Commit();
746 bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id) {
747 if (!LazyOpen(false))
748 return false;
750 const char* kSql =
751 "DELETE FROM Namespaces WHERE cache_id = ?";
753 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
754 statement.BindInt64(0, cache_id);
756 return statement.Run();
759 bool AppCacheDatabase::FindOnlineWhiteListForCache(
760 int64 cache_id, std::vector<OnlineWhiteListRecord>* records) {
761 DCHECK(records && records->empty());
762 if (!LazyOpen(false))
763 return false;
765 const char* kSql =
766 "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
767 " WHERE cache_id = ?";
769 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
770 statement.BindInt64(0, cache_id);
772 while (statement.Step()) {
773 records->push_back(OnlineWhiteListRecord());
774 this->ReadOnlineWhiteListRecord(statement, &records->back());
775 DCHECK(records->back().cache_id == cache_id);
777 return statement.Succeeded();
780 bool AppCacheDatabase::InsertOnlineWhiteList(
781 const OnlineWhiteListRecord* record) {
782 if (!LazyOpen(true))
783 return false;
785 const char* kSql =
786 "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
787 " VALUES (?, ?, ?)";
789 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
790 statement.BindInt64(0, record->cache_id);
791 statement.BindString(1, record->namespace_url.spec());
792 statement.BindBool(2, record->is_pattern);
794 return statement.Run();
797 bool AppCacheDatabase::InsertOnlineWhiteListRecords(
798 const std::vector<OnlineWhiteListRecord>& records) {
799 if (records.empty())
800 return true;
801 sql::Transaction transaction(db_.get());
802 if (!transaction.Begin())
803 return false;
804 std::vector<OnlineWhiteListRecord>::const_iterator iter = records.begin();
805 while (iter != records.end()) {
806 if (!InsertOnlineWhiteList(&(*iter)))
807 return false;
808 ++iter;
810 return transaction.Commit();
813 bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id) {
814 if (!LazyOpen(false))
815 return false;
817 const char* kSql =
818 "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
820 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
821 statement.BindInt64(0, cache_id);
823 return statement.Run();
826 bool AppCacheDatabase::GetDeletableResponseIds(
827 std::vector<int64>* response_ids, int64 max_rowid, int limit) {
828 if (!LazyOpen(false))
829 return false;
831 const char* kSql =
832 "SELECT response_id FROM DeletableResponseIds "
833 " WHERE rowid <= ?"
834 " LIMIT ?";
836 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
837 statement.BindInt64(0, max_rowid);
838 statement.BindInt64(1, limit);
840 while (statement.Step())
841 response_ids->push_back(statement.ColumnInt64(0));
842 return statement.Succeeded();
845 bool AppCacheDatabase::InsertDeletableResponseIds(
846 const std::vector<int64>& response_ids) {
847 const char* kSql =
848 "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
849 return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
852 bool AppCacheDatabase::DeleteDeletableResponseIds(
853 const std::vector<int64>& response_ids) {
854 const char* kSql =
855 "DELETE FROM DeletableResponseIds WHERE response_id = ?";
856 return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
859 bool AppCacheDatabase::RunCachedStatementWithIds(
860 const sql::StatementID& statement_id, const char* sql,
861 const std::vector<int64>& ids) {
862 DCHECK(sql);
863 if (!LazyOpen(true))
864 return false;
866 sql::Transaction transaction(db_.get());
867 if (!transaction.Begin())
868 return false;
870 sql::Statement statement(db_->GetCachedStatement(statement_id, sql));
872 std::vector<int64>::const_iterator iter = ids.begin();
873 while (iter != ids.end()) {
874 statement.BindInt64(0, *iter);
875 if (!statement.Run())
876 return false;
877 statement.Reset(true);
878 ++iter;
881 return transaction.Commit();
884 bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
885 const char* sql, int64* result) {
886 DCHECK(sql);
887 sql::Statement statement(db_->GetUniqueStatement(sql));
888 if (!statement.Step()) {
889 return false;
891 *result = statement.ColumnInt64(0);
892 return true;
895 bool AppCacheDatabase::FindResponseIdsForCacheHelper(
896 int64 cache_id, std::vector<int64>* ids_vector,
897 std::set<int64>* ids_set) {
898 DCHECK(ids_vector || ids_set);
899 DCHECK(!(ids_vector && ids_set));
900 if (!LazyOpen(false))
901 return false;
903 const char* kSql =
904 "SELECT response_id FROM Entries WHERE cache_id = ?";
906 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
908 statement.BindInt64(0, cache_id);
909 while (statement.Step()) {
910 int64 id = statement.ColumnInt64(0);
911 if (ids_set)
912 ids_set->insert(id);
913 else
914 ids_vector->push_back(id);
917 return statement.Succeeded();
920 void AppCacheDatabase::ReadGroupRecord(
921 const sql::Statement& statement, GroupRecord* record) {
922 record->group_id = statement.ColumnInt64(0);
923 record->origin = GURL(statement.ColumnString(1));
924 record->manifest_url = GURL(statement.ColumnString(2));
925 record->creation_time =
926 base::Time::FromInternalValue(statement.ColumnInt64(3));
927 record->last_access_time =
928 base::Time::FromInternalValue(statement.ColumnInt64(4));
931 void AppCacheDatabase::ReadCacheRecord(
932 const sql::Statement& statement, CacheRecord* record) {
933 record->cache_id = statement.ColumnInt64(0);
934 record->group_id = statement.ColumnInt64(1);
935 record->online_wildcard = statement.ColumnBool(2);
936 record->update_time =
937 base::Time::FromInternalValue(statement.ColumnInt64(3));
938 record->cache_size = statement.ColumnInt64(4);
941 void AppCacheDatabase::ReadEntryRecord(
942 const sql::Statement& statement, EntryRecord* record) {
943 record->cache_id = statement.ColumnInt64(0);
944 record->url = GURL(statement.ColumnString(1));
945 record->flags = statement.ColumnInt(2);
946 record->response_id = statement.ColumnInt64(3);
947 record->response_size = statement.ColumnInt64(4);
950 void AppCacheDatabase::ReadNamespaceRecords(
951 sql::Statement* statement,
952 NamespaceRecordVector* intercepts,
953 NamespaceRecordVector* fallbacks) {
954 while (statement->Step()) {
955 AppCacheNamespaceType type = static_cast<AppCacheNamespaceType>(
956 statement->ColumnInt(2));
957 NamespaceRecordVector* records =
958 (type == APPCACHE_FALLBACK_NAMESPACE) ? fallbacks : intercepts;
959 records->push_back(NamespaceRecord());
960 ReadNamespaceRecord(statement, &records->back());
964 void AppCacheDatabase::ReadNamespaceRecord(
965 const sql::Statement* statement, NamespaceRecord* record) {
966 record->cache_id = statement->ColumnInt64(0);
967 record->origin = GURL(statement->ColumnString(1));
968 int type_with_executable_bit = statement->ColumnInt(2);
969 record->namespace_.namespace_url = GURL(statement->ColumnString(3));
970 record->namespace_.target_url = GURL(statement->ColumnString(4));
971 record->namespace_.is_pattern = statement->ColumnBool(5);
973 // Note: quick and dirty storage for the 'executable' bit w/o changing
974 // schemas, we use the high bit of 'type' field.
975 record->namespace_.type = static_cast<AppCacheNamespaceType>
976 (type_with_executable_bit & 0x7ffffff);
977 record->namespace_.is_executable =
978 (type_with_executable_bit & 0x80000000) != 0;
979 DCHECK(!record->namespace_.is_executable ||
980 base::CommandLine::ForCurrentProcess()->HasSwitch(
981 kEnableExecutableHandlers));
984 void AppCacheDatabase::ReadOnlineWhiteListRecord(
985 const sql::Statement& statement, OnlineWhiteListRecord* record) {
986 record->cache_id = statement.ColumnInt64(0);
987 record->namespace_url = GURL(statement.ColumnString(1));
988 record->is_pattern = statement.ColumnBool(2);
991 bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
992 if (db_)
993 return true;
995 // If we tried and failed once, don't try again in the same session
996 // to avoid creating an incoherent mess on disk.
997 if (is_disabled_)
998 return false;
1000 // Avoid creating a database at all if we can.
1001 bool use_in_memory_db = db_file_path_.empty();
1002 if (!create_if_needed &&
1003 (use_in_memory_db || !base::PathExists(db_file_path_))) {
1004 return false;
1007 db_.reset(new sql::Connection);
1008 meta_table_.reset(new sql::MetaTable);
1010 db_->set_histogram_tag("AppCache");
1012 bool opened = false;
1013 if (use_in_memory_db) {
1014 opened = db_->OpenInMemory();
1015 } else if (!base::CreateDirectory(db_file_path_.DirName())) {
1016 LOG(ERROR) << "Failed to create appcache directory.";
1017 } else {
1018 opened = db_->Open(db_file_path_);
1019 if (opened)
1020 db_->Preload();
1023 if (!opened || !db_->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
1024 LOG(ERROR) << "Failed to open the appcache database.";
1025 AppCacheHistograms::CountInitResult(
1026 AppCacheHistograms::SQL_DATABASE_ERROR);
1028 // We're unable to open the database. This is a fatal error
1029 // which we can't recover from. We try to handle it by deleting
1030 // the existing appcache data and starting with a clean slate in
1031 // this browser session.
1032 if (!use_in_memory_db && DeleteExistingAndCreateNewDatabase())
1033 return true;
1035 Disable();
1036 return false;
1039 AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK);
1040 was_corruption_detected_ = false;
1041 db_->set_error_callback(
1042 base::Bind(&AppCacheDatabase::OnDatabaseError, base::Unretained(this)));
1043 return true;
1046 bool AppCacheDatabase::EnsureDatabaseVersion() {
1047 if (!sql::MetaTable::DoesTableExist(db_.get()))
1048 return CreateSchema();
1050 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1051 return false;
1053 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
1054 LOG(WARNING) << "AppCache database is too new.";
1055 return false;
1058 std::string stored_flags;
1059 meta_table_->GetValue(kExperimentFlagsKey, &stored_flags);
1060 if (stored_flags != GetActiveExperimentFlags())
1061 return false;
1063 if (meta_table_->GetVersionNumber() < kCurrentVersion)
1064 return UpgradeSchema();
1066 #ifndef NDEBUG
1067 DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
1068 for (int i = 0; i < kTableCount; ++i) {
1069 DCHECK(db_->DoesTableExist(kTables[i].table_name));
1071 for (int i = 0; i < kIndexCount; ++i) {
1072 DCHECK(db_->DoesIndexExist(kIndexes[i].index_name));
1074 #endif
1076 return true;
1079 bool AppCacheDatabase::CreateSchema() {
1080 sql::Transaction transaction(db_.get());
1081 if (!transaction.Begin())
1082 return false;
1084 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1085 return false;
1087 if (!meta_table_->SetValue(kExperimentFlagsKey,
1088 GetActiveExperimentFlags())) {
1089 return false;
1092 for (int i = 0; i < kTableCount; ++i) {
1093 if (!CreateTable(db_.get(), kTables[i]))
1094 return false;
1097 for (int i = 0; i < kIndexCount; ++i) {
1098 if (!CreateIndex(db_.get(), kIndexes[i]))
1099 return false;
1102 return transaction.Commit();
1105 bool AppCacheDatabase::UpgradeSchema() {
1106 #if defined(APPCACHE_USE_SIMPLE_CACHE)
1107 return DeleteExistingAndCreateNewDatabase();
1108 #else
1109 if (meta_table_->GetVersionNumber() == 3) {
1110 // version 3 was pre 12/17/2011
1111 DCHECK_EQ(strcmp(kNamespacesTable, kTables[3].table_name), 0);
1112 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[6].table_name), 0);
1113 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[7].table_name), 0);
1114 DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[8].table_name), 0);
1116 const TableInfo kNamespaceTable_v4 = {
1117 kNamespacesTable,
1118 "(cache_id INTEGER,"
1119 " origin TEXT," // intentionally not normalized
1120 " type INTEGER,"
1121 " namespace_url TEXT,"
1122 " target_url TEXT)"
1125 // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
1126 // but without the is_pattern column added in v5.
1127 sql::Transaction transaction(db_.get());
1128 if (!transaction.Begin() ||
1129 !CreateTable(db_.get(), kNamespaceTable_v4)) {
1130 return false;
1133 // Move data from the old table to the new table, setting the
1134 // 'type' for all current records to the value for
1135 // APPCACHE_FALLBACK_NAMESPACE.
1136 DCHECK_EQ(0, static_cast<int>(APPCACHE_FALLBACK_NAMESPACE));
1137 if (!db_->Execute(
1138 "INSERT INTO Namespaces"
1139 " SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
1140 " FROM FallbackNameSpaces")) {
1141 return false;
1144 // Drop the old table, indexes on that table are also removed by this.
1145 if (!db_->Execute("DROP TABLE FallbackNameSpaces"))
1146 return false;
1148 // Create new indexes.
1149 if (!CreateIndex(db_.get(), kIndexes[6]) ||
1150 !CreateIndex(db_.get(), kIndexes[7]) ||
1151 !CreateIndex(db_.get(), kIndexes[8])) {
1152 return false;
1155 meta_table_->SetVersionNumber(4);
1156 meta_table_->SetCompatibleVersionNumber(4);
1157 if (!transaction.Commit())
1158 return false;
1161 if (meta_table_->GetVersionNumber() == 4) {
1162 // version 4 pre 3/30/2013
1163 // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
1164 DCHECK_EQ(strcmp(kNamespacesTable, "Namespaces"), 0);
1165 sql::Transaction transaction(db_.get());
1166 if (!transaction.Begin())
1167 return false;
1168 if (!db_->Execute(
1169 "ALTER TABLE Namespaces ADD COLUMN"
1170 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1171 return false;
1173 if (!db_->Execute(
1174 "ALTER TABLE OnlineWhitelists ADD COLUMN"
1175 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1176 return false;
1178 meta_table_->SetVersionNumber(5);
1179 meta_table_->SetCompatibleVersionNumber(5);
1180 return transaction.Commit();
1183 // If there is no upgrade path for the version on disk to the current
1184 // version, nuke everything and start over.
1185 return DeleteExistingAndCreateNewDatabase();
1186 #endif
1189 void AppCacheDatabase::ResetConnectionAndTables() {
1190 meta_table_.reset();
1191 db_.reset();
1194 bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
1195 DCHECK(!db_file_path_.empty());
1196 DCHECK(base::PathExists(db_file_path_));
1197 VLOG(1) << "Deleting existing appcache data and starting over.";
1199 ResetConnectionAndTables();
1201 // This also deletes the disk cache data.
1202 base::FilePath directory = db_file_path_.DirName();
1203 if (!base::DeleteFile(directory, true))
1204 return false;
1206 // Make sure the steps above actually deleted things.
1207 if (base::PathExists(directory))
1208 return false;
1210 if (!base::CreateDirectory(directory))
1211 return false;
1213 // So we can't go recursive.
1214 if (is_recreating_)
1215 return false;
1217 base::AutoReset<bool> auto_reset(&is_recreating_, true);
1218 return LazyOpen(true);
1221 void AppCacheDatabase::OnDatabaseError(int err, sql::Statement* stmt) {
1222 was_corruption_detected_ |= sql::IsErrorCatastrophic(err);
1223 if (!db_->ShouldIgnoreSqliteError(err))
1224 DLOG(ERROR) << db_->GetErrorMessage();
1225 // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
1228 } // namespace content