diff options
author | benm@chromium.org <benm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-10 13:31:59 +0000 |
---|---|---|
committer | benm@chromium.org <benm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-10 13:31:59 +0000 |
commit | 98b6f8b1b8f96602e74d431eff2296c582b01275 (patch) | |
tree | 9074ed279c02247caca0e58bd28f720fa350e484 /webkit | |
parent | 9517c6326e123542fab29f20874dcdd8d48d7f83 (diff) | |
download | chromium_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.txt | 4 | ||||
-rw-r--r-- | webkit/data/dom_storage/webcore_test_database.localstorage | 0 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_database.cc | 260 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_database.h | 113 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_database_unittest.cc | 329 | ||||
-rw-r--r-- | webkit/dom_storage/webkit_dom_storage.gypi | 2 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell.gypi | 3 |
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', |