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 "sql/connection.h"
13 #include "sql/meta_table.h"
14 #include "sql/statement.h"
15 #include "sql/transaction.h"
16 #include "storage/browser/quota/special_storage_policy.h"
21 // Definitions for database schema.
23 const int kCurrentVersion
= 4;
24 const int kCompatibleVersion
= 2;
26 const char kHostQuotaTable
[] = "HostQuotaTable";
27 const char kOriginInfoTable
[] = "OriginInfoTable";
28 const char kIsOriginTableBootstrapped
[] = "IsOriginTableBootstrapped";
30 bool VerifyValidQuotaConfig(const char* key
) {
31 return (key
!= NULL
&&
32 (!strcmp(key
, QuotaDatabase::kDesiredAvailableSpaceKey
) ||
33 !strcmp(key
, QuotaDatabase::kTemporaryQuotaOverrideKey
)));
36 const int kCommitIntervalMs
= 30000;
38 } // anonymous namespace
41 const char QuotaDatabase::kDesiredAvailableSpaceKey
[] = "DesiredAvailableSpace";
42 const char QuotaDatabase::kTemporaryQuotaOverrideKey
[] =
43 "TemporaryQuotaOverride";
45 const QuotaDatabase::TableSchema
QuotaDatabase::kTables
[] = {
47 "(host TEXT NOT NULL,"
48 " type INTEGER NOT NULL,"
49 " quota INTEGER DEFAULT 0,"
50 " UNIQUE(host, type))" },
52 "(origin TEXT NOT NULL,"
53 " type INTEGER NOT NULL,"
54 " used_count INTEGER DEFAULT 0,"
55 " last_access_time INTEGER DEFAULT 0,"
56 " last_modified_time INTEGER DEFAULT 0,"
57 " UNIQUE(origin, type))" },
61 const QuotaDatabase::IndexSchema
QuotaDatabase::kIndexes
[] = {
70 { "OriginLastAccessTimeIndex",
74 { "OriginLastModifiedTimeIndex",
76 "(last_modified_time)",
80 struct QuotaDatabase::QuotaTableImporter
{
81 bool Append(const QuotaTableEntry
& entry
) {
82 entries
.push_back(entry
);
85 std::vector
<QuotaTableEntry
> entries
;
88 // Clang requires explicit out-of-line constructors for them.
89 QuotaDatabase::QuotaTableEntry::QuotaTableEntry()
90 : type(kStorageTypeUnknown
),
94 QuotaDatabase::QuotaTableEntry::QuotaTableEntry(
95 const std::string
& host
,
103 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry()
104 : type(kStorageTypeUnknown
),
108 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry(
112 const base::Time
& last_access_time
,
113 const base::Time
& last_modified_time
)
116 used_count(used_count
),
117 last_access_time(last_access_time
),
118 last_modified_time(last_modified_time
) {
121 // QuotaDatabase ------------------------------------------------------------
122 QuotaDatabase::QuotaDatabase(const base::FilePath
& path
)
123 : db_file_path_(path
),
124 is_recreating_(false),
125 is_disabled_(false) {
128 QuotaDatabase::~QuotaDatabase() {
130 db_
->CommitTransaction();
134 void QuotaDatabase::CloseConnection() {
139 bool QuotaDatabase::GetHostQuota(
140 const std::string
& host
, StorageType type
, int64
* quota
) {
142 if (!LazyOpen(false))
147 " FROM HostQuotaTable"
148 " WHERE host = ? AND type = ?";
150 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
151 statement
.BindString(0, host
);
152 statement
.BindInt(1, static_cast<int>(type
));
154 if (!statement
.Step())
157 *quota
= statement
.ColumnInt64(0);
161 bool QuotaDatabase::SetHostQuota(
162 const std::string
& host
, StorageType type
, int64 quota
) {
168 "INSERT OR REPLACE INTO HostQuotaTable"
169 " (quota, host, type)"
171 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
172 statement
.BindInt64(0, quota
);
173 statement
.BindString(1, host
);
174 statement
.BindInt(2, static_cast<int>(type
));
176 if (!statement
.Run())
183 bool QuotaDatabase::SetOriginLastAccessTime(
184 const GURL
& origin
, StorageType type
, base::Time last_access_time
) {
188 sql::Statement statement
;
191 if (FindOriginUsedCount(origin
, type
, &used_count
)) {
194 "UPDATE OriginInfoTable"
195 " SET used_count = ?, last_access_time = ?"
196 " WHERE origin = ? AND type = ?";
197 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
200 "INSERT INTO OriginInfoTable"
201 " (used_count, last_access_time, origin, type)"
202 " VALUES (?, ?, ?, ?)";
203 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
205 statement
.BindInt(0, used_count
);
206 statement
.BindInt64(1, last_access_time
.ToInternalValue());
207 statement
.BindString(2, origin
.spec());
208 statement
.BindInt(3, static_cast<int>(type
));
210 if (!statement
.Run())
217 bool QuotaDatabase::SetOriginLastModifiedTime(
218 const GURL
& origin
, StorageType type
, base::Time last_modified_time
) {
222 sql::Statement statement
;
225 if (FindOriginUsedCount(origin
, type
, &dummy
)) {
227 "UPDATE OriginInfoTable"
228 " SET last_modified_time = ?"
229 " WHERE origin = ? AND type = ?";
230 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
233 "INSERT INTO OriginInfoTable"
234 " (last_modified_time, origin, type) VALUES (?, ?, ?)";
235 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
237 statement
.BindInt64(0, last_modified_time
.ToInternalValue());
238 statement
.BindString(1, origin
.spec());
239 statement
.BindInt(2, static_cast<int>(type
));
241 if (!statement
.Run())
248 bool QuotaDatabase::RegisterInitialOriginInfo(
249 const std::set
<GURL
>& origins
, StorageType type
) {
253 typedef std::set
<GURL
>::const_iterator itr_type
;
254 for (itr_type itr
= origins
.begin(), end
= origins
.end();
257 "INSERT OR IGNORE INTO OriginInfoTable"
258 " (origin, type) VALUES (?, ?)";
259 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
260 statement
.BindString(0, itr
->spec());
261 statement
.BindInt(1, static_cast<int>(type
));
263 if (!statement
.Run())
271 bool QuotaDatabase::DeleteHostQuota(
272 const std::string
& host
, StorageType type
) {
273 if (!LazyOpen(false))
277 "DELETE FROM HostQuotaTable"
278 " WHERE host = ? AND type = ?";
280 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
281 statement
.BindString(0, host
);
282 statement
.BindInt(1, static_cast<int>(type
));
284 if (!statement
.Run())
291 bool QuotaDatabase::DeleteOriginInfo(
292 const GURL
& origin
, StorageType type
) {
293 if (!LazyOpen(false))
297 "DELETE FROM OriginInfoTable"
298 " WHERE origin = ? AND type = ?";
300 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
301 statement
.BindString(0, origin
.spec());
302 statement
.BindInt(1, static_cast<int>(type
));
304 if (!statement
.Run())
311 bool QuotaDatabase::GetQuotaConfigValue(const char* key
, int64
* value
) {
312 if (!LazyOpen(false))
314 DCHECK(VerifyValidQuotaConfig(key
));
315 return meta_table_
->GetValue(key
, value
);
318 bool QuotaDatabase::SetQuotaConfigValue(const char* key
, int64 value
) {
321 DCHECK(VerifyValidQuotaConfig(key
));
322 return meta_table_
->SetValue(key
, value
);
325 bool QuotaDatabase::GetLRUOrigin(
327 const std::set
<GURL
>& exceptions
,
328 SpecialStoragePolicy
* special_storage_policy
,
331 if (!LazyOpen(false))
334 const char* kSql
= "SELECT origin FROM OriginInfoTable"
336 " ORDER BY last_access_time ASC";
338 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
339 statement
.BindInt(0, static_cast<int>(type
));
341 while (statement
.Step()) {
342 GURL
url(statement
.ColumnString(0));
343 if (exceptions
.find(url
) != exceptions
.end())
345 if (special_storage_policy
&&
346 (special_storage_policy
->IsStorageDurable(url
) ||
347 special_storage_policy
->IsStorageUnlimited(url
)))
354 return statement
.Succeeded();
357 bool QuotaDatabase::GetOriginsModifiedSince(
358 StorageType type
, std::set
<GURL
>* origins
, base::Time modified_since
) {
360 if (!LazyOpen(false))
363 const char* kSql
= "SELECT origin FROM OriginInfoTable"
364 " WHERE type = ? AND last_modified_time >= ?";
366 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
367 statement
.BindInt(0, static_cast<int>(type
));
368 statement
.BindInt64(1, modified_since
.ToInternalValue());
371 while (statement
.Step())
372 origins
->insert(GURL(statement
.ColumnString(0)));
374 return statement
.Succeeded();
377 bool QuotaDatabase::IsOriginDatabaseBootstrapped() {
382 return meta_table_
->GetValue(kIsOriginTableBootstrapped
, &flag
) && flag
;
385 bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag
) {
389 return meta_table_
->SetValue(kIsOriginTableBootstrapped
, bootstrap_flag
);
392 void QuotaDatabase::Commit() {
396 if (timer_
.IsRunning())
399 db_
->CommitTransaction();
400 db_
->BeginTransaction();
403 void QuotaDatabase::ScheduleCommit() {
404 if (timer_
.IsRunning())
406 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(kCommitIntervalMs
),
407 this, &QuotaDatabase::Commit
);
410 bool QuotaDatabase::FindOriginUsedCount(
411 const GURL
& origin
, StorageType type
, int* used_count
) {
413 if (!LazyOpen(false))
417 "SELECT used_count FROM OriginInfoTable"
418 " WHERE origin = ? AND type = ?";
420 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
421 statement
.BindString(0, origin
.spec());
422 statement
.BindInt(1, static_cast<int>(type
));
424 if (!statement
.Step())
427 *used_count
= statement
.ColumnInt(0);
431 bool QuotaDatabase::LazyOpen(bool create_if_needed
) {
435 // If we tried and failed once, don't try again in the same session
436 // to avoid creating an incoherent mess on disk.
440 bool in_memory_only
= db_file_path_
.empty();
441 if (!create_if_needed
&&
442 (in_memory_only
|| !base::PathExists(db_file_path_
))) {
446 db_
.reset(new sql::Connection
);
447 meta_table_
.reset(new sql::MetaTable
);
449 db_
->set_histogram_tag("Quota");
452 if (in_memory_only
) {
453 opened
= db_
->OpenInMemory();
454 } else if (!base::CreateDirectory(db_file_path_
.DirName())) {
455 LOG(ERROR
) << "Failed to create quota database directory.";
457 opened
= db_
->Open(db_file_path_
);
462 if (!opened
|| !EnsureDatabaseVersion()) {
463 LOG(ERROR
) << "Failed to open the quota database.";
470 // Start a long-running transaction.
471 db_
->BeginTransaction();
476 bool QuotaDatabase::EnsureDatabaseVersion() {
477 static const size_t kTableCount
= arraysize(kTables
);
478 static const size_t kIndexCount
= arraysize(kIndexes
);
479 if (!sql::MetaTable::DoesTableExist(db_
.get()))
480 return CreateSchema(db_
.get(), meta_table_
.get(),
481 kCurrentVersion
, kCompatibleVersion
,
482 kTables
, kTableCount
,
483 kIndexes
, kIndexCount
);
485 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
488 if (meta_table_
->GetCompatibleVersionNumber() > kCurrentVersion
) {
489 LOG(WARNING
) << "Quota database is too new.";
493 if (meta_table_
->GetVersionNumber() < kCurrentVersion
) {
494 if (!UpgradeSchema(meta_table_
->GetVersionNumber()))
495 return ResetSchema();
499 DCHECK(sql::MetaTable::DoesTableExist(db_
.get()));
500 for (size_t i
= 0; i
< kTableCount
; ++i
) {
501 DCHECK(db_
->DoesTableExist(kTables
[i
].table_name
));
509 bool QuotaDatabase::CreateSchema(
510 sql::Connection
* database
,
511 sql::MetaTable
* meta_table
,
512 int schema_version
, int compatible_version
,
513 const TableSchema
* tables
, size_t tables_size
,
514 const IndexSchema
* indexes
, size_t indexes_size
) {
515 // TODO(kinuko): Factor out the common code to create databases.
516 sql::Transaction
transaction(database
);
517 if (!transaction
.Begin())
520 if (!meta_table
->Init(database
, schema_version
, compatible_version
))
523 for (size_t i
= 0; i
< tables_size
; ++i
) {
524 std::string
sql("CREATE TABLE ");
525 sql
+= tables
[i
].table_name
;
526 sql
+= tables
[i
].columns
;
527 if (!database
->Execute(sql
.c_str())) {
528 VLOG(1) << "Failed to execute " << sql
;
533 for (size_t i
= 0; i
< indexes_size
; ++i
) {
535 if (indexes
[i
].unique
)
536 sql
+= "CREATE UNIQUE INDEX ";
538 sql
+= "CREATE INDEX ";
539 sql
+= indexes
[i
].index_name
;
541 sql
+= indexes
[i
].table_name
;
542 sql
+= indexes
[i
].columns
;
543 if (!database
->Execute(sql
.c_str())) {
544 VLOG(1) << "Failed to execute " << sql
;
549 return transaction
.Commit();
552 bool QuotaDatabase::ResetSchema() {
553 DCHECK(!db_file_path_
.empty());
554 DCHECK(base::PathExists(db_file_path_
));
555 VLOG(1) << "Deleting existing quota data and starting over.";
560 if (!sql::Connection::Delete(db_file_path_
))
563 // So we can't go recursive.
567 base::AutoReset
<bool> auto_reset(&is_recreating_
, true);
568 return LazyOpen(true);
571 bool QuotaDatabase::UpgradeSchema(int current_version
) {
572 if (current_version
== 2) {
573 QuotaTableImporter importer
;
574 typedef std::vector
<QuotaTableEntry
> QuotaTableEntries
;
575 if (!DumpQuotaTable(base::Bind(&QuotaTableImporter::Append
,
576 base::Unretained(&importer
)))) {
580 for (QuotaTableEntries::const_iterator iter
= importer
.entries
.begin();
581 iter
!= importer
.entries
.end(); ++iter
) {
582 if (!SetHostQuota(iter
->host
, iter
->type
, iter
->quota
))
591 bool QuotaDatabase::DumpQuotaTable(const QuotaTableCallback
& callback
) {
595 const char* kSql
= "SELECT * FROM HostQuotaTable";
596 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
598 while (statement
.Step()) {
599 QuotaTableEntry entry
= QuotaTableEntry(
600 statement
.ColumnString(0),
601 static_cast<StorageType
>(statement
.ColumnInt(1)),
602 statement
.ColumnInt64(2));
604 if (!callback
.Run(entry
))
608 return statement
.Succeeded();
611 bool QuotaDatabase::DumpOriginInfoTable(
612 const OriginInfoTableCallback
& callback
) {
617 const char* kSql
= "SELECT * FROM OriginInfoTable";
618 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
620 while (statement
.Step()) {
621 OriginInfoTableEntry
entry(
622 GURL(statement
.ColumnString(0)),
623 static_cast<StorageType
>(statement
.ColumnInt(1)),
624 statement
.ColumnInt(2),
625 base::Time::FromInternalValue(statement
.ColumnInt64(3)),
626 base::Time::FromInternalValue(statement
.ColumnInt64(4)));
628 if (!callback
.Run(entry
))
632 return statement
.Succeeded();
635 bool operator<(const QuotaDatabase::QuotaTableEntry
& lhs
,
636 const QuotaDatabase::QuotaTableEntry
& rhs
) {
637 if (lhs
.host
< rhs
.host
) return true;
638 if (rhs
.host
< lhs
.host
) return false;
639 if (lhs
.type
< rhs
.type
) return true;
640 if (rhs
.type
< lhs
.type
) return false;
641 return lhs
.quota
< rhs
.quota
;
644 bool operator<(const QuotaDatabase::OriginInfoTableEntry
& lhs
,
645 const QuotaDatabase::OriginInfoTableEntry
& rhs
) {
646 if (lhs
.origin
< rhs
.origin
) return true;
647 if (rhs
.origin
< lhs
.origin
) return false;
648 if (lhs
.type
< rhs
.type
) return true;
649 if (rhs
.type
< lhs
.type
) return false;
650 if (lhs
.used_count
< rhs
.used_count
) return true;
651 if (rhs
.used_count
< lhs
.used_count
) return false;
652 return lhs
.last_access_time
< rhs
.last_access_time
;
655 } // namespace storage