summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authorbenm@chromium.org <benm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-10 13:31:59 +0000
committerbenm@chromium.org <benm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-10 13:31:59 +0000
commit98b6f8b1b8f96602e74d431eff2296c582b01275 (patch)
tree9074ed279c02247caca0e58bd28f720fa350e484 /webkit
parent9517c6326e123542fab29f20874dcdd8d48d7f83 (diff)
downloadchromium_src-98b6f8b1b8f96602e74d431eff2296c582b01275.zip
chromium_src-98b6f8b1b8f96602e74d431eff2296c582b01275.tar.gz
chromium_src-98b6f8b1b8f96602e74d431eff2296c582b01275.tar.bz2
Create a class to represent a Dom Storage Database.
Add a class that can read/write a local storage database in the same format as WebCore's StorageArea. Also add a method to sql::Statement to query the declared type of a column and a method to return a string16 from a Blob. BUG=106763 Review URL: http://codereview.chromium.org/9159020 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@121442 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/data/dom_storage/README.txt4
-rw-r--r--webkit/data/dom_storage/webcore_test_database.localstorage0
-rw-r--r--webkit/dom_storage/dom_storage_database.cc260
-rw-r--r--webkit/dom_storage/dom_storage_database.h113
-rw-r--r--webkit/dom_storage/dom_storage_database_unittest.cc329
-rw-r--r--webkit/dom_storage/webkit_dom_storage.gypi2
-rw-r--r--webkit/tools/test_shell/test_shell.gypi3
7 files changed, 710 insertions, 1 deletions
diff --git a/webkit/data/dom_storage/README.txt b/webkit/data/dom_storage/README.txt
new file mode 100644
index 0000000..6b6e37e
--- /dev/null
+++ b/webkit/data/dom_storage/README.txt
@@ -0,0 +1,4 @@
+webcore_test_database.localstorage - This is a sample local storage database
+ generated by Chromium 17.0.963.38 beta (using the WebCore DOM Storage
+ backend). It is used in webkit/dom_storage/dom_storage_database_unittest.cc
+ to verify that the Chromium backend can read a WebCore generated database.
diff --git a/webkit/data/dom_storage/webcore_test_database.localstorage b/webkit/data/dom_storage/webcore_test_database.localstorage
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webkit/data/dom_storage/webcore_test_database.localstorage
diff --git a/webkit/dom_storage/dom_storage_database.cc b/webkit/dom_storage/dom_storage_database.cc
new file mode 100644
index 0000000..673ddde
--- /dev/null
+++ b/webkit/dom_storage/dom_storage_database.cc
@@ -0,0 +1,260 @@
+// 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()) {
+ NOTREACHED() << "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;
+ }
+ }
+
+ // 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 (!database_exists) {
+ // This is a new database, create the table and we're done!
+ if (CreateTableV2())
+ return true;
+ } else {
+ // The database exists already - check if we need to upgrade
+ // and whether it's usable (i.e. not corrupted).
+ SchemaVersion current_version = DetectSchemaVersion();
+
+ if (current_version == V2) {
+ return true;
+ } else if (current_version == V1) {
+ if (UpgradeVersion1To2())
+ return true;
+ }
+ }
+
+ // This is the exceptional case - to try and recover we'll attempt
+ // to delete the file and start again.
+ Close();
+ return DeleteFileAndRecreate();
+}
+
+DomStorageDatabase::SchemaVersion DomStorageDatabase::DetectSchemaVersion() {
+ DCHECK(IsOpen());
+
+ // Connection::Open() may succeed even if the file we try and open is not a
+ // database, 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 when we later try and
+ // run statements. So we run a query here that will not DCHECK but fail
+ // on an invalid database to verify that what we've opened is usable.
+ if (db_->ExecuteAndReturnErrorCode("PRAGMA auto_vacuum") != SQLITE_OK)
+ return INVALID;
+
+ // Look at the current schema - if it doesn't look right, assume corrupt.
+ if (!db_->DoesTableExist("ItemTable") ||
+ !db_->DoesColumnExist("ItemTable", "key") ||
+ !db_->DoesColumnExist("ItemTable", "value"))
+ return INVALID;
+
+ // We must use a unique statement here as we aren't going to step it.
+ sql::Statement statement(
+ db_->GetUniqueStatement("SELECT key,value from ItemTable LIMIT 1"));
+ if (statement.DeclaredColumnType(0) != sql::COLUMN_TYPE_TEXT)
+ return INVALID;
+
+ switch (statement.DeclaredColumnType(1)) {
+ case sql::COLUMN_TYPE_BLOB:
+ return V2;
+ case sql::COLUMN_TYPE_TEXT:
+ return V1;
+ default:
+ return INVALID;
+ }
+ NOTREACHED();
+ return INVALID;
+}
+
+bool DomStorageDatabase::CreateTableV2() {
+ DCHECK(IsOpen());
+
+ return db_->Execute(
+ "CREATE TABLE 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::UpgradeVersion1To2() {
+ DCHECK(IsOpen());
+ DCHECK(DetectSchemaVersion() == V1);
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
+ "SELECT * FROM ItemTable"));
+ DCHECK(statement.is_valid());
+
+ // 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());
+ return migration.Begin() &&
+ db_->Execute("DROP TABLE ItemTable") &&
+ CreateTableV2() &&
+ CommitChanges(false, values) &&
+ migration.Commit();
+}
+
+void DomStorageDatabase::Close() {
+ db_.reset(NULL);
+}
+
+} // namespace dom_storage
diff --git a/webkit/dom_storage/dom_storage_database.h b/webkit/dom_storage/dom_storage_database.h
new file mode 100644
index 0000000..e651a55
--- /dev/null
+++ b/webkit/dom_storage/dom_storage_database.h
@@ -0,0 +1,113 @@
+// 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.
+
+#ifndef WEBKIT_DOM_STORAGE_DOM_STORAGE_DATABASE_H_
+#define WEBKIT_DOM_STORAGE_DOM_STORAGE_DATABASE_H_
+#pragma once
+
+#include <map>
+
+#include "base/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/nullable_string16.h"
+#include "base/string16.h"
+#include "sql/connection.h"
+#include "webkit/dom_storage/dom_storage_types.h"
+
+namespace dom_storage {
+
+// Represents a SQLite based backing for DOM storage data. This
+// class is designed to be used on a single thread.
+class DomStorageDatabase {
+ public:
+ explicit DomStorageDatabase(const FilePath& file_path);
+ ~DomStorageDatabase();
+
+ // Reads all the key, value pairs stored in the database and returns
+ // them. |result| is assumed to be empty and any duplicate keys will
+ // be overwritten. If the database exists on disk then it will be
+ // opened. If it does not exist then it will not be created and
+ // |result| will be unmodified.
+ void ReadAllValues(ValuesMap* result);
+
+ // Updates the backing database. Will remove all keys before updating
+ // the database if |clear_all_first| is set. Then all entries in
+ // |changes| will be examined - keys mapped to a null NullableString16
+ // will be removed and all others will be inserted/updated as appropriate.
+ bool CommitChanges(bool clear_all_first, const ValuesMap& changes);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest, SimpleOpenAndClose);
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest, TestLazyOpenIsLazy);
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest, TestDetectSchemaVersion);
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest,
+ TestLazyOpenUpgradesDatabase);
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest, SimpleWriteAndReadBack);
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest, WriteWithClear);
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest,
+ UpgradeFromV1ToV2WithData);
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest, TestSimpleRemoveOneValue);
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest,
+ TestCanOpenAndReadWebCoreDatabase);
+ FRIEND_TEST_ALL_PREFIXES(DomStorageDatabaseTest,
+ TestCanOpenFileThatIsNotADatabase);
+
+ enum SchemaVersion {
+ INVALID,
+ V1,
+ V2
+ };
+
+ // Open the database at file_path_ if it exists already and creates it if
+ // |create_if_needed| is true.
+ // Ensures we are at the correct database version and creates or updates
+ // tables as necessary. Returns false on failure.
+ bool LazyOpen(bool create_if_needed);
+
+ // Analyses the database to verify that the connection that is open is indeed
+ // a valid database and works out the schema version.
+ SchemaVersion DetectSchemaVersion();
+
+ // Creates the database table at V2. Returns true if the table was created
+ // successfully, false otherwise. Will return false if the table already
+ // exists.
+ bool CreateTableV2();
+
+ // If we have issues while trying to open the file (corrupted databse,
+ // failing to upgrade, that sort of thing) this function will remove
+ // the file from disk and attempt to create a new database from
+ // scratch.
+ bool DeleteFileAndRecreate();
+
+ // Version 1 -> 2 migrates the value column in the ItemTable from a TEXT
+ // to a BLOB. Exisitng data is preserved on success. Returns false if the
+ // upgrade failed. If true is returned, the database is guaranteed to be at
+ // version 2.
+ bool UpgradeVersion1To2();
+
+ void Close();
+ bool IsOpen() const { return db_.get() ? db_->is_open() : false; }
+
+#ifdef UNIT_TEST
+ // This constructor allows us to bypass the DCHECK in the public
+ // constructor that normally verifies a valid file path was passed to
+ // back the database on disk. We want to be able to run unit tests
+ // from in-memory databases where possible, so we use an empty
+ // backing file path to signify we should open the database in memory
+ // inside LazyOpen. This constructor will allow us to bypass the
+ // DCHECK when running the unit tests.
+ DomStorageDatabase();
+#endif
+
+ // Path to the database on disk.
+ FilePath file_path_;
+ scoped_ptr<sql::Connection> db_;
+ bool failed_to_open_;
+ bool tried_to_recreate_;
+};
+
+} // namespace dom_storage
+
+#endif // WEBKIT_DOM_STORAGE_DOM_STORAGE_DATABASE_H_
diff --git a/webkit/dom_storage/dom_storage_database_unittest.cc b/webkit/dom_storage/dom_storage_database_unittest.cc
new file mode 100644
index 0000000..7e79b80
--- /dev/null
+++ b/webkit/dom_storage/dom_storage_database_unittest.cc
@@ -0,0 +1,329 @@
+// 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_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "base/utf_string_conversions.h"
+#include "sql/statement.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dom_storage {
+
+DomStorageDatabase::DomStorageDatabase()
+ : db_(NULL),
+ failed_to_open_(false),
+ tried_to_recreate_(false) {
+}
+
+void CreateV1Table(sql::Connection* db) {
+ ASSERT_TRUE(db->is_open());
+ ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
+ ASSERT_TRUE(db->Execute(
+ "CREATE TABLE ItemTable ("
+ "key TEXT UNIQUE ON CONFLICT REPLACE, "
+ "value TEXT NOT NULL ON CONFLICT FAIL)"));
+}
+
+void CreateV2Table(sql::Connection* db) {
+ ASSERT_TRUE(db->is_open());
+ ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
+ ASSERT_TRUE(db->Execute(
+ "CREATE TABLE ItemTable ("
+ "key TEXT UNIQUE ON CONFLICT REPLACE, "
+ "value BLOB NOT NULL ON CONFLICT FAIL)"));
+}
+
+void CreateInvalidKeyColumnTable(sql::Connection* db) {
+ // Create a table with the key type as FLOAT - this is "invalid"
+ // as far as the DOM Storage db is concerned.
+ ASSERT_TRUE(db->is_open());
+ ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
+ ASSERT_TRUE(db->Execute(
+ "CREATE TABLE IF NOT EXISTS ItemTable ("
+ "key FLOAT UNIQUE ON CONFLICT REPLACE, "
+ "value BLOB NOT NULL ON CONFLICT FAIL)"));
+}
+void CreateInvalidValueColumnTable(sql::Connection* db) {
+ // Create a table with the value type as FLOAT - this is "invalid"
+ // as far as the DOM Storage db is concerned.
+ ASSERT_TRUE(db->is_open());
+ ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
+ ASSERT_TRUE(db->Execute(
+ "CREATE TABLE IF NOT EXISTS ItemTable ("
+ "key TEXT UNIQUE ON CONFLICT REPLACE, "
+ "value FLOAT NOT NULL ON CONFLICT FAIL)"));
+}
+
+void InsertDataV1(sql::Connection* db,
+ const string16& key,
+ const string16& value) {
+ sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE,
+ "INSERT INTO ItemTable VALUES (?,?)"));
+ statement.BindString16(0, key);
+ statement.BindString16(1, value);
+ ASSERT_TRUE(statement.is_valid());
+ statement.Run();
+}
+
+void CheckValuesMatch(DomStorageDatabase* db,
+ const ValuesMap& expected) {
+ ValuesMap values_read;
+ db->ReadAllValues(&values_read);
+ EXPECT_EQ(expected.size(), values_read.size());
+
+ ValuesMap::const_iterator it = values_read.begin();
+ for (; it != values_read.end(); ++it) {
+ string16 key = it->first;
+ NullableString16 value = it->second;
+ NullableString16 expected_value = expected.find(key)->second;
+ EXPECT_EQ(expected_value.string(), value.string());
+ EXPECT_EQ(expected_value.is_null(), value.is_null());
+ }
+}
+
+void CreateMapWithValues(ValuesMap* values) {
+ string16 kCannedKeys[] = {
+ ASCIIToUTF16("test"),
+ ASCIIToUTF16("company"),
+ ASCIIToUTF16("date"),
+ ASCIIToUTF16("empty")
+ };
+ NullableString16 kCannedValues[] = {
+ NullableString16(ASCIIToUTF16("123"), false),
+ NullableString16(ASCIIToUTF16("Google"), false),
+ NullableString16(ASCIIToUTF16("18-01-2012"), false),
+ NullableString16(ASCIIToUTF16(""), false)
+ };
+ for (unsigned i = 0; i < sizeof(kCannedKeys) / sizeof(kCannedKeys[0]); i++)
+ (*values)[kCannedKeys[i]] = kCannedValues[i];
+}
+
+TEST(DomStorageDatabaseTest, SimpleOpenAndClose) {
+ DomStorageDatabase db;
+ EXPECT_FALSE(db.IsOpen());
+ ASSERT_TRUE(db.LazyOpen(true));
+ EXPECT_TRUE(db.IsOpen());
+ EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion());
+ db.Close();
+ EXPECT_FALSE(db.IsOpen());
+}
+
+TEST(DomStorageDatabaseTest, TestLazyOpenIsLazy) {
+ // This test needs to operate with a file on disk to ensure that we will
+ // open a file that already exists when only invoking ReadAllValues.
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath file_name = temp_dir.path().AppendASCII("TestDomStorageDatabase.db");
+
+ DomStorageDatabase db(file_name);
+ EXPECT_FALSE(db.IsOpen());
+ ValuesMap values;
+ db.ReadAllValues(&values);
+ // Reading an empty db should not open the database.
+ EXPECT_FALSE(db.IsOpen());
+
+ values[ASCIIToUTF16("key")] = NullableString16(ASCIIToUTF16("value"), false);
+ db.CommitChanges(false, values);
+ // Writing content should open the database.
+ EXPECT_TRUE(db.IsOpen());
+
+ db.Close();
+ ASSERT_FALSE(db.IsOpen());
+
+ // Reading from an existing database should open the database.
+ CheckValuesMatch(&db, values);
+ EXPECT_TRUE(db.IsOpen());
+}
+
+TEST(DomStorageDatabaseTest, TestDetectSchemaVersion) {
+ DomStorageDatabase db;
+ db.db_.reset(new sql::Connection());
+ ASSERT_TRUE(db.db_->OpenInMemory());
+
+ CreateInvalidValueColumnTable(db.db_.get());
+ EXPECT_EQ(DomStorageDatabase::INVALID, db.DetectSchemaVersion());
+
+ CreateInvalidKeyColumnTable(db.db_.get());
+ EXPECT_EQ(DomStorageDatabase::INVALID, db.DetectSchemaVersion());
+
+ CreateV1Table(db.db_.get());
+ EXPECT_EQ(DomStorageDatabase::V1, db.DetectSchemaVersion());
+
+ CreateV2Table(db.db_.get());
+ EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion());
+}
+
+TEST(DomStorageDatabaseTest, TestLazyOpenUpgradesDatabase) {
+ // This test needs to operate with a file on disk so that we
+ // can create a table at version 1 and then close it again
+ // so that LazyOpen sees there is work to do (LazyOpen will return
+ // early if the database is already open).
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath file_name = temp_dir.path().AppendASCII("TestDomStorageDatabase.db");
+
+ DomStorageDatabase db(file_name);
+ db.db_.reset(new sql::Connection());
+ ASSERT_TRUE(db.db_->Open(file_name));
+ CreateV1Table(db.db_.get());
+ db.Close();
+
+ EXPECT_TRUE(db.LazyOpen(true));
+ EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion());
+}
+
+TEST(DomStorageDatabaseTest, SimpleWriteAndReadBack) {
+ DomStorageDatabase db;
+
+ ValuesMap storage;
+ CreateMapWithValues(&storage);
+
+ EXPECT_TRUE(db.CommitChanges(false, storage));
+ CheckValuesMatch(&db, storage);
+}
+
+TEST(DomStorageDatabaseTest, WriteWithClear) {
+ DomStorageDatabase db;
+
+ ValuesMap storage;
+ CreateMapWithValues(&storage);
+
+ ASSERT_TRUE(db.CommitChanges(false, storage));
+ CheckValuesMatch(&db, storage);
+
+ // Insert some values, clearing the database first.
+ storage.clear();
+ storage[ASCIIToUTF16("another_key")] =
+ NullableString16(ASCIIToUTF16("test"), false);
+ ASSERT_TRUE(db.CommitChanges(true, storage));
+ CheckValuesMatch(&db, storage);
+
+ // Now clear the values without inserting any new ones.
+ storage.clear();
+ ASSERT_TRUE(db.CommitChanges(true, storage));
+ CheckValuesMatch(&db, storage);
+}
+
+TEST(DomStorageDatabaseTest, UpgradeFromV1ToV2WithData) {
+ const string16 kCannedKey = ASCIIToUTF16("foo");
+ const NullableString16 kCannedValue(ASCIIToUTF16("bar"), false);
+ ValuesMap expected;
+ expected[kCannedKey] = kCannedValue;
+
+ DomStorageDatabase db;
+ db.db_.reset(new sql::Connection());
+ ASSERT_TRUE(db.db_->OpenInMemory());
+ CreateV1Table(db.db_.get());
+ InsertDataV1(db.db_.get(), kCannedKey, kCannedValue.string());
+
+ ASSERT_TRUE(db.UpgradeVersion1To2());
+
+ EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion());
+
+ CheckValuesMatch(&db, expected);
+}
+
+TEST(DomStorageDatabaseTest, TestSimpleRemoveOneValue) {
+ DomStorageDatabase db;
+
+ ASSERT_TRUE(db.LazyOpen(true));
+ const string16 kCannedKey = ASCIIToUTF16("test");
+ const NullableString16 kCannedValue(ASCIIToUTF16("data"), false);
+ ValuesMap expected;
+ expected[kCannedKey] = kCannedValue;
+
+ // First write some data into the database.
+ ASSERT_TRUE(db.CommitChanges(false, expected));
+ CheckValuesMatch(&db, expected);
+
+ ValuesMap values;
+ // A null string in the map should mean that that key gets
+ // removed.
+ values[kCannedKey] = NullableString16(true);
+ EXPECT_TRUE(db.CommitChanges(false, values));
+
+ expected.clear();
+ CheckValuesMatch(&db, expected);
+}
+
+// TODO(benm): Enable this test in follow up patch once the test data has
+// landed. (try-bots don't like adding binary files like test databases :) )
+TEST(DomStorageDatabaseTest, DISABLED_TestCanOpenAndReadWebCoreDatabase) {
+ FilePath webcore_database;
+ PathService::Get(base::DIR_SOURCE_ROOT, &webcore_database);
+ webcore_database = webcore_database.AppendASCII("webkit");
+ webcore_database = webcore_database.AppendASCII("data");
+ webcore_database = webcore_database.AppendASCII("dom_storage");
+ webcore_database =
+ webcore_database.AppendASCII("webcore_test_database.localstorage");
+
+ ASSERT_TRUE(file_util::PathExists(webcore_database));
+
+ DomStorageDatabase db(webcore_database);
+ ValuesMap values;
+ db.ReadAllValues(&values);
+ EXPECT_TRUE(db.IsOpen());
+ EXPECT_EQ(2u, values.size());
+
+ ValuesMap::const_iterator it =
+ values.find(ASCIIToUTF16("value"));
+ EXPECT_TRUE(it != values.end());
+ EXPECT_EQ(ASCIIToUTF16("I am in local storage!"), it->second.string());
+
+ it = values.find(ASCIIToUTF16("timestamp"));
+ EXPECT_TRUE(it != values.end());
+ EXPECT_EQ(ASCIIToUTF16("1326738338841"), it->second.string());
+
+ it = values.find(ASCIIToUTF16("not_there"));
+ EXPECT_TRUE(it == values.end());
+}
+
+TEST(DomStorageDatabaseTest, TestCanOpenFileThatIsNotADatabase) {
+ // Write into the temporary file first.
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath file_name = temp_dir.path().AppendASCII("TestDomStorageDatabase.db");
+
+ const char kData[] = "I am not a database.";
+ file_util::WriteFile(file_name, kData, strlen(kData));
+
+ {
+ // Try and open the file. As it's not a database, we should end up deleting
+ // it and creating a new, valid file, so everything should actually
+ // succeed.
+ DomStorageDatabase db(file_name);
+ ValuesMap values;
+ CreateMapWithValues(&values);
+ EXPECT_TRUE(db.CommitChanges(true, values));
+ EXPECT_TRUE(db.CommitChanges(false, values));
+ EXPECT_TRUE(db.IsOpen());
+
+ CheckValuesMatch(&db, values);
+ }
+
+ {
+ // Try to open a directory, we should fail gracefully and not attempt
+ // to delete it.
+ DomStorageDatabase db(temp_dir.path());
+ ValuesMap values;
+ CreateMapWithValues(&values);
+ EXPECT_FALSE(db.CommitChanges(true, values));
+ EXPECT_FALSE(db.CommitChanges(false, values));
+ EXPECT_FALSE(db.IsOpen());
+
+ values.clear();
+
+ db.ReadAllValues(&values);
+ EXPECT_EQ(0u, values.size());
+ EXPECT_FALSE(db.IsOpen());
+
+ EXPECT_TRUE(file_util::PathExists(temp_dir.path()));
+ }
+}
+
+} // namespace dom_storage
diff --git a/webkit/dom_storage/webkit_dom_storage.gypi b/webkit/dom_storage/webkit_dom_storage.gypi
index 5c469ce..65df554 100644
--- a/webkit/dom_storage/webkit_dom_storage.gypi
+++ b/webkit/dom_storage/webkit_dom_storage.gypi
@@ -19,6 +19,8 @@
'dom_storage_area.h',
'dom_storage_context.cc',
'dom_storage_context.h',
+ 'dom_storage_database.cc',
+ 'dom_storage_database.h',
'dom_storage_host.cc',
'dom_storage_host.h',
'dom_storage_map.cc',
diff --git a/webkit/tools/test_shell/test_shell.gypi b/webkit/tools/test_shell/test_shell.gypi
index 5e2b49b..787b403 100644
--- a/webkit/tools/test_shell/test_shell.gypi
+++ b/webkit/tools/test_shell/test_shell.gypi
@@ -1,4 +1,4 @@
-# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# 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.
@@ -405,6 +405,7 @@
'../../database/database_util_unittest.cc',
'../../database/quota_table_unittest.cc',
'../../dom_storage/dom_storage_area_unittest.cc',
+ '../../dom_storage/dom_storage_database_unittest.cc',
'../../dom_storage/dom_storage_map_unittest.cc',
'../../fileapi/file_system_directory_database_unittest.cc',
'../../fileapi/file_system_file_util_unittest.cc',