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