Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(681)

Unified Diff: webkit/dom_storage/dom_storage_database.cc

Issue 9159020: Create a class to represent a DOM Storage Database. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Tidy up. Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..a6bba7bf3b0fc397d6ad5171fb734c1473ebd666
--- /dev/null
+++ b/webkit/dom_storage/dom_storage_database.cc
@@ -0,0 +1,254 @@
+// 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(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.";
michaeln 2012/02/08 06:11:33 this could probably be a NOTREACHED() instead
benm (inactive) 2012/02/08 14:59:40 Done.
+ 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();
michaeln 2012/02/08 06:11:33 i still don't see the reason for two CreateTable m
benm (inactive) 2012/02/08 14:59:40 OK, done.
+}
+
+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

Powered by Google App Engine
This is Rietveld 408576698