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 ¤t_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 ¤t_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 |