1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/appcache/appcache_database.h"
7 #include "base/auto_reset.h"
9 #include "base/command_line.h"
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "content/browser/appcache/appcache_entry.h"
14 #include "content/browser/appcache/appcache_histograms.h"
15 #include "sql/connection.h"
16 #include "sql/error_delegate_util.h"
17 #include "sql/meta_table.h"
18 #include "sql/statement.h"
19 #include "sql/transaction.h"
23 // Schema -------------------------------------------------------------------
26 #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(kTables
);
157 const int kIndexCount
= arraysize(kIndexes
);
159 bool CreateTable(sql::Connection
* db
, const TableInfo
& info
) {
160 std::string
sql("CREATE TABLE ");
161 sql
+= info
.table_name
;
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() {
213 CommitLazyLastAccessTimes();
216 void AppCacheDatabase::Disable() {
217 VLOG(1) << "Disabling appcache database.";
219 ResetConnectionAndTables();
222 int64
AppCacheDatabase::GetOriginUsage(const GURL
& origin
) {
223 std::vector
<CacheRecord
> records
;
224 if (!FindCachesForOrigin(origin
, &records
))
227 int64 origin_usage
= 0;
228 std::vector
<CacheRecord
>::const_iterator iter
= records
.begin();
229 while (iter
!= records
.end()) {
230 origin_usage
+= iter
->cache_size
;
236 bool AppCacheDatabase::GetAllOriginUsage(std::map
<GURL
, int64
>* usage_map
) {
237 std::set
<GURL
> origins
;
238 if (!FindOriginsWithGroups(&origins
))
240 for (std::set
<GURL
>::const_iterator origin
= origins
.begin();
241 origin
!= origins
.end(); ++origin
) {
242 (*usage_map
)[*origin
] = GetOriginUsage(*origin
);
247 bool AppCacheDatabase::FindOriginsWithGroups(std::set
<GURL
>* origins
) {
248 DCHECK(origins
&& origins
->empty());
249 if (!LazyOpen(false))
253 "SELECT DISTINCT(origin) FROM Groups";
255 sql::Statement
statement(db_
->GetUniqueStatement(kSql
));
257 while (statement
.Step())
258 origins
->insert(GURL(statement
.ColumnString(0)));
260 return statement
.Succeeded();
263 bool AppCacheDatabase::FindLastStorageIds(
264 int64
* last_group_id
, int64
* last_cache_id
, int64
* last_response_id
,
265 int64
* last_deletable_response_rowid
) {
266 DCHECK(last_group_id
&& last_cache_id
&& last_response_id
&&
267 last_deletable_response_rowid
);
271 *last_response_id
= 0;
272 *last_deletable_response_rowid
= 0;
274 if (!LazyOpen(false))
277 const char* kMaxGroupIdSql
= "SELECT MAX(group_id) FROM Groups";
278 const char* kMaxCacheIdSql
= "SELECT MAX(cache_id) FROM Caches";
279 const char* kMaxResponseIdFromEntriesSql
=
280 "SELECT MAX(response_id) FROM Entries";
281 const char* kMaxResponseIdFromDeletablesSql
=
282 "SELECT MAX(response_id) FROM DeletableResponseIds";
283 const char* kMaxDeletableResponseRowIdSql
=
284 "SELECT MAX(rowid) FROM DeletableResponseIds";
287 int64 max_response_id_from_entries
;
288 int64 max_response_id_from_deletables
;
289 int64 max_deletable_response_rowid
;
290 if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql
, &max_group_id
) ||
291 !RunUniqueStatementWithInt64Result(kMaxCacheIdSql
, &max_cache_id
) ||
292 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql
,
293 &max_response_id_from_entries
) ||
294 !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql
,
295 &max_response_id_from_deletables
) ||
296 !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql
,
297 &max_deletable_response_rowid
)) {
301 *last_group_id
= max_group_id
;
302 *last_cache_id
= max_cache_id
;
303 *last_response_id
= std::max(max_response_id_from_entries
,
304 max_response_id_from_deletables
);
305 *last_deletable_response_rowid
= max_deletable_response_rowid
;
309 bool AppCacheDatabase::FindGroup(int64 group_id
, GroupRecord
* record
) {
311 if (!LazyOpen(false))
315 "SELECT group_id, origin, manifest_url,"
316 " creation_time, last_access_time"
317 " FROM Groups WHERE group_id = ?";
319 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
321 statement
.BindInt64(0, group_id
);
322 if (!statement
.Step())
325 ReadGroupRecord(statement
, record
);
326 DCHECK(record
->group_id
== group_id
);
330 bool AppCacheDatabase::FindGroupForManifestUrl(
331 const GURL
& manifest_url
, GroupRecord
* record
) {
333 if (!LazyOpen(false))
337 "SELECT group_id, origin, manifest_url,"
338 " creation_time, last_access_time"
339 " FROM Groups WHERE manifest_url = ?";
341 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
342 statement
.BindString(0, manifest_url
.spec());
344 if (!statement
.Step())
347 ReadGroupRecord(statement
, record
);
348 DCHECK(record
->manifest_url
== manifest_url
);
352 bool AppCacheDatabase::FindGroupsForOrigin(
353 const GURL
& origin
, std::vector
<GroupRecord
>* records
) {
354 DCHECK(records
&& records
->empty());
355 if (!LazyOpen(false))
359 "SELECT group_id, origin, manifest_url,"
360 " creation_time, last_access_time"
361 " FROM Groups WHERE origin = ?";
363 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
364 statement
.BindString(0, origin
.spec());
366 while (statement
.Step()) {
367 records
->push_back(GroupRecord());
368 ReadGroupRecord(statement
, &records
->back());
369 DCHECK(records
->back().origin
== origin
);
372 return statement
.Succeeded();
375 bool AppCacheDatabase::FindGroupForCache(int64 cache_id
, GroupRecord
* record
) {
377 if (!LazyOpen(false))
381 "SELECT g.group_id, g.origin, g.manifest_url,"
382 " g.creation_time, g.last_access_time"
383 " FROM Groups g, Caches c"
384 " WHERE c.cache_id = ? AND c.group_id = g.group_id";
386 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
387 statement
.BindInt64(0, cache_id
);
389 if (!statement
.Step())
392 ReadGroupRecord(statement
, record
);
396 bool AppCacheDatabase::UpdateLastAccessTime(
397 int64 group_id
, base::Time time
) {
402 "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
404 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
405 statement
.BindInt64(0, time
.ToInternalValue());
406 statement
.BindInt64(1, group_id
);
408 return statement
.Run();
411 bool AppCacheDatabase::LazyUpdateLastAccessTime(
412 int64 group_id
, base::Time time
) {
415 lazy_last_access_times_
[group_id
] = time
;
419 bool AppCacheDatabase::CommitLazyLastAccessTimes() {
420 if (lazy_last_access_times_
.empty())
422 if (!LazyOpen(false))
425 sql::Transaction
transaction(db_
.get());
426 if (!transaction
.Begin())
428 for (const auto& pair
: lazy_last_access_times_
)
429 UpdateLastAccessTime(pair
.first
, pair
.second
);
430 lazy_last_access_times_
.clear();
431 return transaction
.Commit();
434 bool AppCacheDatabase::InsertGroup(const GroupRecord
* record
) {
440 " (group_id, origin, manifest_url, creation_time, last_access_time)"
441 " VALUES(?, ?, ?, ?, ?)";
443 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
444 statement
.BindInt64(0, record
->group_id
);
445 statement
.BindString(1, record
->origin
.spec());
446 statement
.BindString(2, record
->manifest_url
.spec());
447 statement
.BindInt64(3, record
->creation_time
.ToInternalValue());
448 statement
.BindInt64(4, record
->last_access_time
.ToInternalValue());
450 return statement
.Run();
453 bool AppCacheDatabase::DeleteGroup(int64 group_id
) {
454 if (!LazyOpen(false))
458 "DELETE FROM Groups WHERE group_id = ?";
460 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
461 statement
.BindInt64(0, group_id
);
463 return statement
.Run();
466 bool AppCacheDatabase::FindCache(int64 cache_id
, CacheRecord
* record
) {
468 if (!LazyOpen(false))
472 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
473 " FROM Caches WHERE cache_id = ?";
475 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
476 statement
.BindInt64(0, cache_id
);
478 if (!statement
.Step())
481 ReadCacheRecord(statement
, record
);
485 bool AppCacheDatabase::FindCacheForGroup(int64 group_id
, CacheRecord
* record
) {
487 if (!LazyOpen(false))
491 "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
492 " FROM Caches WHERE group_id = ?";
494 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
495 statement
.BindInt64(0, group_id
);
497 if (!statement
.Step())
500 ReadCacheRecord(statement
, record
);
504 bool AppCacheDatabase::FindCachesForOrigin(
505 const GURL
& origin
, std::vector
<CacheRecord
>* records
) {
507 std::vector
<GroupRecord
> group_records
;
508 if (!FindGroupsForOrigin(origin
, &group_records
))
511 CacheRecord cache_record
;
512 std::vector
<GroupRecord
>::const_iterator iter
= group_records
.begin();
513 while (iter
!= group_records
.end()) {
514 if (FindCacheForGroup(iter
->group_id
, &cache_record
))
515 records
->push_back(cache_record
);
521 bool AppCacheDatabase::InsertCache(const CacheRecord
* record
) {
526 "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
527 " update_time, cache_size)"
528 " VALUES(?, ?, ?, ?, ?)";
530 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
531 statement
.BindInt64(0, record
->cache_id
);
532 statement
.BindInt64(1, record
->group_id
);
533 statement
.BindBool(2, record
->online_wildcard
);
534 statement
.BindInt64(3, record
->update_time
.ToInternalValue());
535 statement
.BindInt64(4, record
->cache_size
);
537 return statement
.Run();
540 bool AppCacheDatabase::DeleteCache(int64 cache_id
) {
541 if (!LazyOpen(false))
545 "DELETE FROM Caches WHERE cache_id = ?";
547 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
548 statement
.BindInt64(0, cache_id
);
550 return statement
.Run();
553 bool AppCacheDatabase::FindEntriesForCache(
554 int64 cache_id
, std::vector
<EntryRecord
>* records
) {
555 DCHECK(records
&& records
->empty());
556 if (!LazyOpen(false))
560 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
561 " WHERE cache_id = ?";
563 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
564 statement
.BindInt64(0, cache_id
);
566 while (statement
.Step()) {
567 records
->push_back(EntryRecord());
568 ReadEntryRecord(statement
, &records
->back());
569 DCHECK(records
->back().cache_id
== cache_id
);
572 return statement
.Succeeded();
575 bool AppCacheDatabase::FindEntriesForUrl(
576 const GURL
& url
, std::vector
<EntryRecord
>* records
) {
577 DCHECK(records
&& records
->empty());
578 if (!LazyOpen(false))
582 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
585 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
586 statement
.BindString(0, url
.spec());
588 while (statement
.Step()) {
589 records
->push_back(EntryRecord());
590 ReadEntryRecord(statement
, &records
->back());
591 DCHECK(records
->back().url
== url
);
594 return statement
.Succeeded();
597 bool AppCacheDatabase::FindEntry(
598 int64 cache_id
, const GURL
& url
, EntryRecord
* record
) {
600 if (!LazyOpen(false))
604 "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
605 " WHERE cache_id = ? AND url = ?";
607 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
608 statement
.BindInt64(0, cache_id
);
609 statement
.BindString(1, url
.spec());
611 if (!statement
.Step())
614 ReadEntryRecord(statement
, record
);
615 DCHECK(record
->cache_id
== cache_id
);
616 DCHECK(record
->url
== url
);
620 bool AppCacheDatabase::InsertEntry(const EntryRecord
* record
) {
625 "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
626 " VALUES(?, ?, ?, ?, ?)";
628 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
629 statement
.BindInt64(0, record
->cache_id
);
630 statement
.BindString(1, record
->url
.spec());
631 statement
.BindInt(2, record
->flags
);
632 statement
.BindInt64(3, record
->response_id
);
633 statement
.BindInt64(4, record
->response_size
);
635 return statement
.Run();
638 bool AppCacheDatabase::InsertEntryRecords(
639 const std::vector
<EntryRecord
>& records
) {
642 sql::Transaction
transaction(db_
.get());
643 if (!transaction
.Begin())
645 std::vector
<EntryRecord
>::const_iterator iter
= records
.begin();
646 while (iter
!= records
.end()) {
647 if (!InsertEntry(&(*iter
)))
651 return transaction
.Commit();
654 bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id
) {
655 if (!LazyOpen(false))
659 "DELETE FROM Entries WHERE cache_id = ?";
661 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
662 statement
.BindInt64(0, cache_id
);
664 return statement
.Run();
667 bool AppCacheDatabase::AddEntryFlags(
668 const GURL
& entry_url
, int64 cache_id
, int additional_flags
) {
669 if (!LazyOpen(false))
673 "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
675 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
676 statement
.BindInt(0, additional_flags
);
677 statement
.BindInt64(1, cache_id
);
678 statement
.BindString(2, entry_url
.spec());
680 return statement
.Run() && db_
->GetLastChangeCount();
683 bool AppCacheDatabase::FindNamespacesForOrigin(
685 std::vector
<NamespaceRecord
>* intercepts
,
686 std::vector
<NamespaceRecord
>* fallbacks
) {
687 DCHECK(intercepts
&& intercepts
->empty());
688 DCHECK(fallbacks
&& fallbacks
->empty());
689 if (!LazyOpen(false))
693 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
694 " FROM Namespaces WHERE origin = ?";
696 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
697 statement
.BindString(0, origin
.spec());
699 ReadNamespaceRecords(&statement
, intercepts
, fallbacks
);
701 return statement
.Succeeded();
704 bool AppCacheDatabase::FindNamespacesForCache(
706 std::vector
<NamespaceRecord
>* intercepts
,
707 std::vector
<NamespaceRecord
>* fallbacks
) {
708 DCHECK(intercepts
&& intercepts
->empty());
709 DCHECK(fallbacks
&& fallbacks
->empty());
710 if (!LazyOpen(false))
714 "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
715 " FROM Namespaces WHERE cache_id = ?";
717 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
718 statement
.BindInt64(0, cache_id
);
720 ReadNamespaceRecords(&statement
, intercepts
, fallbacks
);
722 return statement
.Succeeded();
725 bool AppCacheDatabase::InsertNamespace(
726 const NamespaceRecord
* record
) {
731 "INSERT INTO Namespaces"
732 " (cache_id, origin, type, namespace_url, target_url, is_pattern)"
733 " VALUES (?, ?, ?, ?, ?, ?)";
735 // Note: quick and dirty storage for the 'executable' bit w/o changing
736 // schemas, we use the high bit of 'type' field.
737 int type_with_executable_bit
= record
->namespace_
.type
;
738 if (record
->namespace_
.is_executable
) {
739 type_with_executable_bit
|= 0x8000000;
740 DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
741 kEnableExecutableHandlers
));
744 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
745 statement
.BindInt64(0, record
->cache_id
);
746 statement
.BindString(1, record
->origin
.spec());
747 statement
.BindInt(2, type_with_executable_bit
);
748 statement
.BindString(3, record
->namespace_
.namespace_url
.spec());
749 statement
.BindString(4, record
->namespace_
.target_url
.spec());
750 statement
.BindBool(5, record
->namespace_
.is_pattern
);
751 return statement
.Run();
754 bool AppCacheDatabase::InsertNamespaceRecords(
755 const std::vector
<NamespaceRecord
>& records
) {
758 sql::Transaction
transaction(db_
.get());
759 if (!transaction
.Begin())
761 std::vector
<NamespaceRecord
>::const_iterator iter
= records
.begin();
762 while (iter
!= records
.end()) {
763 if (!InsertNamespace(&(*iter
)))
767 return transaction
.Commit();
770 bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id
) {
771 if (!LazyOpen(false))
775 "DELETE FROM Namespaces WHERE cache_id = ?";
777 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
778 statement
.BindInt64(0, cache_id
);
780 return statement
.Run();
783 bool AppCacheDatabase::FindOnlineWhiteListForCache(
784 int64 cache_id
, std::vector
<OnlineWhiteListRecord
>* records
) {
785 DCHECK(records
&& records
->empty());
786 if (!LazyOpen(false))
790 "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
791 " WHERE cache_id = ?";
793 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
794 statement
.BindInt64(0, cache_id
);
796 while (statement
.Step()) {
797 records
->push_back(OnlineWhiteListRecord());
798 this->ReadOnlineWhiteListRecord(statement
, &records
->back());
799 DCHECK(records
->back().cache_id
== cache_id
);
801 return statement
.Succeeded();
804 bool AppCacheDatabase::InsertOnlineWhiteList(
805 const OnlineWhiteListRecord
* record
) {
810 "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
813 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
814 statement
.BindInt64(0, record
->cache_id
);
815 statement
.BindString(1, record
->namespace_url
.spec());
816 statement
.BindBool(2, record
->is_pattern
);
818 return statement
.Run();
821 bool AppCacheDatabase::InsertOnlineWhiteListRecords(
822 const std::vector
<OnlineWhiteListRecord
>& records
) {
825 sql::Transaction
transaction(db_
.get());
826 if (!transaction
.Begin())
828 std::vector
<OnlineWhiteListRecord
>::const_iterator iter
= records
.begin();
829 while (iter
!= records
.end()) {
830 if (!InsertOnlineWhiteList(&(*iter
)))
834 return transaction
.Commit();
837 bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id
) {
838 if (!LazyOpen(false))
842 "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
844 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
845 statement
.BindInt64(0, cache_id
);
847 return statement
.Run();
850 bool AppCacheDatabase::GetDeletableResponseIds(
851 std::vector
<int64
>* response_ids
, int64 max_rowid
, int limit
) {
852 if (!LazyOpen(false))
856 "SELECT response_id FROM DeletableResponseIds "
860 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
861 statement
.BindInt64(0, max_rowid
);
862 statement
.BindInt64(1, limit
);
864 while (statement
.Step())
865 response_ids
->push_back(statement
.ColumnInt64(0));
866 return statement
.Succeeded();
869 bool AppCacheDatabase::InsertDeletableResponseIds(
870 const std::vector
<int64
>& response_ids
) {
872 "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
873 return RunCachedStatementWithIds(SQL_FROM_HERE
, kSql
, response_ids
);
876 bool AppCacheDatabase::DeleteDeletableResponseIds(
877 const std::vector
<int64
>& response_ids
) {
879 "DELETE FROM DeletableResponseIds WHERE response_id = ?";
880 return RunCachedStatementWithIds(SQL_FROM_HERE
, kSql
, response_ids
);
883 bool AppCacheDatabase::RunCachedStatementWithIds(
884 const sql::StatementID
& statement_id
, const char* sql
,
885 const std::vector
<int64
>& ids
) {
890 sql::Transaction
transaction(db_
.get());
891 if (!transaction
.Begin())
894 sql::Statement
statement(db_
->GetCachedStatement(statement_id
, sql
));
896 std::vector
<int64
>::const_iterator iter
= ids
.begin();
897 while (iter
!= ids
.end()) {
898 statement
.BindInt64(0, *iter
);
899 if (!statement
.Run())
901 statement
.Reset(true);
905 return transaction
.Commit();
908 bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
909 const char* sql
, int64
* result
) {
911 sql::Statement
statement(db_
->GetUniqueStatement(sql
));
912 if (!statement
.Step()) {
915 *result
= statement
.ColumnInt64(0);
919 bool AppCacheDatabase::FindResponseIdsForCacheHelper(
920 int64 cache_id
, std::vector
<int64
>* ids_vector
,
921 std::set
<int64
>* ids_set
) {
922 DCHECK(ids_vector
|| ids_set
);
923 DCHECK(!(ids_vector
&& ids_set
));
924 if (!LazyOpen(false))
928 "SELECT response_id FROM Entries WHERE cache_id = ?";
930 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
932 statement
.BindInt64(0, cache_id
);
933 while (statement
.Step()) {
934 int64 id
= statement
.ColumnInt64(0);
938 ids_vector
->push_back(id
);
941 return statement
.Succeeded();
944 void AppCacheDatabase::ReadGroupRecord(
945 const sql::Statement
& statement
, GroupRecord
* record
) {
946 record
->group_id
= statement
.ColumnInt64(0);
947 record
->origin
= GURL(statement
.ColumnString(1));
948 record
->manifest_url
= GURL(statement
.ColumnString(2));
949 record
->creation_time
=
950 base::Time::FromInternalValue(statement
.ColumnInt64(3));
952 const auto found
= lazy_last_access_times_
.find(record
->group_id
);
953 if (found
!= lazy_last_access_times_
.end()) {
954 record
->last_access_time
= found
->second
;
956 record
->last_access_time
=
957 base::Time::FromInternalValue(statement
.ColumnInt64(4));
961 void AppCacheDatabase::ReadCacheRecord(
962 const sql::Statement
& statement
, CacheRecord
* record
) {
963 record
->cache_id
= statement
.ColumnInt64(0);
964 record
->group_id
= statement
.ColumnInt64(1);
965 record
->online_wildcard
= statement
.ColumnBool(2);
966 record
->update_time
=
967 base::Time::FromInternalValue(statement
.ColumnInt64(3));
968 record
->cache_size
= statement
.ColumnInt64(4);
971 void AppCacheDatabase::ReadEntryRecord(
972 const sql::Statement
& statement
, EntryRecord
* record
) {
973 record
->cache_id
= statement
.ColumnInt64(0);
974 record
->url
= GURL(statement
.ColumnString(1));
975 record
->flags
= statement
.ColumnInt(2);
976 record
->response_id
= statement
.ColumnInt64(3);
977 record
->response_size
= statement
.ColumnInt64(4);
980 void AppCacheDatabase::ReadNamespaceRecords(
981 sql::Statement
* statement
,
982 NamespaceRecordVector
* intercepts
,
983 NamespaceRecordVector
* fallbacks
) {
984 while (statement
->Step()) {
985 AppCacheNamespaceType type
= static_cast<AppCacheNamespaceType
>(
986 statement
->ColumnInt(2));
987 NamespaceRecordVector
* records
=
988 (type
== APPCACHE_FALLBACK_NAMESPACE
) ? fallbacks
: intercepts
;
989 records
->push_back(NamespaceRecord());
990 ReadNamespaceRecord(statement
, &records
->back());
994 void AppCacheDatabase::ReadNamespaceRecord(
995 const sql::Statement
* statement
, NamespaceRecord
* record
) {
996 record
->cache_id
= statement
->ColumnInt64(0);
997 record
->origin
= GURL(statement
->ColumnString(1));
998 int type_with_executable_bit
= statement
->ColumnInt(2);
999 record
->namespace_
.namespace_url
= GURL(statement
->ColumnString(3));
1000 record
->namespace_
.target_url
= GURL(statement
->ColumnString(4));
1001 record
->namespace_
.is_pattern
= statement
->ColumnBool(5);
1003 // Note: quick and dirty storage for the 'executable' bit w/o changing
1004 // schemas, we use the high bit of 'type' field.
1005 record
->namespace_
.type
= static_cast<AppCacheNamespaceType
>
1006 (type_with_executable_bit
& 0x7ffffff);
1007 record
->namespace_
.is_executable
=
1008 (type_with_executable_bit
& 0x80000000) != 0;
1009 DCHECK(!record
->namespace_
.is_executable
||
1010 base::CommandLine::ForCurrentProcess()->HasSwitch(
1011 kEnableExecutableHandlers
));
1014 void AppCacheDatabase::ReadOnlineWhiteListRecord(
1015 const sql::Statement
& statement
, OnlineWhiteListRecord
* record
) {
1016 record
->cache_id
= statement
.ColumnInt64(0);
1017 record
->namespace_url
= GURL(statement
.ColumnString(1));
1018 record
->is_pattern
= statement
.ColumnBool(2);
1021 bool AppCacheDatabase::LazyOpen(bool create_if_needed
) {
1025 // If we tried and failed once, don't try again in the same session
1026 // to avoid creating an incoherent mess on disk.
1030 // Avoid creating a database at all if we can.
1031 bool use_in_memory_db
= db_file_path_
.empty();
1032 if (!create_if_needed
&&
1033 (use_in_memory_db
|| !base::PathExists(db_file_path_
))) {
1037 db_
.reset(new sql::Connection
);
1038 meta_table_
.reset(new sql::MetaTable
);
1040 db_
->set_histogram_tag("AppCache");
1042 bool opened
= false;
1043 if (use_in_memory_db
) {
1044 opened
= db_
->OpenInMemory();
1045 } else if (!base::CreateDirectory(db_file_path_
.DirName())) {
1046 LOG(ERROR
) << "Failed to create appcache directory.";
1048 opened
= db_
->Open(db_file_path_
);
1053 if (!opened
|| !db_
->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
1054 LOG(ERROR
) << "Failed to open the appcache database.";
1055 AppCacheHistograms::CountInitResult(
1056 AppCacheHistograms::SQL_DATABASE_ERROR
);
1058 // We're unable to open the database. This is a fatal error
1059 // which we can't recover from. We try to handle it by deleting
1060 // the existing appcache data and starting with a clean slate in
1061 // this browser session.
1062 if (!use_in_memory_db
&& DeleteExistingAndCreateNewDatabase())
1069 AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK
);
1070 was_corruption_detected_
= false;
1071 db_
->set_error_callback(
1072 base::Bind(&AppCacheDatabase::OnDatabaseError
, base::Unretained(this)));
1076 bool AppCacheDatabase::EnsureDatabaseVersion() {
1077 if (!sql::MetaTable::DoesTableExist(db_
.get()))
1078 return CreateSchema();
1080 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
1083 if (meta_table_
->GetCompatibleVersionNumber() > kCurrentVersion
) {
1084 LOG(WARNING
) << "AppCache database is too new.";
1088 std::string stored_flags
;
1089 meta_table_
->GetValue(kExperimentFlagsKey
, &stored_flags
);
1090 if (stored_flags
!= GetActiveExperimentFlags())
1093 if (meta_table_
->GetVersionNumber() < kCurrentVersion
)
1094 return UpgradeSchema();
1097 DCHECK(sql::MetaTable::DoesTableExist(db_
.get()));
1098 for (int i
= 0; i
< kTableCount
; ++i
) {
1099 DCHECK(db_
->DoesTableExist(kTables
[i
].table_name
));
1101 for (int i
= 0; i
< kIndexCount
; ++i
) {
1102 DCHECK(db_
->DoesIndexExist(kIndexes
[i
].index_name
));
1109 bool AppCacheDatabase::CreateSchema() {
1110 sql::Transaction
transaction(db_
.get());
1111 if (!transaction
.Begin())
1114 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
1117 if (!meta_table_
->SetValue(kExperimentFlagsKey
,
1118 GetActiveExperimentFlags())) {
1122 for (int i
= 0; i
< kTableCount
; ++i
) {
1123 if (!CreateTable(db_
.get(), kTables
[i
]))
1127 for (int i
= 0; i
< kIndexCount
; ++i
) {
1128 if (!CreateIndex(db_
.get(), kIndexes
[i
]))
1132 return transaction
.Commit();
1135 bool AppCacheDatabase::UpgradeSchema() {
1136 #if defined(APPCACHE_USE_SIMPLE_CACHE)
1137 return DeleteExistingAndCreateNewDatabase();
1139 if (meta_table_
->GetVersionNumber() == 3) {
1140 // version 3 was pre 12/17/2011
1141 DCHECK_EQ(strcmp(kNamespacesTable
, kTables
[3].table_name
), 0);
1142 DCHECK_EQ(strcmp(kNamespacesTable
, kIndexes
[6].table_name
), 0);
1143 DCHECK_EQ(strcmp(kNamespacesTable
, kIndexes
[7].table_name
), 0);
1144 DCHECK_EQ(strcmp(kNamespacesTable
, kIndexes
[8].table_name
), 0);
1146 const TableInfo kNamespaceTable_v4
= {
1148 "(cache_id INTEGER,"
1149 " origin TEXT," // intentionally not normalized
1151 " namespace_url TEXT,"
1155 // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
1156 // but without the is_pattern column added in v5.
1157 sql::Transaction
transaction(db_
.get());
1158 if (!transaction
.Begin() ||
1159 !CreateTable(db_
.get(), kNamespaceTable_v4
)) {
1163 // Move data from the old table to the new table, setting the
1164 // 'type' for all current records to the value for
1165 // APPCACHE_FALLBACK_NAMESPACE.
1166 DCHECK_EQ(0, static_cast<int>(APPCACHE_FALLBACK_NAMESPACE
));
1168 "INSERT INTO Namespaces"
1169 " SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
1170 " FROM FallbackNameSpaces")) {
1174 // Drop the old table, indexes on that table are also removed by this.
1175 if (!db_
->Execute("DROP TABLE FallbackNameSpaces"))
1178 // Create new indexes.
1179 if (!CreateIndex(db_
.get(), kIndexes
[6]) ||
1180 !CreateIndex(db_
.get(), kIndexes
[7]) ||
1181 !CreateIndex(db_
.get(), kIndexes
[8])) {
1185 meta_table_
->SetVersionNumber(4);
1186 meta_table_
->SetCompatibleVersionNumber(4);
1187 if (!transaction
.Commit())
1191 if (meta_table_
->GetVersionNumber() == 4) {
1192 // version 4 pre 3/30/2013
1193 // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
1194 DCHECK_EQ(strcmp(kNamespacesTable
, "Namespaces"), 0);
1195 sql::Transaction
transaction(db_
.get());
1196 if (!transaction
.Begin())
1199 "ALTER TABLE Namespaces ADD COLUMN"
1200 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1204 "ALTER TABLE OnlineWhitelists ADD COLUMN"
1205 " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1208 meta_table_
->SetVersionNumber(5);
1209 meta_table_
->SetCompatibleVersionNumber(5);
1210 return transaction
.Commit();
1213 // If there is no upgrade path for the version on disk to the current
1214 // version, nuke everything and start over.
1215 return DeleteExistingAndCreateNewDatabase();
1219 void AppCacheDatabase::ResetConnectionAndTables() {
1220 meta_table_
.reset();
1224 bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
1225 DCHECK(!db_file_path_
.empty());
1226 DCHECK(base::PathExists(db_file_path_
));
1227 VLOG(1) << "Deleting existing appcache data and starting over.";
1229 ResetConnectionAndTables();
1231 // This also deletes the disk cache data.
1232 base::FilePath directory
= db_file_path_
.DirName();
1233 if (!base::DeleteFile(directory
, true))
1236 // Make sure the steps above actually deleted things.
1237 if (base::PathExists(directory
))
1240 if (!base::CreateDirectory(directory
))
1243 // So we can't go recursive.
1247 base::AutoReset
<bool> auto_reset(&is_recreating_
, true);
1248 return LazyOpen(true);
1251 void AppCacheDatabase::OnDatabaseError(int err
, sql::Statement
* stmt
) {
1252 was_corruption_detected_
|= sql::IsErrorCatastrophic(err
);
1253 if (!db_
->ShouldIgnoreSqliteError(err
))
1254 DLOG(ERROR
) << db_
->GetErrorMessage();
1255 // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
1258 } // namespace content