Chromium Code Reviews| 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..73afb2e1d40afbfc3692dc5b07b97083a4fe531f |
| --- /dev/null |
| +++ b/content/browser/indexed_db/indexed_db_backing_store.cc |
| @@ -0,0 +1,2518 @@ |
| +// 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 <public/Platform.h> |
| +#include <public/WebIDBKey.h> |
| +#include "base/file_util.h" |
| +#include "base/files/file_path.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/Source/Platform/chromium/public/WebIDBKeyPath.h" |
| + |
| +// TODO: Make blink push the version during the open() call. |
| +static const uint32_t kWireVersion = 2; |
| + |
| +namespace content { |
| + |
| +using namespace IndexedDBLevelDBCoding; |
| + |
| +const int64_t KeyGeneratorInitialNumber = |
|
jamesr
2013/05/21 23:56:06
constants in chromium should be named kFooBarBaz
jsbell
2013/05/22 17:54:44
Done.
|
| + 1; // From the IndexedDB specification. |
| + |
| +enum IndexedDBBackingStoreErrorSource { |
| + // 0 - 2 are no longer used. |
| + kFindKeyInIndex = 3, |
|
jamesr
2013/05/21 23:56:06
enum values should be SHOUTY
jsbell
2013/05/22 17:54:44
Done.
|
| + kGetIDBDatabaseMetaData, |
| + kGetIndexes, |
| + kGetKeyGeneratorCurrentNumber, |
| + kGetObjectStores, |
| + kGetRecord, |
| + kKeyExistsInObjectStore, |
| + kLoadCurrentRow, |
| + kSetupMetadata, |
| + kGetPrimaryKeyViaIndex, |
| + kKeyExistsInIndex, |
| + kVersionExists, |
| + kDeleteObjectStore, |
| + kSetMaxObjectStoreId, |
| + kSetMaxIndexId, |
| + kGetNewDatabaseId, |
| + kGetNewVersionNumber, |
| + kCreateIDBDatabaseMetaData, |
| + kDeleteDatabase, |
| + kTransactionCommit, |
| + kIndexedDBLevelDBBackingStoreInternalErrorMax, |
| +}; |
| + |
| +static void RecordInternalError(const char* type, |
| + IndexedDBBackingStoreErrorSource location) { |
| + string16 name = ASCIIToUTF16("WebCore.IndexedDB.BackingStore.") + |
| + UTF8ToUTF16(type) + ASCIIToUTF16("Error"); |
| + base::Histogram::FactoryGet(UTF16ToUTF8(name), |
| + 1, |
| + kIndexedDBLevelDBBackingStoreInternalErrorMax, |
| + kIndexedDBLevelDBBackingStoreInternalErrorMax + 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, |
|
jamesr
2013/05/21 23:56:06
seems overwrapped. did clang-format put this on a
jsbell
2013/05/22 17:54:44
Yeah, we're just letting clang-format do it's thin
jsbell
2013/05/23 21:10:49
A clang-format update appears to have addressed th
|
| + const LevelDBSlice& key, |
| + int64_t& 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_t value) { |
| + DCHECK(value >= 0); |
| + transaction->Put(key, EncodeInt(value)); |
| +} |
| + |
| +template <typename DBOrTransaction> |
| +WARN_UNUSED_RESULT static bool |
| +GetVarInt(DBOrTransaction* db, |
| + const LevelDBSlice& key, |
| + int64_t& found_int, |
| + bool& found) { |
| + std::vector<char> result; |
| + bool ok = db->Get(key, result, found); |
| + if (!ok) |
| + return false; |
| + if (!found) |
| + return true; |
| + |
| + found = DecodeVarInt(result.begin(), result.end(), found_int) == result.end(); |
| + return true; |
| +} |
| + |
| +static void PutVarInt(LevelDBTransaction* transaction, |
| + const LevelDBSlice& key, |
| + int64_t 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; |
| + |
| + found_string = DecodeString(&result[0], &result[0] + result.size()); |
| + 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 IndexedDBLevelDBCoding::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. |
| +const int64_t latest_known_schema_version = 2; |
|
jamesr
2013/05/21 23:56:06
should be kLatest...., and probably static
jsbell
2013/05/22 17:54:44
Done.
|
| +WARN_UNUSED_RESULT static bool IsSchemaKnown(LevelDBDatabase* db, bool& known) { |
| + int64_t 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 > latest_known_schema_version) { |
| + known = false; |
| + return true; |
| + } |
| + |
| + const uint32_t latest_known_data_version = kWireVersion; |
| + int64_t 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_t 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_t db_schema_version = 0; |
| + int64_t 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(kSetupMetadata); |
| + return false; |
| + } |
| + if (!found) { |
| + // Initialize new backing store. |
| + db_schema_version = latest_known_schema_version; |
| + 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(db_schema_version <= latest_known_schema_version); |
| + 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_t database_id = 0; |
| + found = false; |
| + bool ok = GetInt(transaction.get(), it->Key(), database_id, found); |
| + if (!ok) { |
| + INTERNAL_READ_ERROR(kSetupMetadata); |
| + return false; |
| + } |
| + if (!found) { |
| + INTERNAL_CONSISTENCY_ERROR(kSetupMetadata); |
| + return false; |
| + } |
| + std::vector<char> int_version_key = DatabaseMetaDataKey::Encode( |
| + database_id, DatabaseMetaDataKey::UserIntVersion); |
| + PutVarInt(transaction.get(), |
| + LevelDBSlice(int_version_key), |
| + IndexedDBDatabaseMetadata::DefaultIntVersion); |
| + } |
| + } |
| + 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(kSetupMetadata); |
| + return false; |
| + } |
| + if (!found) { |
| + INTERNAL_CONSISTENCY_ERROR(kSetupMetadata); |
| + 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(db_schema_version == latest_known_schema_version); |
| + DCHECK(db_data_version == latest_known_data_version); |
| + |
| + if (!transaction->Commit()) { |
| + INTERNAL_WRITE_ERROR(kSetupMetadata); |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +template <typename DBOrTransaction> |
| +WARN_UNUSED_RESULT static bool |
| +GetMaxObjectStoreId(DBOrTransaction* db, |
| + int64_t database_id, |
| + int64_t& max_object_store_id) { |
| + const std::vector<char> max_object_store_id_key = DatabaseMetaDataKey::Encode( |
| + database_id, DatabaseMetaDataKey::MaxObjectStoreId); |
| + 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_t& 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(max_object_store_id >= 0); |
| + return true; |
| +} |
| + |
| +class DefaultLevelDBFactory : public LevelDBFactory { |
| + public: |
| + virtual scoped_ptr<LevelDBDatabase> OpenLevelDB( |
| + const string16& file_name, |
| + const LevelDBComparator* comparator) OVERRIDE { |
| + return LevelDBDatabase::Open( |
| + base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(file_name)), comparator); |
| + } |
| + virtual bool DestroyLevelDB(const string16& file_name) OVERRIDE { |
| + return LevelDBDatabase::Destroy( |
| + base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(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_t 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 { |
| + IndexedDBLevelDBBackingStoreOpenMemorySuccess, |
|
jamesr
2013/05/21 23:56:06
should be SHOUTY
jsbell
2013/05/22 17:54:44
Done.
|
| + IndexedDBLevelDBBackingStoreOpenSuccess, |
| + IndexedDBLevelDBBackingStoreOpenFailedDirectory, |
| + IndexedDBLevelDBBackingStoreOpenFailedUnknownSchema, |
| + IndexedDBLevelDBBackingStoreOpenCleanupDestroyFailed, |
| + IndexedDBLevelDBBackingStoreOpenCleanupReopenFailed, |
| + IndexedDBLevelDBBackingStoreOpenCleanupReopenSuccess, |
| + IndexedDBLevelDBBackingStoreOpenFailedIOErrCheckingSchema, |
| + IndexedDBLevelDBBackingStoreOpenFailedUnknownErr, |
| + IndexedDBLevelDBBackingStoreOpenMemoryFailed, |
| + IndexedDBLevelDBBackingStoreOpenAttemptNonASCII, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| +}; |
| + |
| +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, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenAttemptNonASCII); |
| + } |
| + base::FilePath file_path_base(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, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenFailedDirectory); |
| + return scoped_refptr<IndexedDBBackingStore>(); |
| + } |
| + |
| + base::FilePath path_utf8 = file_path_base.Append( |
| + UTF16ToUTF8(database_identifier) + ".indexeddb.leveldb"); |
| + string16 path_utf16 = UTF8ToUTF16(path_utf8.value()); |
| + |
| + db = leveldb_factory->OpenLevelDB(path_utf16, 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, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenFailedIOErrCheckingSchema); |
| + 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, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenFailedUnknownSchema); |
| + db.reset(); |
| + } |
| + } |
| + |
| + if (db) { |
| + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", |
| + 1, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenSuccess); |
| + } else { |
| + LOG(ERROR) << "IndexedDB backing store open failed, attempting cleanup"; |
| + bool success = leveldb_factory->DestroyLevelDB(path_utf16); |
| + if (!success) { |
| + LOG(ERROR) << "IndexedDB backing store cleanup failed"; |
| + base::Histogram::FactoryGet( |
| + "WebCore.IndexedDB.BackingStore.OpenStatus", |
| + 1, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenCleanupDestroyFailed); |
| + return scoped_refptr<IndexedDBBackingStore>(); |
| + } |
| + |
| + LOG(ERROR) << "IndexedDB backing store cleanup succeeded, reopening"; |
| + db = leveldb_factory->OpenLevelDB(path_utf16, comparator.get()); |
| + if (!db) { |
| + LOG(ERROR) << "IndexedDB backing store reopen after recovery failed"; |
| + base::Histogram::FactoryGet( |
| + "WebCore.IndexedDB.BackingStore.OpenStatus", |
| + 1, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenCleanupReopenFailed); |
| + return scoped_refptr<IndexedDBBackingStore>(); |
| + } |
| + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", |
| + 1, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenCleanupReopenSuccess); |
| + } |
| + |
| + if (!db) { |
| + NOTREACHED(); |
| + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", |
| + 1, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenFailedUnknownErr); |
| + 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, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenMemoryFailed); |
| + return scoped_refptr<IndexedDBBackingStore>(); |
| + } |
| + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", |
| + 1, |
| + IndexedDBLevelDBBackingStoreOpenMax, |
| + IndexedDBLevelDBBackingStoreOpenMax + 1, |
| + base::HistogramBase::kUmaTargetedHistogramFlag) |
| + ->Add(IndexedDBLevelDBBackingStoreOpenMemorySuccess); |
| + |
| + return Create(identifier, db.Pass(), comparator.Pass()); |
| +} |
| + |
| +scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Create( |
| + const string16& identifier, |
| + scoped_ptr<LevelDBDatabase> db, |
| + scoped_ptr<LevelDBComparator> comparator) { |
| + // TODO: 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(kGetIDBDatabaseMetaData); |
| + return false; |
| + } |
| + if (!found) |
| + return true; |
| + |
| + ok = GetString(db_.get(), |
| + LevelDBSlice(DatabaseMetaDataKey::Encode( |
| + metadata->id, DatabaseMetaDataKey::UserVersion)), |
| + metadata->version, |
| + found); |
| + if (!ok) { |
| + INTERNAL_READ_ERROR(kGetIDBDatabaseMetaData); |
| + return false; |
| + } |
| + if (!found) { |
| + INTERNAL_CONSISTENCY_ERROR(kGetIDBDatabaseMetaData); |
| + return false; |
| + } |
| + |
| + ok = GetVarInt(db_.get(), |
| + LevelDBSlice(DatabaseMetaDataKey::Encode( |
| + metadata->id, DatabaseMetaDataKey::UserIntVersion)), |
| + metadata->int_version, |
| + found); |
| + if (!ok) { |
| + INTERNAL_READ_ERROR(kGetIDBDatabaseMetaData); |
| + return false; |
| + } |
| + if (!found) { |
| + INTERNAL_CONSISTENCY_ERROR(kGetIDBDatabaseMetaData); |
| + return false; |
| + } |
| + |
| + if (metadata->int_version == IndexedDBDatabaseMetadata::DefaultIntVersion) |
| + metadata->int_version = IndexedDBDatabaseMetadata::NoIntVersion; |
| + |
| + ok = GetMaxObjectStoreId( |
| + db_.get(), metadata->id, metadata->max_object_store_id); |
| + if (!ok) { |
| + INTERNAL_READ_ERROR(kGetIDBDatabaseMetaData); |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +WARN_UNUSED_RESULT static bool GetNewDatabaseId(LevelDBDatabase* db, |
| + int64_t& new_id) { |
| + scoped_refptr<LevelDBTransaction> transaction = |
| + LevelDBTransaction::Create(db); |
| + |
| + new_id = -1; |
| + int64_t max_database_id = -1; |
| + bool found = false; |
| + bool ok = GetInt(transaction.get(), |
| + LevelDBSlice(MaxDatabaseIdKey::Encode()), |
| + max_database_id, |
| + found); |
| + if (!ok) { |
| + INTERNAL_READ_ERROR(kGetNewDatabaseId); |
| + return false; |
| + } |
| + if (!found) |
| + max_database_id = 0; |
| + |
| + DCHECK(max_database_id >= 0); |
| + |
| + int64_t database_id = max_database_id + 1; |
| + PutInt( |
| + transaction.get(), LevelDBSlice(MaxDatabaseIdKey::Encode()), database_id); |
| + if (!transaction->Commit()) { |
| + INTERNAL_WRITE_ERROR(kGetNewDatabaseId); |
| + return false; |
| + } |
| + new_id = database_id; |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::CreateIDBDatabaseMetaData(const string16& name, |
| + const string16& version, |
| + int64_t int_version, |
| + int64_t& row_id) { |
| + bool ok = GetNewDatabaseId(db_.get(), row_id); |
| + if (!ok) |
| + return false; |
| + DCHECK(row_id >= 0); |
| + |
| + if (int_version == IndexedDBDatabaseMetadata::NoIntVersion) |
| + int_version = IndexedDBDatabaseMetadata::DefaultIntVersion; |
| + |
| + 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::UserVersion)), |
| + version); |
| + PutVarInt(transaction.get(), |
| + LevelDBSlice(DatabaseMetaDataKey::Encode( |
| + row_id, DatabaseMetaDataKey::UserIntVersion)), |
| + int_version); |
| + if (!transaction->Commit()) { |
| + INTERNAL_WRITE_ERROR(kCreateIDBDatabaseMetaData); |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::UpdateIDBDatabaseIntVersion( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t row_id, |
| + int64_t int_version) { |
| + if (int_version == IndexedDBDatabaseMetadata::NoIntVersion) |
| + int_version = IndexedDBDatabaseMetadata::DefaultIntVersion; |
| + DCHECK(int_version >= 0) << "int_version was " << int_version; |
| + PutVarInt(Transaction::LevelDBTransactionFrom(transaction), |
| + LevelDBSlice(DatabaseMetaDataKey::Encode( |
| + row_id, DatabaseMetaDataKey::UserIntVersion)), |
| + int_version); |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::UpdateIDBDatabaseMetaData( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t row_id, |
| + const string16& version) { |
| + PutString(Transaction::LevelDBTransactionFrom(transaction), |
| + LevelDBSlice(DatabaseMetaDataKey::Encode( |
| + row_id, DatabaseMetaDataKey::UserVersion)), |
| + 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::OriginName); |
| + const std::vector<char> stop_key = DatabaseMetaDataKey::Encode( |
| + metadata.id + 1, DatabaseMetaDataKey::OriginName); |
| + 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(kDeleteDatabase); |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +static bool CheckObjectStoreAndMetaDataType(const LevelDBIterator* it, |
| + const std::vector<char>& stop_key, |
| + int64_t object_store_id, |
| + int64_t 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: This should do some error handling rather than plowing ahead when bad |
| +// data is encountered. |
| +bool IndexedDBBackingStore::GetObjectStores( |
| + int64_t 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(kGetObjectStores); |
| + // Possible stale metadata, but don't fail the load. |
| + it->Next(); |
| + continue; |
| + } |
| + |
| + int64_t object_store_id = meta_data_key.ObjectStoreId(); |
| + |
| + // TODO: 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::KeyPath)) { |
| + INTERNAL_CONSISTENCY_ERROR(kGetObjectStores); |
| + break; |
| + } |
| + IndexedDBKeyPath key_path = |
| + DecodeIDBKeyPath(it->Value().begin(), it->Value().end()); |
| + |
| + it->Next(); |
| + if (!CheckObjectStoreAndMetaDataType( |
| + it.get(), |
| + stop_key, |
| + object_store_id, |
| + ObjectStoreMetaDataKey::AutoIncrement)) { |
| + INTERNAL_CONSISTENCY_ERROR(kGetObjectStores); |
| + 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(kGetObjectStores); |
| + break; |
| + } |
| + |
| + it->Next(); // Last version. |
| + if (!CheckObjectStoreAndMetaDataType(it.get(), |
| + stop_key, |
| + object_store_id, |
| + ObjectStoreMetaDataKey::LastVersion)) { |
| + INTERNAL_CONSISTENCY_ERROR(kGetObjectStores); |
| + break; |
| + } |
| + |
| + it->Next(); // Maximum index id allocated. |
| + if (!CheckObjectStoreAndMetaDataType(it.get(), |
| + stop_key, |
| + object_store_id, |
| + ObjectStoreMetaDataKey::MaxIndexId)) { |
| + INTERNAL_CONSISTENCY_ERROR(kGetObjectStores); |
| + break; |
| + } |
| + int64_t 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::HasKeyPath)) { |
| + 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(kGetObjectStores); |
| + break; |
| + } |
| + if (!has_key_path) |
| + key_path = IndexedDBKeyPath(); |
| + it->Next(); |
| + } |
| + |
| + int64_t key_generator_current_number = -1; |
| + if (CheckObjectStoreAndMetaDataType( |
| + it.get(), |
| + stop_key, |
| + object_store_id, |
| + ObjectStoreMetaDataKey::KeyGeneratorCurrentNumber)) { |
| + key_generator_current_number = |
| + DecodeInt(it->Value().begin(), it->Value().end()); |
| + // TODO: 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(key_generator_current_number >= KeyGeneratorInitialNumber); |
| + 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_t database_id, |
| + int64_t object_store_id) { |
| + const std::vector<char> max_object_store_id_key = DatabaseMetaDataKey::Encode( |
| + database_id, DatabaseMetaDataKey::MaxObjectStoreId); |
| + int64_t max_object_store_id = -1; |
| + bool ok = GetMaxObjectStoreId( |
| + transaction, max_object_store_id_key, max_object_store_id); |
| + if (!ok) { |
| + INTERNAL_READ_ERROR(kSetMaxObjectStoreId); |
| + return false; |
| + } |
| + |
| + if (object_store_id <= max_object_store_id) { |
| + INTERNAL_CONSISTENCY_ERROR(kSetMaxObjectStoreId); |
| + return false; |
| + } |
| + PutInt(transaction, LevelDBSlice(max_object_store_id_key), object_store_id); |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::CreateObjectStore( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t database_id, |
| + int64_t 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::KeyPath); |
| + const std::vector<char> auto_increment_key = ObjectStoreMetaDataKey::Encode( |
| + database_id, object_store_id, ObjectStoreMetaDataKey::AutoIncrement); |
| + 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::LastVersion); |
| + const std::vector<char> max_index_id_key = ObjectStoreMetaDataKey::Encode( |
| + database_id, object_store_id, ObjectStoreMetaDataKey::MaxIndexId); |
| + const std::vector<char> has_key_path_key = ObjectStoreMetaDataKey::Encode( |
| + database_id, object_store_id, ObjectStoreMetaDataKey::HasKeyPath); |
| + const std::vector<char> key_generator_current_number_key = |
| + ObjectStoreMetaDataKey::Encode( |
| + database_id, |
| + object_store_id, |
| + ObjectStoreMetaDataKey::KeyGeneratorCurrentNumber); |
| + 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), MinimumIndexId); |
| + PutBool( |
| + leveldb_transaction, LevelDBSlice(has_key_path_key), !key_path.IsNull()); |
| + PutInt(leveldb_transaction, |
| + LevelDBSlice(key_generator_current_number_key), |
| + KeyGeneratorInitialNumber); |
| + PutInt(leveldb_transaction, LevelDBSlice(names_key), object_store_id); |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::DeleteObjectStore( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t database_id, |
| + int64_t 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(kDeleteObjectStore); |
| + return false; |
| + } |
| + if (!found) { |
| + INTERNAL_CONSISTENCY_ERROR(kDeleteObjectStore); |
| + 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_t database_id, |
| + int64_t 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(kGetRecord); |
| + return false; |
| + } |
| + if (!found) |
| + return true; |
| + |
| + int64_t version; |
| + std::vector<char>::iterator p = |
| + DecodeVarInt(data.begin(), data.end(), version); |
| + if (p == data.begin()) { |
| + INTERNAL_READ_ERROR(kGetRecord); |
| + return false; |
| + } |
| + |
| + record.assign(p, data.end()); |
| + return true; |
| +} |
| + |
| +WARN_UNUSED_RESULT static bool GetNewVersionNumber( |
| + LevelDBTransaction* transaction, |
| + int64_t database_id, |
| + int64_t object_store_id, |
| + int64_t& new_version_number) { |
| + const std::vector<char> last_version_key = ObjectStoreMetaDataKey::Encode( |
| + database_id, object_store_id, ObjectStoreMetaDataKey::LastVersion); |
| + |
| + new_version_number = -1; |
| + int64_t last_version = -1; |
| + bool found = false; |
| + bool ok = |
| + GetInt(transaction, LevelDBSlice(last_version_key), last_version, found); |
| + if (!ok) { |
| + INTERNAL_READ_ERROR(kGetNewVersionNumber); |
| + return false; |
| + } |
| + if (!found) |
| + last_version = 0; |
| + |
| + DCHECK(last_version >= 0); |
| + |
| + int64_t version = last_version + 1; |
| + PutInt(transaction, LevelDBSlice(last_version_key), version); |
| + |
| + DCHECK(version > last_version); // TODO: Think about how we want to handle |
| + // the overflow scenario. |
| + |
| + new_version_number = version; |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::PutRecord( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t database_id, |
| + int64_t 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_t version = -1; |
| + bool ok = GetNewVersionNumber( |
| + leveldb_transaction, database_id, object_store_id, version); |
| + if (!ok) |
| + return false; |
| + DCHECK(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_t database_id, |
| + int64_t 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_t database_id, |
| + int64_t 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_t database_id, |
| + int64_t object_store_id, |
| + int64_t& 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::KeyGeneratorCurrentNumber); |
| + |
| + 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(kGetKeyGeneratorCurrentNumber); |
| + 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. |
| + 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_t 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_t n = static_cast<int64_t>(user_key->number()); |
| + if (n > max_numeric_key) |
| + max_numeric_key = n; |
| + } |
| + } |
| + |
| + key_generator_current_number = max_numeric_key + 1; |
| + } |
| + |
| + return key_generator_current_number; |
| +} |
| + |
| +bool IndexedDBBackingStore::MaybeUpdateKeyGeneratorCurrentNumber( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t database_id, |
| + int64_t object_store_id, |
| + int64_t 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_t 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::KeyGeneratorCurrentNumber); |
| + PutInt(leveldb_transaction, |
| + LevelDBSlice(key_generator_current_number_key), |
| + new_number); |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::KeyExistsInObjectStore( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t database_id, |
| + int64_t 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(kKeyExistsInObjectStore); |
| + return false; |
| + } |
| + if (!found) |
| + return true; |
| + |
| + int64_t version; |
| + if (DecodeVarInt(data.begin(), data.end(), version) == data.begin()) |
| + return false; |
| + |
| + found_record_identifier->Reset(EncodeIDBKey(key), version); |
| + return true; |
| +} |
| + |
| +static bool CheckIndexAndMetaDataKey(const LevelDBIterator* it, |
| + const std::vector<char>& stop_key, |
| + int64_t 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: This should do some error handling rather than plowing ahead when bad |
| +// data is encountered. |
| +bool IndexedDBBackingStore::GetIndexes( |
| + int64_t database_id, |
| + int64_t 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(kGetIndexes); |
| + // Possible stale metadata due to http://webkit.org/b/85557 but don't fail |
| + // the load. |
| + it->Next(); |
| + continue; |
| + } |
| + |
| + // TODO: Do this by direct key lookup rather than iteration, to simplify. |
| + int64_t 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(kGetIndexes); |
| + break; |
| + } |
| + bool index_unique = DecodeBool(it->Value().begin(), it->Value().end()); |
| + |
| + it->Next(); // key_path |
| + if (!CheckIndexAndMetaDataKey( |
| + it.get(), stop_key, index_id, IndexMetaDataKey::KeyPath)) { |
| + INTERNAL_CONSISTENCY_ERROR(kGetIndexes); |
| + 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::MultiEntry)) { |
| + 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_t database_id, |
| + int64_t object_store_id, |
| + int64_t index_id) { |
| + int64_t max_index_id = -1; |
| + const std::vector<char> max_index_id_key = ObjectStoreMetaDataKey::Encode( |
| + database_id, object_store_id, ObjectStoreMetaDataKey::MaxIndexId); |
| + bool found = false; |
| + bool ok = |
| + GetInt(transaction, LevelDBSlice(max_index_id_key), max_index_id, found); |
| + if (!ok) { |
| + INTERNAL_READ_ERROR(kSetMaxIndexId); |
| + return false; |
| + } |
| + if (!found) |
| + max_index_id = MinimumIndexId; |
| + |
| + if (index_id <= max_index_id) { |
| + INTERNAL_CONSISTENCY_ERROR(kSetMaxIndexId); |
| + return false; |
| + } |
| + |
| + PutInt(transaction, LevelDBSlice(max_index_id_key), index_id); |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::CreateIndex( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t database_id, |
| + int64_t object_store_id, |
| + int64_t 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::KeyPath); |
| + const std::vector<char> multi_entry_key = IndexMetaDataKey::Encode( |
| + database_id, object_store_id, index_id, IndexMetaDataKey::MultiEntry); |
| + |
| + 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_t database_id, |
| + int64_t object_store_id, |
| + int64_t 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_t database_id, |
| + int64_t object_store_id, |
| + int64_t 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_t database_id, |
| + int64_t object_store_id, |
| + int64_t 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(kVersionExists); |
| + return false; |
| + } |
| + if (!exists) |
| + return true; |
| + |
| + exists = (DecodeInt(data.begin(), data.end()) == version); |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::FindKeyInIndex( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t database_id, |
| + int64_t object_store_id, |
| + int64_t 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_t version; |
| + const char* p = |
| + DecodeVarInt(it->Value().begin(), it->Value().end(), version); |
| + if (!p) { |
| + INTERNAL_READ_ERROR(kFindKeyInIndex); |
| + 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_t database_id, |
| + int64_t object_store_id, |
| + int64_t 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(kGetPrimaryKeyViaIndex); |
| + return false; |
| + } |
| + if (found) { |
| + DecodeIDBKey(&*found_encoded_primary_key.begin(), |
| + &*found_encoded_primary_key.end(), |
| + primary_key); |
| + return true; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool IndexedDBBackingStore::KeyExistsInIndex( |
| + IndexedDBBackingStore::Transaction* transaction, |
| + int64_t database_id, |
| + int64_t object_store_id, |
| + int64_t 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(kKeyExistsInIndex); |
| + return false; |
| + } |
| + if (!exists) |
| + return true; |
| + |
| + DecodeIDBKey(&*found_encoded_primary_key.begin(), |
| + &*found_encoded_primary_key.end(), |
| + 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: 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) { |
| + if (cursor_options_.low_open) |
| + return CompareIndexKeys(iterator_->Key(), |
| + LevelDBSlice(cursor_options_.low_key)) > |
| + 0; |
| + |
| + return CompareIndexKeys(iterator_->Key(), |
| + LevelDBSlice(cursor_options_.low_key)) >= |
| + 0; |
| + } |
| + if (cursor_options_.high_open) |
| + return CompareIndexKeys(iterator_->Key(), |
| + LevelDBSlice(cursor_options_.high_key)) < |
| + 0; |
| + |
| + return CompareIndexKeys(iterator_->Key(), |
| + LevelDBSlice(cursor_options_.high_key)) <= |
| + 0; |
| +} |
| + |
| +bool IndexedDBBackingStore::Cursor::IsPastBounds() const { |
| + if (cursor_options_.forward) { |
| + if (cursor_options_.high_open) |
| + return CompareIndexKeys(iterator_->Key(), |
| + LevelDBSlice(cursor_options_.high_key)) >= |
| + 0; |
|
jamesr
2013/05/21 23:56:06
odd indentation here. if this is supposed to be a
jsbell
2013/05/22 17:54:44
clang-format. Unfortunately, if you move the 0; to
jsbell
2013/05/23 21:10:49
clang-format is still wrapping before the 0 which
|
| + return CompareIndexKeys(iterator_->Key(), |
| + LevelDBSlice(cursor_options_.high_key)) > |
| + 0; |
| + } |
| + |
| + if (cursor_options_.low_open) |
| + return CompareIndexKeys(iterator_->Key(), |
| + LevelDBSlice(cursor_options_.low_key)) <= |
| + 0; |
| + return CompareIndexKeys(iterator_->Key(), |
| + LevelDBSlice(cursor_options_.low_key)) < |
| + 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: |
| + 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(kLoadCurrentRow); |
| + return false; |
| + } |
| + |
| + current_key_ = object_store_data_key.user_key(); |
| + |
| + int64_t version; |
| + const char* value_position = DecodeVarInt( |
| + iterator_->Value().begin(), iterator_->Value().end(), version); |
| + if (!value_position) { |
| + INTERNAL_READ_ERROR(kLoadCurrentRow); |
| + return false; |
| + } |
| + |
| + // TODO: 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: |
| + 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(kLoadCurrentRow); |
| + return false; |
| + } |
| + |
| + current_key_ = object_store_data_key.user_key(); |
| + |
| + int64_t version; |
| + const char* value_position = DecodeVarInt( |
| + iterator_->Value().begin(), iterator_->Value().end(), version); |
| + if (!value_position) { |
| + INTERNAL_READ_ERROR(kLoadCurrentRow); |
| + return false; |
| + } |
| + |
| + // TODO: 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_(other->primary_key_.get()) {} |
| + |
| + 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_t index_data_version; |
| + const char* value_position = DecodeVarInt( |
| + iterator_->Value().begin(), iterator_->Value().end(), index_data_version); |
| + if (!value_position) { |
| + INTERNAL_READ_ERROR(kLoadCurrentRow); |
| + return false; |
| + } |
| + |
| + value_position = |
| + DecodeIDBKey(value_position, iterator_->Value().end(), &primary_key_); |
| + if (!value_position) { |
| + INTERNAL_READ_ERROR(kLoadCurrentRow); |
| + 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(kLoadCurrentRow); |
| + return false; |
| + } |
| + if (!found) { |
| + transaction_->Remove(iterator_->Key()); |
| + return false; |
| + } |
| + |
| + int64_t object_store_data_version; |
| + const char* t = |
| + DecodeVarInt(&*result.begin(), &*result.end(), object_store_data_version); |
| + if (!t) { |
| + INTERNAL_READ_ERROR(kLoadCurrentRow); |
| + 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_(other->primary_key_.get()), |
| + 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_t index_data_version; |
| + value_position = |
| + DecodeVarInt(value_position, value_limit, index_data_version); |
| + if (!value_position) { |
| + INTERNAL_READ_ERROR(kLoadCurrentRow); |
| + return false; |
| + } |
| + value_position = DecodeIDBKey(value_position, value_limit, &primary_key_); |
| + if (!value_position) { |
| + INTERNAL_READ_ERROR(kLoadCurrentRow); |
| + 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(kLoadCurrentRow); |
| + return false; |
| + } |
| + if (!found) { |
| + transaction_->Remove(iterator_->Key()); |
| + return false; |
| + } |
| + |
| + int64_t object_store_data_version; |
| + value_position = |
| + DecodeVarInt(&*result.begin(), &*result.end(), object_store_data_version); |
| + if (!value_position) { |
| + INTERNAL_READ_ERROR(kLoadCurrentRow); |
| + return false; |
| + } |
| + |
| + if (object_store_data_version != index_data_version) { |
| + transaction_->Remove(iterator_->Key()); |
| + return false; |
| + } |
| + |
| + // TODO: Make value_position an iterator. |
| + std::vector<char> value(std::vector<char>::const_iterator(value_position), |
| + std::vector<char>::const_iterator(result.end())); |
| + current_value_.swap(value); |
| + return true; |
| +} |
| + |
| +bool ObjectStoreCursorOptions( |
| + LevelDBTransaction* transaction, |
| + int64_t database_id, |
| + int64_t object_store_id, |
| + const IndexedDBKeyRange& range, |
| + IndexedDB::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 == IndexedDB::CursorNextNoDuplicate || |
| + direction == IndexedDB::CursorNext); |
| + cursor_options.unique = (direction == IndexedDB::CursorNextNoDuplicate || |
| + direction == IndexedDB::CursorPrevNoDuplicate); |
| + |
| + 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_t database_id, |
| + int64_t object_store_id, |
| + int64_t index_id, |
| + const IndexedDBKeyRange& range, |
| + IndexedDB::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 == IndexedDB::CursorNextNoDuplicate || |
| + direction == IndexedDB::CursorNext); |
| + cursor_options.unique = (direction == IndexedDB::CursorNextNoDuplicate || |
| + direction == IndexedDB::CursorPrevNoDuplicate); |
| + |
| + 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_t database_id, |
| + int64_t object_store_id, |
| + const IndexedDBKeyRange& range, |
| + IndexedDB::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_t database_id, |
| + int64_t object_store_id, |
| + const IndexedDBKeyRange& range, |
| + IndexedDB::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_t database_id, |
| + int64_t object_store_id, |
| + int64_t index_id, |
| + const IndexedDBKeyRange& range, |
| + IndexedDB::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_t database_id, |
| + int64_t object_store_id, |
| + int64_t index_id, |
| + const IndexedDBKeyRange& range, |
| + IndexedDB::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(kTransactionCommit); |
| + return result; |
| +} |
| + |
| +void IndexedDBBackingStore::Transaction::Rollback() { |
| + IDB_TRACE("IndexedDBBackingStore::Transaction::rollback"); |
| + DCHECK(transaction_); |
| + transaction_->Rollback(); |
| + transaction_ = NULL; |
| +} |
| + |
| +} // namespace content |