1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "webkit/browser/appcache/appcache_database.h"
7 #include "base/auto_reset.h"
9 #include "base/command_line.h"
10 #include "base/file_util.h"
11 #include "base/logging.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "sql/connection.h"
14 #include "sql/error_delegate_util.h"
15 #include "sql/meta_table.h"
16 #include "sql/statement.h"
17 #include "sql/transaction.h"
18 #include "webkit/browser/appcache/appcache_entry.h"
19 #include "webkit/browser/appcache/appcache_histograms.h"
23 // Schema -------------------------------------------------------------------
26 #if defined(APPCACHE_USE_SIMPLE_CACHE)
27 const int kCurrentVersion
= 6;
28 const int kCompatibleVersion
= 6;
30 const int kCurrentVersion
= 5;
31 const int kCompatibleVersion
= 5;
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";
49 const char* table_name
;
54 const char* index_name
;
55 const char* table_name
;
60 const TableInfo kTables
[] = {
62 "(group_id INTEGER PRIMARY KEY,"
65 " creation_time INTEGER,"
66 " last_access_time INTEGER)" },
69 "(cache_id INTEGER PRIMARY KEY,"
71 " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
72 " update_time INTEGER,"
73 " cache_size INTEGER)" }, // intentionally not normalized
79 " response_id INTEGER,"
80 " response_size INTEGER)" },
84 " origin TEXT," // intentionally not normalized
86 " namespace_url TEXT,"
88 " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
90 { kOnlineWhiteListsTable
,
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",
105 { "GroupsManifestIndex",
110 { "CachesGroupIndex",
115 { "EntriesCacheIndex",
120 { "EntriesCacheAndUrlIndex",
125 { "EntriesResponseIdIndex",
130 { "NamespacesCacheIndex",
135 { "NamespacesOriginIndex",
140 { "NamespacesCacheAndUrlIndex",
142 "(cache_id, namespace_url)",
145 { "OnlineWhiteListCacheIndex",
146 kOnlineWhiteListsTable
,
150 { "DeletableResponsesIdIndex",
151 kDeletableResponseIdsTable
,
156 const int kTableCount
= ARRAYSIZE_UNSAFE(kTables
);
157 const int kIndexCount
= ARRAYSIZE_UNSAFE(kIndexes
);
159 bool CreateTable(sql::Connection
* db
, const TableInfo
& info
) {
160 std::string
sql("CREATE TABLE ");
161 sql
+= info
.table_name
;
163 return db
->Execute(sql
.c_str());
166 bool CreateIndex(sql::Connection
* db
, const IndexInfo
& info
) {
169 sql
+= "CREATE UNIQUE INDEX ";
171 sql
+= "CREATE INDEX ";
172 sql
+= info
.index_name
;
174 sql
+= info
.table_name
;
176 return db
->Execute(sql
.c_str());
179 std::string
GetActiveExperimentFlags() {
180 if (CommandLine::ForCurrentProcess()->HasSwitch(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() {
214 void AppCacheDatabase::Disable() {
215 VLOG(1) << "Disabling appcache database.";
217 ResetConnectionAndTables();
220 int64
AppCacheDatabase::GetOriginUsage(const GURL
& origin
) {
221 std::vector
<CacheRecord
> records
;
222 if (!FindCachesForOrigin(origin
, &records
))
225 int64 origin_usage
= 0;
226 std::vector
<CacheRecord
>::const_iterator iter
= records
.begin();
227 while (iter
!= records
.end()) {
228 origin_usage
+= iter
->cache_size
;
234 bool AppCacheDatabase::GetAllOriginUsage(std::map
<GURL
, int64
>* usage_map
) {
235 std::set
<GURL
> origins
;
236 if (!FindOriginsWithGroups(&origins
))
238 for (std::set
<GURL
>::const_iterator origin
= origins
.begin();
239 origin
!= origins
.end(); ++origin
) {
240 (*usage_map
)[*origin
] = GetOriginUsage(*origin
);
245 bool AppCacheDatabase::FindOriginsWithGroups(std::set
<GURL
>* origins
) {
246 DCHECK(origins
&& origins
->empty());
247 if (!LazyOpen(false))
251 "SELECT DISTINCT(origin) FROM Groups";
253 sql::Statement
statement(db_
->GetUniqueStatement(kSql
));
255 while (statement
.Step())
256 origins
->insert(GURL(statement
.ColumnString(0)));
258 return statement
.Succeeded();
261 bool AppCacheDatabase::FindLastStorageIds(
262 int64
* last_group_id
, int64
* last_cache_id
, int64
* last_response_id
,
263 int64
* last_deletable_response_rowid
) {
264 DCHECK(last_group_id
&& last_cache_id
&& last_response_id
&&
265 last_deletable_response_rowid
);
269 *last_response_id
= 0;
270 *last_deletable_response_rowid
= 0;
272 if (!LazyOpen(false))
275 const char* kMaxGroupIdSql
= "SELECT MAX(group_id) FROM Groups";
276 const char* kMaxCacheIdSql
= "SELECT MAX(cache_id) FROM Caches";
277 const char* kMaxResponseIdFromEntriesSql
=
278 "SELECT MAX(response_id) FROM Entries";
279 const char* kMaxResponseIdFromDeletablesSql
=
280 "SELECT MAX(response_id) FROM DeletableResponseIds";
281 const char* kMaxDeletableResponseRowIdSql
=
282 "SELECT MAX(rowid) FROM DeletableResponseIds";
285 int64 max_response_id_from_entries
;
286 int64 max_response_id_from_deletables
;
287 int64 max_deletable_response_rowid
;
288 if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql
, &max_group_id
) ||
289 !RunUniqueStatementWithInt64Result(kMaxCacheIdSql
, &max_cache_id
) ||
290 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql
,
291 &max_response_id_from_entries
) ||
292 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql
,
293 &max_response_id_from_deletables
) ||
294 !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql
,
295 &max_deletable_response_rowid
)) {
299 *last_group_id
= max_group_id
;
300 *last_cache_id
= max_cache_id
;
301 *last_response_id
= std::max(max_response_id_from_entries
,
302 max_response_id_from_deletables
);
303 *last_deletable_response_rowid
= max_deletable_response_rowid
;
307 bool AppCacheDatabase::FindGroup(int64 group_id
, GroupRecord
* record
) {
309 if (!LazyOpen(false))
313 "SELECT group_id, origin, manifest_url,"
314 " creation_time, last_access_time"
315 " FROM Groups WHERE group_id = ?";
317 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
319 statement
.BindInt64(0, group_id
);
320 if (!statement
.Step())
323 ReadGroupRecord(statement
, record
);
324 DCHECK(record
->group_id
== group_id
);
328 bool AppCacheDatabase::FindGroupForManifestUrl(
329 const GURL
& manifest_url
, GroupRecord
* record
) {
331 if (!LazyOpen(false))
335 "SELECT group_id, origin, manifest_url,"
336 " creation_time, last_access_time"
337 " FROM Groups WHERE manifest_url = ?";
339 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
340 statement
.BindString(0, manifest_url
.spec());
342 if (!statement
.Step())
345 ReadGroupRecord(statement
, record
);
346 DCHECK(record
->manifest_url
== manifest_url
);
350 bool AppCacheDatabase::FindGroupsForOrigin(
351 const GURL
& origin
, std::vector
<GroupRecord
>* records
) {
352 DCHECK(records
&& records
->empty());
353 if (!LazyOpen(false))
357 "SELECT group_id, origin, manifest_url,"
358 " creation_time, last_access_time"
359 " FROM Groups WHERE origin = ?";
361 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
362 statement
.BindString(0, origin
.spec());
364 while (statement
.Step()) {
365 records
->push_back(GroupRecord());
366 ReadGroupRecord(statement
, &records
->back());
367 DCHECK(records
->back().origin
== origin
);
370 return statement
.Succeeded();
373 bool AppCacheDatabase::FindGroupForCache(int64 cache_id
, GroupRecord
* record
) {
375 if (!LazyOpen(false))
379 "SELECT g.group_id, g.origin, g.manifest_url,"
380 " g.creation_time, g.last_access_time"
381 " FROM Groups g, Caches c"
382 " WHERE c.cache_id = ? AND c.group_id = g.group_id";
384 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
385 statement
.BindInt64(0, cache_id
);
387 if (!statement
.Step())
390 ReadGroupRecord(statement
, record
);
394 bool AppCacheDatabase::UpdateGroupLastAccessTime(
395 int64 group_id
, base::Time time
) {
400 "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
402 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
403 statement
.BindInt64(0, time
.ToInternalValue());
404 statement
.BindInt64(1, group_id
);
406 return statement
.Run() && db_
->GetLastChangeCount();
409 bool AppCacheDatabase::InsertGroup(const GroupRecord
* record
) {
415 " (group_id, origin, manifest_url, creation_time, last_access_time)"
416 " VALUES(?, ?, ?, ?, ?)";
418 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
419 statement
.BindInt64(0, record
->group_id
);
420 statement
.BindString(1, record
->origin
.spec());
421 statement
.BindString(2, record
->manifest_url
.spec());
422 statement
.BindInt64(3, record
->creation_time
.ToInternalValue());
423 statement
.BindInt64(4, record
->last_access_time
.ToInternalValue());
425 return statement
.Run();
428 bool AppCacheDatabase::DeleteGroup(int64 group_id
) {
429 if (!LazyOpen(false))
433 "DELETE FROM Groups WHERE group_id = ?";
435 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
436 statement
.BindInt64(0, group_id
);
438 return statement
.Run();
441 bool AppCacheDatabase::FindCache(int64 cache_id
, CacheRecord
* record
) {
443 if (!LazyOpen(false))
447 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
448 " FROM Caches WHERE cache_id = ?";
450 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
451 statement
.BindInt64(0, cache_id
);
453 if (!statement
.Step())
456 ReadCacheRecord(statement
, record
);
460 bool AppCacheDatabase::FindCacheForGroup(int64 group_id
, CacheRecord
* record
) {
462 if (!LazyOpen(false))
466 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
467 " FROM Caches WHERE group_id = ?";
469 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
470 statement
.BindInt64(0, group_id
);
472 if (!statement
.Step())
475 ReadCacheRecord(statement
, record
);
479 bool AppCacheDatabase::FindCachesForOrigin(
480 const GURL
& origin
, std::vector
<CacheRecord
>* records
) {
482 std::vector
<GroupRecord
> group_records
;
483 if (!FindGroupsForOrigin(origin
, &group_records
))
486 CacheRecord cache_record
;
487 std::vector
<GroupRecord
>::const_iterator iter
= group_records
.begin();
488 while (iter
!= group_records
.end()) {
489 if (FindCacheForGroup(iter
->group_id
, &cache_record
))
490 records
->push_back(cache_record
);
496 bool AppCacheDatabase::InsertCache(const CacheRecord
* record
) {
501 "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
502 " update_time, cache_size)"
503 " VALUES(?, ?, ?, ?, ?)";
505 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
506 statement
.BindInt64(0, record
->cache_id
);
507 statement
.BindInt64(1, record
->group_id
);
508 statement
.BindBool(2, record
->online_wildcard
);
509 statement
.BindInt64(3, record
->update_time
.ToInternalValue());
510 statement
.BindInt64(4, record
->cache_size
);
512 return statement
.Run();
515 bool AppCacheDatabase::DeleteCache(int64 cache_id
) {
516 if (!LazyOpen(false))
520 "DELETE FROM Caches WHERE cache_id = ?";
522 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
523 statement
.BindInt64(0, cache_id
);
525 return statement
.Run();
528 bool AppCacheDatabase::FindEntriesForCache(
529 int64 cache_id
, std::vector
<EntryRecord
>* records
) {
530 DCHECK(records
&& records
->empty());
531 if (!LazyOpen(false))
535 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
536 " WHERE cache_id = ?";
538 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
539 statement
.BindInt64(0, cache_id
);
541 while (statement
.Step()) {
542 records
->push_back(EntryRecord());
543 ReadEntryRecord(statement
, &records
->back());
544 DCHECK(records
->back().cache_id
== cache_id
);
547 return statement
.Succeeded();
550 bool AppCacheDatabase::FindEntriesForUrl(
551 const GURL
& url
, std::vector
<EntryRecord
>* records
) {
552 DCHECK(records
&& records
->empty());
553 if (!LazyOpen(false))
557 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
560 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
561 statement
.BindString(0, url
.spec());
563 while (statement
.Step()) {
564 records
->push_back(EntryRecord());
565 ReadEntryRecord(statement
, &records
->back());
566 DCHECK(records
->back().url
== url
);
569 return statement
.Succeeded();
572 bool AppCacheDatabase::FindEntry(
573 int64 cache_id
, const GURL
& url
, EntryRecord
* record
) {
575 if (!LazyOpen(false))
579 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
580 " WHERE cache_id = ? AND url = ?";
582 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
583 statement
.BindInt64(0, cache_id
);
584 statement
.BindString(1, url
.spec());
586 if (!statement
.Step())
589 ReadEntryRecord(statement
, record
);
590 DCHECK(record
->cache_id
== cache_id
);
591 DCHECK(record
->url
== url
);
595 bool AppCacheDatabase::InsertEntry(const EntryRecord
* record
) {
600 "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
601 " VALUES(?, ?, ?, ?, ?)";
603 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
604 statement
.BindInt64(0, record
->cache_id
);
605 statement
.BindString(1, record
->url
.spec());
606 statement
.BindInt(2, record
->flags
);
607 statement
.BindInt64(3, record
->response_id
);
608 statement
.BindInt64(4, record
->response_size
);
610 return statement
.Run();
613 bool AppCacheDatabase::InsertEntryRecords(
614 const std::vector
<EntryRecord
>& records
) {
617 sql::Transaction
transaction(db_
.get());
618 if (!transaction
.Begin())
620 std::vector
<EntryRecord
>::const_iterator iter
= records
.begin();
621 while (iter
!= records
.end()) {
622 if (!InsertEntry(&(*iter
)))
626 return transaction
.Commit();
629 bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id
) {
630 if (!LazyOpen(false))
634 "DELETE FROM Entries WHERE cache_id = ?";
636 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
637 statement
.BindInt64(0, cache_id
);
639 return statement
.Run();
642 bool AppCacheDatabase::AddEntryFlags(
643 const GURL
& entry_url
, int64 cache_id
, int additional_flags
) {
644 if (!LazyOpen(false))
648 "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
650 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
651 statement
.BindInt(0, additional_flags
);
652 statement
.BindInt64(1, cache_id
);
653 statement
.BindString(2, entry_url
.spec());
655 return statement
.Run() && db_
->GetLastChangeCount();
658 bool AppCacheDatabase::FindNamespacesForOrigin(
660 std::vector
<NamespaceRecord
>* intercepts
,
661 std::vector
<NamespaceRecord
>* fallbacks
) {
662 DCHECK(intercepts
&& intercepts
->empty());
663 DCHECK(fallbacks
&& fallbacks
->empty());
664 if (!LazyOpen(false))
668 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
669 " FROM Namespaces WHERE origin = ?";
671 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
672 statement
.BindString(0, origin
.spec());
674 ReadNamespaceRecords(&statement
, intercepts
, fallbacks
);
676 return statement
.Succeeded();
679 bool AppCacheDatabase::FindNamespacesForCache(
681 std::vector
<NamespaceRecord
>* intercepts
,
682 std::vector
<NamespaceRecord
>* fallbacks
) {
683 DCHECK(intercepts
&& intercepts
->empty());
684 DCHECK(fallbacks
&& fallbacks
->empty());
685 if (!LazyOpen(false))
689 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
690 " FROM Namespaces WHERE cache_id = ?";
692 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
693 statement
.BindInt64(0, cache_id
);
695 ReadNamespaceRecords(&statement
, intercepts
, fallbacks
);
697 return statement
.Succeeded();
700 bool AppCacheDatabase::InsertNamespace(
701 const NamespaceRecord
* record
) {
706 "INSERT INTO Namespaces"
707 " (cache_id, origin, type, namespace_url, target_url, is_pattern)"
708 " VALUES (?, ?, ?, ?, ?, ?)";
710 // Note: quick and dirty storage for the 'executable' bit w/o changing
711 // schemas, we use the high bit of 'type' field.
712 int type_with_executable_bit
= record
->namespace_
.type
;
713 if (record
->namespace_
.is_executable
) {
714 type_with_executable_bit
|= 0x8000000;
715 DCHECK(CommandLine::ForCurrentProcess()->HasSwitch(
716 kEnableExecutableHandlers
));
719 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
720 statement
.BindInt64(0, record
->cache_id
);
721 statement
.BindString(1, record
->origin
.spec());
722 statement
.BindInt(2, type_with_executable_bit
);
723 statement
.BindString(3, record
->namespace_
.namespace_url
.spec());
724 statement
.BindString(4, record
->namespace_
.target_url
.spec());
725 statement
.BindBool(5, record
->namespace_
.is_pattern
);
726 return statement
.Run();
729 bool AppCacheDatabase::InsertNamespaceRecords(
730 const std::vector
<NamespaceRecord
>& records
) {
733 sql::Transaction
transaction(db_
.get());
734 if (!transaction
.Begin())
736 std::vector
<NamespaceRecord
>::const_iterator iter
= records
.begin();
737 while (iter
!= records
.end()) {
738 if (!InsertNamespace(&(*iter
)))
742 return transaction
.Commit();
745 bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id
) {
746 if (!LazyOpen(false))
750 "DELETE FROM Namespaces WHERE cache_id = ?";
752 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
753 statement
.BindInt64(0, cache_id
);
755 return statement
.Run();
758 bool AppCacheDatabase::FindOnlineWhiteListForCache(
759 int64 cache_id
, std::vector
<OnlineWhiteListRecord
>* records
) {
760 DCHECK(records
&& records
->empty());
761 if (!LazyOpen(false))
765 "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
766 " WHERE cache_id = ?";
768 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
769 statement
.BindInt64(0, cache_id
);
771 while (statement
.Step()) {
772 records
->push_back(OnlineWhiteListRecord());
773 this->ReadOnlineWhiteListRecord(statement
, &records
->back());
774 DCHECK(records
->back().cache_id
== cache_id
);
776 return statement
.Succeeded();
779 bool AppCacheDatabase::InsertOnlineWhiteList(
780 const OnlineWhiteListRecord
* record
) {
785 "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
788 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
789 statement
.BindInt64(0, record
->cache_id
);
790 statement
.BindString(1, record
->namespace_url
.spec());
791 statement
.BindBool(2, record
->is_pattern
);
793 return statement
.Run();
796 bool AppCacheDatabase::InsertOnlineWhiteListRecords(
797 const std::vector
<OnlineWhiteListRecord
>& records
) {
800 sql::Transaction
transaction(db_
.get());
801 if (!transaction
.Begin())
803 std::vector
<OnlineWhiteListRecord
>::const_iterator iter
= records
.begin();
804 while (iter
!= records
.end()) {
805 if (!InsertOnlineWhiteList(&(*iter
)))
809 return transaction
.Commit();
812 bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id
) {
813 if (!LazyOpen(false))
817 "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
819 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
820 statement
.BindInt64(0, cache_id
);
822 return statement
.Run();
825 bool AppCacheDatabase::GetDeletableResponseIds(
826 std::vector
<int64
>* response_ids
, int64 max_rowid
, int limit
) {
827 if (!LazyOpen(false))
831 "SELECT response_id FROM DeletableResponseIds "
835 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
836 statement
.BindInt64(0, max_rowid
);
837 statement
.BindInt64(1, limit
);
839 while (statement
.Step())
840 response_ids
->push_back(statement
.ColumnInt64(0));
841 return statement
.Succeeded();
844 bool AppCacheDatabase::InsertDeletableResponseIds(
845 const std::vector
<int64
>& response_ids
) {
847 "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
848 return RunCachedStatementWithIds(SQL_FROM_HERE
, kSql
, response_ids
);
851 bool AppCacheDatabase::DeleteDeletableResponseIds(
852 const std::vector
<int64
>& response_ids
) {
854 "DELETE FROM DeletableResponseIds WHERE response_id = ?";
855 return RunCachedStatementWithIds(SQL_FROM_HERE
, kSql
, response_ids
);
858 bool AppCacheDatabase::RunCachedStatementWithIds(
859 const sql::StatementID
& statement_id
, const char* sql
,
860 const std::vector
<int64
>& ids
) {
865 sql::Transaction
transaction(db_
.get());
866 if (!transaction
.Begin())
869 sql::Statement
statement(db_
->GetCachedStatement(statement_id
, sql
));
871 std::vector
<int64
>::const_iterator iter
= ids
.begin();
872 while (iter
!= ids
.end()) {
873 statement
.BindInt64(0, *iter
);
874 if (!statement
.Run())
876 statement
.Reset(true);
880 return transaction
.Commit();
883 bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
884 const char* sql
, int64
* result
) {
886 sql::Statement
statement(db_
->GetUniqueStatement(sql
));
887 if (!statement
.Step()) {
890 *result
= statement
.ColumnInt64(0);
894 bool AppCacheDatabase::FindResponseIdsForCacheHelper(
895 int64 cache_id
, std::vector
<int64
>* ids_vector
,
896 std::set
<int64
>* ids_set
) {
897 DCHECK(ids_vector
|| ids_set
);
898 DCHECK(!(ids_vector
&& ids_set
));
899 if (!LazyOpen(false))
903 "SELECT response_id FROM Entries WHERE cache_id = ?";
905 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
907 statement
.BindInt64(0, cache_id
);
908 while (statement
.Step()) {
909 int64 id
= statement
.ColumnInt64(0);
913 ids_vector
->push_back(id
);
916 return statement
.Succeeded();
919 void AppCacheDatabase::ReadGroupRecord(
920 const sql::Statement
& statement
, GroupRecord
* record
) {
921 record
->group_id
= statement
.ColumnInt64(0);
922 record
->origin
= GURL(statement
.ColumnString(1));
923 record
->manifest_url
= GURL(statement
.ColumnString(2));
924 record
->creation_time
=
925 base::Time::FromInternalValue(statement
.ColumnInt64(3));
926 record
->last_access_time
=
927 base::Time::FromInternalValue(statement
.ColumnInt64(4));
930 void AppCacheDatabase::ReadCacheRecord(
931 const sql::Statement
& statement
, CacheRecord
* record
) {
932 record
->cache_id
= statement
.ColumnInt64(0);
933 record
->group_id
= statement
.ColumnInt64(1);
934 record
->online_wildcard
= statement
.ColumnBool(2);
935 record
->update_time
=
936 base::Time::FromInternalValue(statement
.ColumnInt64(3));
937 record
->cache_size
= statement
.ColumnInt64(4);
940 void AppCacheDatabase::ReadEntryRecord(
941 const sql::Statement
& statement
, EntryRecord
* record
) {
942 record
->cache_id
= statement
.ColumnInt64(0);
943 record
->url
= GURL(statement
.ColumnString(1));
944 record
->flags
= statement
.ColumnInt(2);
945 record
->response_id
= statement
.ColumnInt64(3);
946 record
->response_size
= statement
.ColumnInt64(4);
949 void AppCacheDatabase::ReadNamespaceRecords(
950 sql::Statement
* statement
,
951 NamespaceRecordVector
* intercepts
,
952 NamespaceRecordVector
* fallbacks
) {
953 while (statement
->Step()) {
954 AppCacheNamespaceType type
= static_cast<AppCacheNamespaceType
>(
955 statement
->ColumnInt(2));
956 NamespaceRecordVector
* records
=
957 (type
== APPCACHE_FALLBACK_NAMESPACE
) ? fallbacks
: intercepts
;
958 records
->push_back(NamespaceRecord());
959 ReadNamespaceRecord(statement
, &records
->back());
963 void AppCacheDatabase::ReadNamespaceRecord(
964 const sql::Statement
* statement
, NamespaceRecord
* record
) {
965 record
->cache_id
= statement
->ColumnInt64(0);
966 record
->origin
= GURL(statement
->ColumnString(1));
967 int type_with_executable_bit
= statement
->ColumnInt(2);
968 record
->namespace_
.namespace_url
= GURL(statement
->ColumnString(3));
969 record
->namespace_
.target_url
= GURL(statement
->ColumnString(4));
970 record
->namespace_
.is_pattern
= statement
->ColumnBool(5);
972 // Note: quick and dirty storage for the 'executable' bit w/o changing
973 // schemas, we use the high bit of 'type' field.
974 record
->namespace_
.type
= static_cast<AppCacheNamespaceType
>
975 (type_with_executable_bit
& 0x7ffffff);
976 record
->namespace_
.is_executable
=
977 (type_with_executable_bit
& 0x80000000) != 0;
978 DCHECK(!record
->namespace_
.is_executable
||
979 CommandLine::ForCurrentProcess()->HasSwitch(kEnableExecutableHandlers
));
982 void AppCacheDatabase::ReadOnlineWhiteListRecord(
983 const sql::Statement
& statement
, OnlineWhiteListRecord
* record
) {
984 record
->cache_id
= statement
.ColumnInt64(0);
985 record
->namespace_url
= GURL(statement
.ColumnString(1));
986 record
->is_pattern
= statement
.ColumnBool(2);
989 bool AppCacheDatabase::LazyOpen(bool create_if_needed
) {
993 // If we tried and failed once, don't try again in the same session
994 // to avoid creating an incoherent mess on disk.
998 // Avoid creating a database at all if we can.
999 bool use_in_memory_db
= db_file_path_
.empty();
1000 if (!create_if_needed
&&
1001 (use_in_memory_db
|| !base::PathExists(db_file_path_
))) {
1005 db_
.reset(new sql::Connection
);
1006 meta_table_
.reset(new sql::MetaTable
);
1008 db_
->set_histogram_tag("AppCache");
1010 bool opened
= false;
1011 if (use_in_memory_db
) {
1012 opened
= db_
->OpenInMemory();
1013 } else if (!base::CreateDirectory(db_file_path_
.DirName())) {
1014 LOG(ERROR
) << "Failed to create appcache directory.";
1016 opened
= db_
->Open(db_file_path_
);
1021 if (!opened
|| !db_
->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
1022 LOG(ERROR
) << "Failed to open the appcache database.";
1023 AppCacheHistograms::CountInitResult(
1024 AppCacheHistograms::SQL_DATABASE_ERROR
);
1026 // We're unable to open the database. This is a fatal error
1027 // which we can't recover from. We try to handle it by deleting
1028 // the existing appcache data and starting with a clean slate in
1029 // this browser session.
1030 if (!use_in_memory_db
&& DeleteExistingAndCreateNewDatabase())
1037 AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK
);
1038 was_corruption_detected_
= false;
1039 db_
->set_error_callback(
1040 base::Bind(&AppCacheDatabase::OnDatabaseError
, base::Unretained(this)));
1044 bool AppCacheDatabase::EnsureDatabaseVersion() {
1045 if (!sql::MetaTable::DoesTableExist(db_
.get()))
1046 return CreateSchema();
1048 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
1051 if (meta_table_
->GetCompatibleVersionNumber() > kCurrentVersion
) {
1052 LOG(WARNING
) << "AppCache database is too new.";
1056 std::string stored_flags
;
1057 meta_table_
->GetValue(kExperimentFlagsKey
, &stored_flags
);
1058 if (stored_flags
!= GetActiveExperimentFlags())
1061 if (meta_table_
->GetVersionNumber() < kCurrentVersion
)
1062 return UpgradeSchema();
1065 DCHECK(sql::MetaTable::DoesTableExist(db_
.get()));
1066 for (int i
= 0; i
< kTableCount
; ++i
) {
1067 DCHECK(db_
->DoesTableExist(kTables
[i
].table_name
));
1069 for (int i
= 0; i
< kIndexCount
; ++i
) {
1070 DCHECK(db_
->DoesIndexExist(kIndexes
[i
].index_name
));
1077 bool AppCacheDatabase::CreateSchema() {
1078 sql::Transaction
transaction(db_
.get());
1079 if (!transaction
.Begin())
1082 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
1085 if (!meta_table_
->SetValue(kExperimentFlagsKey
,
1086 GetActiveExperimentFlags())) {
1090 for (int i
= 0; i
< kTableCount
; ++i
) {
1091 if (!CreateTable(db_
.get(), kTables
[i
]))
1095 for (int i
= 0; i
< kIndexCount
; ++i
) {
1096 if (!CreateIndex(db_
.get(), kIndexes
[i
]))
1100 return transaction
.Commit();
1103 bool AppCacheDatabase::UpgradeSchema() {
1104 #if defined(APPCACHE_USE_SIMPLE_CACHE)
1105 return DeleteExistingAndCreateNewDatabase();
1107 if (meta_table_
->GetVersionNumber() == 3) {
1108 // version 3 was pre 12/17/2011
1109 DCHECK_EQ(strcmp(kNamespacesTable
, kTables
[3].table_name
), 0);
1110 DCHECK_EQ(strcmp(kNamespacesTable
, kIndexes
[6].table_name
), 0);
1111 DCHECK_EQ(strcmp(kNamespacesTable
, kIndexes
[7].table_name
), 0);
1112 DCHECK_EQ(strcmp(kNamespacesTable
, kIndexes
[8].table_name
), 0);
1114 const TableInfo kNamespaceTable_v4
= {
1116 "(cache_id INTEGER,"
1117 " origin TEXT," // intentionally not normalized
1119 " namespace_url TEXT,"
1123 // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
1124 // but without the is_pattern column added in v5.
1125 sql::Transaction
transaction(db_
.get());
1126 if (!transaction
.Begin() ||
1127 !CreateTable(db_
.get(), kNamespaceTable_v4
)) {
1131 // Move data from the old table to the new table, setting the
1132 // 'type' for all current records to the value for
1133 // APPCACHE_FALLBACK_NAMESPACE.
1134 DCHECK_EQ(0, static_cast<int>(APPCACHE_FALLBACK_NAMESPACE
));
1136 "INSERT INTO Namespaces"
1137 " SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
1138 " FROM FallbackNameSpaces")) {
1142 // Drop the old table, indexes on that table are also removed by this.
1143 if (!db_
->Execute("DROP TABLE FallbackNameSpaces"))
1146 // Create new indexes.
1147 if (!CreateIndex(db_
.get(), kIndexes
[6]) ||
1148 !CreateIndex(db_
.get(), kIndexes
[7]) ||
1149 !CreateIndex(db_
.get(), kIndexes
[8])) {
1153 meta_table_
->SetVersionNumber(4);
1154 meta_table_
->SetCompatibleVersionNumber(4);
1155 if (!transaction
.Commit())
1159 if (meta_table_
->GetVersionNumber() == 4) {
1160 // version 4 pre 3/30/2013
1161 // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
1162 DCHECK_EQ(strcmp(kNamespacesTable
, "Namespaces"), 0);
1163 sql::Transaction
transaction(db_
.get());
1164 if (!transaction
.Begin())
1167 "ALTER TABLE Namespaces ADD COLUMN"
1168 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1172 "ALTER TABLE OnlineWhitelists ADD COLUMN"
1173 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1176 meta_table_
->SetVersionNumber(5);
1177 meta_table_
->SetCompatibleVersionNumber(5);
1178 return transaction
.Commit();
1181 // If there is no upgrade path for the version on disk to the current
1182 // version, nuke everything and start over.
1183 return DeleteExistingAndCreateNewDatabase();
1187 void AppCacheDatabase::ResetConnectionAndTables() {
1188 meta_table_
.reset();
1192 bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
1193 DCHECK(!db_file_path_
.empty());
1194 DCHECK(base::PathExists(db_file_path_
));
1195 VLOG(1) << "Deleting existing appcache data and starting over.";
1197 ResetConnectionAndTables();
1199 // This also deletes the disk cache data.
1200 base::FilePath directory
= db_file_path_
.DirName();
1201 if (!base::DeleteFile(directory
, true))
1204 // Make sure the steps above actually deleted things.
1205 if (base::PathExists(directory
))
1208 if (!base::CreateDirectory(directory
))
1211 // So we can't go recursive.
1215 base::AutoReset
<bool> auto_reset(&is_recreating_
, true);
1216 return LazyOpen(true);
1219 void AppCacheDatabase::OnDatabaseError(int err
, sql::Statement
* stmt
) {
1220 was_corruption_detected_
|= sql::IsErrorCatastrophic(err
);
1221 if (!db_
->ShouldIgnoreSqliteError(err
))
1222 DLOG(ERROR
) << db_
->GetErrorMessage();
1223 // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
1226 } // namespace appcache