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"
10 #include "base/auto_reset.h"
11 #include "base/bind.h"
12 #include "base/files/file_util.h"
13 #include "base/time/time.h"
14 #include "sql/connection.h"
15 #include "sql/meta_table.h"
16 #include "sql/statement.h"
17 #include "sql/transaction.h"
18 #include "storage/browser/quota/special_storage_policy.h"
24 // Definitions for database schema.
26 const int kCurrentVersion
= 4;
27 const int kCompatibleVersion
= 2;
29 const char kHostQuotaTable
[] = "HostQuotaTable";
30 const char kOriginInfoTable
[] = "OriginInfoTable";
31 const char kIsOriginTableBootstrapped
[] = "IsOriginTableBootstrapped";
33 bool VerifyValidQuotaConfig(const char* key
) {
34 return (key
!= NULL
&&
35 (!strcmp(key
, QuotaDatabase::kDesiredAvailableSpaceKey
) ||
36 !strcmp(key
, QuotaDatabase::kTemporaryQuotaOverrideKey
)));
39 const int kCommitIntervalMs
= 30000;
41 } // anonymous namespace
44 const char QuotaDatabase::kDesiredAvailableSpaceKey
[] = "DesiredAvailableSpace";
45 const char QuotaDatabase::kTemporaryQuotaOverrideKey
[] =
46 "TemporaryQuotaOverride";
48 const QuotaDatabase::TableSchema
QuotaDatabase::kTables
[] = {
50 "(host TEXT NOT NULL,"
51 " type INTEGER NOT NULL,"
52 " quota INTEGER DEFAULT 0,"
53 " UNIQUE(host, type))" },
55 "(origin TEXT NOT NULL,"
56 " type INTEGER NOT NULL,"
57 " used_count INTEGER DEFAULT 0,"
58 " last_access_time INTEGER DEFAULT 0,"
59 " last_modified_time INTEGER DEFAULT 0,"
60 " UNIQUE(origin, type))" },
64 const QuotaDatabase::IndexSchema
QuotaDatabase::kIndexes
[] = {
73 { "OriginLastAccessTimeIndex",
77 { "OriginLastModifiedTimeIndex",
79 "(last_modified_time)",
83 struct QuotaDatabase::QuotaTableImporter
{
84 bool Append(const QuotaTableEntry
& entry
) {
85 entries
.push_back(entry
);
88 std::vector
<QuotaTableEntry
> entries
;
91 // Clang requires explicit out-of-line constructors for them.
92 QuotaDatabase::QuotaTableEntry::QuotaTableEntry()
93 : type(kStorageTypeUnknown
),
97 QuotaDatabase::QuotaTableEntry::QuotaTableEntry(
98 const std::string
& host
,
106 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry()
107 : type(kStorageTypeUnknown
),
111 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry(
115 const base::Time
& last_access_time
,
116 const base::Time
& last_modified_time
)
119 used_count(used_count
),
120 last_access_time(last_access_time
),
121 last_modified_time(last_modified_time
) {
124 // QuotaDatabase ------------------------------------------------------------
125 QuotaDatabase::QuotaDatabase(const base::FilePath
& path
)
126 : db_file_path_(path
),
127 is_recreating_(false),
128 is_disabled_(false) {
131 QuotaDatabase::~QuotaDatabase() {
133 db_
->CommitTransaction();
137 void QuotaDatabase::CloseConnection() {
142 bool QuotaDatabase::GetHostQuota(
143 const std::string
& host
, StorageType type
, int64
* quota
) {
145 if (!LazyOpen(false))
150 " FROM HostQuotaTable"
151 " WHERE host = ? AND type = ?";
153 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
154 statement
.BindString(0, host
);
155 statement
.BindInt(1, static_cast<int>(type
));
157 if (!statement
.Step())
160 *quota
= statement
.ColumnInt64(0);
164 bool QuotaDatabase::SetHostQuota(
165 const std::string
& host
, StorageType type
, int64 quota
) {
171 "INSERT OR REPLACE INTO HostQuotaTable"
172 " (quota, host, type)"
174 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
175 statement
.BindInt64(0, quota
);
176 statement
.BindString(1, host
);
177 statement
.BindInt(2, static_cast<int>(type
));
179 if (!statement
.Run())
186 bool QuotaDatabase::SetOriginLastAccessTime(
187 const GURL
& origin
, StorageType type
, base::Time last_access_time
) {
191 sql::Statement statement
;
194 if (FindOriginUsedCount(origin
, type
, &used_count
)) {
197 "UPDATE OriginInfoTable"
198 " SET used_count = ?, last_access_time = ?"
199 " WHERE origin = ? AND type = ?";
200 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
203 "INSERT INTO OriginInfoTable"
204 " (used_count, last_access_time, origin, type)"
205 " VALUES (?, ?, ?, ?)";
206 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
208 statement
.BindInt(0, used_count
);
209 statement
.BindInt64(1, last_access_time
.ToInternalValue());
210 statement
.BindString(2, origin
.spec());
211 statement
.BindInt(3, static_cast<int>(type
));
213 if (!statement
.Run())
220 bool QuotaDatabase::SetOriginLastModifiedTime(
221 const GURL
& origin
, StorageType type
, base::Time last_modified_time
) {
225 sql::Statement statement
;
228 if (FindOriginUsedCount(origin
, type
, &dummy
)) {
230 "UPDATE OriginInfoTable"
231 " SET last_modified_time = ?"
232 " WHERE origin = ? AND type = ?";
233 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
236 "INSERT INTO OriginInfoTable"
237 " (last_modified_time, origin, type) VALUES (?, ?, ?)";
238 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
240 statement
.BindInt64(0, last_modified_time
.ToInternalValue());
241 statement
.BindString(1, origin
.spec());
242 statement
.BindInt(2, static_cast<int>(type
));
244 if (!statement
.Run())
251 bool QuotaDatabase::RegisterInitialOriginInfo(
252 const std::set
<GURL
>& origins
, StorageType type
) {
256 typedef std::set
<GURL
>::const_iterator itr_type
;
257 for (itr_type itr
= origins
.begin(), end
= origins
.end();
260 "INSERT OR IGNORE INTO OriginInfoTable"
261 " (origin, type) VALUES (?, ?)";
262 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
263 statement
.BindString(0, itr
->spec());
264 statement
.BindInt(1, static_cast<int>(type
));
266 if (!statement
.Run())
274 bool QuotaDatabase::DeleteHostQuota(
275 const std::string
& host
, StorageType type
) {
276 if (!LazyOpen(false))
280 "DELETE FROM HostQuotaTable"
281 " WHERE host = ? AND type = ?";
283 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
284 statement
.BindString(0, host
);
285 statement
.BindInt(1, static_cast<int>(type
));
287 if (!statement
.Run())
294 bool QuotaDatabase::DeleteOriginInfo(
295 const GURL
& origin
, StorageType type
) {
296 if (!LazyOpen(false))
300 "DELETE FROM OriginInfoTable"
301 " WHERE origin = ? AND type = ?";
303 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
304 statement
.BindString(0, origin
.spec());
305 statement
.BindInt(1, static_cast<int>(type
));
307 if (!statement
.Run())
314 bool QuotaDatabase::GetQuotaConfigValue(const char* key
, int64
* value
) {
315 if (!LazyOpen(false))
317 DCHECK(VerifyValidQuotaConfig(key
));
318 return meta_table_
->GetValue(key
, value
);
321 bool QuotaDatabase::SetQuotaConfigValue(const char* key
, int64 value
) {
324 DCHECK(VerifyValidQuotaConfig(key
));
325 return meta_table_
->SetValue(key
, value
);
328 bool QuotaDatabase::GetLRUOrigin(
330 const std::set
<GURL
>& exceptions
,
331 SpecialStoragePolicy
* special_storage_policy
,
334 if (!LazyOpen(false))
337 const char* kSql
= "SELECT origin FROM OriginInfoTable"
339 " ORDER BY last_access_time ASC";
341 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
342 statement
.BindInt(0, static_cast<int>(type
));
344 while (statement
.Step()) {
345 GURL
url(statement
.ColumnString(0));
346 if (exceptions
.find(url
) != exceptions
.end())
348 if (special_storage_policy
&&
349 special_storage_policy
->IsStorageUnlimited(url
))
356 return statement
.Succeeded();
359 bool QuotaDatabase::GetOriginsModifiedSince(
360 StorageType type
, std::set
<GURL
>* origins
, base::Time modified_since
) {
362 if (!LazyOpen(false))
365 const char* kSql
= "SELECT origin FROM OriginInfoTable"
366 " WHERE type = ? AND last_modified_time >= ?";
368 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
369 statement
.BindInt(0, static_cast<int>(type
));
370 statement
.BindInt64(1, modified_since
.ToInternalValue());
373 while (statement
.Step())
374 origins
->insert(GURL(statement
.ColumnString(0)));
376 return statement
.Succeeded();
379 bool QuotaDatabase::IsOriginDatabaseBootstrapped() {
384 return meta_table_
->GetValue(kIsOriginTableBootstrapped
, &flag
) && flag
;
387 bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag
) {
391 return meta_table_
->SetValue(kIsOriginTableBootstrapped
, bootstrap_flag
);
394 void QuotaDatabase::Commit() {
398 if (timer_
.IsRunning())
401 db_
->CommitTransaction();
402 db_
->BeginTransaction();
405 void QuotaDatabase::ScheduleCommit() {
406 if (timer_
.IsRunning())
408 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(kCommitIntervalMs
),
409 this, &QuotaDatabase::Commit
);
412 bool QuotaDatabase::FindOriginUsedCount(
413 const GURL
& origin
, StorageType type
, int* used_count
) {
415 if (!LazyOpen(false))
419 "SELECT used_count FROM OriginInfoTable"
420 " WHERE origin = ? AND type = ?";
422 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
423 statement
.BindString(0, origin
.spec());
424 statement
.BindInt(1, static_cast<int>(type
));
426 if (!statement
.Step())
429 *used_count
= statement
.ColumnInt(0);
433 bool QuotaDatabase::LazyOpen(bool create_if_needed
) {
437 // If we tried and failed once, don't try again in the same session
438 // to avoid creating an incoherent mess on disk.
442 bool in_memory_only
= db_file_path_
.empty();
443 if (!create_if_needed
&&
444 (in_memory_only
|| !base::PathExists(db_file_path_
))) {
448 db_
.reset(new sql::Connection
);
449 meta_table_
.reset(new sql::MetaTable
);
451 db_
->set_histogram_tag("Quota");
454 if (in_memory_only
) {
455 opened
= db_
->OpenInMemory();
456 } else if (!base::CreateDirectory(db_file_path_
.DirName())) {
457 LOG(ERROR
) << "Failed to create quota database directory.";
459 opened
= db_
->Open(db_file_path_
);
464 if (!opened
|| !EnsureDatabaseVersion()) {
465 LOG(ERROR
) << "Failed to open the quota database.";
472 // Start a long-running transaction.
473 db_
->BeginTransaction();
478 bool QuotaDatabase::EnsureDatabaseVersion() {
479 static const size_t kTableCount
= arraysize(kTables
);
480 static const size_t kIndexCount
= arraysize(kIndexes
);
481 if (!sql::MetaTable::DoesTableExist(db_
.get()))
482 return CreateSchema(db_
.get(), meta_table_
.get(),
483 kCurrentVersion
, kCompatibleVersion
,
484 kTables
, kTableCount
,
485 kIndexes
, kIndexCount
);
487 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
490 if (meta_table_
->GetCompatibleVersionNumber() > kCurrentVersion
) {
491 LOG(WARNING
) << "Quota database is too new.";
495 if (meta_table_
->GetVersionNumber() < kCurrentVersion
) {
496 if (!UpgradeSchema(meta_table_
->GetVersionNumber()))
497 return ResetSchema();
501 DCHECK(sql::MetaTable::DoesTableExist(db_
.get()));
502 for (size_t i
= 0; i
< kTableCount
; ++i
) {
503 DCHECK(db_
->DoesTableExist(kTables
[i
].table_name
));
511 bool QuotaDatabase::CreateSchema(
512 sql::Connection
* database
,
513 sql::MetaTable
* meta_table
,
514 int schema_version
, int compatible_version
,
515 const TableSchema
* tables
, size_t tables_size
,
516 const IndexSchema
* indexes
, size_t indexes_size
) {
517 // TODO(kinuko): Factor out the common code to create databases.
518 sql::Transaction
transaction(database
);
519 if (!transaction
.Begin())
522 if (!meta_table
->Init(database
, schema_version
, compatible_version
))
525 for (size_t i
= 0; i
< tables_size
; ++i
) {
526 std::string
sql("CREATE TABLE ");
527 sql
+= tables
[i
].table_name
;
528 sql
+= tables
[i
].columns
;
529 if (!database
->Execute(sql
.c_str())) {
530 VLOG(1) << "Failed to execute " << sql
;
535 for (size_t i
= 0; i
< indexes_size
; ++i
) {
537 if (indexes
[i
].unique
)
538 sql
+= "CREATE UNIQUE INDEX ";
540 sql
+= "CREATE INDEX ";
541 sql
+= indexes
[i
].index_name
;
543 sql
+= indexes
[i
].table_name
;
544 sql
+= indexes
[i
].columns
;
545 if (!database
->Execute(sql
.c_str())) {
546 VLOG(1) << "Failed to execute " << sql
;
551 return transaction
.Commit();
554 bool QuotaDatabase::ResetSchema() {
555 DCHECK(!db_file_path_
.empty());
556 DCHECK(base::PathExists(db_file_path_
));
557 VLOG(1) << "Deleting existing quota data and starting over.";
562 if (!sql::Connection::Delete(db_file_path_
))
565 // So we can't go recursive.
569 base::AutoReset
<bool> auto_reset(&is_recreating_
, true);
570 return LazyOpen(true);
573 bool QuotaDatabase::UpgradeSchema(int current_version
) {
574 if (current_version
== 2) {
575 QuotaTableImporter importer
;
576 typedef std::vector
<QuotaTableEntry
> QuotaTableEntries
;
577 if (!DumpQuotaTable(base::Bind(&QuotaTableImporter::Append
,
578 base::Unretained(&importer
)))) {
582 for (QuotaTableEntries::const_iterator iter
= importer
.entries
.begin();
583 iter
!= importer
.entries
.end(); ++iter
) {
584 if (!SetHostQuota(iter
->host
, iter
->type
, iter
->quota
))
593 bool QuotaDatabase::DumpQuotaTable(const QuotaTableCallback
& callback
) {
597 const char* kSql
= "SELECT * FROM HostQuotaTable";
598 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
600 while (statement
.Step()) {
601 QuotaTableEntry entry
= QuotaTableEntry(
602 statement
.ColumnString(0),
603 static_cast<StorageType
>(statement
.ColumnInt(1)),
604 statement
.ColumnInt64(2));
606 if (!callback
.Run(entry
))
610 return statement
.Succeeded();
613 bool QuotaDatabase::DumpOriginInfoTable(
614 const OriginInfoTableCallback
& callback
) {
619 const char* kSql
= "SELECT * FROM OriginInfoTable";
620 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
622 while (statement
.Step()) {
623 OriginInfoTableEntry
entry(
624 GURL(statement
.ColumnString(0)),
625 static_cast<StorageType
>(statement
.ColumnInt(1)),
626 statement
.ColumnInt(2),
627 base::Time::FromInternalValue(statement
.ColumnInt64(3)),
628 base::Time::FromInternalValue(statement
.ColumnInt64(4)));
630 if (!callback
.Run(entry
))
634 return statement
.Succeeded();
637 bool operator<(const QuotaDatabase::QuotaTableEntry
& lhs
,
638 const QuotaDatabase::QuotaTableEntry
& rhs
) {
639 if (lhs
.host
< rhs
.host
) return true;
640 if (rhs
.host
< lhs
.host
) return false;
641 if (lhs
.type
< rhs
.type
) return true;
642 if (rhs
.type
< lhs
.type
) return false;
643 return lhs
.quota
< rhs
.quota
;
646 bool operator<(const QuotaDatabase::OriginInfoTableEntry
& lhs
,
647 const QuotaDatabase::OriginInfoTableEntry
& rhs
) {
648 if (lhs
.origin
< rhs
.origin
) return true;
649 if (rhs
.origin
< lhs
.origin
) return false;
650 if (lhs
.type
< rhs
.type
) return true;
651 if (rhs
.type
< lhs
.type
) return false;
652 if (lhs
.used_count
< rhs
.used_count
) return true;
653 if (rhs
.used_count
< lhs
.used_count
) return false;
654 return lhs
.last_access_time
< rhs
.last_access_time
;
657 } // namespace storage