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"
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"
23 // Schema -------------------------------------------------------------------
26 const int kCurrentVersion
= 7;
27 const int kCompatibleVersion
= 7;
28 const bool kCreateIfNeeded
= true;
29 const bool kDontCreate
= false;
31 // A mechanism to run experiments that may affect in data being persisted
32 // in different ways such that when the experiment is toggled on/off via
33 // cmd line flags, the database gets reset. The active flags are stored at
34 // the time of database creation and compared when reopening. If different
35 // the database is reset.
36 const char kExperimentFlagsKey
[] = "ExperimentFlags";
38 const char kGroupsTable
[] = "Groups";
39 const char kCachesTable
[] = "Caches";
40 const char kEntriesTable
[] = "Entries";
41 const char kNamespacesTable
[] = "Namespaces";
42 const char kOnlineWhiteListsTable
[] = "OnlineWhiteLists";
43 const char kDeletableResponseIdsTable
[] = "DeletableResponseIds";
46 const char* table_name
;
51 const char* index_name
;
52 const char* table_name
;
57 const TableInfo kTables
[] = {
59 "(group_id INTEGER PRIMARY KEY,"
62 " creation_time INTEGER,"
63 " last_access_time INTEGER,"
64 " last_full_update_check_time INTEGER,"
65 " first_evictable_error_time INTEGER)" },
68 "(cache_id INTEGER PRIMARY KEY,"
70 " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
71 " update_time INTEGER,"
72 " cache_size INTEGER)" }, // intentionally not normalized
78 " response_id INTEGER,"
79 " response_size INTEGER)" },
83 " origin TEXT," // intentionally not normalized
85 " namespace_url TEXT,"
87 " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
89 { kOnlineWhiteListsTable
,
91 " namespace_url TEXT,"
92 " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
94 { kDeletableResponseIdsTable
,
95 "(response_id INTEGER NOT NULL)" },
98 const IndexInfo kIndexes
[] = {
99 { "GroupsOriginIndex",
104 { "GroupsManifestIndex",
109 { "CachesGroupIndex",
114 { "EntriesCacheIndex",
119 { "EntriesCacheAndUrlIndex",
124 { "EntriesResponseIdIndex",
129 { "NamespacesCacheIndex",
134 { "NamespacesOriginIndex",
139 { "NamespacesCacheAndUrlIndex",
141 "(cache_id, namespace_url)",
144 { "OnlineWhiteListCacheIndex",
145 kOnlineWhiteListsTable
,
149 { "DeletableResponsesIdIndex",
150 kDeletableResponseIdsTable
,
155 const int kTableCount
= arraysize(kTables
);
156 const int kIndexCount
= arraysize(kIndexes
);
158 bool CreateTable(sql::Connection
* db
, const TableInfo
& info
) {
159 std::string
sql("CREATE TABLE ");
160 sql
+= info
.table_name
;
162 return db
->Execute(sql
.c_str());
165 bool CreateIndex(sql::Connection
* db
, const IndexInfo
& info
) {
168 sql
+= "CREATE UNIQUE INDEX ";
170 sql
+= "CREATE INDEX ";
171 sql
+= info
.index_name
;
173 sql
+= info
.table_name
;
175 return db
->Execute(sql
.c_str());
178 std::string
GetActiveExperimentFlags() {
179 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
180 kEnableExecutableHandlers
))
181 return std::string("executableHandlersEnabled");
182 return std::string();
187 // AppCacheDatabase ----------------------------------------------------------
189 AppCacheDatabase::GroupRecord::GroupRecord()
193 AppCacheDatabase::GroupRecord::~GroupRecord() {
196 AppCacheDatabase::NamespaceRecord::NamespaceRecord()
200 AppCacheDatabase::NamespaceRecord::~NamespaceRecord() {
204 AppCacheDatabase::AppCacheDatabase(const base::FilePath
& path
)
205 : db_file_path_(path
),
207 is_recreating_(false),
208 was_corruption_detected_(false) {
211 AppCacheDatabase::~AppCacheDatabase() {
212 CommitLazyLastAccessTimes();
215 void AppCacheDatabase::Disable() {
216 VLOG(1) << "Disabling appcache database.";
218 ResetConnectionAndTables();
221 int64
AppCacheDatabase::GetOriginUsage(const GURL
& origin
) {
222 std::vector
<CacheRecord
> records
;
223 if (!FindCachesForOrigin(origin
, &records
))
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
;
235 bool AppCacheDatabase::GetAllOriginUsage(std::map
<GURL
, int64
>* usage_map
) {
236 std::set
<GURL
> origins
;
237 if (!FindOriginsWithGroups(&origins
))
239 for (std::set
<GURL
>::const_iterator origin
= origins
.begin();
240 origin
!= origins
.end(); ++origin
) {
241 (*usage_map
)[*origin
] = GetOriginUsage(*origin
);
246 bool AppCacheDatabase::FindOriginsWithGroups(std::set
<GURL
>* origins
) {
247 DCHECK(origins
&& origins
->empty());
248 if (!LazyOpen(kDontCreate
))
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
);
270 *last_response_id
= 0;
271 *last_deletable_response_rowid
= 0;
273 if (!LazyOpen(kDontCreate
))
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";
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
)) {
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
;
308 bool AppCacheDatabase::FindGroup(int64 group_id
, GroupRecord
* record
) {
310 if (!LazyOpen(kDontCreate
))
314 "SELECT group_id, origin, manifest_url,"
315 " creation_time, last_access_time,"
316 " last_full_update_check_time,"
317 " first_evictable_error_time"
318 " FROM Groups WHERE group_id = ?";
320 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
322 statement
.BindInt64(0, group_id
);
323 if (!statement
.Step())
326 ReadGroupRecord(statement
, record
);
327 DCHECK(record
->group_id
== group_id
);
331 bool AppCacheDatabase::FindGroupForManifestUrl(
332 const GURL
& manifest_url
, GroupRecord
* record
) {
334 if (!LazyOpen(kDontCreate
))
338 "SELECT group_id, origin, manifest_url,"
339 " creation_time, last_access_time,"
340 " last_full_update_check_time,"
341 " first_evictable_error_time"
342 " FROM Groups WHERE manifest_url = ?";
344 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
345 statement
.BindString(0, manifest_url
.spec());
347 if (!statement
.Step())
350 ReadGroupRecord(statement
, record
);
351 DCHECK(record
->manifest_url
== manifest_url
);
355 bool AppCacheDatabase::FindGroupsForOrigin(
356 const GURL
& origin
, std::vector
<GroupRecord
>* records
) {
357 DCHECK(records
&& records
->empty());
358 if (!LazyOpen(kDontCreate
))
362 "SELECT group_id, origin, manifest_url,"
363 " creation_time, last_access_time,"
364 " last_full_update_check_time,"
365 " first_evictable_error_time"
366 " FROM Groups WHERE origin = ?";
368 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
369 statement
.BindString(0, origin
.spec());
371 while (statement
.Step()) {
372 records
->push_back(GroupRecord());
373 ReadGroupRecord(statement
, &records
->back());
374 DCHECK(records
->back().origin
== origin
);
377 return statement
.Succeeded();
380 bool AppCacheDatabase::FindGroupForCache(int64 cache_id
, GroupRecord
* record
) {
382 if (!LazyOpen(kDontCreate
))
386 "SELECT g.group_id, g.origin, g.manifest_url,"
387 " g.creation_time, g.last_access_time,"
388 " g.last_full_update_check_time,"
389 " g.first_evictable_error_time"
390 " FROM Groups g, Caches c"
391 " WHERE c.cache_id = ? AND c.group_id = g.group_id";
393 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
394 statement
.BindInt64(0, cache_id
);
396 if (!statement
.Step())
399 ReadGroupRecord(statement
, record
);
403 bool AppCacheDatabase::InsertGroup(const GroupRecord
* record
) {
404 if (!LazyOpen(kCreateIfNeeded
))
409 " (group_id, origin, manifest_url, creation_time, last_access_time,"
410 " last_full_update_check_time, first_evictable_error_time)"
411 " VALUES(?, ?, ?, ?, ?, ?, ?)";
412 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
413 statement
.BindInt64(0, record
->group_id
);
414 statement
.BindString(1, record
->origin
.spec());
415 statement
.BindString(2, record
->manifest_url
.spec());
416 statement
.BindInt64(3, record
->creation_time
.ToInternalValue());
417 statement
.BindInt64(4, record
->last_access_time
.ToInternalValue());
418 statement
.BindInt64(5, record
->last_full_update_check_time
.ToInternalValue());
419 statement
.BindInt64(6, record
->first_evictable_error_time
.ToInternalValue());
420 return statement
.Run();
423 bool AppCacheDatabase::DeleteGroup(int64 group_id
) {
424 if (!LazyOpen(kDontCreate
))
428 "DELETE FROM Groups WHERE group_id = ?";
429 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
430 statement
.BindInt64(0, group_id
);
431 return statement
.Run();
434 bool AppCacheDatabase::UpdateLastAccessTime(
435 int64 group_id
, base::Time time
) {
436 if (!LazyUpdateLastAccessTime(group_id
, time
))
438 return CommitLazyLastAccessTimes();
441 bool AppCacheDatabase::LazyUpdateLastAccessTime(
442 int64 group_id
, base::Time time
) {
443 if (!LazyOpen(kCreateIfNeeded
))
445 lazy_last_access_times_
[group_id
] = time
;
449 bool AppCacheDatabase::CommitLazyLastAccessTimes() {
450 if (lazy_last_access_times_
.empty())
452 if (!LazyOpen(kDontCreate
))
455 sql::Transaction
transaction(db_
.get());
456 if (!transaction
.Begin())
458 for (const auto& pair
: lazy_last_access_times_
) {
460 "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
461 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
462 statement
.BindInt64(0, pair
.second
.ToInternalValue()); // time
463 statement
.BindInt64(1, pair
.first
); // group_id
466 lazy_last_access_times_
.clear();
467 return transaction
.Commit();
470 bool AppCacheDatabase::UpdateEvictionTimes(
472 base::Time last_full_update_check_time
,
473 base::Time first_evictable_error_time
) {
474 if (!LazyOpen(kCreateIfNeeded
))
479 " SET last_full_update_check_time = ?, first_evictable_error_time = ?"
480 " WHERE group_id = ?";
481 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
482 statement
.BindInt64(0, last_full_update_check_time
.ToInternalValue());
483 statement
.BindInt64(1, first_evictable_error_time
.ToInternalValue());
484 statement
.BindInt64(2, group_id
);
485 return statement
.Run(); // Will succeed even if group_id is invalid.
488 bool AppCacheDatabase::FindCache(int64 cache_id
, CacheRecord
* record
) {
490 if (!LazyOpen(kDontCreate
))
494 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
495 " FROM Caches WHERE cache_id = ?";
497 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
498 statement
.BindInt64(0, cache_id
);
500 if (!statement
.Step())
503 ReadCacheRecord(statement
, record
);
507 bool AppCacheDatabase::FindCacheForGroup(int64 group_id
, CacheRecord
* record
) {
509 if (!LazyOpen(kDontCreate
))
513 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
514 " FROM Caches WHERE group_id = ?";
516 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
517 statement
.BindInt64(0, group_id
);
519 if (!statement
.Step())
522 ReadCacheRecord(statement
, record
);
526 bool AppCacheDatabase::FindCachesForOrigin(
527 const GURL
& origin
, std::vector
<CacheRecord
>* records
) {
529 std::vector
<GroupRecord
> group_records
;
530 if (!FindGroupsForOrigin(origin
, &group_records
))
533 CacheRecord cache_record
;
534 std::vector
<GroupRecord
>::const_iterator iter
= group_records
.begin();
535 while (iter
!= group_records
.end()) {
536 if (FindCacheForGroup(iter
->group_id
, &cache_record
))
537 records
->push_back(cache_record
);
543 bool AppCacheDatabase::InsertCache(const CacheRecord
* record
) {
544 if (!LazyOpen(kCreateIfNeeded
))
548 "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
549 " update_time, cache_size)"
550 " VALUES(?, ?, ?, ?, ?)";
552 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
553 statement
.BindInt64(0, record
->cache_id
);
554 statement
.BindInt64(1, record
->group_id
);
555 statement
.BindBool(2, record
->online_wildcard
);
556 statement
.BindInt64(3, record
->update_time
.ToInternalValue());
557 statement
.BindInt64(4, record
->cache_size
);
559 return statement
.Run();
562 bool AppCacheDatabase::DeleteCache(int64 cache_id
) {
563 if (!LazyOpen(kDontCreate
))
567 "DELETE FROM Caches WHERE cache_id = ?";
569 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
570 statement
.BindInt64(0, cache_id
);
572 return statement
.Run();
575 bool AppCacheDatabase::FindEntriesForCache(
576 int64 cache_id
, std::vector
<EntryRecord
>* records
) {
577 DCHECK(records
&& records
->empty());
578 if (!LazyOpen(kDontCreate
))
582 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
583 " WHERE cache_id = ?";
585 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
586 statement
.BindInt64(0, cache_id
);
588 while (statement
.Step()) {
589 records
->push_back(EntryRecord());
590 ReadEntryRecord(statement
, &records
->back());
591 DCHECK(records
->back().cache_id
== cache_id
);
594 return statement
.Succeeded();
597 bool AppCacheDatabase::FindEntriesForUrl(
598 const GURL
& url
, std::vector
<EntryRecord
>* records
) {
599 DCHECK(records
&& records
->empty());
600 if (!LazyOpen(kDontCreate
))
604 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
607 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
608 statement
.BindString(0, url
.spec());
610 while (statement
.Step()) {
611 records
->push_back(EntryRecord());
612 ReadEntryRecord(statement
, &records
->back());
613 DCHECK(records
->back().url
== url
);
616 return statement
.Succeeded();
619 bool AppCacheDatabase::FindEntry(
620 int64 cache_id
, const GURL
& url
, EntryRecord
* record
) {
622 if (!LazyOpen(kDontCreate
))
626 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
627 " WHERE cache_id = ? AND url = ?";
629 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
630 statement
.BindInt64(0, cache_id
);
631 statement
.BindString(1, url
.spec());
633 if (!statement
.Step())
636 ReadEntryRecord(statement
, record
);
637 DCHECK(record
->cache_id
== cache_id
);
638 DCHECK(record
->url
== url
);
642 bool AppCacheDatabase::InsertEntry(const EntryRecord
* record
) {
643 if (!LazyOpen(kCreateIfNeeded
))
647 "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
648 " VALUES(?, ?, ?, ?, ?)";
650 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
651 statement
.BindInt64(0, record
->cache_id
);
652 statement
.BindString(1, record
->url
.spec());
653 statement
.BindInt(2, record
->flags
);
654 statement
.BindInt64(3, record
->response_id
);
655 statement
.BindInt64(4, record
->response_size
);
657 return statement
.Run();
660 bool AppCacheDatabase::InsertEntryRecords(
661 const std::vector
<EntryRecord
>& records
) {
664 sql::Transaction
transaction(db_
.get());
665 if (!transaction
.Begin())
667 std::vector
<EntryRecord
>::const_iterator iter
= records
.begin();
668 while (iter
!= records
.end()) {
669 if (!InsertEntry(&(*iter
)))
673 return transaction
.Commit();
676 bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id
) {
677 if (!LazyOpen(kDontCreate
))
681 "DELETE FROM Entries WHERE cache_id = ?";
683 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
684 statement
.BindInt64(0, cache_id
);
686 return statement
.Run();
689 bool AppCacheDatabase::AddEntryFlags(
690 const GURL
& entry_url
, int64 cache_id
, int additional_flags
) {
691 if (!LazyOpen(kDontCreate
))
695 "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
697 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
698 statement
.BindInt(0, additional_flags
);
699 statement
.BindInt64(1, cache_id
);
700 statement
.BindString(2, entry_url
.spec());
702 return statement
.Run() && db_
->GetLastChangeCount();
705 bool AppCacheDatabase::FindNamespacesForOrigin(
707 std::vector
<NamespaceRecord
>* intercepts
,
708 std::vector
<NamespaceRecord
>* fallbacks
) {
709 DCHECK(intercepts
&& intercepts
->empty());
710 DCHECK(fallbacks
&& fallbacks
->empty());
711 if (!LazyOpen(kDontCreate
))
715 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
716 " FROM Namespaces WHERE origin = ?";
718 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
719 statement
.BindString(0, origin
.spec());
721 ReadNamespaceRecords(&statement
, intercepts
, fallbacks
);
723 return statement
.Succeeded();
726 bool AppCacheDatabase::FindNamespacesForCache(
728 std::vector
<NamespaceRecord
>* intercepts
,
729 std::vector
<NamespaceRecord
>* fallbacks
) {
730 DCHECK(intercepts
&& intercepts
->empty());
731 DCHECK(fallbacks
&& fallbacks
->empty());
732 if (!LazyOpen(kDontCreate
))
736 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
737 " FROM Namespaces WHERE cache_id = ?";
739 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
740 statement
.BindInt64(0, cache_id
);
742 ReadNamespaceRecords(&statement
, intercepts
, fallbacks
);
744 return statement
.Succeeded();
747 bool AppCacheDatabase::InsertNamespace(
748 const NamespaceRecord
* record
) {
749 if (!LazyOpen(kCreateIfNeeded
))
753 "INSERT INTO Namespaces"
754 " (cache_id, origin, type, namespace_url, target_url, is_pattern)"
755 " VALUES (?, ?, ?, ?, ?, ?)";
757 // Note: quick and dirty storage for the 'executable' bit w/o changing
758 // schemas, we use the high bit of 'type' field.
759 int type_with_executable_bit
= record
->namespace_
.type
;
760 if (record
->namespace_
.is_executable
) {
761 type_with_executable_bit
|= 0x8000000;
762 DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
763 kEnableExecutableHandlers
));
766 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
767 statement
.BindInt64(0, record
->cache_id
);
768 statement
.BindString(1, record
->origin
.spec());
769 statement
.BindInt(2, type_with_executable_bit
);
770 statement
.BindString(3, record
->namespace_
.namespace_url
.spec());
771 statement
.BindString(4, record
->namespace_
.target_url
.spec());
772 statement
.BindBool(5, record
->namespace_
.is_pattern
);
773 return statement
.Run();
776 bool AppCacheDatabase::InsertNamespaceRecords(
777 const std::vector
<NamespaceRecord
>& records
) {
780 sql::Transaction
transaction(db_
.get());
781 if (!transaction
.Begin())
783 std::vector
<NamespaceRecord
>::const_iterator iter
= records
.begin();
784 while (iter
!= records
.end()) {
785 if (!InsertNamespace(&(*iter
)))
789 return transaction
.Commit();
792 bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id
) {
793 if (!LazyOpen(kDontCreate
))
797 "DELETE FROM Namespaces WHERE cache_id = ?";
799 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
800 statement
.BindInt64(0, cache_id
);
802 return statement
.Run();
805 bool AppCacheDatabase::FindOnlineWhiteListForCache(
806 int64 cache_id
, std::vector
<OnlineWhiteListRecord
>* records
) {
807 DCHECK(records
&& records
->empty());
808 if (!LazyOpen(kDontCreate
))
812 "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
813 " WHERE cache_id = ?";
815 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
816 statement
.BindInt64(0, cache_id
);
818 while (statement
.Step()) {
819 records
->push_back(OnlineWhiteListRecord());
820 this->ReadOnlineWhiteListRecord(statement
, &records
->back());
821 DCHECK(records
->back().cache_id
== cache_id
);
823 return statement
.Succeeded();
826 bool AppCacheDatabase::InsertOnlineWhiteList(
827 const OnlineWhiteListRecord
* record
) {
828 if (!LazyOpen(kCreateIfNeeded
))
832 "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
835 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
836 statement
.BindInt64(0, record
->cache_id
);
837 statement
.BindString(1, record
->namespace_url
.spec());
838 statement
.BindBool(2, record
->is_pattern
);
840 return statement
.Run();
843 bool AppCacheDatabase::InsertOnlineWhiteListRecords(
844 const std::vector
<OnlineWhiteListRecord
>& records
) {
847 sql::Transaction
transaction(db_
.get());
848 if (!transaction
.Begin())
850 std::vector
<OnlineWhiteListRecord
>::const_iterator iter
= records
.begin();
851 while (iter
!= records
.end()) {
852 if (!InsertOnlineWhiteList(&(*iter
)))
856 return transaction
.Commit();
859 bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id
) {
860 if (!LazyOpen(kDontCreate
))
864 "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
866 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
867 statement
.BindInt64(0, cache_id
);
869 return statement
.Run();
872 bool AppCacheDatabase::GetDeletableResponseIds(
873 std::vector
<int64
>* response_ids
, int64 max_rowid
, int limit
) {
874 if (!LazyOpen(kDontCreate
))
878 "SELECT response_id FROM DeletableResponseIds "
882 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
883 statement
.BindInt64(0, max_rowid
);
884 statement
.BindInt64(1, limit
);
886 while (statement
.Step())
887 response_ids
->push_back(statement
.ColumnInt64(0));
888 return statement
.Succeeded();
891 bool AppCacheDatabase::InsertDeletableResponseIds(
892 const std::vector
<int64
>& response_ids
) {
894 "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
895 return RunCachedStatementWithIds(SQL_FROM_HERE
, kSql
, response_ids
);
898 bool AppCacheDatabase::DeleteDeletableResponseIds(
899 const std::vector
<int64
>& response_ids
) {
901 "DELETE FROM DeletableResponseIds WHERE response_id = ?";
902 return RunCachedStatementWithIds(SQL_FROM_HERE
, kSql
, response_ids
);
905 bool AppCacheDatabase::RunCachedStatementWithIds(
906 const sql::StatementID
& statement_id
, const char* sql
,
907 const std::vector
<int64
>& ids
) {
909 if (!LazyOpen(kCreateIfNeeded
))
912 sql::Transaction
transaction(db_
.get());
913 if (!transaction
.Begin())
916 sql::Statement
statement(db_
->GetCachedStatement(statement_id
, sql
));
918 std::vector
<int64
>::const_iterator iter
= ids
.begin();
919 while (iter
!= ids
.end()) {
920 statement
.BindInt64(0, *iter
);
921 if (!statement
.Run())
923 statement
.Reset(true);
927 return transaction
.Commit();
930 bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
931 const char* sql
, int64
* result
) {
933 sql::Statement
statement(db_
->GetUniqueStatement(sql
));
934 if (!statement
.Step()) {
937 *result
= statement
.ColumnInt64(0);
941 bool AppCacheDatabase::FindResponseIdsForCacheHelper(
942 int64 cache_id
, std::vector
<int64
>* ids_vector
,
943 std::set
<int64
>* ids_set
) {
944 DCHECK(ids_vector
|| ids_set
);
945 DCHECK(!(ids_vector
&& ids_set
));
946 if (!LazyOpen(kDontCreate
))
950 "SELECT response_id FROM Entries WHERE cache_id = ?";
952 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
954 statement
.BindInt64(0, cache_id
);
955 while (statement
.Step()) {
956 int64 id
= statement
.ColumnInt64(0);
960 ids_vector
->push_back(id
);
963 return statement
.Succeeded();
966 void AppCacheDatabase::ReadGroupRecord(
967 const sql::Statement
& statement
, GroupRecord
* record
) {
968 record
->group_id
= statement
.ColumnInt64(0);
969 record
->origin
= GURL(statement
.ColumnString(1));
970 record
->manifest_url
= GURL(statement
.ColumnString(2));
971 record
->creation_time
=
972 base::Time::FromInternalValue(statement
.ColumnInt64(3));
974 const auto found
= lazy_last_access_times_
.find(record
->group_id
);
975 if (found
!= lazy_last_access_times_
.end()) {
976 record
->last_access_time
= found
->second
;
978 record
->last_access_time
=
979 base::Time::FromInternalValue(statement
.ColumnInt64(4));
982 record
->last_full_update_check_time
=
983 base::Time::FromInternalValue(statement
.ColumnInt64(5));
984 record
->first_evictable_error_time
=
985 base::Time::FromInternalValue(statement
.ColumnInt64(6));
988 void AppCacheDatabase::ReadCacheRecord(
989 const sql::Statement
& statement
, CacheRecord
* record
) {
990 record
->cache_id
= statement
.ColumnInt64(0);
991 record
->group_id
= statement
.ColumnInt64(1);
992 record
->online_wildcard
= statement
.ColumnBool(2);
993 record
->update_time
=
994 base::Time::FromInternalValue(statement
.ColumnInt64(3));
995 record
->cache_size
= statement
.ColumnInt64(4);
998 void AppCacheDatabase::ReadEntryRecord(
999 const sql::Statement
& statement
, EntryRecord
* record
) {
1000 record
->cache_id
= statement
.ColumnInt64(0);
1001 record
->url
= GURL(statement
.ColumnString(1));
1002 record
->flags
= statement
.ColumnInt(2);
1003 record
->response_id
= statement
.ColumnInt64(3);
1004 record
->response_size
= statement
.ColumnInt64(4);
1007 void AppCacheDatabase::ReadNamespaceRecords(
1008 sql::Statement
* statement
,
1009 NamespaceRecordVector
* intercepts
,
1010 NamespaceRecordVector
* fallbacks
) {
1011 while (statement
->Step()) {
1012 AppCacheNamespaceType type
= static_cast<AppCacheNamespaceType
>(
1013 statement
->ColumnInt(2));
1014 NamespaceRecordVector
* records
=
1015 (type
== APPCACHE_FALLBACK_NAMESPACE
) ? fallbacks
: intercepts
;
1016 records
->push_back(NamespaceRecord());
1017 ReadNamespaceRecord(statement
, &records
->back());
1021 void AppCacheDatabase::ReadNamespaceRecord(
1022 const sql::Statement
* statement
, NamespaceRecord
* record
) {
1023 record
->cache_id
= statement
->ColumnInt64(0);
1024 record
->origin
= GURL(statement
->ColumnString(1));
1025 int type_with_executable_bit
= statement
->ColumnInt(2);
1026 record
->namespace_
.namespace_url
= GURL(statement
->ColumnString(3));
1027 record
->namespace_
.target_url
= GURL(statement
->ColumnString(4));
1028 record
->namespace_
.is_pattern
= statement
->ColumnBool(5);
1030 // Note: quick and dirty storage for the 'executable' bit w/o changing
1031 // schemas, we use the high bit of 'type' field.
1032 record
->namespace_
.type
= static_cast<AppCacheNamespaceType
>
1033 (type_with_executable_bit
& 0x7ffffff);
1034 record
->namespace_
.is_executable
=
1035 (type_with_executable_bit
& 0x80000000) != 0;
1036 DCHECK(!record
->namespace_
.is_executable
||
1037 base::CommandLine::ForCurrentProcess()->HasSwitch(
1038 kEnableExecutableHandlers
));
1041 void AppCacheDatabase::ReadOnlineWhiteListRecord(
1042 const sql::Statement
& statement
, OnlineWhiteListRecord
* record
) {
1043 record
->cache_id
= statement
.ColumnInt64(0);
1044 record
->namespace_url
= GURL(statement
.ColumnString(1));
1045 record
->is_pattern
= statement
.ColumnBool(2);
1048 bool AppCacheDatabase::LazyOpen(bool create_if_needed
) {
1052 // If we tried and failed once, don't try again in the same session
1053 // to avoid creating an incoherent mess on disk.
1057 // Avoid creating a database at all if we can.
1058 bool use_in_memory_db
= db_file_path_
.empty();
1059 if (!create_if_needed
&&
1060 (use_in_memory_db
|| !base::PathExists(db_file_path_
))) {
1064 db_
.reset(new sql::Connection
);
1065 meta_table_
.reset(new sql::MetaTable
);
1067 db_
->set_histogram_tag("AppCache");
1069 bool opened
= false;
1070 if (use_in_memory_db
) {
1071 opened
= db_
->OpenInMemory();
1072 } else if (!base::CreateDirectory(db_file_path_
.DirName())) {
1073 LOG(ERROR
) << "Failed to create appcache directory.";
1075 opened
= db_
->Open(db_file_path_
);
1080 if (!opened
|| !db_
->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
1081 LOG(ERROR
) << "Failed to open the appcache database.";
1082 AppCacheHistograms::CountInitResult(
1083 AppCacheHistograms::SQL_DATABASE_ERROR
);
1085 // We're unable to open the database. This is a fatal error
1086 // which we can't recover from. We try to handle it by deleting
1087 // the existing appcache data and starting with a clean slate in
1088 // this browser session.
1089 if (!use_in_memory_db
&& DeleteExistingAndCreateNewDatabase())
1096 AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK
);
1097 was_corruption_detected_
= false;
1098 db_
->set_error_callback(
1099 base::Bind(&AppCacheDatabase::OnDatabaseError
, base::Unretained(this)));
1103 bool AppCacheDatabase::EnsureDatabaseVersion() {
1104 if (!sql::MetaTable::DoesTableExist(db_
.get()))
1105 return CreateSchema();
1107 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
1110 if (meta_table_
->GetCompatibleVersionNumber() > kCurrentVersion
) {
1111 LOG(WARNING
) << "AppCache database is too new.";
1115 std::string stored_flags
;
1116 meta_table_
->GetValue(kExperimentFlagsKey
, &stored_flags
);
1117 if (stored_flags
!= GetActiveExperimentFlags())
1120 if (meta_table_
->GetVersionNumber() < kCurrentVersion
)
1121 return UpgradeSchema();
1124 DCHECK(sql::MetaTable::DoesTableExist(db_
.get()));
1125 for (int i
= 0; i
< kTableCount
; ++i
) {
1126 DCHECK(db_
->DoesTableExist(kTables
[i
].table_name
));
1128 for (int i
= 0; i
< kIndexCount
; ++i
) {
1129 DCHECK(db_
->DoesIndexExist(kIndexes
[i
].index_name
));
1136 bool AppCacheDatabase::CreateSchema() {
1137 sql::Transaction
transaction(db_
.get());
1138 if (!transaction
.Begin())
1141 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
1144 if (!meta_table_
->SetValue(kExperimentFlagsKey
,
1145 GetActiveExperimentFlags())) {
1149 for (int i
= 0; i
< kTableCount
; ++i
) {
1150 if (!CreateTable(db_
.get(), kTables
[i
]))
1154 for (int i
= 0; i
< kIndexCount
; ++i
) {
1155 if (!CreateIndex(db_
.get(), kIndexes
[i
]))
1159 return transaction
.Commit();
1162 bool AppCacheDatabase::UpgradeSchema() {
1163 #if defined(APPCACHE_USE_SIMPLE_CACHE)
1164 if (meta_table_
->GetVersionNumber() < 6)
1165 return DeleteExistingAndCreateNewDatabase();
1167 if (meta_table_
->GetVersionNumber() == 3) {
1168 // version 3 was pre 12/17/2011
1169 DCHECK_EQ(strcmp(kNamespacesTable
, kTables
[3].table_name
), 0);
1170 DCHECK_EQ(strcmp(kNamespacesTable
, kIndexes
[6].table_name
), 0);
1171 DCHECK_EQ(strcmp(kNamespacesTable
, kIndexes
[7].table_name
), 0);
1172 DCHECK_EQ(strcmp(kNamespacesTable
, kIndexes
[8].table_name
), 0);
1174 const TableInfo kNamespaceTable_v4
= {
1176 "(cache_id INTEGER,"
1177 " origin TEXT," // intentionally not normalized
1179 " namespace_url TEXT,"
1183 // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
1184 // but without the is_pattern column added in v5.
1185 sql::Transaction
transaction(db_
.get());
1186 if (!transaction
.Begin() ||
1187 !CreateTable(db_
.get(), kNamespaceTable_v4
)) {
1191 // Move data from the old table to the new table, setting the
1192 // 'type' for all current records to the value for
1193 // APPCACHE_FALLBACK_NAMESPACE.
1194 DCHECK_EQ(0, static_cast<int>(APPCACHE_FALLBACK_NAMESPACE
));
1196 "INSERT INTO Namespaces"
1197 " SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
1198 " FROM FallbackNameSpaces")) {
1202 // Drop the old table, indexes on that table are also removed by this.
1203 if (!db_
->Execute("DROP TABLE FallbackNameSpaces"))
1206 // Create new indexes.
1207 if (!CreateIndex(db_
.get(), kIndexes
[6]) ||
1208 !CreateIndex(db_
.get(), kIndexes
[7]) ||
1209 !CreateIndex(db_
.get(), kIndexes
[8])) {
1213 meta_table_
->SetVersionNumber(4);
1214 meta_table_
->SetCompatibleVersionNumber(4);
1215 if (!transaction
.Commit())
1219 if (meta_table_
->GetVersionNumber() == 4) {
1220 // version 4 pre 3/30/2013
1221 // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
1222 DCHECK_EQ(strcmp(kNamespacesTable
, "Namespaces"), 0);
1223 sql::Transaction
transaction(db_
.get());
1224 if (!transaction
.Begin())
1227 "ALTER TABLE Namespaces ADD COLUMN"
1228 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1232 "ALTER TABLE OnlineWhitelists ADD COLUMN"
1233 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1236 meta_table_
->SetVersionNumber(5);
1237 meta_table_
->SetCompatibleVersionNumber(5);
1238 if (!transaction
.Commit())
1242 #if defined(APPCACHE_USE_SIMPLE_CACHE)
1243 // The schema version number was increased to 6 when we switched to the
1244 // SimpleCache for Android, but the SQL part of the schema is identical
1245 // to v5 on desktop chrome.
1246 if (meta_table_
->GetVersionNumber() == 6) {
1248 if (meta_table_
->GetVersionNumber() == 5) {
1250 // Versions 5 and 6 were pre-July 2015.
1251 // Version 7 adds support for expiring caches that are failing to update.
1252 sql::Transaction
transaction(db_
.get());
1253 if (!transaction
.Begin() ||
1255 "ALTER TABLE Groups ADD COLUMN"
1256 " last_full_update_check_time INTEGER") ||
1258 "ALTER TABLE Groups ADD COLUMN"
1259 " first_evictable_error_time INTEGER") ||
1262 " SET last_full_update_check_time ="
1263 " (SELECT update_time FROM Caches"
1264 " WHERE Caches.group_id = Groups.group_id)")) {
1267 meta_table_
->SetVersionNumber(7);
1268 meta_table_
->SetCompatibleVersionNumber(7);
1269 return transaction
.Commit();
1272 // If there is no upgrade path for the version on disk to the current
1273 // version, nuke everything and start over.
1274 return DeleteExistingAndCreateNewDatabase();
1277 void AppCacheDatabase::ResetConnectionAndTables() {
1278 meta_table_
.reset();
1282 bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
1283 DCHECK(!db_file_path_
.empty());
1284 DCHECK(base::PathExists(db_file_path_
));
1285 VLOG(1) << "Deleting existing appcache data and starting over.";
1287 ResetConnectionAndTables();
1289 // This also deletes the disk cache data.
1290 base::FilePath directory
= db_file_path_
.DirName();
1291 if (!base::DeleteFile(directory
, true))
1294 // Make sure the steps above actually deleted things.
1295 if (base::PathExists(directory
))
1298 if (!base::CreateDirectory(directory
))
1301 // So we can't go recursive.
1305 base::AutoReset
<bool> auto_reset(&is_recreating_
, true);
1306 return LazyOpen(kCreateIfNeeded
);
1309 void AppCacheDatabase::OnDatabaseError(int err
, sql::Statement
* stmt
) {
1310 was_corruption_detected_
|= sql::IsErrorCatastrophic(err
);
1311 if (!db_
->ShouldIgnoreSqliteError(err
))
1312 DLOG(ERROR
) << db_
->GetErrorMessage();
1313 // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
1316 } // namespace content