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 "webkit/quota/quota_database.h"
9 #include "base/auto_reset.h"
10 #include "base/bind.h"
11 #include "base/file_util.h"
12 #include "base/time.h"
13 #include "googleurl/src/gurl.h"
14 #include "sql/connection.h"
15 #include "sql/meta_table.h"
16 #include "sql/statement.h"
17 #include "sql/transaction.h"
18 #include "webkit/quota/special_storage_policy.h"
23 // Definitions for database schema.
25 const int kCurrentVersion
= 4;
26 const int kCompatibleVersion
= 2;
28 const char kHostQuotaTable
[] = "HostQuotaTable";
29 const char kOriginInfoTable
[] = "OriginInfoTable";
30 const char kIsOriginTableBootstrapped
[] = "IsOriginTableBootstrapped";
32 bool VerifyValidQuotaConfig(const char* key
) {
33 return (key
!= NULL
&&
34 (!strcmp(key
, QuotaDatabase::kDesiredAvailableSpaceKey
) ||
35 !strcmp(key
, QuotaDatabase::kTemporaryQuotaOverrideKey
)));
38 const int kCommitIntervalMs
= 30000;
40 } // anonymous namespace
43 const char QuotaDatabase::kDesiredAvailableSpaceKey
[] = "DesiredAvailableSpace";
44 const char QuotaDatabase::kTemporaryQuotaOverrideKey
[] =
45 "TemporaryQuotaOverride";
47 const QuotaDatabase::TableSchema
QuotaDatabase::kTables
[] = {
49 "(host TEXT NOT NULL,"
50 " type INTEGER NOT NULL,"
51 " quota INTEGER DEFAULT 0,"
52 " UNIQUE(host, type))" },
54 "(origin TEXT NOT NULL,"
55 " type INTEGER NOT NULL,"
56 " used_count INTEGER DEFAULT 0,"
57 " last_access_time INTEGER DEFAULT 0,"
58 " last_modified_time INTEGER DEFAULT 0,"
59 " UNIQUE(origin, type))" },
63 const QuotaDatabase::IndexSchema
QuotaDatabase::kIndexes
[] = {
72 { "OriginLastAccessTimeIndex",
76 { "OriginLastModifiedTimeIndex",
78 "(last_modified_time)",
82 struct QuotaDatabase::QuotaTableImporter
{
83 bool Append(const QuotaTableEntry
& entry
) {
84 entries
.push_back(entry
);
87 std::vector
<QuotaTableEntry
> entries
;
90 // Clang requires explicit out-of-line constructors for them.
91 QuotaDatabase::QuotaTableEntry::QuotaTableEntry()
92 : type(kStorageTypeUnknown
),
96 QuotaDatabase::QuotaTableEntry::QuotaTableEntry(
97 const std::string
& host
,
105 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry()
106 : type(kStorageTypeUnknown
),
110 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry(
114 const base::Time
& last_access_time
,
115 const base::Time
& last_modified_time
)
118 used_count(used_count
),
119 last_access_time(last_access_time
),
120 last_modified_time(last_modified_time
) {
123 // QuotaDatabase ------------------------------------------------------------
124 QuotaDatabase::QuotaDatabase(const FilePath
& path
)
125 : db_file_path_(path
),
126 is_recreating_(false),
127 is_disabled_(false) {
130 QuotaDatabase::~QuotaDatabase() {
132 db_
->CommitTransaction();
136 void QuotaDatabase::CloseConnection() {
141 bool QuotaDatabase::GetHostQuota(
142 const std::string
& host
, StorageType type
, int64
* quota
) {
144 if (!LazyOpen(false))
149 " FROM HostQuotaTable"
150 " WHERE host = ? AND type = ?";
152 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
153 statement
.BindString(0, host
);
154 statement
.BindInt(1, static_cast<int>(type
));
156 if (!statement
.Step())
159 *quota
= statement
.ColumnInt64(0);
163 bool QuotaDatabase::SetHostQuota(
164 const std::string
& host
, StorageType type
, int64 quota
) {
170 "INSERT OR REPLACE INTO HostQuotaTable"
171 " (quota, host, type)"
173 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
174 statement
.BindInt64(0, quota
);
175 statement
.BindString(1, host
);
176 statement
.BindInt(2, static_cast<int>(type
));
178 if (!statement
.Run())
185 bool QuotaDatabase::SetOriginLastAccessTime(
186 const GURL
& origin
, StorageType type
, base::Time last_access_time
) {
190 sql::Statement statement
;
193 if (FindOriginUsedCount(origin
, type
, &used_count
)) {
196 "UPDATE OriginInfoTable"
197 " SET used_count = ?, last_access_time = ?"
198 " WHERE origin = ? AND type = ?";
199 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
202 "INSERT INTO OriginInfoTable"
203 " (used_count, last_access_time, origin, type)"
204 " VALUES (?, ?, ?, ?)";
205 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
207 statement
.BindInt(0, used_count
);
208 statement
.BindInt64(1, last_access_time
.ToInternalValue());
209 statement
.BindString(2, origin
.spec());
210 statement
.BindInt(3, static_cast<int>(type
));
212 if (!statement
.Run())
219 bool QuotaDatabase::SetOriginLastModifiedTime(
220 const GURL
& origin
, StorageType type
, base::Time last_modified_time
) {
224 sql::Statement statement
;
227 if (FindOriginUsedCount(origin
, type
, &dummy
)) {
229 "UPDATE OriginInfoTable"
230 " SET last_modified_time = ?"
231 " WHERE origin = ? AND type = ?";
232 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
235 "INSERT INTO OriginInfoTable"
236 " (last_modified_time, origin, type) VALUES (?, ?, ?)";
237 statement
.Assign(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
239 statement
.BindInt64(0, last_modified_time
.ToInternalValue());
240 statement
.BindString(1, origin
.spec());
241 statement
.BindInt(2, static_cast<int>(type
));
243 if (!statement
.Run())
250 bool QuotaDatabase::RegisterInitialOriginInfo(
251 const std::set
<GURL
>& origins
, StorageType type
) {
255 typedef std::set
<GURL
>::const_iterator itr_type
;
256 for (itr_type itr
= origins
.begin(), end
= origins
.end();
259 "INSERT OR IGNORE INTO OriginInfoTable"
260 " (origin, type) VALUES (?, ?)";
261 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
262 statement
.BindString(0, itr
->spec());
263 statement
.BindInt(1, static_cast<int>(type
));
265 if (!statement
.Run())
273 bool QuotaDatabase::DeleteHostQuota(
274 const std::string
& host
, StorageType type
) {
275 if (!LazyOpen(false))
279 "DELETE FROM HostQuotaTable"
280 " WHERE host = ? AND type = ?";
282 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
283 statement
.BindString(0, host
);
284 statement
.BindInt(1, static_cast<int>(type
));
286 if (!statement
.Run())
293 bool QuotaDatabase::DeleteOriginInfo(
294 const GURL
& origin
, StorageType type
) {
295 if (!LazyOpen(false))
299 "DELETE FROM OriginInfoTable"
300 " WHERE origin = ? AND type = ?";
302 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
303 statement
.BindString(0, origin
.spec());
304 statement
.BindInt(1, static_cast<int>(type
));
306 if (!statement
.Run())
313 bool QuotaDatabase::GetQuotaConfigValue(const char* key
, int64
* value
) {
314 if (!LazyOpen(false))
316 DCHECK(VerifyValidQuotaConfig(key
));
317 return meta_table_
->GetValue(key
, value
);
320 bool QuotaDatabase::SetQuotaConfigValue(const char* key
, int64 value
) {
323 DCHECK(VerifyValidQuotaConfig(key
));
324 return meta_table_
->SetValue(key
, value
);
327 bool QuotaDatabase::GetLRUOrigin(
329 const std::set
<GURL
>& exceptions
,
330 SpecialStoragePolicy
* special_storage_policy
,
333 if (!LazyOpen(false))
336 const char* kSql
= "SELECT origin FROM OriginInfoTable"
338 " ORDER BY last_access_time ASC";
340 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
341 statement
.BindInt(0, static_cast<int>(type
));
343 while (statement
.Step()) {
344 GURL
url(statement
.ColumnString(0));
345 if (exceptions
.find(url
) != exceptions
.end())
347 if (special_storage_policy
&&
348 special_storage_policy
->IsStorageUnlimited(url
))
355 return statement
.Succeeded();
358 bool QuotaDatabase::GetOriginsModifiedSince(
359 StorageType type
, std::set
<GURL
>* origins
, base::Time modified_since
) {
361 if (!LazyOpen(false))
364 const char* kSql
= "SELECT origin FROM OriginInfoTable"
365 " WHERE type = ? AND last_modified_time >= ?";
367 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
368 statement
.BindInt(0, static_cast<int>(type
));
369 statement
.BindInt64(1, modified_since
.ToInternalValue());
372 while (statement
.Step())
373 origins
->insert(GURL(statement
.ColumnString(0)));
375 return statement
.Succeeded();
378 bool QuotaDatabase::IsOriginDatabaseBootstrapped() {
383 return meta_table_
->GetValue(kIsOriginTableBootstrapped
, &flag
) && flag
;
386 bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag
) {
390 return meta_table_
->SetValue(kIsOriginTableBootstrapped
, bootstrap_flag
);
393 void QuotaDatabase::Commit() {
397 if (timer_
.IsRunning())
400 db_
->CommitTransaction();
401 db_
->BeginTransaction();
404 void QuotaDatabase::ScheduleCommit() {
405 if (timer_
.IsRunning())
407 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(kCommitIntervalMs
),
408 this, &QuotaDatabase::Commit
);
411 bool QuotaDatabase::FindOriginUsedCount(
412 const GURL
& origin
, StorageType type
, int* used_count
) {
414 if (!LazyOpen(false))
418 "SELECT used_count FROM OriginInfoTable"
419 " WHERE origin = ? AND type = ?";
421 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
422 statement
.BindString(0, origin
.spec());
423 statement
.BindInt(1, static_cast<int>(type
));
425 if (!statement
.Step())
428 *used_count
= statement
.ColumnInt(0);
432 bool QuotaDatabase::LazyOpen(bool create_if_needed
) {
436 // If we tried and failed once, don't try again in the same session
437 // to avoid creating an incoherent mess on disk.
441 bool in_memory_only
= db_file_path_
.empty();
442 if (!create_if_needed
&&
443 (in_memory_only
|| !file_util::PathExists(db_file_path_
))) {
447 db_
.reset(new sql::Connection
);
448 meta_table_
.reset(new sql::MetaTable
);
450 db_
->set_error_histogram_name("Sqlite.Quota.Error");
453 if (in_memory_only
) {
454 opened
= db_
->OpenInMemory();
455 } else if (!file_util::CreateDirectory(db_file_path_
.DirName())) {
456 LOG(ERROR
) << "Failed to create quota database directory.";
458 opened
= db_
->Open(db_file_path_
);
463 if (!opened
|| !EnsureDatabaseVersion()) {
464 LOG(ERROR
) << "Failed to open the quota database.";
471 // Start a long-running transaction.
472 db_
->BeginTransaction();
477 bool QuotaDatabase::EnsureDatabaseVersion() {
478 static const size_t kTableCount
= ARRAYSIZE_UNSAFE(kTables
);
479 static const size_t kIndexCount
= ARRAYSIZE_UNSAFE(kIndexes
);
480 if (!sql::MetaTable::DoesTableExist(db_
.get()))
481 return CreateSchema(db_
.get(), meta_table_
.get(),
482 kCurrentVersion
, kCompatibleVersion
,
483 kTables
, kTableCount
,
484 kIndexes
, kIndexCount
);
486 if (!meta_table_
->Init(db_
.get(), kCurrentVersion
, kCompatibleVersion
))
489 if (meta_table_
->GetCompatibleVersionNumber() > kCurrentVersion
) {
490 LOG(WARNING
) << "Quota database is too new.";
494 if (meta_table_
->GetVersionNumber() < kCurrentVersion
) {
495 if (!UpgradeSchema(meta_table_
->GetVersionNumber()))
496 return ResetSchema();
500 DCHECK(sql::MetaTable::DoesTableExist(db_
.get()));
501 for (size_t i
= 0; i
< kTableCount
; ++i
) {
502 DCHECK(db_
->DoesTableExist(kTables
[i
].table_name
));
510 bool QuotaDatabase::CreateSchema(
511 sql::Connection
* database
,
512 sql::MetaTable
* meta_table
,
513 int schema_version
, int compatible_version
,
514 const TableSchema
* tables
, size_t tables_size
,
515 const IndexSchema
* indexes
, size_t indexes_size
) {
516 // TODO(kinuko): Factor out the common code to create databases.
517 sql::Transaction
transaction(database
);
518 if (!transaction
.Begin())
521 if (!meta_table
->Init(database
, schema_version
, compatible_version
))
524 for (size_t i
= 0; i
< tables_size
; ++i
) {
525 std::string
sql("CREATE TABLE ");
526 sql
+= tables
[i
].table_name
;
527 sql
+= tables
[i
].columns
;
528 if (!database
->Execute(sql
.c_str())) {
529 VLOG(1) << "Failed to execute " << sql
;
534 for (size_t i
= 0; i
< indexes_size
; ++i
) {
536 if (indexes
[i
].unique
)
537 sql
+= "CREATE UNIQUE INDEX ";
539 sql
+= "CREATE INDEX ";
540 sql
+= indexes
[i
].index_name
;
542 sql
+= indexes
[i
].table_name
;
543 sql
+= indexes
[i
].columns
;
544 if (!database
->Execute(sql
.c_str())) {
545 VLOG(1) << "Failed to execute " << sql
;
550 return transaction
.Commit();
553 bool QuotaDatabase::ResetSchema() {
554 DCHECK(!db_file_path_
.empty());
555 DCHECK(file_util::PathExists(db_file_path_
));
556 VLOG(1) << "Deleting existing quota data and starting over.";
561 if (!file_util::Delete(db_file_path_
, true))
564 // Make sure the steps above actually deleted things.
565 if (file_util::PathExists(db_file_path_
))
568 // So we can't go recursive.
572 base::AutoReset
<bool> auto_reset(&is_recreating_
, true);
573 return LazyOpen(true);
576 bool QuotaDatabase::UpgradeSchema(int current_version
) {
577 if (current_version
== 2) {
578 QuotaTableImporter importer
;
579 typedef std::vector
<QuotaTableEntry
> QuotaTableEntries
;
580 if (!DumpQuotaTable(new QuotaTableCallback(base::Bind(
581 &QuotaTableImporter::Append
, base::Unretained(&importer
)))))
584 for (QuotaTableEntries::const_iterator iter
= importer
.entries
.begin();
585 iter
!= importer
.entries
.end(); ++iter
) {
586 if (!SetHostQuota(iter
->host
, iter
->type
, iter
->quota
))
595 bool QuotaDatabase::DumpQuotaTable(QuotaTableCallback
* callback
) {
596 scoped_ptr
<QuotaTableCallback
> callback_deleter(callback
);
600 const char* kSql
= "SELECT * FROM HostQuotaTable";
601 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
603 while (statement
.Step()) {
604 QuotaTableEntry entry
= QuotaTableEntry(
605 statement
.ColumnString(0),
606 static_cast<StorageType
>(statement
.ColumnInt(1)),
607 statement
.ColumnInt64(2));
609 if (!callback
->Run(entry
))
613 return statement
.Succeeded();
616 bool QuotaDatabase::DumpOriginInfoTable(
617 OriginInfoTableCallback
* callback
) {
618 scoped_ptr
<OriginInfoTableCallback
> callback_deleter(callback
);
623 const char* kSql
= "SELECT * FROM OriginInfoTable";
624 sql::Statement
statement(db_
->GetCachedStatement(SQL_FROM_HERE
, kSql
));
626 while (statement
.Step()) {
627 OriginInfoTableEntry
entry(
628 GURL(statement
.ColumnString(0)),
629 static_cast<StorageType
>(statement
.ColumnInt(1)),
630 statement
.ColumnInt(2),
631 base::Time::FromInternalValue(statement
.ColumnInt64(3)),
632 base::Time::FromInternalValue(statement
.ColumnInt64(4)));
634 if (!callback
->Run(entry
))
638 return statement
.Succeeded();
641 bool operator<(const QuotaDatabase::QuotaTableEntry
& lhs
,
642 const QuotaDatabase::QuotaTableEntry
& rhs
) {
643 if (lhs
.host
< rhs
.host
) return true;
644 if (rhs
.host
< lhs
.host
) return false;
645 if (lhs
.type
< rhs
.type
) return true;
646 if (rhs
.type
< lhs
.type
) return false;
647 return lhs
.quota
< rhs
.quota
;
650 bool operator<(const QuotaDatabase::OriginInfoTableEntry
& lhs
,
651 const QuotaDatabase::OriginInfoTableEntry
& rhs
) {
652 if (lhs
.origin
< rhs
.origin
) return true;
653 if (rhs
.origin
< lhs
.origin
) return false;
654 if (lhs
.type
< rhs
.type
) return true;
655 if (rhs
.type
< lhs
.type
) return false;
656 if (lhs
.used_count
< rhs
.used_count
) return true;
657 if (rhs
.used_count
< lhs
.used_count
) return false;
658 return lhs
.last_access_time
< rhs
.last_access_time
;