Include all dupe types (event when value is zero) in scan stats.
[chromium-blink-merge.git] / storage / browser / quota / quota_database.cc
blob85e0000645c35c8d3dac722c82d997a69ebcb6c2
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"
7 #include <vector>
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"
18 namespace storage {
19 namespace {
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
40 // static
41 const char QuotaDatabase::kDesiredAvailableSpaceKey[] = "DesiredAvailableSpace";
42 const char QuotaDatabase::kTemporaryQuotaOverrideKey[] =
43 "TemporaryQuotaOverride";
45 const QuotaDatabase::TableSchema QuotaDatabase::kTables[] = {
46 { kHostQuotaTable,
47 "(host TEXT NOT NULL,"
48 " type INTEGER NOT NULL,"
49 " quota INTEGER DEFAULT 0,"
50 " UNIQUE(host, type))" },
51 { kOriginInfoTable,
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))" },
60 // static
61 const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = {
62 { "HostIndex",
63 kHostQuotaTable,
64 "(host)",
65 false },
66 { "OriginInfoIndex",
67 kOriginInfoTable,
68 "(origin)",
69 false },
70 { "OriginLastAccessTimeIndex",
71 kOriginInfoTable,
72 "(last_access_time)",
73 false },
74 { "OriginLastModifiedTimeIndex",
75 kOriginInfoTable,
76 "(last_modified_time)",
77 false },
80 struct QuotaDatabase::QuotaTableImporter {
81 bool Append(const QuotaTableEntry& entry) {
82 entries.push_back(entry);
83 return true;
85 std::vector<QuotaTableEntry> entries;
88 // Clang requires explicit out-of-line constructors for them.
89 QuotaDatabase::QuotaTableEntry::QuotaTableEntry()
90 : type(kStorageTypeUnknown),
91 quota(0) {
94 QuotaDatabase::QuotaTableEntry::QuotaTableEntry(
95 const std::string& host,
96 StorageType type,
97 int64 quota)
98 : host(host),
99 type(type),
100 quota(quota) {
103 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry()
104 : type(kStorageTypeUnknown),
105 used_count(0) {
108 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry(
109 const GURL& origin,
110 StorageType type,
111 int used_count,
112 const base::Time& last_access_time,
113 const base::Time& last_modified_time)
114 : origin(origin),
115 type(type),
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() {
129 if (db_) {
130 db_->CommitTransaction();
134 void QuotaDatabase::CloseConnection() {
135 meta_table_.reset();
136 db_.reset();
139 bool QuotaDatabase::GetHostQuota(
140 const std::string& host, StorageType type, int64* quota) {
141 DCHECK(quota);
142 if (!LazyOpen(false))
143 return false;
145 const char* kSql =
146 "SELECT quota"
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())
155 return false;
157 *quota = statement.ColumnInt64(0);
158 return true;
161 bool QuotaDatabase::SetHostQuota(
162 const std::string& host, StorageType type, int64 quota) {
163 DCHECK_GE(quota, 0);
164 if (!LazyOpen(true))
165 return false;
167 const char* kSql =
168 "INSERT OR REPLACE INTO HostQuotaTable"
169 " (quota, host, type)"
170 " VALUES (?, ?, ?)";
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())
177 return false;
179 ScheduleCommit();
180 return true;
183 bool QuotaDatabase::SetOriginLastAccessTime(
184 const GURL& origin, StorageType type, base::Time last_access_time) {
185 if (!LazyOpen(true))
186 return false;
188 sql::Statement statement;
190 int used_count = 1;
191 if (FindOriginUsedCount(origin, type, &used_count)) {
192 ++used_count;
193 const char* kSql =
194 "UPDATE OriginInfoTable"
195 " SET used_count = ?, last_access_time = ?"
196 " WHERE origin = ? AND type = ?";
197 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
198 } else {
199 const char* 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())
211 return false;
213 ScheduleCommit();
214 return true;
217 bool QuotaDatabase::SetOriginLastModifiedTime(
218 const GURL& origin, StorageType type, base::Time last_modified_time) {
219 if (!LazyOpen(true))
220 return false;
222 sql::Statement statement;
224 int dummy;
225 if (FindOriginUsedCount(origin, type, &dummy)) {
226 const char* kSql =
227 "UPDATE OriginInfoTable"
228 " SET last_modified_time = ?"
229 " WHERE origin = ? AND type = ?";
230 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
231 } else {
232 const char* 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())
242 return false;
244 ScheduleCommit();
245 return true;
248 bool QuotaDatabase::RegisterInitialOriginInfo(
249 const std::set<GURL>& origins, StorageType type) {
250 if (!LazyOpen(true))
251 return false;
253 typedef std::set<GURL>::const_iterator itr_type;
254 for (itr_type itr = origins.begin(), end = origins.end();
255 itr != end; ++itr) {
256 const char* kSql =
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())
264 return false;
267 ScheduleCommit();
268 return true;
271 bool QuotaDatabase::DeleteHostQuota(
272 const std::string& host, StorageType type) {
273 if (!LazyOpen(false))
274 return false;
276 const char* kSql =
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())
285 return false;
287 ScheduleCommit();
288 return true;
291 bool QuotaDatabase::DeleteOriginInfo(
292 const GURL& origin, StorageType type) {
293 if (!LazyOpen(false))
294 return false;
296 const char* kSql =
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())
305 return false;
307 ScheduleCommit();
308 return true;
311 bool QuotaDatabase::GetQuotaConfigValue(const char* key, int64* value) {
312 if (!LazyOpen(false))
313 return false;
314 DCHECK(VerifyValidQuotaConfig(key));
315 return meta_table_->GetValue(key, value);
318 bool QuotaDatabase::SetQuotaConfigValue(const char* key, int64 value) {
319 if (!LazyOpen(true))
320 return false;
321 DCHECK(VerifyValidQuotaConfig(key));
322 return meta_table_->SetValue(key, value);
325 bool QuotaDatabase::GetLRUOrigin(
326 StorageType type,
327 const std::set<GURL>& exceptions,
328 SpecialStoragePolicy* special_storage_policy,
329 GURL* origin) {
330 DCHECK(origin);
331 if (!LazyOpen(false))
332 return false;
334 const char* kSql = "SELECT origin FROM OriginInfoTable"
335 " WHERE type = ?"
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())
344 continue;
345 if (special_storage_policy &&
346 special_storage_policy->IsStorageUnlimited(url))
347 continue;
348 *origin = url;
349 return true;
352 *origin = GURL();
353 return statement.Succeeded();
356 bool QuotaDatabase::GetOriginsModifiedSince(
357 StorageType type, std::set<GURL>* origins, base::Time modified_since) {
358 DCHECK(origins);
359 if (!LazyOpen(false))
360 return false;
362 const char* kSql = "SELECT origin FROM OriginInfoTable"
363 " WHERE type = ? AND last_modified_time >= ?";
365 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
366 statement.BindInt(0, static_cast<int>(type));
367 statement.BindInt64(1, modified_since.ToInternalValue());
369 origins->clear();
370 while (statement.Step())
371 origins->insert(GURL(statement.ColumnString(0)));
373 return statement.Succeeded();
376 bool QuotaDatabase::IsOriginDatabaseBootstrapped() {
377 if (!LazyOpen(true))
378 return false;
380 int flag = 0;
381 return meta_table_->GetValue(kIsOriginTableBootstrapped, &flag) && flag;
384 bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag) {
385 if (!LazyOpen(true))
386 return false;
388 return meta_table_->SetValue(kIsOriginTableBootstrapped, bootstrap_flag);
391 void QuotaDatabase::Commit() {
392 if (!db_)
393 return;
395 if (timer_.IsRunning())
396 timer_.Stop();
398 db_->CommitTransaction();
399 db_->BeginTransaction();
402 void QuotaDatabase::ScheduleCommit() {
403 if (timer_.IsRunning())
404 return;
405 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCommitIntervalMs),
406 this, &QuotaDatabase::Commit);
409 bool QuotaDatabase::FindOriginUsedCount(
410 const GURL& origin, StorageType type, int* used_count) {
411 DCHECK(used_count);
412 if (!LazyOpen(false))
413 return false;
415 const char* kSql =
416 "SELECT used_count FROM OriginInfoTable"
417 " WHERE origin = ? AND type = ?";
419 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
420 statement.BindString(0, origin.spec());
421 statement.BindInt(1, static_cast<int>(type));
423 if (!statement.Step())
424 return false;
426 *used_count = statement.ColumnInt(0);
427 return true;
430 bool QuotaDatabase::LazyOpen(bool create_if_needed) {
431 if (db_)
432 return true;
434 // If we tried and failed once, don't try again in the same session
435 // to avoid creating an incoherent mess on disk.
436 if (is_disabled_)
437 return false;
439 bool in_memory_only = db_file_path_.empty();
440 if (!create_if_needed &&
441 (in_memory_only || !base::PathExists(db_file_path_))) {
442 return false;
445 db_.reset(new sql::Connection);
446 meta_table_.reset(new sql::MetaTable);
448 db_->set_histogram_tag("Quota");
450 bool opened = false;
451 if (in_memory_only) {
452 opened = db_->OpenInMemory();
453 } else if (!base::CreateDirectory(db_file_path_.DirName())) {
454 LOG(ERROR) << "Failed to create quota database directory.";
455 } else {
456 opened = db_->Open(db_file_path_);
457 if (opened)
458 db_->Preload();
461 if (!opened || !EnsureDatabaseVersion()) {
462 LOG(ERROR) << "Failed to open the quota database.";
463 is_disabled_ = true;
464 db_.reset();
465 meta_table_.reset();
466 return false;
469 // Start a long-running transaction.
470 db_->BeginTransaction();
472 return true;
475 bool QuotaDatabase::EnsureDatabaseVersion() {
476 static const size_t kTableCount = arraysize(kTables);
477 static const size_t kIndexCount = arraysize(kIndexes);
478 if (!sql::MetaTable::DoesTableExist(db_.get()))
479 return CreateSchema(db_.get(), meta_table_.get(),
480 kCurrentVersion, kCompatibleVersion,
481 kTables, kTableCount,
482 kIndexes, kIndexCount);
484 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
485 return false;
487 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
488 LOG(WARNING) << "Quota database is too new.";
489 return false;
492 if (meta_table_->GetVersionNumber() < kCurrentVersion) {
493 if (!UpgradeSchema(meta_table_->GetVersionNumber()))
494 return ResetSchema();
497 #ifndef NDEBUG
498 DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
499 for (size_t i = 0; i < kTableCount; ++i) {
500 DCHECK(db_->DoesTableExist(kTables[i].table_name));
502 #endif
504 return true;
507 // static
508 bool QuotaDatabase::CreateSchema(
509 sql::Connection* database,
510 sql::MetaTable* meta_table,
511 int schema_version, int compatible_version,
512 const TableSchema* tables, size_t tables_size,
513 const IndexSchema* indexes, size_t indexes_size) {
514 // TODO(kinuko): Factor out the common code to create databases.
515 sql::Transaction transaction(database);
516 if (!transaction.Begin())
517 return false;
519 if (!meta_table->Init(database, schema_version, compatible_version))
520 return false;
522 for (size_t i = 0; i < tables_size; ++i) {
523 std::string sql("CREATE TABLE ");
524 sql += tables[i].table_name;
525 sql += tables[i].columns;
526 if (!database->Execute(sql.c_str())) {
527 VLOG(1) << "Failed to execute " << sql;
528 return false;
532 for (size_t i = 0; i < indexes_size; ++i) {
533 std::string sql;
534 if (indexes[i].unique)
535 sql += "CREATE UNIQUE INDEX ";
536 else
537 sql += "CREATE INDEX ";
538 sql += indexes[i].index_name;
539 sql += " ON ";
540 sql += indexes[i].table_name;
541 sql += indexes[i].columns;
542 if (!database->Execute(sql.c_str())) {
543 VLOG(1) << "Failed to execute " << sql;
544 return false;
548 return transaction.Commit();
551 bool QuotaDatabase::ResetSchema() {
552 DCHECK(!db_file_path_.empty());
553 DCHECK(base::PathExists(db_file_path_));
554 VLOG(1) << "Deleting existing quota data and starting over.";
556 db_.reset();
557 meta_table_.reset();
559 if (!sql::Connection::Delete(db_file_path_))
560 return false;
562 // So we can't go recursive.
563 if (is_recreating_)
564 return false;
566 base::AutoReset<bool> auto_reset(&is_recreating_, true);
567 return LazyOpen(true);
570 bool QuotaDatabase::UpgradeSchema(int current_version) {
571 if (current_version == 2) {
572 QuotaTableImporter importer;
573 typedef std::vector<QuotaTableEntry> QuotaTableEntries;
574 if (!DumpQuotaTable(base::Bind(&QuotaTableImporter::Append,
575 base::Unretained(&importer)))) {
576 return false;
578 ResetSchema();
579 for (QuotaTableEntries::const_iterator iter = importer.entries.begin();
580 iter != importer.entries.end(); ++iter) {
581 if (!SetHostQuota(iter->host, iter->type, iter->quota))
582 return false;
584 Commit();
585 return true;
587 return false;
590 bool QuotaDatabase::DumpQuotaTable(const QuotaTableCallback& callback) {
591 if (!LazyOpen(true))
592 return false;
594 const char* kSql = "SELECT * FROM HostQuotaTable";
595 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
597 while (statement.Step()) {
598 QuotaTableEntry entry = QuotaTableEntry(
599 statement.ColumnString(0),
600 static_cast<StorageType>(statement.ColumnInt(1)),
601 statement.ColumnInt64(2));
603 if (!callback.Run(entry))
604 return true;
607 return statement.Succeeded();
610 bool QuotaDatabase::DumpOriginInfoTable(
611 const OriginInfoTableCallback& callback) {
613 if (!LazyOpen(true))
614 return false;
616 const char* kSql = "SELECT * FROM OriginInfoTable";
617 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
619 while (statement.Step()) {
620 OriginInfoTableEntry entry(
621 GURL(statement.ColumnString(0)),
622 static_cast<StorageType>(statement.ColumnInt(1)),
623 statement.ColumnInt(2),
624 base::Time::FromInternalValue(statement.ColumnInt64(3)),
625 base::Time::FromInternalValue(statement.ColumnInt64(4)));
627 if (!callback.Run(entry))
628 return true;
631 return statement.Succeeded();
634 bool operator<(const QuotaDatabase::QuotaTableEntry& lhs,
635 const QuotaDatabase::QuotaTableEntry& rhs) {
636 if (lhs.host < rhs.host) return true;
637 if (rhs.host < lhs.host) return false;
638 if (lhs.type < rhs.type) return true;
639 if (rhs.type < lhs.type) return false;
640 return lhs.quota < rhs.quota;
643 bool operator<(const QuotaDatabase::OriginInfoTableEntry& lhs,
644 const QuotaDatabase::OriginInfoTableEntry& rhs) {
645 if (lhs.origin < rhs.origin) return true;
646 if (rhs.origin < lhs.origin) return false;
647 if (lhs.type < rhs.type) return true;
648 if (rhs.type < lhs.type) return false;
649 if (lhs.used_count < rhs.used_count) return true;
650 if (rhs.used_count < lhs.used_count) return false;
651 return lhs.last_access_time < rhs.last_access_time;
654 } // namespace storage