1 // Copyright 2013 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 "storage/browser/quota/quota_database.h"
9 #include "base/auto_reset.h"
10 #include "base/bind.h"
11 #include "base/files/file_util.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "sql/connection.h"
14 #include "sql/meta_table.h"
15 #include "sql/statement.h"
16 #include "sql/transaction.h"
17 #include "storage/browser/quota/special_storage_policy.h"
22 // Definitions for database schema.
24 const int kCurrentVersion
= 4;
25 const int kCompatibleVersion
= 2;
27 const char kHostQuotaTable
[] = "HostQuotaTable";
28 const char kOriginInfoTable
[] = "OriginInfoTable";
29 const char kIsOriginTableBootstrapped
[] = "IsOriginTableBootstrapped";
31 bool VerifyValidQuotaConfig(const char* key
) {
32 return (key
!= NULL
&&
33 (!strcmp(key
, QuotaDatabase::kDesiredAvailableSpaceKey
) ||
34 !strcmp(key
, QuotaDatabase::kTemporaryQuotaOverrideKey
)));
37 const int kCommitIntervalMs
= 30000;
40 // This enum is logged to UMA so only append to it - don't change
41 // the meaning of the existing values.
45 NON_GOOGLE_DURABLE
= 3,
46 GOOGLE_UNLIMITED_EXTENSION
= 4,
47 NON_GOOGLE_UNLIMITED_EXTENSION
= 5,
53 void HistogramOriginType(const OriginType
& entry
) {
54 UMA_HISTOGRAM_ENUMERATION("Quota.LRUOriginTypes", entry
, MAX_ORIGIN_TYPE
);
57 } // anonymous namespace
60 const char QuotaDatabase::kDesiredAvailableSpaceKey
[] = "DesiredAvailableSpace";
61 const char QuotaDatabase::kTemporaryQuotaOverrideKey
[] =
62 "TemporaryQuotaOverride";
64 const QuotaDatabase::TableSchema
QuotaDatabase::kTables
[] = {
66 "(host TEXT NOT NULL,"
67 " type INTEGER NOT NULL,"
68 " quota INTEGER DEFAULT 0,"
69 " UNIQUE(host, type))" },
71 "(origin TEXT NOT NULL,"
72 " type INTEGER NOT NULL,"
73 " used_count INTEGER DEFAULT 0,"
74 " last_access_time INTEGER DEFAULT 0,"
75 " last_modified_time INTEGER DEFAULT 0,"
76 " UNIQUE(origin, type))" },
80 const QuotaDatabase::IndexSchema
QuotaDatabase::kIndexes
[] = {
89 { "OriginLastAccessTimeIndex",
93 { "OriginLastModifiedTimeIndex",
95 "(last_modified_time)",
99 struct QuotaDatabase::QuotaTableImporter
{
100 bool Append(const QuotaTableEntry
& entry
) {
101 entries
.push_back(entry
);
104 std::vector
<QuotaTableEntry
> entries
;
107 // Clang requires explicit out-of-line constructors for them.
108 QuotaDatabase::QuotaTableEntry::QuotaTableEntry()
109 : type(kStorageTypeUnknown
),
113 QuotaDatabase::QuotaTableEntry::QuotaTableEntry(
114 const std::string
& host
,
122 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry()
123 : type(kStorageTypeUnknown
),
127 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry(
131 const base::Time
& last_access_time
,
132 const base::Time
& last_modified_time
)
135 used_count(used_count
),
136 last_access_time(last_access_time
),
137 last_modified_time(last_modified_time
) {
140 // QuotaDatabase ------------------------------------------------------------
141 QuotaDatabase::QuotaDatabase(const base::FilePath
& path
)
142 : db_file_path_(path
),
143 is_recreating_(false),
144 is_disabled_(false) {
147 QuotaDatabase::~QuotaDatabase() {
149 db_
->CommitTransaction();
153 void QuotaDatabase::CloseConnection() {
158 bool QuotaDatabase::GetHostQuota(
159 const std::string
& host
, StorageType type
, int64
* quota
) {
161 if (!LazyOpen(false))
166 " FROM HostQuotaTable"
167 " WHERE host = ? AND type = ?";
169 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
170 statement
.BindString(0, host
);
171 statement
.BindInt(1, static_cast<int>(type
));
173 if (!statement
.Step())
176 *quota
= statement
.ColumnInt64(0);
180 bool QuotaDatabase::SetHostQuota(
181 const std::string
& host
, StorageType type
, int64 quota
) {
187 "INSERT OR REPLACE INTO HostQuotaTable"
188 " (quota, host, type)"
190 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
191 statement
.BindInt64(0, quota
);
192 statement
.BindString(1, host
);
193 statement
.BindInt(2, static_cast<int>(type
));
195 if (!statement
.Run())
202 bool QuotaDatabase::SetOriginLastAccessTime(
203 const GURL
& origin
, StorageType type
, base::Time last_access_time
) {
207 sql::Statement statement
;
210 if (FindOriginUsedCount(origin
, type
, &used_count
)) {
213 "UPDATE OriginInfoTable"
214 " SET used_count = ?, last_access_time = ?"
215 " WHERE origin = ? AND type = ?";
216 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
219 "INSERT INTO OriginInfoTable"
220 " (used_count, last_access_time, origin, type)"
221 " VALUES (?, ?, ?, ?)";
222 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
224 statement
.BindInt(0, used_count
);
225 statement
.BindInt64(1, last_access_time
.ToInternalValue());
226 statement
.BindString(2, origin
.spec());
227 statement
.BindInt(3, static_cast<int>(type
));
229 if (!statement
.Run())
236 bool QuotaDatabase::SetOriginLastModifiedTime(
237 const GURL
& origin
, StorageType type
, base::Time last_modified_time
) {
241 sql::Statement statement
;
244 if (FindOriginUsedCount(origin
, type
, &dummy
)) {
246 "UPDATE OriginInfoTable"
247 " SET last_modified_time = ?"
248 " WHERE origin = ? AND type = ?";
249 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
252 "INSERT INTO OriginInfoTable"
253 " (last_modified_time, origin, type) VALUES (?, ?, ?)";
254 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
256 statement
.BindInt64(0, last_modified_time
.ToInternalValue());
257 statement
.BindString(1, origin
.spec());
258 statement
.BindInt(2, static_cast<int>(type
));
260 if (!statement
.Run())
267 bool QuotaDatabase::RegisterInitialOriginInfo(
268 const std::set
<GURL
>& origins
, StorageType type
) {
272 typedef std::set
<GURL
>::const_iterator itr_type
;
273 for (itr_type itr
= origins
.begin(), end
= origins
.end();
276 "INSERT OR IGNORE INTO OriginInfoTable"
277 " (origin, type) VALUES (?, ?)";
278 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
279 statement
.BindString(0, itr
->spec());
280 statement
.BindInt(1, static_cast<int>(type
));
282 if (!statement
.Run())
290 bool QuotaDatabase::DeleteHostQuota(
291 const std::string
& host
, StorageType type
) {
292 if (!LazyOpen(false))
296 "DELETE FROM HostQuotaTable"
297 " WHERE host = ? AND type = ?";
299 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
300 statement
.BindString(0, host
);
301 statement
.BindInt(1, static_cast<int>(type
));
303 if (!statement
.Run())
310 bool QuotaDatabase::DeleteOriginInfo(
311 const GURL
& origin
, StorageType type
) {
312 if (!LazyOpen(false))
316 "DELETE FROM OriginInfoTable"
317 " WHERE origin = ? AND type = ?";
319 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
320 statement
.BindString(0, origin
.spec());
321 statement
.BindInt(1, static_cast<int>(type
));
323 if (!statement
.Run())
330 bool QuotaDatabase::GetQuotaConfigValue(const char* key
, int64
* value
) {
331 if (!LazyOpen(false))
333 DCHECK(VerifyValidQuotaConfig(key
));
334 return meta_table_
->GetValue(key
, value
);
337 bool QuotaDatabase::SetQuotaConfigValue(const char* key
, int64 value
) {
340 DCHECK(VerifyValidQuotaConfig(key
));
341 return meta_table_
->SetValue(key
, value
);
344 bool QuotaDatabase::GetLRUOrigin(
346 const std::set
<GURL
>& exceptions
,
347 SpecialStoragePolicy
* special_storage_policy
,
350 if (!LazyOpen(false))
353 const char* kSql
= "SELECT origin FROM OriginInfoTable"
355 " ORDER BY last_access_time ASC";
357 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
358 statement
.BindInt(0, static_cast<int>(type
));
360 while (statement
.Step()) {
361 GURL
url(statement
.ColumnString(0));
362 if (exceptions
.find(url
) != exceptions
.end()) {
363 HistogramOriginType(IN_USE
);
366 if (special_storage_policy
) {
367 bool is_google
= url
.DomainIs("google.com");
368 if (special_storage_policy
->IsStorageDurable(url
)) {
369 HistogramOriginType(is_google
? GOOGLE_DURABLE
: NON_GOOGLE_DURABLE
);
372 if (special_storage_policy
->IsStorageUnlimited(url
)) {
373 HistogramOriginType(is_google
? GOOGLE_UNLIMITED_EXTENSION
374 : NON_GOOGLE_UNLIMITED_EXTENSION
);
378 HistogramOriginType(OTHER
);
383 HistogramOriginType(NONE
);
385 return statement
.Succeeded();
388 bool QuotaDatabase::GetOriginsModifiedSince(
389 StorageType type
, std::set
<GURL
>* origins
, base::Time modified_since
) {
391 if (!LazyOpen(false))
394 const char* kSql
= "SELECT origin FROM OriginInfoTable"
395 " WHERE type = ? AND last_modified_time >= ?";
397 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
398 statement
.BindInt(0, static_cast<int>(type
));
399 statement
.BindInt64(1, modified_since
.ToInternalValue());
402 while (statement
.Step())
403 origins
->insert(GURL(statement
.ColumnString(0)));
405 return statement
.Succeeded();
408 bool QuotaDatabase::IsOriginDatabaseBootstrapped() {
413 return meta_table_
->GetValue(kIsOriginTableBootstrapped
, &flag
) && flag
;
416 bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag
) {
420 return meta_table_
->SetValue(kIsOriginTableBootstrapped
, bootstrap_flag
);
423 void QuotaDatabase::Commit() {
427 if (timer_
.IsRunning())
430 db_
->CommitTransaction();
431 db_
->BeginTransaction();
434 void QuotaDatabase::ScheduleCommit() {
435 if (timer_
.IsRunning())
437 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(kCommitIntervalMs
),
438 this, &QuotaDatabase::Commit
);
441 bool QuotaDatabase::FindOriginUsedCount(
442 const GURL
& origin
, StorageType type
, int* used_count
) {
444 if (!LazyOpen(false))
448 "SELECT used_count FROM OriginInfoTable"
449 " WHERE origin = ? AND type = ?";
451 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
452 statement
.BindString(0, origin
.spec());
453 statement
.BindInt(1, static_cast<int>(type
));
455 if (!statement
.Step())
458 *used_count
= statement
.ColumnInt(0);
462 bool QuotaDatabase::LazyOpen(bool create_if_needed
) {
466 // If we tried and failed once, don't try again in the same session
467 // to avoid creating an incoherent mess on disk.
471 bool in_memory_only
= db_file_path_
.empty();
472 if (!create_if_needed
&&
473 (in_memory_only
|| !base::PathExists(db_file_path_
))) {
477 db_
.reset(new sql::Connection
);
478 meta_table_
.reset(new sql::MetaTable
);
480 db_
->set_histogram_tag("Quota");
483 if (in_memory_only
) {
484 opened
= db_
->OpenInMemory();
485 } else if (!base::CreateDirectory(db_file_path_
.DirName())) {
486 LOG(ERROR
) << "Failed to create quota database directory.";
488 opened
= db_
->Open(db_file_path_
);
493 if (!opened
|| !EnsureDatabaseVersion()) {
494 LOG(ERROR
) << "Could not open the quota database, resetting.";
495 if (!ResetSchema()) {
496 LOG(ERROR
) << "Failed to reset the quota database.";
504 // Start a long-running transaction.
505 db_
->BeginTransaction();
510 bool QuotaDatabase::EnsureDatabaseVersion() {
511 static const size_t kTableCount
= arraysize(kTables
);
512 static const size_t kIndexCount
= arraysize(kIndexes
);
513 if (!sql::MetaTable::DoesTableExist(db_
.get()))
514 return CreateSchema(db_
.get(), meta_table_
.get(),
515 kCurrentVersion
, kCompatibleVersion
,
516 kTables
, kTableCount
,
517 kIndexes
, kIndexCount
);
519 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
522 if (meta_table_
->GetCompatibleVersionNumber() > kCurrentVersion
) {
523 LOG(WARNING
) << "Quota database is too new.";
527 if (meta_table_
->GetVersionNumber() < kCurrentVersion
) {
528 if (!UpgradeSchema(meta_table_
->GetVersionNumber()))
529 return ResetSchema();
533 DCHECK(sql::MetaTable::DoesTableExist(db_
.get()));
534 for (size_t i
= 0; i
< kTableCount
; ++i
) {
535 DCHECK(db_
->DoesTableExist(kTables
[i
].table_name
));
543 bool QuotaDatabase::CreateSchema(
544 sql::Connection
* database
,
545 sql::MetaTable
* meta_table
,
546 int schema_version
, int compatible_version
,
547 const TableSchema
* tables
, size_t tables_size
,
548 const IndexSchema
* indexes
, size_t indexes_size
) {
549 // TODO(kinuko): Factor out the common code to create databases.
550 sql::Transaction
transaction(database
);
551 if (!transaction
.Begin())
554 if (!meta_table
->Init(database
, schema_version
, compatible_version
))
557 for (size_t i
= 0; i
< tables_size
; ++i
) {
558 std::string
sql("CREATE TABLE ");
559 sql
+= tables
[i
].table_name
;
560 sql
+= tables
[i
].columns
;
561 if (!database
->Execute(sql
.c_str())) {
562 VLOG(1) << "Failed to execute " << sql
;
567 for (size_t i
= 0; i
< indexes_size
; ++i
) {
569 if (indexes
[i
].unique
)
570 sql
+= "CREATE UNIQUE INDEX ";
572 sql
+= "CREATE INDEX ";
573 sql
+= indexes
[i
].index_name
;
575 sql
+= indexes
[i
].table_name
;
576 sql
+= indexes
[i
].columns
;
577 if (!database
->Execute(sql
.c_str())) {
578 VLOG(1) << "Failed to execute " << sql
;
583 return transaction
.Commit();
586 bool QuotaDatabase::ResetSchema() {
587 DCHECK(!db_file_path_
.empty());
588 DCHECK(base::PathExists(db_file_path_
));
589 VLOG(1) << "Deleting existing quota data and starting over.";
594 if (!sql::Connection::Delete(db_file_path_
))
597 // So we can't go recursive.
601 base::AutoReset
<bool> auto_reset(&is_recreating_
, true);
602 return LazyOpen(true);
605 bool QuotaDatabase::UpgradeSchema(int current_version
) {
606 if (current_version
== 2) {
607 QuotaTableImporter importer
;
608 typedef std::vector
<QuotaTableEntry
> QuotaTableEntries
;
609 if (!DumpQuotaTable(base::Bind(&QuotaTableImporter::Append
,
610 base::Unretained(&importer
)))) {
614 for (QuotaTableEntries::const_iterator iter
= importer
.entries
.begin();
615 iter
!= importer
.entries
.end(); ++iter
) {
616 if (!SetHostQuota(iter
->host
, iter
->type
, iter
->quota
))
625 bool QuotaDatabase::DumpQuotaTable(const QuotaTableCallback
& callback
) {
629 const char* kSql
= "SELECT * FROM HostQuotaTable";
630 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
632 while (statement
.Step()) {
633 QuotaTableEntry entry
= QuotaTableEntry(
634 statement
.ColumnString(0),
635 static_cast<StorageType
>(statement
.ColumnInt(1)),
636 statement
.ColumnInt64(2));
638 if (!callback
.Run(entry
))
642 return statement
.Succeeded();
645 bool QuotaDatabase::DumpOriginInfoTable(
646 const OriginInfoTableCallback
& callback
) {
651 const char* kSql
= "SELECT * FROM OriginInfoTable";
652 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
654 while (statement
.Step()) {
655 OriginInfoTableEntry
entry(
656 GURL(statement
.ColumnString(0)),
657 static_cast<StorageType
>(statement
.ColumnInt(1)),
658 statement
.ColumnInt(2),
659 base::Time::FromInternalValue(statement
.ColumnInt64(3)),
660 base::Time::FromInternalValue(statement
.ColumnInt64(4)));
662 if (!callback
.Run(entry
))
666 return statement
.Succeeded();
669 bool operator<(const QuotaDatabase::QuotaTableEntry
& lhs
,
670 const QuotaDatabase::QuotaTableEntry
& rhs
) {
671 if (lhs
.host
< rhs
.host
) return true;
672 if (rhs
.host
< lhs
.host
) return false;
673 if (lhs
.type
< rhs
.type
) return true;
674 if (rhs
.type
< lhs
.type
) return false;
675 return lhs
.quota
< rhs
.quota
;
678 bool operator<(const QuotaDatabase::OriginInfoTableEntry
& lhs
,
679 const QuotaDatabase::OriginInfoTableEntry
& rhs
) {
680 if (lhs
.origin
< rhs
.origin
) return true;
681 if (rhs
.origin
< lhs
.origin
) return false;
682 if (lhs
.type
< rhs
.type
) return true;
683 if (rhs
.type
< lhs
.type
) return false;
684 if (lhs
.used_count
< rhs
.used_count
) return true;
685 if (rhs
.used_count
< lhs
.used_count
) return false;
686 return lhs
.last_access_time
< rhs
.last_access_time
;
689 } // namespace storage