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