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

Unified Diff: content/browser/indexed_db/indexed_db_backing_store.cc

Issue 15564008: Migrate the IndexedDB backend from Blink to Chromium (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Update to ToT again Created 7 years, 7 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: content/browser/indexed_db/indexed_db_backing_store.cc
diff --git a/content/browser/indexed_db/indexed_db_backing_store.cc b/content/browser/indexed_db/indexed_db_backing_store.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1596aefea840186406f8a8f826f8c65fec81f1f0
--- /dev/null
+++ b/content/browser/indexed_db/indexed_db_backing_store.cc
@@ -0,0 +1,2543 @@
+// Copyright (c) 2013 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 "content/browser/indexed_db/indexed_db_backing_store.h"
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
+#include "content/browser/indexed_db/indexed_db_metadata.h"
+#include "content/browser/indexed_db/indexed_db_tracing.h"
+#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_database.h"
+#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_slice.h"
+#include "content/browser/indexed_db/leveldb/leveldb_transaction.h"
+#include "content/common/indexed_db/indexed_db_key.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+#include "content/common/indexed_db/indexed_db_key_range.h"
+#include "third_party/WebKit/public/platform/WebIDBKey.h"
+#include "third_party/WebKit/public/platform/WebIDBKeyPath.h"
+
+// TODO(jsbell): Make blink push the version during the open() call.
+static const uint32 kWireVersion = 2;
+
+namespace content {
+
+static const int64 kKeyGeneratorInitialNumber =
+ 1; // From the IndexedDB specification.
+
+enum IndexedDBBackingStoreErrorSource {
+ // 0 - 2 are no longer used.
+ FIND_KEY_IN_INDEX = 3,
+ GET_IDBDATABASE_METADATA,
+ GET_INDEXES,
+ GET_KEY_GENERATOR_CURRENT_NUMBER,
+ GET_OBJECT_STORES,
+ GET_RECORD,
+ KEY_EXISTS_IN_OBJECT_STORE,
+ LOAD_CURRENT_ROW,
+ SET_UP_METADATA,
+ GET_PRIMARY_KEY_VIA_INDEX,
+ KEY_EXISTS_IN_INDEX,
+ VERSION_EXISTS,
+ DELETE_OBJECT_STORE,
+ SET_MAX_OBJECT_STORE_ID,
+ SET_MAX_INDEX_ID,
+ GET_NEW_DATABASE_ID,
+ GET_NEW_VERSION_NUMBER,
+ CREATE_IDBDATABASE_METADATA,
+ DELETE_DATABASE,
+ TRANSACTION_COMMIT_METHOD, // TRANSACTION_COMMIT is a WinNT.h macro
+ INTERNAL_ERROR_MAX,
+};
+
+static void RecordInternalError(const char* type,
+ IndexedDBBackingStoreErrorSource location) {
+ string16 name = ASCIIToUTF16("WebCore.IndexedDB.BackingStore.") +
+ UTF8ToUTF16(type) + ASCIIToUTF16("Error");
+ base::Histogram::FactoryGet(UTF16ToUTF8(name),
+ 1,
+ INTERNAL_ERROR_MAX,
+ INTERNAL_ERROR_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(location);
+}
+
+// Use to signal conditions that usually indicate developer error, but
+// could be caused by data corruption. A macro is used instead of an
+// inline function so that the assert and log report the line number.
+#define REPORT_ERROR(type, location) \
+ do { \
+ LOG(ERROR) << "IndexedDB " type " Error: " #location; \
+ NOTREACHED(); \
+ RecordInternalError(type, location); \
+ } while (0)
+
+#define INTERNAL_READ_ERROR(location) REPORT_ERROR("Read", location)
+#define INTERNAL_CONSISTENCY_ERROR(location) \
+ REPORT_ERROR("Consistency", location)
+#define INTERNAL_WRITE_ERROR(location) REPORT_ERROR("Write", location)
+
+static void PutBool(LevelDBTransaction* transaction,
+ const LevelDBSlice& key,
+ bool value) {
+ transaction->Put(key, EncodeBool(value));
+}
+
+template <typename DBOrTransaction>
+static bool GetInt(DBOrTransaction* db,
+ const LevelDBSlice& key,
+ int64& found_int,
+ bool& found) {
+ std::vector<char> result;
+ bool ok = db->Get(key, result, found);
+ if (!ok)
+ return false;
+ if (!found)
+ return true;
+
+ found_int = DecodeInt(result.begin(), result.end());
+ return true;
+}
+
+static void PutInt(LevelDBTransaction* transaction,
+ const LevelDBSlice& key,
+ int64 value) {
+ DCHECK_GE(value, 0);
+ transaction->Put(key, EncodeInt(value));
+}
+
+template <typename DBOrTransaction>
+WARN_UNUSED_RESULT static bool GetVarInt(DBOrTransaction* db,
+ const LevelDBSlice& key,
+ int64& found_int,
+ bool& found) {
+ std::vector<char> result;
+ bool ok = db->Get(key, result, found);
+ if (!ok)
+ return false;
+ if (!found)
+ return true;
+ if (!result.size())
+ return false;
+
+ found = DecodeVarInt(&*result.begin(), &*result.rbegin() + 1, found_int) ==
+ &*result.rbegin() + 1;
+ return true;
+}
+
+static void PutVarInt(LevelDBTransaction* transaction,
+ const LevelDBSlice& key,
+ int64 value) {
+ transaction->Put(key, EncodeVarInt(value));
+}
+
+template <typename DBOrTransaction>
+WARN_UNUSED_RESULT static bool GetString(DBOrTransaction* db,
+ const LevelDBSlice& key,
+ string16& found_string,
+ bool& found) {
+ std::vector<char> result;
+ found = false;
+ bool ok = db->Get(key, result, found);
+ if (!ok)
+ return false;
+ if (!found)
+ return true;
+ if (!result.size()) {
+ found_string.clear();
+ return true;
+ }
+
+ found_string = DecodeString(&*result.begin(), &*result.rbegin() + 1);
+ return true;
+}
+
+static void PutString(LevelDBTransaction* transaction,
+ const LevelDBSlice& key,
+ const string16& value) {
+ transaction->Put(key, EncodeString(value));
+}
+
+static void PutIDBKeyPath(LevelDBTransaction* transaction,
+ const LevelDBSlice& key,
+ const IndexedDBKeyPath& value) {
+ transaction->Put(key, EncodeIDBKeyPath(value));
+}
+
+static int CompareKeys(const LevelDBSlice& a, const LevelDBSlice& b) {
+ return Compare(a, b);
+}
+
+static int CompareIndexKeys(const LevelDBSlice& a, const LevelDBSlice& b) {
+ return Compare(a, b, true);
+}
+
+class Comparator : public LevelDBComparator {
+ public:
+ virtual int Compare(const LevelDBSlice& a, const LevelDBSlice& b) const
+ OVERRIDE {
+ return content::Compare(a, b);
+ }
+ virtual const char* Name() const OVERRIDE { return "idb_cmp1"; }
+};
+
+// 0 - Initial version.
+// 1 - Adds UserIntVersion to DatabaseMetaData.
+// 2 - Adds DataVersion to to global metadata.
+static const int64 kLatestKnownSchemaVersion = 2;
+WARN_UNUSED_RESULT static bool IsSchemaKnown(LevelDBDatabase* db, bool* known) {
+ int64 db_schema_version = 0;
+ bool found = false;
+ bool ok = GetInt(
+ db, LevelDBSlice(SchemaVersionKey::Encode()), db_schema_version, found);
+ if (!ok)
+ return false;
+ if (!found) {
+ *known = true;
+ return true;
+ }
+ if (db_schema_version > kLatestKnownSchemaVersion) {
+ *known = false;
+ return true;
+ }
+
+ const uint32 latest_known_data_version = kWireVersion;
+ int64 db_data_version = 0;
+ ok = GetInt(
+ db, LevelDBSlice(DataVersionKey::Encode()), db_data_version, found);
+ if (!ok)
+ return false;
+ if (!found) {
+ *known = true;
+ return true;
+ }
+
+ if (db_data_version > latest_known_data_version) {
+ *known = false;
+ return true;
+ }
+
+ *known = true;
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool SetUpMetadata(LevelDBDatabase* db,
+ const string16& origin) {
+ const uint32 latest_known_data_version = kWireVersion;
+ const std::vector<char> schema_version_key = SchemaVersionKey::Encode();
+ const std::vector<char> data_version_key = DataVersionKey::Encode();
+
+ scoped_refptr<LevelDBTransaction> transaction =
+ LevelDBTransaction::Create(db);
+
+ int64 db_schema_version = 0;
+ int64 db_data_version = 0;
+ bool found = false;
+ bool ok = GetInt(transaction.get(),
+ LevelDBSlice(schema_version_key),
+ db_schema_version,
+ found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ if (!found) {
+ // Initialize new backing store.
+ db_schema_version = kLatestKnownSchemaVersion;
+ PutInt(
+ transaction.get(), LevelDBSlice(schema_version_key), db_schema_version);
+ db_data_version = latest_known_data_version;
+ PutInt(transaction.get(), LevelDBSlice(data_version_key), db_data_version);
+ } else {
+ // Upgrade old backing store.
+ DCHECK_LE(db_schema_version, kLatestKnownSchemaVersion);
+ if (db_schema_version < 1) {
+ db_schema_version = 1;
+ PutInt(transaction.get(),
+ LevelDBSlice(schema_version_key),
+ db_schema_version);
+ const std::vector<char> start_key =
+ DatabaseNameKey::EncodeMinKeyForOrigin(origin);
+ const std::vector<char> stop_key =
+ DatabaseNameKey::EncodeStopKeyForOrigin(origin);
+ scoped_ptr<LevelDBIterator> it = db->CreateIterator();
+ for (it->Seek(LevelDBSlice(start_key));
+ it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0;
+ it->Next()) {
+ int64 database_id = 0;
+ found = false;
+ bool ok = GetInt(transaction.get(), it->Key(), database_id, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ if (!found) {
+ INTERNAL_CONSISTENCY_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ std::vector<char> int_version_key = DatabaseMetaDataKey::Encode(
+ database_id, DatabaseMetaDataKey::USER_INT_VERSION);
+ PutVarInt(transaction.get(),
+ LevelDBSlice(int_version_key),
+ IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
+ }
+ }
+ if (db_schema_version < 2) {
+ db_schema_version = 2;
+ PutInt(transaction.get(),
+ LevelDBSlice(schema_version_key),
+ db_schema_version);
+ db_data_version = kWireVersion;
+ PutInt(
+ transaction.get(), LevelDBSlice(data_version_key), db_data_version);
+ }
+ }
+
+ // All new values will be written using this serialization version.
+ found = false;
+ ok = GetInt(transaction.get(),
+ LevelDBSlice(data_version_key),
+ db_data_version,
+ found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ if (!found) {
+ INTERNAL_CONSISTENCY_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ if (db_data_version < latest_known_data_version) {
+ db_data_version = latest_known_data_version;
+ PutInt(transaction.get(), LevelDBSlice(data_version_key), db_data_version);
+ }
+
+ DCHECK_EQ(db_schema_version, kLatestKnownSchemaVersion);
+ DCHECK_EQ(db_data_version, latest_known_data_version);
+
+ if (!transaction->Commit()) {
+ INTERNAL_WRITE_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ return true;
+}
+
+template <typename DBOrTransaction>
+WARN_UNUSED_RESULT static bool GetMaxObjectStoreId(DBOrTransaction* db,
+ int64 database_id,
+ int64& max_object_store_id) {
+ const std::vector<char> max_object_store_id_key = DatabaseMetaDataKey::Encode(
+ database_id, DatabaseMetaDataKey::MAX_OBJECT_STORE_ID);
+ bool ok =
+ GetMaxObjectStoreId(db, max_object_store_id_key, max_object_store_id);
+ return ok;
+}
+
+template <typename DBOrTransaction>
+WARN_UNUSED_RESULT static bool GetMaxObjectStoreId(
+ DBOrTransaction* db,
+ const std::vector<char>& max_object_store_id_key,
+ int64& max_object_store_id) {
+ max_object_store_id = -1;
+ bool found = false;
+ bool ok = GetInt(
+ db, LevelDBSlice(max_object_store_id_key), max_object_store_id, found);
+ if (!ok)
+ return false;
+ if (!found)
+ max_object_store_id = 0;
+
+ DCHECK_GE(max_object_store_id, 0);
+ return true;
+}
+
+class DefaultLevelDBFactory : public LevelDBFactory {
+ public:
+ virtual scoped_ptr<LevelDBDatabase> OpenLevelDB(
+ const base::FilePath& file_name,
+ const LevelDBComparator* comparator) OVERRIDE {
+ return LevelDBDatabase::Open(file_name, comparator);
+ }
+ virtual bool DestroyLevelDB(const base::FilePath& file_name) OVERRIDE {
+ return LevelDBDatabase::Destroy(file_name);
+ }
+};
+
+IndexedDBBackingStore::IndexedDBBackingStore(
+ const string16& identifier,
+ scoped_ptr<LevelDBDatabase> db,
+ scoped_ptr<LevelDBComparator> comparator)
+ : identifier_(identifier),
+ db_(db.Pass()),
+ comparator_(comparator.Pass()),
+ weak_factory_(this) {}
+
+IndexedDBBackingStore::~IndexedDBBackingStore() {
+ // db_'s destructor uses comparator_. The order of destruction is important.
+ db_.reset();
+ comparator_.reset();
+}
+
+IndexedDBBackingStore::RecordIdentifier::RecordIdentifier(
+ const std::vector<char>& primary_key,
+ int64 version)
+ : primary_key_(primary_key), version_(version) {
+ DCHECK(!primary_key.empty());
+}
+IndexedDBBackingStore::RecordIdentifier::RecordIdentifier()
+ : primary_key_(), version_(-1) {}
+IndexedDBBackingStore::RecordIdentifier::~RecordIdentifier() {}
+
+IndexedDBBackingStore::Cursor::CursorOptions::CursorOptions() {}
+IndexedDBBackingStore::Cursor::CursorOptions::~CursorOptions() {}
+
+enum IndexedDBLevelDBBackingStoreOpenResult {
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_SUCCESS,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_SUCCESS,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_DIRECTORY,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_SCHEMA,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_DESTROY_FAILED,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_FAILED,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_SUCCESS,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_IO_ERROR_CHECKING_SCHEMA,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_ERR,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_FAILED,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_ATTEMPT_NON_ASCII,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+};
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
+ const string16& database_identifier,
+ const string16& path_base_arg,
+ const string16& file_identifier) {
+ DefaultLevelDBFactory leveldb_factory;
+ return IndexedDBBackingStore::Open(
+ database_identifier, path_base_arg, file_identifier, &leveldb_factory);
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
+ const string16& database_identifier,
+ const string16& path_base_arg,
+ const string16& file_identifier,
+ LevelDBFactory* leveldb_factory) {
+ IDB_TRACE("IndexedDBBackingStore::open");
+ DCHECK(!path_base_arg.empty());
+ string16 path_base = path_base_arg;
+
+ scoped_ptr<LevelDBComparator> comparator(new Comparator());
+ scoped_ptr<LevelDBDatabase> db;
+
+ if (!IsStringASCII(path_base)) {
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_ATTEMPT_NON_ASCII);
+ }
+ base::FilePath file_path_base =
+ base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(path_base));
+ if (!file_util::CreateDirectory(file_path_base)) {
+ LOG(ERROR) << "Unable to create IndexedDB database path " << path_base;
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_DIRECTORY);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+
+ // TODO(jsbell): Rework to use FilePath throughout.
+ base::FilePath identifier_path =
+ base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(database_identifier));
+ base::FilePath file_path =
+ file_path_base.Append(identifier_path).AppendASCII(".indexeddb.leveldb");
+
+ db = leveldb_factory->OpenLevelDB(file_path, comparator.get());
+
+ if (db) {
+ bool known = false;
+ bool ok = IsSchemaKnown(db.get(), &known);
+ if (!ok) {
+ LOG(ERROR) << "IndexedDB had IO error checking schema, treating it as "
+ "failure to open";
+ base::Histogram::FactoryGet(
+ "WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_IO_ERROR_CHECKING_SCHEMA);
+ db.reset();
+ } else if (!known) {
+ LOG(ERROR) << "IndexedDB backing store had unknown schema, treating it "
+ "as failure to open";
+ base::Histogram::FactoryGet(
+ "WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_SCHEMA);
+ db.reset();
+ }
+ }
+
+ if (db) {
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_SUCCESS);
+ } else {
+ LOG(ERROR) << "IndexedDB backing store open failed, attempting cleanup";
+ bool success = leveldb_factory->DestroyLevelDB(file_path);
+ if (!success) {
+ LOG(ERROR) << "IndexedDB backing store cleanup failed";
+ base::Histogram::FactoryGet(
+ "WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_DESTROY_FAILED);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+
+ LOG(ERROR) << "IndexedDB backing store cleanup succeeded, reopening";
+ db = leveldb_factory->OpenLevelDB(file_path, comparator.get());
+ if (!db) {
+ LOG(ERROR) << "IndexedDB backing store reopen after recovery failed";
+ base::Histogram::FactoryGet(
+ "WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_FAILED);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_SUCCESS);
+ }
+
+ if (!db) {
+ NOTREACHED();
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_ERR);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+
+ return Create(file_identifier, db.Pass(), comparator.Pass());
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::OpenInMemory(
+ const string16& identifier) {
+ DefaultLevelDBFactory leveldb_factory;
+ return IndexedDBBackingStore::OpenInMemory(identifier, &leveldb_factory);
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::OpenInMemory(
+ const string16& identifier,
+ LevelDBFactory* leveldb_factory) {
+ IDB_TRACE("IndexedDBBackingStore::open_in_memory");
+
+ scoped_ptr<LevelDBComparator> comparator(new Comparator());
+ scoped_ptr<LevelDBDatabase> db =
+ LevelDBDatabase::OpenInMemory(comparator.get());
+ if (!db) {
+ LOG(ERROR) << "LevelDBDatabase::open_in_memory failed.";
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_FAILED);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_SUCCESS);
+
+ return Create(identifier, db.Pass(), comparator.Pass());
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Create(
+ const string16& identifier,
+ scoped_ptr<LevelDBDatabase> db,
+ scoped_ptr<LevelDBComparator> comparator) {
+ // TODO(jsbell): Handle comparator name changes.
+ scoped_refptr<IndexedDBBackingStore> backing_store(
+ new IndexedDBBackingStore(identifier, db.Pass(), comparator.Pass()));
+
+ if (!SetUpMetadata(backing_store->db_.get(), identifier))
+ return scoped_refptr<IndexedDBBackingStore>();
+
+ return backing_store;
+}
+
+std::vector<string16> IndexedDBBackingStore::GetDatabaseNames() {
+ std::vector<string16> found_names;
+ const std::vector<char> start_key =
+ DatabaseNameKey::EncodeMinKeyForOrigin(identifier_);
+ const std::vector<char> stop_key =
+ DatabaseNameKey::EncodeStopKeyForOrigin(identifier_);
+
+ DCHECK(found_names.empty());
+
+ scoped_ptr<LevelDBIterator> it = db_->CreateIterator();
+ for (it->Seek(LevelDBSlice(start_key));
+ it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0;
+ it->Next()) {
+ const char* p = it->Key().begin();
+ const char* limit = it->Key().end();
+
+ DatabaseNameKey database_name_key;
+ p = DatabaseNameKey::Decode(p, limit, &database_name_key);
+ DCHECK(p);
+
+ found_names.push_back(database_name_key.database_name());
+ }
+ return found_names;
+}
+
+bool IndexedDBBackingStore::GetIDBDatabaseMetaData(
+ const string16& name,
+ IndexedDBDatabaseMetadata* metadata,
+ bool& found) {
+ const std::vector<char> key = DatabaseNameKey::Encode(identifier_, name);
+ found = false;
+
+ bool ok = GetInt(db_.get(), LevelDBSlice(key), metadata->id, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+ if (!found)
+ return true;
+
+ ok = GetString(db_.get(),
+ LevelDBSlice(DatabaseMetaDataKey::Encode(
+ metadata->id, DatabaseMetaDataKey::USER_VERSION)),
+ metadata->version,
+ found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+ if (!found) {
+ INTERNAL_CONSISTENCY_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+
+ ok = GetVarInt(db_.get(),
+ LevelDBSlice(DatabaseMetaDataKey::Encode(
+ metadata->id, DatabaseMetaDataKey::USER_INT_VERSION)),
+ metadata->int_version,
+ found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+ if (!found) {
+ INTERNAL_CONSISTENCY_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+
+ if (metadata->int_version == IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION)
+ metadata->int_version = IndexedDBDatabaseMetadata::NO_INT_VERSION;
+
+ ok = GetMaxObjectStoreId(
+ db_.get(), metadata->id, metadata->max_object_store_id);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool GetNewDatabaseId(LevelDBDatabase* db,
+ int64& new_id) {
+ scoped_refptr<LevelDBTransaction> transaction =
+ LevelDBTransaction::Create(db);
+
+ new_id = -1;
+ int64 max_database_id = -1;
+ bool found = false;
+ bool ok = GetInt(transaction.get(),
+ LevelDBSlice(MaxDatabaseIdKey::Encode()),
+ max_database_id,
+ found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_NEW_DATABASE_ID);
+ return false;
+ }
+ if (!found)
+ max_database_id = 0;
+
+ DCHECK_GE(max_database_id, 0);
+
+ int64 database_id = max_database_id + 1;
+ PutInt(
+ transaction.get(), LevelDBSlice(MaxDatabaseIdKey::Encode()), database_id);
+ if (!transaction->Commit()) {
+ INTERNAL_WRITE_ERROR(GET_NEW_DATABASE_ID);
+ return false;
+ }
+ new_id = database_id;
+ return true;
+}
+
+bool IndexedDBBackingStore::CreateIDBDatabaseMetaData(const string16& name,
+ const string16& version,
+ int64 int_version,
+ int64& row_id) {
+ bool ok = GetNewDatabaseId(db_.get(), row_id);
+ if (!ok)
+ return false;
+ DCHECK_GE(row_id, 0);
+
+ if (int_version == IndexedDBDatabaseMetadata::NO_INT_VERSION)
+ int_version = IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION;
+
+ scoped_refptr<LevelDBTransaction> transaction =
+ LevelDBTransaction::Create(db_.get());
+ PutInt(transaction.get(),
+ LevelDBSlice(DatabaseNameKey::Encode(identifier_, name)),
+ row_id);
+ PutString(transaction.get(),
+ LevelDBSlice(DatabaseMetaDataKey::Encode(
+ row_id, DatabaseMetaDataKey::USER_VERSION)),
+ version);
+ PutVarInt(transaction.get(),
+ LevelDBSlice(DatabaseMetaDataKey::Encode(
+ row_id, DatabaseMetaDataKey::USER_INT_VERSION)),
+ int_version);
+ if (!transaction->Commit()) {
+ INTERNAL_WRITE_ERROR(CREATE_IDBDATABASE_METADATA);
+ return false;
+ }
+ return true;
+}
+
+bool IndexedDBBackingStore::UpdateIDBDatabaseIntVersion(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 row_id,
+ int64 int_version) {
+ if (int_version == IndexedDBDatabaseMetadata::NO_INT_VERSION)
+ int_version = IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION;
+ DCHECK_GE(int_version, 0) << "int_version was " << int_version;
+ PutVarInt(Transaction::LevelDBTransactionFrom(transaction),
+ LevelDBSlice(DatabaseMetaDataKey::Encode(
+ row_id, DatabaseMetaDataKey::USER_INT_VERSION)),
+ int_version);
+ return true;
+}
+
+bool IndexedDBBackingStore::UpdateIDBDatabaseMetaData(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 row_id,
+ const string16& version) {
+ PutString(Transaction::LevelDBTransactionFrom(transaction),
+ LevelDBSlice(DatabaseMetaDataKey::Encode(
+ row_id, DatabaseMetaDataKey::USER_VERSION)),
+ version);
+ return true;
+}
+
+static void DeleteRange(LevelDBTransaction* transaction,
+ const std::vector<char>& begin,
+ const std::vector<char>& end) {
+ scoped_ptr<LevelDBIterator> it = transaction->CreateIterator();
+ for (it->Seek(LevelDBSlice(begin));
+ it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(end)) < 0;
+ it->Next())
+ transaction->Remove(it->Key());
+}
+
+bool IndexedDBBackingStore::DeleteDatabase(const string16& name) {
+ IDB_TRACE("IndexedDBBackingStore::delete_database");
+ scoped_ptr<LevelDBWriteOnlyTransaction> transaction =
+ LevelDBWriteOnlyTransaction::Create(db_.get());
+
+ IndexedDBDatabaseMetadata metadata;
+ bool success = false;
+ bool ok = GetIDBDatabaseMetaData(name, &metadata, success);
+ if (!ok)
+ return false;
+ if (!success)
+ return true;
+
+ const std::vector<char> start_key = DatabaseMetaDataKey::Encode(
+ metadata.id, DatabaseMetaDataKey::ORIGIN_NAME);
+ const std::vector<char> stop_key = DatabaseMetaDataKey::Encode(
+ metadata.id + 1, DatabaseMetaDataKey::ORIGIN_NAME);
+ scoped_ptr<LevelDBIterator> it = db_->CreateIterator();
+ for (it->Seek(LevelDBSlice(start_key));
+ it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0;
+ it->Next())
+ transaction->Remove(it->Key());
+
+ const std::vector<char> key = DatabaseNameKey::Encode(identifier_, name);
+ transaction->Remove(LevelDBSlice(key));
+
+ if (!transaction->Commit()) {
+ INTERNAL_WRITE_ERROR(DELETE_DATABASE);
+ return false;
+ }
+ return true;
+}
+
+static bool CheckObjectStoreAndMetaDataType(const LevelDBIterator* it,
+ const std::vector<char>& stop_key,
+ int64 object_store_id,
+ int64 meta_data_type) {
+ if (!it->IsValid() || CompareKeys(it->Key(), LevelDBSlice(stop_key)) >= 0)
+ return false;
+
+ ObjectStoreMetaDataKey meta_data_key;
+ const char* p = ObjectStoreMetaDataKey::Decode(
+ it->Key().begin(), it->Key().end(), &meta_data_key);
+ DCHECK(p);
+ if (meta_data_key.ObjectStoreId() != object_store_id)
+ return false;
+ if (meta_data_key.MetaDataType() != meta_data_type)
+ return false;
+ return true;
+}
+
+// TODO(jsbell): This should do some error handling rather than
+// plowing ahead when bad data is encountered.
+bool IndexedDBBackingStore::GetObjectStores(
+ int64 database_id,
+ IndexedDBDatabaseMetadata::ObjectStoreMap* object_stores) {
+ IDB_TRACE("IndexedDBBackingStore::get_object_stores");
+ if (!KeyPrefix::IsValidDatabaseId(database_id))
+ return false;
+ const std::vector<char> start_key =
+ ObjectStoreMetaDataKey::Encode(database_id, 1, 0);
+ const std::vector<char> stop_key =
+ ObjectStoreMetaDataKey::EncodeMaxKey(database_id);
+
+ DCHECK(object_stores->empty());
+
+ scoped_ptr<LevelDBIterator> it = db_->CreateIterator();
+ it->Seek(LevelDBSlice(start_key));
+ while (it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0) {
+ const char* p = it->Key().begin();
+ const char* limit = it->Key().end();
+
+ ObjectStoreMetaDataKey meta_data_key;
+ p = ObjectStoreMetaDataKey::Decode(p, limit, &meta_data_key);
+ DCHECK(p);
+ if (meta_data_key.MetaDataType() != ObjectStoreMetaDataKey::NAME) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ // Possible stale metadata, but don't fail the load.
+ it->Next();
+ continue;
+ }
+
+ int64 object_store_id = meta_data_key.ObjectStoreId();
+
+ // TODO(jsbell): Do this by direct key lookup rather than iteration, to
+ // simplify.
+ string16 object_store_name =
+ DecodeString(it->Value().begin(), it->Value().end());
+
+ it->Next();
+ if (!CheckObjectStoreAndMetaDataType(it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_PATH)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+ IndexedDBKeyPath key_path =
+ DecodeIDBKeyPath(it->Value().begin(), it->Value().end());
+
+ it->Next();
+ if (!CheckObjectStoreAndMetaDataType(
+ it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::AUTO_INCREMENT)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+ bool auto_increment = DecodeBool(it->Value().begin(), it->Value().end());
+
+ it->Next(); // Is evicatble.
+ if (!CheckObjectStoreAndMetaDataType(it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::EVICTABLE)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+
+ it->Next(); // Last version.
+ if (!CheckObjectStoreAndMetaDataType(
+ it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::LAST_VERSION)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+
+ it->Next(); // Maximum index id allocated.
+ if (!CheckObjectStoreAndMetaDataType(
+ it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::MAX_INDEX_ID)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+ int64 max_index_id = DecodeInt(it->Value().begin(), it->Value().end());
+
+ it->Next(); // [optional] has key path (is not null)
+ if (CheckObjectStoreAndMetaDataType(it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::HAS_KEY_PATH)) {
+ bool has_key_path = DecodeBool(it->Value().begin(), it->Value().end());
+ // This check accounts for two layers of legacy coding:
+ // (1) Initially, has_key_path was added to distinguish null vs. string.
+ // (2) Later, null vs. string vs. array was stored in the key_path itself.
+ // So this check is only relevant for string-type key_paths.
+ if (!has_key_path &&
+ (key_path.type() == WebKit::WebIDBKeyPath::StringType &&
+ !key_path.string().empty())) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+ if (!has_key_path)
+ key_path = IndexedDBKeyPath();
+ it->Next();
+ }
+
+ int64 key_generator_current_number = -1;
+ if (CheckObjectStoreAndMetaDataType(
+ it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER)) {
+ key_generator_current_number =
+ DecodeInt(it->Value().begin(), it->Value().end());
+ // TODO(jsbell): Return key_generator_current_number, cache in
+ // object store, and write lazily to backing store. For now,
+ // just assert that if it was written it was valid.
+ DCHECK_GE(key_generator_current_number, kKeyGeneratorInitialNumber);
+ it->Next();
+ }
+
+ IndexedDBObjectStoreMetadata metadata(object_store_name,
+ object_store_id,
+ key_path,
+ auto_increment,
+ max_index_id);
+ if (!GetIndexes(database_id, object_store_id, &metadata.indexes))
+ return false;
+ (*object_stores)[object_store_id] = metadata;
+ }
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool SetMaxObjectStoreId(
+ LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id) {
+ const std::vector<char> max_object_store_id_key = DatabaseMetaDataKey::Encode(
+ database_id, DatabaseMetaDataKey::MAX_OBJECT_STORE_ID);
+ int64 max_object_store_id = -1;
+ bool ok = GetMaxObjectStoreId(
+ transaction, max_object_store_id_key, max_object_store_id);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_MAX_OBJECT_STORE_ID);
+ return false;
+ }
+
+ if (object_store_id <= max_object_store_id) {
+ INTERNAL_CONSISTENCY_ERROR(SET_MAX_OBJECT_STORE_ID);
+ return false;
+ }
+ PutInt(transaction, LevelDBSlice(max_object_store_id_key), object_store_id);
+ return true;
+}
+
+bool IndexedDBBackingStore::CreateObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool auto_increment) {
+ IDB_TRACE("IndexedDBBackingStore::create_object_store");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ if (!SetMaxObjectStoreId(leveldb_transaction, database_id, object_store_id))
+ return false;
+
+ const std::vector<char> name_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::NAME);
+ const std::vector<char> key_path_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::KEY_PATH);
+ const std::vector<char> auto_increment_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::AUTO_INCREMENT);
+ const std::vector<char> evictable_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::EVICTABLE);
+ const std::vector<char> last_version_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::LAST_VERSION);
+ const std::vector<char> max_index_id_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID);
+ const std::vector<char> has_key_path_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::HAS_KEY_PATH);
+ const std::vector<char> key_generator_current_number_key =
+ ObjectStoreMetaDataKey::Encode(
+ database_id,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER);
+ const std::vector<char> names_key =
+ ObjectStoreNamesKey::Encode(database_id, name);
+
+ PutString(leveldb_transaction, LevelDBSlice(name_key), name);
+ PutIDBKeyPath(leveldb_transaction, LevelDBSlice(key_path_key), key_path);
+ PutInt(leveldb_transaction, LevelDBSlice(auto_increment_key), auto_increment);
+ PutInt(leveldb_transaction, LevelDBSlice(evictable_key), false);
+ PutInt(leveldb_transaction, LevelDBSlice(last_version_key), 1);
+ PutInt(leveldb_transaction, LevelDBSlice(max_index_id_key), kMinimumIndexId);
+ PutBool(
+ leveldb_transaction, LevelDBSlice(has_key_path_key), !key_path.IsNull());
+ PutInt(leveldb_transaction,
+ LevelDBSlice(key_generator_current_number_key),
+ kKeyGeneratorInitialNumber);
+ PutInt(leveldb_transaction, LevelDBSlice(names_key), object_store_id);
+ return true;
+}
+
+bool IndexedDBBackingStore::DeleteObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id) {
+ IDB_TRACE("IndexedDBBackingStore::delete_object_store");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ string16 object_store_name;
+ bool found = false;
+ bool ok = GetString(
+ leveldb_transaction,
+ LevelDBSlice(ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::NAME)),
+ object_store_name,
+ found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(DELETE_OBJECT_STORE);
+ return false;
+ }
+ if (!found) {
+ INTERNAL_CONSISTENCY_ERROR(DELETE_OBJECT_STORE);
+ return false;
+ }
+
+ DeleteRange(
+ leveldb_transaction,
+ ObjectStoreMetaDataKey::Encode(database_id, object_store_id, 0),
+ ObjectStoreMetaDataKey::EncodeMaxKey(database_id, object_store_id));
+
+ leveldb_transaction->Remove(LevelDBSlice(
+ ObjectStoreNamesKey::Encode(database_id, object_store_name)));
+
+ DeleteRange(leveldb_transaction,
+ IndexFreeListKey::Encode(database_id, object_store_id, 0),
+ IndexFreeListKey::EncodeMaxKey(database_id, object_store_id));
+ DeleteRange(leveldb_transaction,
+ IndexMetaDataKey::Encode(database_id, object_store_id, 0, 0),
+ IndexMetaDataKey::EncodeMaxKey(database_id, object_store_id));
+
+ return ClearObjectStore(transaction, database_id, object_store_id);
+}
+
+bool IndexedDBBackingStore::GetRecord(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& key,
+ std::vector<char>& record) {
+ IDB_TRACE("IndexedDBBackingStore::get_record");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ const std::vector<char> leveldb_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, key);
+ std::vector<char> data;
+
+ record.clear();
+
+ bool found = false;
+ bool ok = leveldb_transaction->Get(LevelDBSlice(leveldb_key), data, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_RECORD);
+ return false;
+ }
+ if (!found)
+ return true;
+ if (!data.size()) {
+ INTERNAL_READ_ERROR(GET_RECORD);
+ return false;
+ }
+
+ int64 version;
+ const char* p = DecodeVarInt(&*data.begin(), &*data.rbegin() + 1, version);
+ if (!p) {
+ INTERNAL_READ_ERROR(GET_RECORD);
+ return false;
+ }
+
+ record.insert(record.end(), p, static_cast<const char*>(&*data.rbegin()) + 1);
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool GetNewVersionNumber(
+ LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64& new_version_number) {
+ const std::vector<char> last_version_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::LAST_VERSION);
+
+ new_version_number = -1;
+ int64 last_version = -1;
+ bool found = false;
+ bool ok =
+ GetInt(transaction, LevelDBSlice(last_version_key), last_version, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_NEW_VERSION_NUMBER);
+ return false;
+ }
+ if (!found)
+ last_version = 0;
+
+ DCHECK_GE(last_version, 0);
+
+ int64 version = last_version + 1;
+ PutInt(transaction, LevelDBSlice(last_version_key), version);
+
+ DCHECK(version >
+ last_version); // TODO(jsbell): Think about how we want to handle
+ // the overflow scenario.
+
+ new_version_number = version;
+ return true;
+}
+
+bool IndexedDBBackingStore::PutRecord(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& key,
+ const std::vector<char>& value,
+ RecordIdentifier* record_identifier) {
+ IDB_TRACE("IndexedDBBackingStore::put_record");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ DCHECK(key.IsValid());
+
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ int64 version = -1;
+ bool ok = GetNewVersionNumber(
+ leveldb_transaction, database_id, object_store_id, version);
+ if (!ok)
+ return false;
+ DCHECK_GE(version, 0);
+ const std::vector<char> object_storedata_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, key);
+
+ std::vector<char> v(EncodeVarInt(version));
+
+ v.insert(v.end(), value.begin(), value.end());
+
+ leveldb_transaction->Put(LevelDBSlice(object_storedata_key), v);
+
+ const std::vector<char> exists_entry_key =
+ ExistsEntryKey::Encode(database_id, object_store_id, key);
+ leveldb_transaction->Put(LevelDBSlice(exists_entry_key), EncodeInt(version));
+
+ record_identifier->Reset(EncodeIDBKey(key), version);
+ return true;
+}
+
+bool IndexedDBBackingStore::ClearObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id) {
+ IDB_TRACE("IndexedDBBackingStore::clear_object_store");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ const std::vector<char> start_key =
+ KeyPrefix(database_id, object_store_id).Encode();
+ const std::vector<char> stop_key =
+ KeyPrefix(database_id, object_store_id + 1).Encode();
+
+ DeleteRange(leveldb_transaction, start_key, stop_key);
+ return true;
+}
+
+bool IndexedDBBackingStore::DeleteRecord(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const RecordIdentifier& record_identifier) {
+ IDB_TRACE("IndexedDBBackingStore::delete_record");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ const std::vector<char> object_store_data_key = ObjectStoreDataKey::Encode(
+ database_id, object_store_id, record_identifier.primary_key());
+ leveldb_transaction->Remove(LevelDBSlice(object_store_data_key));
+
+ const std::vector<char> exists_entry_key = ExistsEntryKey::Encode(
+ database_id, object_store_id, record_identifier.primary_key());
+ leveldb_transaction->Remove(LevelDBSlice(exists_entry_key));
+ return true;
+}
+
+bool IndexedDBBackingStore::GetKeyGeneratorCurrentNumber(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64& key_generator_current_number) {
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ const std::vector<char> key_generator_current_number_key =
+ ObjectStoreMetaDataKey::Encode(
+ database_id,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER);
+
+ key_generator_current_number = -1;
+ std::vector<char> data;
+
+ bool found = false;
+ bool ok = leveldb_transaction->Get(
+ LevelDBSlice(key_generator_current_number_key), data, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_KEY_GENERATOR_CURRENT_NUMBER);
+ return false;
+ }
+ if (found) {
+ key_generator_current_number = DecodeInt(data.begin(), data.end());
+ } else {
+ // Previously, the key generator state was not stored explicitly
+ // but derived from the maximum numeric key present in existing
+ // data. This violates the spec as the data may be cleared but the
+ // key generator state must be preserved.
+ // TODO(jsbell): Fix this for all stores on database open?
+ const std::vector<char> start_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, MinIDBKey());
+ const std::vector<char> stop_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, MaxIDBKey());
+
+ scoped_ptr<LevelDBIterator> it = leveldb_transaction->CreateIterator();
+ int64 max_numeric_key = 0;
+
+ for (it->Seek(LevelDBSlice(start_key));
+ it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0;
+ it->Next()) {
+ const char* p = it->Key().begin();
+ const char* limit = it->Key().end();
+
+ ObjectStoreDataKey data_key;
+ p = ObjectStoreDataKey::Decode(p, limit, &data_key);
+ DCHECK(p);
+
+ scoped_ptr<IndexedDBKey> user_key = data_key.user_key();
+ if (user_key->type() == WebKit::WebIDBKey::NumberType) {
+ int64 n = static_cast<int64>(user_key->number());
+ if (n > max_numeric_key)
+ max_numeric_key = n;
+ }
+ }
+
+ key_generator_current_number = max_numeric_key + 1;
+ }
+
+ return true;
+}
+
+bool IndexedDBBackingStore::MaybeUpdateKeyGeneratorCurrentNumber(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 new_number,
+ bool check_current) {
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ if (check_current) {
+ int64 current_number;
+ bool ok = GetKeyGeneratorCurrentNumber(
+ transaction, database_id, object_store_id, current_number);
+ if (!ok)
+ return false;
+ if (new_number <= current_number)
+ return true;
+ }
+
+ const std::vector<char> key_generator_current_number_key =
+ ObjectStoreMetaDataKey::Encode(
+ database_id,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER);
+ PutInt(leveldb_transaction,
+ LevelDBSlice(key_generator_current_number_key),
+ new_number);
+ return true;
+}
+
+bool IndexedDBBackingStore::KeyExistsInObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& key,
+ RecordIdentifier* found_record_identifier,
+ bool& found) {
+ IDB_TRACE("IndexedDBBackingStore::key_exists_in_object_store");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ found = false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ const std::vector<char> leveldb_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, key);
+ std::vector<char> data;
+
+ bool ok = leveldb_transaction->Get(LevelDBSlice(leveldb_key), data, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(KEY_EXISTS_IN_OBJECT_STORE);
+ return false;
+ }
+ if (!found)
+ return true;
+ if (!data.size()) {
+ INTERNAL_READ_ERROR(KEY_EXISTS_IN_OBJECT_STORE);
+ return false;
+ }
+
+ int64 version;
+ if (DecodeVarInt(&*data.begin(), &*data.rbegin() + 1, version) == 0)
+ return false;
+
+ found_record_identifier->Reset(EncodeIDBKey(key), version);
+ return true;
+}
+
+static bool CheckIndexAndMetaDataKey(const LevelDBIterator* it,
+ const std::vector<char>& stop_key,
+ int64 index_id,
+ unsigned char meta_data_type) {
+ if (!it->IsValid() || CompareKeys(it->Key(), LevelDBSlice(stop_key)) >= 0)
+ return false;
+
+ IndexMetaDataKey meta_data_key;
+ const char* p = IndexMetaDataKey::Decode(
+ it->Key().begin(), it->Key().end(), &meta_data_key);
+ DCHECK(p);
+ if (meta_data_key.IndexId() != index_id)
+ return false;
+ if (meta_data_key.meta_data_type() != meta_data_type)
+ return false;
+ return true;
+}
+
+// TODO(jsbell): This should do some error handling rather than plowing ahead
+// when bad
+// data is encountered.
+bool IndexedDBBackingStore::GetIndexes(
+ int64 database_id,
+ int64 object_store_id,
+ IndexedDBObjectStoreMetadata::IndexMap* indexes) {
+ IDB_TRACE("IndexedDBBackingStore::get_indexes");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ const std::vector<char> start_key =
+ IndexMetaDataKey::Encode(database_id, object_store_id, 0, 0);
+ const std::vector<char> stop_key =
+ IndexMetaDataKey::Encode(database_id, object_store_id + 1, 0, 0);
+
+ DCHECK(indexes->empty());
+
+ scoped_ptr<LevelDBIterator> it = db_->CreateIterator();
+ it->Seek(LevelDBSlice(start_key));
+ while (it->IsValid() &&
+ CompareKeys(LevelDBSlice(it->Key()), LevelDBSlice(stop_key)) < 0) {
+ const char* p = it->Key().begin();
+ const char* limit = it->Key().end();
+
+ IndexMetaDataKey meta_data_key;
+ p = IndexMetaDataKey::Decode(p, limit, &meta_data_key);
+ DCHECK(p);
+ if (meta_data_key.meta_data_type() != IndexMetaDataKey::NAME) {
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+ // Possible stale metadata due to http://webkit.org/b/85557 but don't fail
+ // the load.
+ it->Next();
+ continue;
+ }
+
+ // TODO(jsbell): Do this by direct key lookup rather than iteration, to
+ // simplify.
+ int64 index_id = meta_data_key.IndexId();
+ string16 index_name = DecodeString(it->Value().begin(), it->Value().end());
+
+ it->Next(); // unique flag
+ if (!CheckIndexAndMetaDataKey(
+ it.get(), stop_key, index_id, IndexMetaDataKey::UNIQUE)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+ break;
+ }
+ bool index_unique = DecodeBool(it->Value().begin(), it->Value().end());
+
+ it->Next(); // key_path
+ if (!CheckIndexAndMetaDataKey(
+ it.get(), stop_key, index_id, IndexMetaDataKey::KEY_PATH)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+ break;
+ }
+ IndexedDBKeyPath key_path =
+ DecodeIDBKeyPath(it->Value().begin(), it->Value().end());
+
+ it->Next(); // [optional] multi_entry flag
+ bool index_multi_entry = false;
+ if (CheckIndexAndMetaDataKey(
+ it.get(), stop_key, index_id, IndexMetaDataKey::MULTI_ENTRY)) {
+ index_multi_entry = DecodeBool(it->Value().begin(), it->Value().end());
+ it->Next();
+ }
+
+ (*indexes)[index_id] = IndexedDBIndexMetadata(
+ index_name, index_id, key_path, index_unique, index_multi_entry);
+ }
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool SetMaxIndexId(LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ int64 max_index_id = -1;
+ const std::vector<char> max_index_id_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID);
+ bool found = false;
+ bool ok =
+ GetInt(transaction, LevelDBSlice(max_index_id_key), max_index_id, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_MAX_INDEX_ID);
+ return false;
+ }
+ if (!found)
+ max_index_id = kMinimumIndexId;
+
+ if (index_id <= max_index_id) {
+ INTERNAL_CONSISTENCY_ERROR(SET_MAX_INDEX_ID);
+ return false;
+ }
+
+ PutInt(transaction, LevelDBSlice(max_index_id_key), index_id);
+ return true;
+}
+
+bool IndexedDBBackingStore::CreateIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool is_unique,
+ bool is_multi_entry) {
+ IDB_TRACE("IndexedDBBackingStore::create_index");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ if (!SetMaxIndexId(
+ leveldb_transaction, database_id, object_store_id, index_id))
+ return false;
+
+ const std::vector<char> name_key = IndexMetaDataKey::Encode(
+ database_id, object_store_id, index_id, IndexMetaDataKey::NAME);
+ const std::vector<char> unique_key = IndexMetaDataKey::Encode(
+ database_id, object_store_id, index_id, IndexMetaDataKey::UNIQUE);
+ const std::vector<char> key_path_key = IndexMetaDataKey::Encode(
+ database_id, object_store_id, index_id, IndexMetaDataKey::KEY_PATH);
+ const std::vector<char> multi_entry_key = IndexMetaDataKey::Encode(
+ database_id, object_store_id, index_id, IndexMetaDataKey::MULTI_ENTRY);
+
+ PutString(leveldb_transaction, LevelDBSlice(name_key), name);
+ PutBool(leveldb_transaction, LevelDBSlice(unique_key), is_unique);
+ PutIDBKeyPath(leveldb_transaction, LevelDBSlice(key_path_key), key_path);
+ PutBool(leveldb_transaction, LevelDBSlice(multi_entry_key), is_multi_entry);
+ return true;
+}
+
+bool IndexedDBBackingStore::DeleteIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ IDB_TRACE("IndexedDBBackingStore::delete_index");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ const std::vector<char> index_meta_data_start =
+ IndexMetaDataKey::Encode(database_id, object_store_id, index_id, 0);
+ const std::vector<char> index_meta_data_end =
+ IndexMetaDataKey::EncodeMaxKey(database_id, object_store_id, index_id);
+ DeleteRange(leveldb_transaction, index_meta_data_start, index_meta_data_end);
+
+ const std::vector<char> index_data_start =
+ IndexDataKey::EncodeMinKey(database_id, object_store_id, index_id);
+ const std::vector<char> index_data_end =
+ IndexDataKey::EncodeMaxKey(database_id, object_store_id, index_id);
+ DeleteRange(leveldb_transaction, index_data_start, index_data_end);
+ return true;
+}
+
+bool IndexedDBBackingStore::PutIndexDataForRecord(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ const RecordIdentifier& record_identifier) {
+ IDB_TRACE("IndexedDBBackingStore::put_index_data_for_record");
+ DCHECK(key.IsValid());
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ const std::vector<char> index_data_key =
+ IndexDataKey::Encode(database_id,
+ object_store_id,
+ index_id,
+ EncodeIDBKey(key),
+ record_identifier.primary_key());
+
+ std::vector<char> data(EncodeVarInt(record_identifier.version()));
+ const std::vector<char>& primary_key = record_identifier.primary_key();
+ data.insert(data.end(), primary_key.begin(), primary_key.end());
+
+ leveldb_transaction->Put(LevelDBSlice(index_data_key), data);
+ return true;
+}
+
+static bool FindGreatestKeyLessThanOrEqual(LevelDBTransaction* transaction,
+ const std::vector<char>& target,
+ std::vector<char>& found_key) {
+ scoped_ptr<LevelDBIterator> it = transaction->CreateIterator();
+ it->Seek(LevelDBSlice(target));
+
+ if (!it->IsValid()) {
+ it->SeekToLast();
+ if (!it->IsValid())
+ return false;
+ }
+
+ while (CompareIndexKeys(LevelDBSlice(it->Key()), LevelDBSlice(target)) > 0) {
+ it->Prev();
+ if (!it->IsValid())
+ return false;
+ }
+
+ do {
+ found_key.assign(it->Key().begin(), it->Key().end());
+
+ // There can be several index keys that compare equal. We want the last one.
+ it->Next();
+ } while (it->IsValid() && !CompareIndexKeys(it->Key(), LevelDBSlice(target)));
+
+ return true;
+}
+
+static bool VersionExists(LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 version,
+ const std::vector<char>& encoded_primary_key,
+ bool& exists) {
+ const std::vector<char> key =
+ ExistsEntryKey::Encode(database_id, object_store_id, encoded_primary_key);
+ std::vector<char> data;
+
+ bool ok = transaction->Get(LevelDBSlice(key), data, exists);
+ if (!ok) {
+ INTERNAL_READ_ERROR(VERSION_EXISTS);
+ return false;
+ }
+ if (!exists)
+ return true;
+
+ exists = (DecodeInt(data.begin(), data.end()) == version);
+ return true;
+}
+
+bool IndexedDBBackingStore::FindKeyInIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ std::vector<char>& found_encoded_primary_key,
+ bool& found) {
+ IDB_TRACE("IndexedDBBackingStore::find_key_in_index");
+ DCHECK(KeyPrefix::ValidIds(database_id, object_store_id, index_id));
+
+ DCHECK(found_encoded_primary_key.empty());
+ found = false;
+
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ const std::vector<char> leveldb_key =
+ IndexDataKey::Encode(database_id, object_store_id, index_id, key);
+ scoped_ptr<LevelDBIterator> it = leveldb_transaction->CreateIterator();
+ it->Seek(LevelDBSlice(leveldb_key));
+
+ for (;;) {
+ if (!it->IsValid())
+ return true;
+ if (CompareIndexKeys(it->Key(), LevelDBSlice(leveldb_key)) > 0)
+ return true;
+
+ int64 version;
+ const char* p =
+ DecodeVarInt(it->Value().begin(), it->Value().end(), version);
+ if (!p) {
+ INTERNAL_READ_ERROR(FIND_KEY_IN_INDEX);
+ return false;
+ }
+ found_encoded_primary_key.insert(
+ found_encoded_primary_key.end(), p, it->Value().end());
+
+ bool exists = false;
+ bool ok = VersionExists(leveldb_transaction,
+ database_id,
+ object_store_id,
+ version,
+ found_encoded_primary_key,
+ exists);
+ if (!ok)
+ return false;
+ if (!exists) {
+ // Delete stale index data entry and continue.
+ leveldb_transaction->Remove(it->Key());
+ it->Next();
+ continue;
+ }
+ found = true;
+ return true;
+ }
+}
+
+bool IndexedDBBackingStore::GetPrimaryKeyViaIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ scoped_ptr<IndexedDBKey>* primary_key) {
+ IDB_TRACE("IndexedDBBackingStore::get_primary_key_via_index");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+
+ bool found = false;
+ std::vector<char> found_encoded_primary_key;
+ bool ok = FindKeyInIndex(transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ key,
+ found_encoded_primary_key,
+ found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_PRIMARY_KEY_VIA_INDEX);
+ return false;
+ }
+ if (!found)
+ return true;
+ if (!found_encoded_primary_key.size()) {
+ INTERNAL_READ_ERROR(GET_PRIMARY_KEY_VIA_INDEX);
+ return false;
+ }
+
+ DecodeIDBKey(&*found_encoded_primary_key.begin(),
+ &*found_encoded_primary_key.rbegin() + 1,
+ primary_key);
+ return true;
+}
+
+bool IndexedDBBackingStore::KeyExistsInIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& index_key,
+ scoped_ptr<IndexedDBKey>* found_primary_key,
+ bool& exists) {
+ IDB_TRACE("IndexedDBBackingStore::key_exists_in_index");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+
+ exists = false;
+ std::vector<char> found_encoded_primary_key;
+ bool ok = FindKeyInIndex(transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ index_key,
+ found_encoded_primary_key,
+ exists);
+ if (!ok) {
+ INTERNAL_READ_ERROR(KEY_EXISTS_IN_INDEX);
+ return false;
+ }
+ if (!exists)
+ return true;
+ if (!found_encoded_primary_key.size()) {
+ INTERNAL_READ_ERROR(KEY_EXISTS_IN_INDEX);
+ return false;
+ }
+
+ DecodeIDBKey(&*found_encoded_primary_key.begin(),
+ &*found_encoded_primary_key.rbegin() + 1,
+ found_primary_key);
+ return true;
+}
+
+IndexedDBBackingStore::Cursor::Cursor(
+ const IndexedDBBackingStore::Cursor* other)
+ : transaction_(other->transaction_),
+ cursor_options_(other->cursor_options_),
+ current_key_(new IndexedDBKey(*other->current_key_)) {
+ if (other->iterator_) {
+ iterator_ = transaction_->CreateIterator();
+
+ if (other->iterator_->IsValid()) {
+ iterator_->Seek(other->iterator_->Key());
+ DCHECK(iterator_->IsValid());
+ }
+ }
+}
+
+IndexedDBBackingStore::Cursor::Cursor(LevelDBTransaction* transaction,
+ const CursorOptions& cursor_options)
+ : transaction_(transaction), cursor_options_(cursor_options) {}
+IndexedDBBackingStore::Cursor::~Cursor() {}
+
+bool IndexedDBBackingStore::Cursor::FirstSeek() {
+ iterator_ = transaction_->CreateIterator();
+ if (cursor_options_.forward)
+ iterator_->Seek(LevelDBSlice(cursor_options_.low_key));
+ else
+ iterator_->Seek(LevelDBSlice(cursor_options_.high_key));
+
+ return ContinueFunction(0, READY);
+}
+
+bool IndexedDBBackingStore::Cursor::Advance(unsigned long count) {
+ while (count--) {
+ if (!ContinueFunction())
+ return false;
+ }
+ return true;
+}
+
+bool IndexedDBBackingStore::Cursor::ContinueFunction(const IndexedDBKey* key,
+ IteratorState next_state) {
+ // TODO(alecflett): avoid a copy here?
+ IndexedDBKey previous_key = current_key_ ? *current_key_ : IndexedDBKey();
+
+ bool first_iteration = true;
+
+ // When iterating with PrevNoDuplicate, spec requires that the
+ // value we yield for each key is the first duplicate in forwards
+ // order.
+ IndexedDBKey last_duplicate_key;
+
+ bool forward = cursor_options_.forward;
+
+ for (;;) {
+ if (next_state == SEEK) {
+ // TODO(jsbell): Optimize seeking for reverse cursors as well.
+ if (first_iteration && key && key->IsValid() && forward) {
+ iterator_->Seek(LevelDBSlice(EncodeKey(*key)));
+ first_iteration = false;
+ } else if (forward) {
+ iterator_->Next();
+ } else {
+ iterator_->Prev();
+ }
+ } else {
+ next_state = SEEK; // for subsequent iterations
+ }
+
+ if (!iterator_->IsValid()) {
+ if (!forward && last_duplicate_key.IsValid()) {
+ // We need to walk forward because we hit the end of
+ // the data.
+ forward = true;
+ continue;
+ }
+
+ return false;
+ }
+
+ if (IsPastBounds()) {
+ if (!forward && last_duplicate_key.IsValid()) {
+ // We need to walk forward because now we're beyond the
+ // bounds defined by the cursor.
+ forward = true;
+ continue;
+ }
+
+ return false;
+ }
+
+ if (!HaveEnteredRange())
+ continue;
+
+ // The row may not load because there's a stale entry in the
+ // index. This is not fatal.
+ if (!LoadCurrentRow())
+ continue;
+
+ if (key && key->IsValid()) {
+ if (forward) {
+ if (current_key_->IsLessThan(*key))
+ continue;
+ } else {
+ if (key->IsLessThan(*current_key_))
+ continue;
+ }
+ }
+
+ if (cursor_options_.unique) {
+ if (previous_key.IsValid() && current_key_->IsEqual(previous_key)) {
+ // We should never be able to walk forward all the way
+ // to the previous key.
+ DCHECK(!last_duplicate_key.IsValid());
+ continue;
+ }
+
+ if (!forward) {
+ if (!last_duplicate_key.IsValid()) {
+ last_duplicate_key = *current_key_;
+ continue;
+ }
+
+ // We need to walk forward because we hit the boundary
+ // between key ranges.
+ if (!last_duplicate_key.IsEqual(*current_key_)) {
+ forward = true;
+ continue;
+ }
+
+ continue;
+ }
+ }
+ break;
+ }
+
+ DCHECK(!last_duplicate_key.IsValid() ||
+ (forward && last_duplicate_key.IsEqual(*current_key_)));
+ return true;
+}
+
+bool IndexedDBBackingStore::Cursor::HaveEnteredRange() const {
+ if (cursor_options_.forward) {
+ int compare = CompareIndexKeys(iterator_->Key(),
+ LevelDBSlice(cursor_options_.low_key));
+ if (cursor_options_.low_open) {
+ return compare > 0;
+ }
+ return compare >= 0;
+ }
+ int compare = CompareIndexKeys(iterator_->Key(),
+ LevelDBSlice(cursor_options_.high_key));
+ if (cursor_options_.high_open) {
+ return compare < 0;
+ }
+ return compare <= 0;
+}
+
+bool IndexedDBBackingStore::Cursor::IsPastBounds() const {
+ if (cursor_options_.forward) {
+ int compare = CompareIndexKeys(iterator_->Key(),
+ LevelDBSlice(cursor_options_.high_key));
+ if (cursor_options_.high_open) {
+ return compare >= 0;
+ }
+ return compare > 0;
+ }
+ int compare =
+ CompareIndexKeys(iterator_->Key(), LevelDBSlice(cursor_options_.low_key));
+ if (cursor_options_.low_open) {
+ return compare <= 0;
+ }
+ return compare < 0;
+}
+
+const IndexedDBKey& IndexedDBBackingStore::Cursor::primary_key() const {
+ return *current_key_;
+}
+
+const IndexedDBBackingStore::RecordIdentifier&
+IndexedDBBackingStore::Cursor::record_identifier() const {
+ return record_identifier_;
+}
+
+class ObjectStoreKeyCursorImpl : public IndexedDBBackingStore::Cursor {
+ public:
+ ObjectStoreKeyCursorImpl(
+ LevelDBTransaction* transaction,
+ const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options)
+ : IndexedDBBackingStore::Cursor(transaction, cursor_options) {}
+
+ virtual Cursor* Clone() OVERRIDE {
+ return new ObjectStoreKeyCursorImpl(this);
+ }
+
+ // IndexedDBBackingStore::Cursor
+ virtual std::vector<char>* Value() OVERRIDE {
+ NOTREACHED();
+ return NULL;
+ }
+ virtual bool LoadCurrentRow() OVERRIDE;
+
+ protected:
+ virtual std::vector<char> EncodeKey(const IndexedDBKey& key) OVERRIDE {
+ return ObjectStoreDataKey::Encode(
+ cursor_options_.database_id, cursor_options_.object_store_id, key);
+ }
+
+ private:
+ explicit ObjectStoreKeyCursorImpl(const ObjectStoreKeyCursorImpl* other)
+ : IndexedDBBackingStore::Cursor(other) {}
+};
+
+bool ObjectStoreKeyCursorImpl::LoadCurrentRow() {
+ const char* key_position = iterator_->Key().begin();
+ const char* key_limit = iterator_->Key().end();
+
+ ObjectStoreDataKey object_store_data_key;
+ key_position = ObjectStoreDataKey::Decode(
+ key_position, key_limit, &object_store_data_key);
+ if (!key_position) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ current_key_ = object_store_data_key.user_key();
+
+ int64 version;
+ const char* value_position = DecodeVarInt(
+ iterator_->Value().begin(), iterator_->Value().end(), version);
+ if (!value_position) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ // TODO(jsbell): This re-encodes what was just decoded; try and optimize.
+ record_identifier_.Reset(EncodeIDBKey(*current_key_), version);
+
+ return true;
+}
+
+class ObjectStoreCursorImpl : public IndexedDBBackingStore::Cursor {
+ public:
+ ObjectStoreCursorImpl(
+ LevelDBTransaction* transaction,
+ const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options)
+ : IndexedDBBackingStore::Cursor(transaction, cursor_options) {}
+
+ virtual Cursor* Clone() OVERRIDE { return new ObjectStoreCursorImpl(this); }
+
+ // IndexedDBBackingStore::Cursor
+ virtual std::vector<char>* Value() OVERRIDE { return &current_value_; }
+ virtual bool LoadCurrentRow() OVERRIDE;
+
+ protected:
+ virtual std::vector<char> EncodeKey(const IndexedDBKey& key) OVERRIDE {
+ return ObjectStoreDataKey::Encode(
+ cursor_options_.database_id, cursor_options_.object_store_id, key);
+ }
+
+ private:
+ explicit ObjectStoreCursorImpl(const ObjectStoreCursorImpl* other)
+ : IndexedDBBackingStore::Cursor(other),
+ current_value_(other->current_value_) {}
+
+ std::vector<char> current_value_;
+};
+
+bool ObjectStoreCursorImpl::LoadCurrentRow() {
+ const char* key_position = iterator_->Key().begin();
+ const char* key_limit = iterator_->Key().end();
+
+ ObjectStoreDataKey object_store_data_key;
+ key_position = ObjectStoreDataKey::Decode(
+ key_position, key_limit, &object_store_data_key);
+ if (!key_position) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ current_key_ = object_store_data_key.user_key();
+
+ int64 version;
+ const char* value_position = DecodeVarInt(
+ iterator_->Value().begin(), iterator_->Value().end(), version);
+ if (!value_position) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ // TODO(jsbell): This re-encodes what was just decoded; try and optimize.
+ record_identifier_.Reset(EncodeIDBKey(*current_key_), version);
+
+ std::vector<char> value;
+ value.insert(value.end(), value_position, iterator_->Value().end());
+ current_value_.swap(value);
+ return true;
+}
+
+class IndexKeyCursorImpl : public IndexedDBBackingStore::Cursor {
+ public:
+ IndexKeyCursorImpl(
+ LevelDBTransaction* transaction,
+ const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options)
+ : IndexedDBBackingStore::Cursor(transaction, cursor_options) {}
+
+ virtual Cursor* Clone() OVERRIDE { return new IndexKeyCursorImpl(this); }
+
+ // IndexedDBBackingStore::Cursor
+ virtual std::vector<char>* Value() OVERRIDE {
+ NOTREACHED();
+ return NULL;
+ }
+ virtual const IndexedDBKey& primary_key() const OVERRIDE {
+ return *primary_key_;
+ }
+ virtual const IndexedDBBackingStore::RecordIdentifier& RecordIdentifier()
+ const {
+ NOTREACHED();
+ return record_identifier_;
+ }
+ virtual bool LoadCurrentRow() OVERRIDE;
+
+ protected:
+ virtual std::vector<char> EncodeKey(const IndexedDBKey& key) OVERRIDE {
+ return IndexDataKey::Encode(cursor_options_.database_id,
+ cursor_options_.object_store_id,
+ cursor_options_.index_id,
+ key);
+ }
+
+ private:
+ explicit IndexKeyCursorImpl(const IndexKeyCursorImpl* other)
+ : IndexedDBBackingStore::Cursor(other),
+ primary_key_(new IndexedDBKey(*other->primary_key_)) {}
+
+ scoped_ptr<IndexedDBKey> primary_key_;
+};
+
+bool IndexKeyCursorImpl::LoadCurrentRow() {
+ const char* key_position = iterator_->Key().begin();
+ const char* key_limit = iterator_->Key().end();
+
+ IndexDataKey index_data_key;
+ key_position = IndexDataKey::Decode(key_position, key_limit, &index_data_key);
+
+ current_key_ = index_data_key.user_key();
+ DCHECK(current_key_);
+
+ int64 index_data_version;
+ const char* value_position = DecodeVarInt(
+ iterator_->Value().begin(), iterator_->Value().end(), index_data_version);
+ if (!value_position) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ value_position =
+ DecodeIDBKey(value_position, iterator_->Value().end(), &primary_key_);
+ if (!value_position) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ std::vector<char> primary_leveldb_key =
+ ObjectStoreDataKey::Encode(index_data_key.DatabaseId(),
+ index_data_key.ObjectStoreId(),
+ *primary_key_);
+
+ std::vector<char> result;
+ bool found = false;
+ bool ok = transaction_->Get(LevelDBSlice(primary_leveldb_key), result, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+ if (!found) {
+ transaction_->Remove(iterator_->Key());
+ return false;
+ }
+ if (!result.size()) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ int64 object_store_data_version;
+ const char* t = DecodeVarInt(
+ &*result.begin(), &*result.rbegin() + 1, object_store_data_version);
+ if (!t) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ if (object_store_data_version != index_data_version) {
+ transaction_->Remove(iterator_->Key());
+ return false;
+ }
+
+ return true;
+}
+
+class IndexCursorImpl : public IndexedDBBackingStore::Cursor {
+ public:
+ IndexCursorImpl(
+ LevelDBTransaction* transaction,
+ const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options)
+ : IndexedDBBackingStore::Cursor(transaction, cursor_options) {}
+
+ virtual Cursor* Clone() OVERRIDE { return new IndexCursorImpl(this); }
+
+ // IndexedDBBackingStore::Cursor
+ virtual std::vector<char>* Value() OVERRIDE { return &current_value_; }
+ virtual const IndexedDBKey& primary_key() const OVERRIDE {
+ return *primary_key_;
+ }
+ virtual const IndexedDBBackingStore::RecordIdentifier& RecordIdentifier()
+ const {
+ NOTREACHED();
+ return record_identifier_;
+ }
+ virtual bool LoadCurrentRow() OVERRIDE;
+
+ protected:
+ virtual std::vector<char> EncodeKey(const IndexedDBKey& key) OVERRIDE {
+ return IndexDataKey::Encode(cursor_options_.database_id,
+ cursor_options_.object_store_id,
+ cursor_options_.index_id,
+ key);
+ }
+
+ private:
+ explicit IndexCursorImpl(const IndexCursorImpl* other)
+ : IndexedDBBackingStore::Cursor(other),
+ primary_key_(new IndexedDBKey(*other->primary_key_)),
+ current_value_(other->current_value_),
+ primary_leveldb_key_(other->primary_leveldb_key_) {}
+
+ scoped_ptr<IndexedDBKey> primary_key_;
+ std::vector<char> current_value_;
+ std::vector<char> primary_leveldb_key_;
+};
+
+bool IndexCursorImpl::LoadCurrentRow() {
+ const char* key_position = iterator_->Key().begin();
+ const char* key_limit = iterator_->Key().end();
+
+ IndexDataKey index_data_key;
+ key_position = IndexDataKey::Decode(key_position, key_limit, &index_data_key);
+
+ current_key_ = index_data_key.user_key();
+ DCHECK(current_key_);
+
+ const char* value_position = iterator_->Value().begin();
+ const char* value_limit = iterator_->Value().end();
+
+ int64 index_data_version;
+ value_position =
+ DecodeVarInt(value_position, value_limit, index_data_version);
+ if (!value_position) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+ value_position = DecodeIDBKey(value_position, value_limit, &primary_key_);
+ if (!value_position) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ primary_leveldb_key_ =
+ ObjectStoreDataKey::Encode(index_data_key.DatabaseId(),
+ index_data_key.ObjectStoreId(),
+ *primary_key_);
+
+ std::vector<char> result;
+ bool found = false;
+ bool ok =
+ transaction_->Get(LevelDBSlice(primary_leveldb_key_), result, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+ if (!found) {
+ transaction_->Remove(iterator_->Key());
+ return false;
+ }
+ if (!result.size()) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ int64 object_store_data_version;
+ value_position = DecodeVarInt(
+ &*result.begin(), &*result.rbegin() + 1, object_store_data_version);
+ if (!value_position) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ if (object_store_data_version != index_data_version) {
+ transaction_->Remove(iterator_->Key());
+ return false;
+ }
+
+ // TODO(jsbell): Make value_position an iterator.
+ std::vector<char> value;
+ value.insert(value.end(),
+ value_position,
+ static_cast<const char*>(&*result.rbegin()) + 1);
+ current_value_.swap(value);
+ return true;
+}
+
+bool ObjectStoreCursorOptions(
+ LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction,
+ IndexedDBBackingStore::Cursor::CursorOptions& cursor_options) {
+ cursor_options.database_id = database_id;
+ cursor_options.object_store_id = object_store_id;
+
+ bool lower_bound = range.lower().IsValid();
+ bool upper_bound = range.upper().IsValid();
+ cursor_options.forward = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE ||
+ direction == indexed_db::CURSOR_NEXT);
+ cursor_options.unique = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE ||
+ direction == indexed_db::CURSOR_PREV_NO_DUPLICATE);
+
+ if (!lower_bound) {
+ cursor_options.low_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, MinIDBKey());
+ cursor_options.low_open = true; // Not included.
+ } else {
+ cursor_options.low_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, range.lower());
+ cursor_options.low_open = range.lowerOpen();
+ }
+
+ if (!upper_bound) {
+ cursor_options.high_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, MaxIDBKey());
+
+ if (cursor_options.forward) {
+ cursor_options.high_open = true; // Not included.
+ } else {
+ // We need a key that exists.
+ if (!FindGreatestKeyLessThanOrEqual(
+ transaction, cursor_options.high_key, cursor_options.high_key))
+ return false;
+ cursor_options.high_open = false;
+ }
+ } else {
+ cursor_options.high_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, range.upper());
+ cursor_options.high_open = range.upperOpen();
+
+ if (!cursor_options.forward) {
+ // For reverse cursors, we need a key that exists.
+ std::vector<char> found_high_key;
+ if (!FindGreatestKeyLessThanOrEqual(
+ transaction, cursor_options.high_key, found_high_key))
+ return false;
+
+ // If the target key should not be included, but we end up with a smaller
+ // key, we should include that.
+ if (cursor_options.high_open &&
+ CompareIndexKeys(LevelDBSlice(found_high_key),
+ LevelDBSlice(cursor_options.high_key)) <
+ 0)
+ cursor_options.high_open = false;
+
+ cursor_options.high_key = found_high_key;
+ }
+ }
+
+ return true;
+}
+
+bool IndexCursorOptions(
+ LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction,
+ IndexedDBBackingStore::Cursor::CursorOptions& cursor_options) {
+ DCHECK(transaction);
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+
+ cursor_options.database_id = database_id;
+ cursor_options.object_store_id = object_store_id;
+ cursor_options.index_id = index_id;
+
+ bool lower_bound = range.lower().IsValid();
+ bool upper_bound = range.upper().IsValid();
+ cursor_options.forward = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE ||
+ direction == indexed_db::CURSOR_NEXT);
+ cursor_options.unique = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE ||
+ direction == indexed_db::CURSOR_PREV_NO_DUPLICATE);
+
+ if (!lower_bound) {
+ cursor_options.low_key =
+ IndexDataKey::EncodeMinKey(database_id, object_store_id, index_id);
+ cursor_options.low_open = false; // Included.
+ } else {
+ cursor_options.low_key = IndexDataKey::Encode(
+ database_id, object_store_id, index_id, range.lower());
+ cursor_options.low_open = range.lowerOpen();
+ }
+
+ if (!upper_bound) {
+ cursor_options.high_key =
+ IndexDataKey::EncodeMaxKey(database_id, object_store_id, index_id);
+ cursor_options.high_open = false; // Included.
+
+ if (!cursor_options.forward) { // We need a key that exists.
+ if (!FindGreatestKeyLessThanOrEqual(
+ transaction, cursor_options.high_key, cursor_options.high_key))
+ return false;
+ cursor_options.high_open = false;
+ }
+ } else {
+ cursor_options.high_key = IndexDataKey::Encode(
+ database_id, object_store_id, index_id, range.upper());
+ cursor_options.high_open = range.upperOpen();
+
+ std::vector<char> found_high_key;
+ if (!FindGreatestKeyLessThanOrEqual(
+ transaction,
+ cursor_options.high_key,
+ found_high_key)) // Seek to the *last* key in the set of non-unique
+ // keys.
+ return false;
+
+ // If the target key should not be included, but we end up with a smaller
+ // key, we should include that.
+ if (cursor_options.high_open &&
+ CompareIndexKeys(LevelDBSlice(found_high_key),
+ LevelDBSlice(cursor_options.high_key)) <
+ 0)
+ cursor_options.high_open = false;
+
+ cursor_options.high_key = found_high_key;
+ }
+
+ return true;
+}
+
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBBackingStore::OpenObjectStoreCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction) {
+ IDB_TRACE("IndexedDBBackingStore::open_object_store_cursor");
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ IndexedDBBackingStore::Cursor::CursorOptions cursor_options;
+ if (!ObjectStoreCursorOptions(leveldb_transaction,
+ database_id,
+ object_store_id,
+ range,
+ direction,
+ cursor_options))
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+ scoped_ptr<ObjectStoreCursorImpl> cursor(
+ new ObjectStoreCursorImpl(leveldb_transaction, cursor_options));
+ if (!cursor->FirstSeek())
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+
+ return cursor.PassAs<IndexedDBBackingStore::Cursor>();
+}
+
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBBackingStore::OpenObjectStoreKeyCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction) {
+ IDB_TRACE("IndexedDBBackingStore::open_object_store_key_cursor");
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ IndexedDBBackingStore::Cursor::CursorOptions cursor_options;
+ if (!ObjectStoreCursorOptions(leveldb_transaction,
+ database_id,
+ object_store_id,
+ range,
+ direction,
+ cursor_options))
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+ scoped_ptr<ObjectStoreKeyCursorImpl> cursor(
+ new ObjectStoreKeyCursorImpl(leveldb_transaction, cursor_options));
+ if (!cursor->FirstSeek())
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+
+ return cursor.PassAs<IndexedDBBackingStore::Cursor>();
+}
+
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBBackingStore::OpenIndexKeyCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction) {
+ IDB_TRACE("IndexedDBBackingStore::open_index_key_cursor");
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ IndexedDBBackingStore::Cursor::CursorOptions cursor_options;
+ if (!IndexCursorOptions(leveldb_transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ range,
+ direction,
+ cursor_options))
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+ scoped_ptr<IndexKeyCursorImpl> cursor(
+ new IndexKeyCursorImpl(leveldb_transaction, cursor_options));
+ if (!cursor->FirstSeek())
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+
+ return cursor.PassAs<IndexedDBBackingStore::Cursor>();
+}
+
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBBackingStore::OpenIndexCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction) {
+ IDB_TRACE("IndexedDBBackingStore::open_index_cursor");
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ IndexedDBBackingStore::Cursor::CursorOptions cursor_options;
+ if (!IndexCursorOptions(leveldb_transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ range,
+ direction,
+ cursor_options))
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+ scoped_ptr<IndexCursorImpl> cursor(
+ new IndexCursorImpl(leveldb_transaction, cursor_options));
+ if (!cursor->FirstSeek())
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+
+ return cursor.PassAs<IndexedDBBackingStore::Cursor>();
+}
+
+IndexedDBBackingStore::Transaction::Transaction(
+ IndexedDBBackingStore* backing_store)
+ : backing_store_(backing_store) {}
+
+IndexedDBBackingStore::Transaction::~Transaction() {}
+
+void IndexedDBBackingStore::Transaction::begin() {
+ IDB_TRACE("IndexedDBBackingStore::Transaction::begin");
+ DCHECK(!transaction_);
+ transaction_ = LevelDBTransaction::Create(backing_store_->db_.get());
+}
+
+bool IndexedDBBackingStore::Transaction::Commit() {
+ IDB_TRACE("IndexedDBBackingStore::Transaction::commit");
+ DCHECK(transaction_);
+ bool result = transaction_->Commit();
+ transaction_ = NULL;
+ if (!result)
+ INTERNAL_WRITE_ERROR(TRANSACTION_COMMIT_METHOD);
+ return result;
+}
+
+void IndexedDBBackingStore::Transaction::Rollback() {
+ IDB_TRACE("IndexedDBBackingStore::Transaction::rollback");
+ DCHECK(transaction_);
+ transaction_->Rollback();
+ transaction_ = NULL;
+}
+
+} // namespace content
« no previous file with comments | « content/browser/indexed_db/indexed_db_backing_store.h ('k') | content/browser/indexed_db/indexed_db_backing_store_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698