| Index: webkit/dom_storage/dom_storage_database.cc
|
| diff --git a/webkit/dom_storage/dom_storage_database.cc b/webkit/dom_storage/dom_storage_database.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..320dd93b16f2efd9a053eb8758e1a109047c940b
|
| --- /dev/null
|
| +++ b/webkit/dom_storage/dom_storage_database.cc
|
| @@ -0,0 +1,255 @@
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "webkit/dom_storage/dom_storage_database.h"
|
| +
|
| +#include "base/file_util.h"
|
| +#include "base/logging.h"
|
| +#include "sql/diagnostic_error_delegate.h"
|
| +#include "sql/statement.h"
|
| +#include "sql/transaction.h"
|
| +#include "third_party/sqlite/sqlite3.h"
|
| +
|
| +namespace {
|
| +
|
| +class HistogramUniquifier {
|
| + public:
|
| + static const char* name() { return "Sqlite.DomStorageDatabase.Error"; }
|
| +};
|
| +
|
| +sql::ErrorDelegate* GetErrorHandlerForDomStorageDatabase() {
|
| + return new sql::DiagnosticErrorDelegate<HistogramUniquifier>();
|
| +}
|
| +
|
| +} // anon namespace
|
| +
|
| +namespace dom_storage {
|
| +
|
| +DomStorageDatabase::DomStorageDatabase(const FilePath& file_path)
|
| + : file_path_(file_path),
|
| + db_(NULL),
|
| + failed_to_open_(false),
|
| + tried_to_recreate_(false) {
|
| + // Note: in normal use we should never get an empty backing path here.
|
| + // However, the unit test for this class defines another constructor
|
| + // that will bypass this check to allow an empty path that signifies
|
| + // we should operate on an in-memory database for performance/reliability
|
| + // reasons.
|
| + DCHECK(!file_path_.empty());
|
| +}
|
| +
|
| +DomStorageDatabase::~DomStorageDatabase() {
|
| +}
|
| +
|
| +void DomStorageDatabase::ReadAllValues(
|
| + DomStorageDatabase::ValuesMap* result) {
|
| + if (!LazyOpen(false))
|
| + return;
|
| +
|
| + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
|
| + "SELECT * from ItemTable"));
|
| + DCHECK(statement.is_valid());
|
| +
|
| + while (statement.Step()) {
|
| + string16 key = statement.ColumnString16(0);
|
| + string16 value;
|
| + statement.ColumnBlobAsString16(1, &value);
|
| + (*result)[key] = NullableString16(value, false);
|
| + }
|
| +}
|
| +
|
| +bool DomStorageDatabase::CommitChanges(bool clear_all_first,
|
| + const ValuesMap& changes) {
|
| + if (!LazyOpen(!changes.empty()))
|
| + return false;
|
| +
|
| + sql::Transaction transaction(db_.get());
|
| + if (!transaction.Begin())
|
| + return false;
|
| +
|
| + if (clear_all_first) {
|
| + if (!db_->Execute("DELETE FROM ItemTable"))
|
| + return false;
|
| + }
|
| +
|
| + ValuesMap::const_iterator it = changes.begin();
|
| + for(; it != changes.end(); ++it) {
|
| + sql::Statement statement;
|
| + string16 key = it->first;
|
| + NullableString16 value = it->second;
|
| + if (value.is_null()) {
|
| + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE,
|
| + "DELETE FROM ItemTable WHERE key=?"));
|
| + statement.BindString16(0, key);
|
| + } else {
|
| + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE,
|
| + "INSERT INTO ItemTable VALUES (?,?)"));
|
| + statement.BindString16(0, key);
|
| + statement.BindBlob(1, value.string().data(),
|
| + value.string().length() * sizeof(char16));
|
| + }
|
| + DCHECK(statement.is_valid());
|
| + statement.Run();
|
| + }
|
| + return transaction.Commit();
|
| +}
|
| +
|
| +bool DomStorageDatabase::LazyOpen(bool create_if_needed) {
|
| + if (failed_to_open_) {
|
| + // Don't try to open a database that we know has failed
|
| + // already.
|
| + return false;
|
| + }
|
| +
|
| + if (IsOpen())
|
| + return true;
|
| +
|
| + bool database_exists = file_util::PathExists(file_path_);
|
| +
|
| + if (!database_exists && !create_if_needed) {
|
| + // If the file doesn't exist already and we haven't been asked to create
|
| + // a file on disk, then we don't bother opening the database. This means
|
| + // we wait until we absolutely need to put something onto disk before we
|
| + // do so.
|
| + return false;
|
| + }
|
| +
|
| + db_.reset(new sql::Connection());
|
| + db_->set_error_delegate(GetErrorHandlerForDomStorageDatabase());
|
| +
|
| + if (file_path_.empty()) {
|
| + // This code path should only be triggered by unit tests.
|
| + if (!db_->OpenInMemory()) {
|
| + LOG(ERROR) << "Unable to open DOM storage database in memory.";
|
| + failed_to_open_ = true;
|
| + return false;
|
| + }
|
| + } else {
|
| + if (!db_->Open(file_path_)) {
|
| + LOG(ERROR) << "Unable to open DOM storage database at "
|
| + << file_path_.value()
|
| + << " error: " << db_->GetErrorMessage();
|
| + if (database_exists && !tried_to_recreate_)
|
| + return DeleteFileAndRecreate();
|
| + failed_to_open_ = true;
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + // Open() may succeed even if the file we try and open is not a database so
|
| + // we check here that what we've got is something sensible.
|
| + // TODO(benm): It might be useful to actually verify the output of the
|
| + // quick_check too and try to recover in the case errors are detected.
|
| + // However, in the case that the database is corrupted to the point that
|
| + // SQLite doesn't actually think it's a database,
|
| + // sql::Connection::GetCachedStatement will DCHECK.
|
| + if (db_->ExecuteAndReturnErrorCode("PRAGMA quick_check(1)") != SQLITE_OK) {
|
| + Close();
|
| + return DeleteFileAndRecreate();
|
| + }
|
| +
|
| + // sql::Connection uses UTF-8 encoding, but WebCore style databases use
|
| + // UTF-16, so ensure we match.
|
| + ignore_result(db_->Execute("PRAGMA encoding=\"UTF-16\""));
|
| +
|
| + // If the table doesn't exist, try to create it at the current version.
|
| + if (!db_->DoesTableExist("ItemTable")) {
|
| + if (CreateTable())
|
| + return true;
|
| + // Couldn't create the table.
|
| + Close();
|
| + return DeleteFileAndRecreate();
|
| + }
|
| +
|
| + // Table exists, so ensure we're at the right version, upgrading if
|
| + // necessary.
|
| + if (UpgradeVersion1To2IfNeeded())
|
| + return true;
|
| +
|
| + // We were not able to verify that the database is at the correct version
|
| + // so close the connection and attempt to recreate the database from
|
| + // scratch.
|
| + Close();
|
| + return DeleteFileAndRecreate();
|
| +}
|
| +
|
| +bool DomStorageDatabase::CreateTable() {
|
| + DCHECK(IsOpen());
|
| + // Current version is 2.
|
| + return CreateTableV2();
|
| +}
|
| +
|
| +bool DomStorageDatabase::CreateTableV2() {
|
| + DCHECK(IsOpen());
|
| +
|
| + return db_->Execute(
|
| + "CREATE TABLE IF NOT EXISTS ItemTable ("
|
| + "key TEXT UNIQUE ON CONFLICT REPLACE, "
|
| + "value BLOB NOT NULL ON CONFLICT FAIL)");
|
| +}
|
| +
|
| +bool DomStorageDatabase::DeleteFileAndRecreate() {
|
| + DCHECK(!IsOpen());
|
| + DCHECK(file_util::PathExists(file_path_));
|
| +
|
| + // We should only try and do this once.
|
| + if (tried_to_recreate_)
|
| + return false;
|
| +
|
| + tried_to_recreate_ = true;
|
| +
|
| + // If it's not a directory and we can delete the file, try and open it again.
|
| + if (!file_util::DirectoryExists(file_path_) &&
|
| + file_util::Delete(file_path_, false))
|
| + return LazyOpen(true);
|
| +
|
| + failed_to_open_ = true;
|
| + return false;
|
| +}
|
| +
|
| +bool DomStorageDatabase::UpgradeVersion1To2IfNeeded() {
|
| + DCHECK(IsOpen());
|
| + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
|
| + "SELECT * FROM ItemTable"));
|
| + DCHECK(statement.is_valid());
|
| +
|
| + // Quick check to see if we need to upgrade or not. The single
|
| + // effect of V1 -> V2 is to change the value column from type
|
| + // TEXT to type BLOB.
|
| + sql::ColType value_column_type = statement.DeclaredColumnType(1);
|
| + if (value_column_type == sql::COLUMN_TYPE_BLOB)
|
| + return true;
|
| +
|
| + if (value_column_type != sql::COLUMN_TYPE_TEXT) {
|
| + // Something is messed up. This is not a V1 database.
|
| + return false;
|
| + }
|
| +
|
| + // Need to migrate from TEXT value column to BLOB.
|
| + // Store the current database content so we can re-insert
|
| + // the data into the new V2 table.
|
| + ValuesMap values;
|
| + while (statement.Step()) {
|
| + string16 key = statement.ColumnString16(0);
|
| + NullableString16 value(statement.ColumnString16(1), false);
|
| + values[key] = value;
|
| + }
|
| +
|
| + sql::Transaction migration(db_.get());
|
| + if (!migration.Begin())
|
| + return false;
|
| +
|
| + if (db_->Execute("DROP TABLE ItemTable")) {
|
| + CreateTableV2();
|
| + if (CommitChanges(false, values))
|
| + return migration.Commit();
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +void DomStorageDatabase::Close() {
|
| + db_.reset(NULL);
|
| +}
|
| +
|
| +} // namespace dom_storage
|
|
|