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..90ad5695af216a4f3aa076e8fde3b15a8578f1ac |
--- /dev/null |
+++ b/webkit/dom_storage/dom_storage_database.cc |
@@ -0,0 +1,192 @@ |
+// 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" |
+ |
+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) { |
+ DCHECK(!file_path_.empty()); |
+} |
+ |
+void DomStorageDatabase::ReadAllValues( |
+ DomStorageDatabase::ValuesMap* result) { |
+ if (!LazyOpen(false)) |
+ return; |
+ |
+ sql::Statement stmt(db_->GetCachedStatement(SQL_FROM_HERE, |
+ "SELECT * from ItemTable")); |
+ DCHECK(stmt.is_valid()); |
+ if (!stmt) |
michaeln
2012/02/01 21:03:11
i'm looking at http://codereview.chromium.org/9249
benm (inactive)
2012/02/02 12:14:49
Removed the early returns but kept DCHECK, as I th
|
+ return; |
+ |
+ while (stmt.Step()) { |
+ string16 key = stmt.ColumnString16(0); |
+ string16 value; |
+ stmt.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(); |
+ sql::Statement stmt; |
michaeln
2012/02/01 21:03:11
can this be moved into the body of the for loop?
a
benm (inactive)
2012/02/02 12:14:49
Done, updated stmt everywhere
|
+ for(; it != changes.end(); ++it) { |
+ string16 key = it->first; |
+ NullableString16 value = it->second; |
+ if (value.is_null()) { |
+ stmt.Assign(db_->GetCachedStatement(SQL_FROM_HERE, |
+ "DELETE FROM ItemTable WHERE key=?")); |
+ stmt.BindString16(0, key); |
+ } else { |
+ stmt.Assign(db_->GetCachedStatement(SQL_FROM_HERE, |
+ "INSERT INTO ItemTable VALUES (?,?)")); |
+ stmt.BindString16(0, key); |
+ string16 value_str = value.string(); |
michaeln
2012/02/01 21:03:11
can the local be removed to avoid making a copy
benm (inactive)
2012/02/02 12:14:49
Done.
|
+ stmt.BindBlob(1, value_str.data(), value_str.length() * sizeof(char16)); |
+ } |
+ DCHECK(stmt.is_valid()); |
+ stmt.Run(); |
+ } |
+ return transaction.Commit(); |
+} |
+ |
+bool DomStorageDatabase::LazyOpen(bool create_if_needed) { |
+ 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 (!db_->Open(file_path_)) { |
+ LOG(WARNING) << "Unable to open DOM storage database at " |
michaeln
2012/02/01 21:03:11
what should we do at this point?
benm (inactive)
2012/02/02 12:14:49
Good question :) My thinking was that it's ok to r
michaeln
2012/02/03 04:45:15
The caller is every method that may want to read o
benm (inactive)
2012/02/03 11:46:40
Good point, added a TODO to handle this failure to
|
+ << file_path_.value(); |
+ return false; |
+ } |
+ |
+ // 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 (!db_->DoesTableExist("ItemTable")) |
+ return CreateTable(); |
+ |
+ // Table exists, so ensure we're at the right version, upgrading if |
+ // necessary. |
+ if (UpgradeVersion1To2IfNeeded()) |
+ return true; |
+ |
+ // Upgrade failed, drop table and start fresh. |
+ if (db_->Execute("DROP TABLE ItemTable")) |
+ return CreateTable(); |
+ |
+ return false; |
michaeln
2012/02/01 21:03:11
upon return, will IsOpen() report 'true' since the
benm (inactive)
2012/02/02 12:14:49
good call
|
+} |
+ |
+bool DomStorageDatabase::CreateTable() { |
+ // Current version is 2. |
+ return CreateTableV2(); |
michaeln
2012/02/01 21:03:11
doesn't look like we really need two separate meth
benm (inactive)
2012/02/02 12:14:49
My thinking was that it's useful to have a specifi
michaeln
2012/02/03 04:45:15
Still don't see what value the indirection adds. T
benm (inactive)
2012/02/03 11:46:40
My thinking is that if we introduce a V3 database
|
+} |
+ |
+bool DomStorageDatabase::CreateTableV2() { |
+ if (!LazyOpen(false)) |
michaeln
2012/02/01 21:03:11
should this method DCHECK that the db_ connection
benm (inactive)
2012/02/02 12:14:49
sgtm
|
+ return false; |
+ |
+ return db_->Execute( |
+ "CREATE TABLE IF NOT EXISTS ItemTable (" |
+ "key TEXT UNIQUE ON CONFLICT REPLACE, " |
+ "value BLOB NOT NULL ON CONFLICT FAIL)"); |
+} |
+ |
+bool DomStorageDatabase::UpgradeVersion1To2IfNeeded() { |
+ sql::Statement stmt(db_->GetCachedStatement(SQL_FROM_HERE, |
+ "SELECT * FROM ItemTable")); |
+ DCHECK(stmt.is_valid()); |
+ if (!stmt) |
+ return false; |
+ |
+ // 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 valueColumnType = stmt.DeclaredColumnType(1); |
michaeln
2012/02/01 21:03:11
nit: nix camelCase variable naming
benm (inactive)
2012/02/02 12:14:49
Done.
|
+ if (valueColumnType == sql::COLUMN_TYPE_BLOB) |
+ return true; |
+ |
+ if (valueColumnType != sql::COLUMN_TYPE_TEXT) { |
+ // Something is messed up. This is not a V1 database. |
michaeln
2012/02/01 21:03:11
what should we do at this point? delete it and sta
benm (inactive)
2012/02/02 12:14:49
Yeah, that is taken care of by the LazyOpen functi
michaeln
2012/02/03 04:45:15
LazyOpen continues to try to work with the existin
benm (inactive)
2012/02/03 11:46:40
Good point. However I don't think that we can get
|
+ return false; |
+ } |
+ |
+ // Need to migrate from TEXT value column to BLOB. |
+ ValuesMap values; |
+ while (stmt.Step()) { |
+ string16 key = stmt.ColumnString16(0); |
+ string16 value = stmt.ColumnString16(1); |
+ values[key] = NullableString16(value, false); |
+ } |
+ |
+ sql::Transaction migration(db_.get()); |
+ if (migration.Begin()) { |
michaeln
2012/02/01 21:03:11
prefer early returns if (!begin()) return false
benm (inactive)
2012/02/02 12:14:49
Done.
|
+ if (db_->Execute("DROP TABLE ItemTable")) { |
+ CreateTableV2(); |
+ if (CommitChanges(false, values)) { |
+ return migration.Commit(); |
+ } |
+ } |
+ } |
+ return false; |
+} |
+ |
+void DomStorageDatabase::Close() { |
+ if (!IsOpen()) |
+ return; |
+ |
+ db_->Close(); |
+ db_.reset(NULL); |
+} |
+ |
+} // namespace dom_storage |