| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "IDBBackingStoreLevelDB.h" |
| |
| #if ENABLE(INDEXED_DATABASE) && USE(LEVELDB) |
| |
| #include "FileSystem.h" |
| #include "HistogramSupport.h" |
| #include "IDBBackingStoreCursorLevelDB.h" |
| #include "IDBBackingStoreTransactionLevelDB.h" |
| #include "IDBIndexWriter.h" |
| #include "IDBKey.h" |
| #include "IDBKeyPath.h" |
| #include "IDBKeyRange.h" |
| #include "IDBLevelDBCoding.h" |
| #include "IDBMetadata.h" |
| #include "IDBTransactionBackendInterface.h" |
| #include "LevelDBComparator.h" |
| #include "LevelDBDatabase.h" |
| #include "LevelDBIterator.h" |
| #include "LevelDBSlice.h" |
| #include "LevelDBTransaction.h" |
| #include "Logging.h" |
| #include "SecurityOrigin.h" |
| #include "SharedBuffer.h" |
| #include <wtf/Assertions.h> |
| |
| namespace WebCore { |
| |
| using namespace IDBLevelDBCoding; |
| |
| const int64_t KeyGeneratorInitialNumber = 1; // From the IndexedDB specification. |
| |
| enum IDBBackingStoreLevelDBErrorSource { |
| // 0 - 2 are no longer used. |
| FindKeyInIndex = 3, |
| GetIDBDatabaseMetaData, |
| GetIndexes, |
| GetKeyGeneratorCurrentNumber, |
| GetObjectStores, |
| GetRecord, |
| KeyExistsInObjectStore, |
| LoadCurrentRow, |
| SetupMetadata, |
| GetPrimaryKeyViaIndex, |
| KeyExistsInIndex, |
| VersionExists, |
| DeleteObjectStore, |
| SetMaxObjectStoreId, |
| SetMaxIndexId, |
| GetNewDatabaseId, |
| GetNewVersionNumber, |
| CreateIDBDatabaseMetaData, |
| DeleteDatabase, |
| IDBLevelDBBackingStoreInternalErrorMax, |
| }; |
| |
| static void recordInternalError(const char* type, IDBBackingStoreLevelDBErrorSource location) |
| { |
| String name = String::format("WebCore.IndexedDB.BackingStore.%sError", type); |
| HistogramSupport::histogramEnumeration(name.utf8().data(), location, IDBLevelDBBackingStoreInternalErrorMax); |
| } |
| |
| // 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 %s Error: %s", type, #location); \ |
| ASSERT_NOT_REACHED(); \ |
| 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_t& foundInt, bool& found) |
| { |
| Vector<char> result; |
| bool ok = db->safeGet(key, result, found); |
| if (!ok) |
| return false; |
| if (!found) |
| return true; |
| |
| foundInt = decodeInt(result.begin(), result.end()); |
| return true; |
| } |
| |
| static void putInt(LevelDBTransaction* transaction, const LevelDBSlice& key, int64_t value) |
| { |
| ASSERT(value >= 0); |
| transaction->put(key, encodeInt(value)); |
| } |
| |
| template <typename DBOrTransaction> |
| WARN_UNUSED_RETURN static bool getVarInt(DBOrTransaction* db, const LevelDBSlice& key, int64_t& foundInt, bool& found) |
| { |
| Vector<char> result; |
| bool ok = db->safeGet(key, result, found); |
| if (!ok) |
| return false; |
| if (!found) |
| return true; |
| |
| found = decodeVarInt(result.begin(), result.end(), foundInt) == 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_RETURN static bool getString(DBOrTransaction* db, const LevelDBSlice& key, String& foundString, bool& found) |
| { |
| Vector<char> result; |
| found = false; |
| bool ok = db->safeGet(key, result, found); |
| if (!ok) |
| return false; |
| if (!found) |
| return true; |
| |
| foundString = decodeString(result.begin(), result.end()); |
| return true; |
| } |
| |
| static void putString(LevelDBTransaction* transaction, const LevelDBSlice& key, const String& value) |
| { |
| transaction->put(key, encodeString(value)); |
| } |
| |
| static void putIDBKeyPath(LevelDBTransaction* transaction, const LevelDBSlice& key, const IDBKeyPath& value) |
| { |
| transaction->put(key, encodeIDBKeyPath(value)); |
| } |
| |
| static int compareKeys(const LevelDBSlice& a, const LevelDBSlice& b) |
| { |
| return compare(a, b); |
| } |
| |
| int IDBBackingStoreLevelDB::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 { return IDBLevelDBCoding::compare(a, b); } |
| virtual const char* name() const { return "idb_cmp1"; } |
| }; |
| |
| // 0 - Initial version. |
| // 1 - Adds UserIntVersion to DatabaseMetaData. |
| // 2 - Adds DataVersion to to global metadata. |
| const int64_t latestKnownSchemaVersion = 2; |
| WARN_UNUSED_RETURN static bool isSchemaKnown(LevelDBDatabase* db, bool& known) |
| { |
| int64_t dbSchemaVersion = 0; |
| bool found = false; |
| bool ok = getInt(db, SchemaVersionKey::encode(), dbSchemaVersion, found); |
| if (!ok) |
| return false; |
| if (!found) { |
| known = true; |
| return true; |
| } |
| if (dbSchemaVersion > latestKnownSchemaVersion) { |
| known = false; |
| return true; |
| } |
| |
| const uint32_t latestKnownDataVersion = SerializedScriptValue::wireFormatVersion(); |
| int64_t dbDataVersion = 0; |
| ok = getInt(db, DataVersionKey::encode(), dbDataVersion, found); |
| if (!ok) |
| return false; |
| if (!found) { |
| known = true; |
| return true; |
| } |
| |
| if (dbDataVersion > latestKnownDataVersion) { |
| known = false; |
| return true; |
| } |
| |
| known = true; |
| return true; |
| } |
| |
| WARN_UNUSED_RETURN static bool setUpMetadata(LevelDBDatabase* db, const String& origin) |
| { |
| const uint32_t latestKnownDataVersion = SerializedScriptValue::wireFormatVersion(); |
| const Vector<char> schemaVersionKey = SchemaVersionKey::encode(); |
| const Vector<char> dataVersionKey = DataVersionKey::encode(); |
| |
| RefPtr<LevelDBTransaction> transaction = LevelDBTransaction::create(db); |
| |
| int64_t dbSchemaVersion = 0; |
| int64_t dbDataVersion = 0; |
| bool found = false; |
| bool ok = getInt(transaction.get(), schemaVersionKey, dbSchemaVersion, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(SetupMetadata); |
| return false; |
| } |
| if (!found) { |
| // Initialize new backing store. |
| dbSchemaVersion = latestKnownSchemaVersion; |
| putInt(transaction.get(), schemaVersionKey, dbSchemaVersion); |
| dbDataVersion = latestKnownDataVersion; |
| putInt(transaction.get(), dataVersionKey, dbDataVersion); |
| } else { |
| // Upgrade old backing store. |
| ASSERT(dbSchemaVersion <= latestKnownSchemaVersion); |
| if (dbSchemaVersion < 1) { |
| dbSchemaVersion = 1; |
| putInt(transaction.get(), schemaVersionKey, dbSchemaVersion); |
| const Vector<char> startKey = DatabaseNameKey::encodeMinKeyForOrigin(origin); |
| const Vector<char> stopKey = DatabaseNameKey::encodeStopKeyForOrigin(origin); |
| OwnPtr<LevelDBIterator> it = db->createIterator(); |
| for (it->seek(startKey); it->isValid() && compareKeys(it->key(), stopKey) < 0; it->next()) { |
| int64_t databaseId = 0; |
| found = false; |
| bool ok = getInt(transaction.get(), it->key(), databaseId, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(SetupMetadata); |
| return false; |
| } |
| if (!found) { |
| INTERNAL_CONSISTENCY_ERROR(SetupMetadata); |
| return false; |
| } |
| Vector<char> intVersionKey = DatabaseMetaDataKey::encode(databaseId, DatabaseMetaDataKey::UserIntVersion); |
| putVarInt(transaction.get(), intVersionKey, IDBDatabaseMetadata::DefaultIntVersion); |
| } |
| } |
| if (dbSchemaVersion < 2) { |
| dbSchemaVersion = 2; |
| putInt(transaction.get(), schemaVersionKey, dbSchemaVersion); |
| dbDataVersion = SerializedScriptValue::wireFormatVersion(); |
| putInt(transaction.get(), dataVersionKey, dbDataVersion); |
| } |
| } |
| |
| // All new values will be written using this serialization version. |
| found = false; |
| ok = getInt(transaction.get(), dataVersionKey, dbDataVersion, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(SetupMetadata); |
| return false; |
| } |
| if (!found) { |
| INTERNAL_CONSISTENCY_ERROR(SetupMetadata); |
| return false; |
| } |
| if (dbDataVersion < latestKnownDataVersion) { |
| dbDataVersion = latestKnownDataVersion; |
| putInt(transaction.get(), dataVersionKey, dbDataVersion); |
| } |
| |
| ASSERT(dbSchemaVersion == latestKnownSchemaVersion); |
| ASSERT(dbDataVersion == latestKnownDataVersion); |
| |
| if (!transaction->commit()) { |
| INTERNAL_WRITE_ERROR(SetupMetadata); |
| return false; |
| } |
| return true; |
| } |
| |
| template <typename DBOrTransaction> |
| WARN_UNUSED_RETURN static bool getMaxObjectStoreId(DBOrTransaction* db, int64_t databaseId, int64_t& maxObjectStoreId) |
| { |
| const Vector<char> maxObjectStoreIdKey = DatabaseMetaDataKey::encode(databaseId, DatabaseMetaDataKey::MaxObjectStoreId); |
| bool ok = getMaxObjectStoreId(db, maxObjectStoreIdKey, maxObjectStoreId); |
| return ok; |
| } |
| |
| template <typename DBOrTransaction> |
| WARN_UNUSED_RETURN static bool getMaxObjectStoreId(DBOrTransaction* db, const Vector<char>& maxObjectStoreIdKey, int64_t& maxObjectStoreId) |
| { |
| maxObjectStoreId = -1; |
| bool found = false; |
| bool ok = getInt(db, maxObjectStoreIdKey, maxObjectStoreId, found); |
| if (!ok) |
| return false; |
| if (!found) |
| maxObjectStoreId = 0; |
| |
| ASSERT(maxObjectStoreId >= 0); |
| return true; |
| } |
| |
| class DefaultLevelDBFactory : public LevelDBFactory { |
| public: |
| virtual PassOwnPtr<LevelDBDatabase> openLevelDB(const String& fileName, const LevelDBComparator* comparator) |
| { |
| return LevelDBDatabase::open(fileName, comparator); |
| } |
| virtual bool destroyLevelDB(const String& fileName) |
| { |
| return LevelDBDatabase::destroy(fileName); |
| } |
| }; |
| |
| IDBBackingStoreLevelDB::IDBBackingStoreLevelDB(const String& identifier, PassOwnPtr<LevelDBDatabase> db, PassOwnPtr<LevelDBComparator> comparator) |
| : m_identifier(identifier) |
| , m_db(db) |
| , m_comparator(comparator) |
| , m_weakFactory(this) |
| { |
| } |
| |
| IDBBackingStoreLevelDB::~IDBBackingStoreLevelDB() |
| { |
| // m_db's destructor uses m_comparator. The order of destruction is important. |
| m_db.clear(); |
| m_comparator.clear(); |
| } |
| |
| enum IDBLevelDBBackingStoreOpenResult { |
| IDBLevelDBBackingStoreOpenMemorySuccess, |
| IDBLevelDBBackingStoreOpenSuccess, |
| IDBLevelDBBackingStoreOpenFailedDirectory, |
| IDBLevelDBBackingStoreOpenFailedUnknownSchema, |
| IDBLevelDBBackingStoreOpenCleanupDestroyFailed, |
| IDBLevelDBBackingStoreOpenCleanupReopenFailed, |
| IDBLevelDBBackingStoreOpenCleanupReopenSuccess, |
| IDBLevelDBBackingStoreOpenFailedIOErrCheckingSchema, |
| IDBLevelDBBackingStoreOpenFailedUnknownErr, |
| IDBLevelDBBackingStoreOpenMemoryFailed, |
| IDBLevelDBBackingStoreOpenAttemptNonASCII, |
| IDBLevelDBBackingStoreOpenMax, |
| }; |
| |
| PassRefPtr<IDBBackingStoreLevelDB> IDBBackingStoreLevelDB::open(const SecurityOrigin& securityOrigin, const String& pathBaseArg, const String& fileIdentifier) |
| { |
| DefaultLevelDBFactory levelDBFactory; |
| return IDBBackingStoreLevelDB::open(securityOrigin, pathBaseArg, fileIdentifier, &levelDBFactory); |
| } |
| |
| PassRefPtr<IDBBackingStoreLevelDB> IDBBackingStoreLevelDB::open(const SecurityOrigin& securityOrigin, const String& pathBaseArg, const String& fileIdentifier, LevelDBFactory* levelDBFactory) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::open"); |
| ASSERT(!pathBaseArg.isEmpty()); |
| String pathBase = pathBaseArg; |
| |
| OwnPtr<LevelDBComparator> comparator = adoptPtr(new Comparator()); |
| OwnPtr<LevelDBDatabase> db; |
| |
| if (!pathBase.containsOnlyASCII()) |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenAttemptNonASCII, IDBLevelDBBackingStoreOpenMax); |
| if (!makeAllDirectories(pathBase)) { |
| LOG_ERROR("Unable to create IndexedDB database path %s", pathBase.utf8().data()); |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenFailedDirectory, IDBLevelDBBackingStoreOpenMax); |
| return PassRefPtr<IDBBackingStoreLevelDB>(); |
| } |
| |
| String path = pathByAppendingComponent(pathBase, securityOrigin.databaseIdentifier() + ".indexeddb.leveldb"); |
| |
| db = levelDBFactory->openLevelDB(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"); |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenFailedIOErrCheckingSchema, IDBLevelDBBackingStoreOpenMax); |
| db.clear(); |
| } else if (!known) { |
| LOG_ERROR("IndexedDB backing store had unknown schema, treating it as failure to open"); |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenFailedUnknownSchema, IDBLevelDBBackingStoreOpenMax); |
| db.clear(); |
| } |
| } |
| |
| if (db) |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenSuccess, IDBLevelDBBackingStoreOpenMax); |
| else { |
| LOG_ERROR("IndexedDB backing store open failed, attempting cleanup"); |
| bool success = levelDBFactory->destroyLevelDB(path); |
| if (!success) { |
| LOG_ERROR("IndexedDB backing store cleanup failed"); |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenCleanupDestroyFailed, IDBLevelDBBackingStoreOpenMax); |
| return PassRefPtr<IDBBackingStoreLevelDB>(); |
| } |
| |
| LOG_ERROR("IndexedDB backing store cleanup succeeded, reopening"); |
| db = levelDBFactory->openLevelDB(path, comparator.get()); |
| if (!db) { |
| LOG_ERROR("IndexedDB backing store reopen after recovery failed"); |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenCleanupReopenFailed, IDBLevelDBBackingStoreOpenMax); |
| return PassRefPtr<IDBBackingStoreLevelDB>(); |
| } |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenCleanupReopenSuccess, IDBLevelDBBackingStoreOpenMax); |
| } |
| |
| if (!db) { |
| ASSERT_NOT_REACHED(); |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenFailedUnknownErr, IDBLevelDBBackingStoreOpenMax); |
| return PassRefPtr<IDBBackingStoreLevelDB>(); |
| } |
| |
| return create(fileIdentifier, db.release(), comparator.release()); |
| } |
| |
| PassRefPtr<IDBBackingStoreLevelDB> IDBBackingStoreLevelDB::openInMemory(const String& identifier) |
| { |
| DefaultLevelDBFactory levelDBFactory; |
| return IDBBackingStoreLevelDB::openInMemory(identifier, &levelDBFactory); |
| } |
| |
| PassRefPtr<IDBBackingStoreLevelDB> IDBBackingStoreLevelDB::openInMemory(const String& identifier, LevelDBFactory*) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::openInMemory"); |
| |
| OwnPtr<LevelDBComparator> comparator = adoptPtr(new Comparator()); |
| OwnPtr<LevelDBDatabase> db = LevelDBDatabase::openInMemory(comparator.get()); |
| if (!db) { |
| LOG_ERROR("LevelDBDatabase::openInMemory failed."); |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenMemoryFailed, IDBLevelDBBackingStoreOpenMax); |
| return PassRefPtr<IDBBackingStoreLevelDB>(); |
| } |
| HistogramSupport::histogramEnumeration("WebCore.IndexedDB.BackingStore.OpenStatus", IDBLevelDBBackingStoreOpenMemorySuccess, IDBLevelDBBackingStoreOpenMax); |
| |
| return create(identifier, db.release(), comparator.release()); |
| } |
| |
| PassRefPtr<IDBBackingStoreLevelDB> IDBBackingStoreLevelDB::create(const String& identifier, PassOwnPtr<LevelDBDatabase> db, PassOwnPtr<LevelDBComparator> comparator) |
| { |
| // FIXME: Handle comparator name changes. |
| RefPtr<IDBBackingStoreLevelDB> backingStore(adoptRef(new IDBBackingStoreLevelDB(identifier, db, comparator))); |
| |
| if (!setUpMetadata(backingStore->m_db.get(), identifier)) |
| return PassRefPtr<IDBBackingStoreLevelDB>(); |
| |
| return backingStore.release(); |
| } |
| |
| Vector<String> IDBBackingStoreLevelDB::getDatabaseNames() |
| { |
| Vector<String> foundNames; |
| const Vector<char> startKey = DatabaseNameKey::encodeMinKeyForOrigin(m_identifier); |
| const Vector<char> stopKey = DatabaseNameKey::encodeStopKeyForOrigin(m_identifier); |
| |
| ASSERT(foundNames.isEmpty()); |
| |
| OwnPtr<LevelDBIterator> it = m_db->createIterator(); |
| for (it->seek(startKey); it->isValid() && compareKeys(it->key(), stopKey) < 0; it->next()) { |
| const char* p = it->key().begin(); |
| const char* limit = it->key().end(); |
| |
| DatabaseNameKey databaseNameKey; |
| p = DatabaseNameKey::decode(p, limit, &databaseNameKey); |
| ASSERT(p); |
| |
| foundNames.append(databaseNameKey.databaseName()); |
| } |
| return foundNames; |
| } |
| |
| bool IDBBackingStoreLevelDB::getIDBDatabaseMetaData(const String& name, IDBDatabaseMetadata* metadata, bool& found) |
| { |
| const Vector<char> key = DatabaseNameKey::encode(m_identifier, name); |
| found = false; |
| |
| bool ok = getInt(m_db.get(), key, metadata->id, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetIDBDatabaseMetaData); |
| return false; |
| } |
| if (!found) |
| return true; |
| |
| // FIXME: The string version is no longer supported, so the levelDB ports should consider refactoring off of it. |
| String stringVersion; |
| ok = getString(m_db.get(), DatabaseMetaDataKey::encode(metadata->id, DatabaseMetaDataKey::UserVersion), stringVersion, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetIDBDatabaseMetaData); |
| return false; |
| } |
| if (!found) { |
| INTERNAL_CONSISTENCY_ERROR(GetIDBDatabaseMetaData); |
| return false; |
| } |
| |
| int64_t version; |
| ok = getVarInt(m_db.get(), DatabaseMetaDataKey::encode(metadata->id, DatabaseMetaDataKey::UserIntVersion), version, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetIDBDatabaseMetaData); |
| return false; |
| } |
| if (!found) { |
| INTERNAL_CONSISTENCY_ERROR(GetIDBDatabaseMetaData); |
| return false; |
| } |
| |
| // FIXME: The versioning semantics have changed since this original code was written, and what was once a negative number |
| // stored in the database is no longer a valid version. |
| if (version < 0) |
| version = 0; |
| metadata->version = version; |
| |
| ok = getMaxObjectStoreId(m_db.get(), metadata->id, metadata->maxObjectStoreId); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetIDBDatabaseMetaData); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void IDBBackingStoreLevelDB::getOrEstablishIDBDatabaseMetadata(const String& name, GetIDBDatabaseMetadataFunction metadataFunction) |
| { |
| const Vector<char> key = DatabaseNameKey::encode(m_identifier, name); |
| bool found = false; |
| |
| IDBDatabaseMetadata resultMetadata; |
| |
| bool ok = getInt(m_db.get(), key, resultMetadata.id, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetIDBDatabaseMetaData); |
| metadataFunction(resultMetadata, false); |
| return; |
| } |
| |
| if (!found) { |
| resultMetadata.name = name; |
| resultMetadata.version = IDBDatabaseMetadata::DefaultIntVersion; |
| |
| metadataFunction(resultMetadata, createIDBDatabaseMetaData(resultMetadata)); |
| return; |
| } |
| |
| int64_t version; |
| ok = getVarInt(m_db.get(), DatabaseMetaDataKey::encode(resultMetadata.id, DatabaseMetaDataKey::UserIntVersion), version, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetIDBDatabaseMetaData); |
| metadataFunction(resultMetadata, false); |
| return; |
| } |
| if (!found) { |
| INTERNAL_CONSISTENCY_ERROR(GetIDBDatabaseMetaData); |
| metadataFunction(resultMetadata, false); |
| return; |
| } |
| |
| // FIXME: The versioning semantics have changed since this original code was written, and what was once a negative number |
| // stored in the database is no longer a valid version. |
| if (version < 0) |
| version = 0; |
| resultMetadata.version = version; |
| |
| ok = getMaxObjectStoreId(m_db.get(), resultMetadata.id, resultMetadata.maxObjectStoreId); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetIDBDatabaseMetaData); |
| metadataFunction(resultMetadata, false); |
| return; |
| } |
| |
| ok = getObjectStores(resultMetadata.id, &resultMetadata.objectStores); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetIDBDatabaseMetaData); |
| metadataFunction(resultMetadata, false); |
| return; |
| } |
| |
| metadataFunction(resultMetadata, true); |
| } |
| |
| WARN_UNUSED_RETURN static bool getNewDatabaseId(LevelDBDatabase* db, int64_t& newId) |
| { |
| RefPtr<LevelDBTransaction> transaction = LevelDBTransaction::create(db); |
| |
| newId = -1; |
| int64_t maxDatabaseId = -1; |
| bool found = false; |
| bool ok = getInt(transaction.get(), MaxDatabaseIdKey::encode(), maxDatabaseId, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetNewDatabaseId); |
| return false; |
| } |
| if (!found) |
| maxDatabaseId = 0; |
| |
| ASSERT(maxDatabaseId >= 0); |
| |
| int64_t databaseId = maxDatabaseId + 1; |
| putInt(transaction.get(), MaxDatabaseIdKey::encode(), databaseId); |
| if (!transaction->commit()) { |
| INTERNAL_WRITE_ERROR(GetNewDatabaseId); |
| return false; |
| } |
| newId = databaseId; |
| return true; |
| } |
| |
| // FIXME: LevelDB needs to support uint64_t as the version type. |
| bool IDBBackingStoreLevelDB::createIDBDatabaseMetaData(IDBDatabaseMetadata& metadata) |
| { |
| bool ok = getNewDatabaseId(m_db.get(), metadata.id); |
| if (!ok) |
| return false; |
| ASSERT(metadata.id >= 0); |
| |
| RefPtr<LevelDBTransaction> transaction = LevelDBTransaction::create(m_db.get()); |
| putInt(transaction.get(), DatabaseNameKey::encode(m_identifier, metadata.name), metadata.id); |
| putVarInt(transaction.get(), DatabaseMetaDataKey::encode(metadata.id, DatabaseMetaDataKey::UserIntVersion), metadata.version); |
| if (!transaction->commit()) { |
| INTERNAL_WRITE_ERROR(CreateIDBDatabaseMetaData); |
| return false; |
| } |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::updateIDBDatabaseVersion(IDBBackingStoreTransactionInterface& transaction, int64_t rowId, uint64_t version) |
| { |
| if (version == IDBDatabaseMetadata::NoIntVersion) |
| version = IDBDatabaseMetadata::DefaultIntVersion; |
| putVarInt(IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction), DatabaseMetaDataKey::encode(rowId, DatabaseMetaDataKey::UserIntVersion), version); |
| return true; |
| } |
| |
| static void deleteRange(LevelDBTransaction* transaction, const Vector<char>& begin, const Vector<char>& end) |
| { |
| OwnPtr<LevelDBIterator> it = transaction->createIterator(); |
| for (it->seek(begin); it->isValid() && compareKeys(it->key(), end) < 0; it->next()) |
| transaction->remove(it->key()); |
| } |
| |
| void IDBBackingStoreLevelDB::deleteDatabase(const String& name, BoolCallbackFunction boolCallbackFunction) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::deleteDatabase"); |
| OwnPtr<LevelDBWriteOnlyTransaction> transaction = LevelDBWriteOnlyTransaction::create(m_db.get()); |
| |
| IDBDatabaseMetadata metadata; |
| bool success = false; |
| bool ok = getIDBDatabaseMetaData(name, &metadata, success); |
| if (!ok) { |
| boolCallbackFunction(false); |
| return; |
| } |
| |
| if (!success) { |
| boolCallbackFunction(true); |
| return; |
| } |
| |
| const Vector<char> startKey = DatabaseMetaDataKey::encode(metadata.id, DatabaseMetaDataKey::OriginName); |
| const Vector<char> stopKey = DatabaseMetaDataKey::encode(metadata.id + 1, DatabaseMetaDataKey::OriginName); |
| OwnPtr<LevelDBIterator> it = m_db->createIterator(); |
| for (it->seek(startKey); it->isValid() && compareKeys(it->key(), stopKey) < 0; it->next()) |
| transaction->remove(it->key()); |
| |
| const Vector<char> key = DatabaseNameKey::encode(m_identifier, name); |
| transaction->remove(key); |
| |
| if (!transaction->commit()) { |
| INTERNAL_WRITE_ERROR(DeleteDatabase); |
| boolCallbackFunction(false); |
| return; |
| } |
| boolCallbackFunction(true); |
| } |
| |
| static bool checkObjectStoreAndMetaDataType(const LevelDBIterator* it, const Vector<char>& stopKey, int64_t objectStoreId, int64_t metaDataType) |
| { |
| if (!it->isValid() || compareKeys(it->key(), stopKey) >= 0) |
| return false; |
| |
| ObjectStoreMetaDataKey metaDataKey; |
| const char* p = ObjectStoreMetaDataKey::decode(it->key().begin(), it->key().end(), &metaDataKey); |
| ASSERT_UNUSED(p, p); |
| if (metaDataKey.objectStoreId() != objectStoreId) |
| return false; |
| if (metaDataKey.metaDataType() != metaDataType) |
| return false; |
| return true; |
| } |
| |
| // FIXME: This should do some error handling rather than plowing ahead when bad data is encountered. |
| bool IDBBackingStoreLevelDB::getObjectStores(int64_t databaseId, IDBDatabaseMetadata::ObjectStoreMap* objectStores) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::getObjectStores"); |
| if (!KeyPrefix::isValidDatabaseId(databaseId)) |
| return false; |
| const Vector<char> startKey = ObjectStoreMetaDataKey::encode(databaseId, 1, 0); |
| const Vector<char> stopKey = ObjectStoreMetaDataKey::encodeMaxKey(databaseId); |
| |
| ASSERT(objectStores->isEmpty()); |
| |
| OwnPtr<LevelDBIterator> it = m_db->createIterator(); |
| it->seek(startKey); |
| while (it->isValid() && compareKeys(it->key(), stopKey) < 0) { |
| const char* p = it->key().begin(); |
| const char* limit = it->key().end(); |
| |
| ObjectStoreMetaDataKey metaDataKey; |
| p = ObjectStoreMetaDataKey::decode(p, limit, &metaDataKey); |
| ASSERT(p); |
| if (metaDataKey.metaDataType() != ObjectStoreMetaDataKey::Name) { |
| INTERNAL_CONSISTENCY_ERROR(GetObjectStores); |
| // Possible stale metadata, but don't fail the load. |
| it->next(); |
| continue; |
| } |
| |
| int64_t objectStoreId = metaDataKey.objectStoreId(); |
| |
| // FIXME: Do this by direct key lookup rather than iteration, to simplify. |
| String objectStoreName = decodeString(it->value().begin(), it->value().end()); |
| |
| it->next(); |
| if (!checkObjectStoreAndMetaDataType(it.get(), stopKey, objectStoreId, ObjectStoreMetaDataKey::KeyPath)) { |
| INTERNAL_CONSISTENCY_ERROR(GetObjectStores); |
| break; |
| } |
| IDBKeyPath keyPath = decodeIDBKeyPath(it->value().begin(), it->value().end()); |
| |
| it->next(); |
| if (!checkObjectStoreAndMetaDataType(it.get(), stopKey, objectStoreId, ObjectStoreMetaDataKey::AutoIncrement)) { |
| INTERNAL_CONSISTENCY_ERROR(GetObjectStores); |
| break; |
| } |
| bool autoIncrement = decodeBool(it->value().begin(), it->value().end()); |
| |
| it->next(); // Is evicatble. |
| if (!checkObjectStoreAndMetaDataType(it.get(), stopKey, objectStoreId, ObjectStoreMetaDataKey::Evictable)) { |
| INTERNAL_CONSISTENCY_ERROR(GetObjectStores); |
| break; |
| } |
| |
| it->next(); // Last version. |
| if (!checkObjectStoreAndMetaDataType(it.get(), stopKey, objectStoreId, ObjectStoreMetaDataKey::LastVersion)) { |
| INTERNAL_CONSISTENCY_ERROR(GetObjectStores); |
| break; |
| } |
| |
| it->next(); // Maximum index id allocated. |
| if (!checkObjectStoreAndMetaDataType(it.get(), stopKey, objectStoreId, ObjectStoreMetaDataKey::MaxIndexId)) { |
| INTERNAL_CONSISTENCY_ERROR(GetObjectStores); |
| break; |
| } |
| int64_t maxIndexId = decodeInt(it->value().begin(), it->value().end()); |
| |
| it->next(); // [optional] has key path (is not null) |
| if (checkObjectStoreAndMetaDataType(it.get(), stopKey, objectStoreId, ObjectStoreMetaDataKey::HasKeyPath)) { |
| bool hasKeyPath = decodeBool(it->value().begin(), it->value().end()); |
| // This check accounts for two layers of legacy coding: |
| // (1) Initially, hasKeyPath was added to distinguish null vs. string. |
| // (2) Later, null vs. string vs. array was stored in the keyPath itself. |
| // So this check is only relevant for string-type keyPaths. |
| if (!hasKeyPath && (keyPath.type() == IDBKeyPath::StringType && !keyPath.string().isEmpty())) { |
| INTERNAL_CONSISTENCY_ERROR(GetObjectStores); |
| break; |
| } |
| if (!hasKeyPath) |
| keyPath = IDBKeyPath(); |
| it->next(); |
| } |
| |
| int64_t keyGeneratorCurrentNumber = -1; |
| if (checkObjectStoreAndMetaDataType(it.get(), stopKey, objectStoreId, ObjectStoreMetaDataKey::KeyGeneratorCurrentNumber)) { |
| keyGeneratorCurrentNumber = decodeInt(it->value().begin(), it->value().end()); |
| // FIXME: Return keyGeneratorCurrentNumber, cache in object store, and write lazily to backing store. |
| // For now, just assert that if it was written it was valid. |
| ASSERT_UNUSED(keyGeneratorCurrentNumber, keyGeneratorCurrentNumber >= KeyGeneratorInitialNumber); |
| it->next(); |
| } |
| |
| IDBObjectStoreMetadata metadata(objectStoreName, objectStoreId, keyPath, autoIncrement, maxIndexId); |
| if (!getIndexes(databaseId, objectStoreId, &metadata.indexes)) |
| return false; |
| objectStores->set(objectStoreId, metadata); |
| } |
| return true; |
| } |
| |
| WARN_UNUSED_RETURN static bool setMaxObjectStoreId(LevelDBTransaction* transaction, int64_t databaseId, int64_t objectStoreId) |
| { |
| const Vector<char> maxObjectStoreIdKey = DatabaseMetaDataKey::encode(databaseId, DatabaseMetaDataKey::MaxObjectStoreId); |
| int64_t maxObjectStoreId = -1; |
| bool ok = getMaxObjectStoreId(transaction, maxObjectStoreIdKey, maxObjectStoreId); |
| if (!ok) { |
| INTERNAL_READ_ERROR(SetMaxObjectStoreId); |
| return false; |
| } |
| |
| if (objectStoreId <= maxObjectStoreId) { |
| INTERNAL_CONSISTENCY_ERROR(SetMaxObjectStoreId); |
| return false; |
| } |
| putInt(transaction, maxObjectStoreIdKey, objectStoreId); |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::createObjectStore(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, const String& name, const IDBKeyPath& keyPath, bool autoIncrement) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::createObjectStore"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| if (!setMaxObjectStoreId(levelDBTransaction, databaseId, objectStoreId)) |
| return false; |
| |
| const Vector<char> nameKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::Name); |
| const Vector<char> keyPathKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::KeyPath); |
| const Vector<char> autoIncrementKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::AutoIncrement); |
| const Vector<char> evictableKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::Evictable); |
| const Vector<char> lastVersionKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::LastVersion); |
| const Vector<char> maxIndexIdKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::MaxIndexId); |
| const Vector<char> hasKeyPathKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::HasKeyPath); |
| const Vector<char> keyGeneratorCurrentNumberKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::KeyGeneratorCurrentNumber); |
| const Vector<char> namesKey = ObjectStoreNamesKey::encode(databaseId, name); |
| |
| putString(levelDBTransaction, nameKey, name); |
| putIDBKeyPath(levelDBTransaction, keyPathKey, keyPath); |
| putInt(levelDBTransaction, autoIncrementKey, autoIncrement); |
| putInt(levelDBTransaction, evictableKey, false); |
| putInt(levelDBTransaction, lastVersionKey, 1); |
| putInt(levelDBTransaction, maxIndexIdKey, MinimumIndexId); |
| putBool(levelDBTransaction, hasKeyPathKey, !keyPath.isNull()); |
| putInt(levelDBTransaction, keyGeneratorCurrentNumberKey, KeyGeneratorInitialNumber); |
| putInt(levelDBTransaction, namesKey, objectStoreId); |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::deleteObjectStore(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::deleteObjectStore"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| |
| String objectStoreName; |
| bool found = false; |
| bool ok = getString(levelDBTransaction, ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::Name), objectStoreName, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(DeleteObjectStore); |
| return false; |
| } |
| if (!found) { |
| INTERNAL_CONSISTENCY_ERROR(DeleteObjectStore); |
| return false; |
| } |
| |
| deleteRange(levelDBTransaction, ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, 0), ObjectStoreMetaDataKey::encodeMaxKey(databaseId, objectStoreId)); |
| |
| levelDBTransaction->remove(ObjectStoreNamesKey::encode(databaseId, objectStoreName)); |
| |
| deleteRange(levelDBTransaction, IndexFreeListKey::encode(databaseId, objectStoreId, 0), IndexFreeListKey::encodeMaxKey(databaseId, objectStoreId)); |
| deleteRange(levelDBTransaction, IndexMetaDataKey::encode(databaseId, objectStoreId, 0, 0), IndexMetaDataKey::encodeMaxKey(databaseId, objectStoreId)); |
| |
| return clearObjectStore(transaction, databaseId, objectStoreId); |
| } |
| |
| bool IDBBackingStoreLevelDB::getRecord(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, const IDBKey& key, Vector<char>& record) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::getRecord"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| |
| const Vector<char> leveldbKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, key); |
| Vector<char> data; |
| |
| record.clear(); |
| |
| bool found = false; |
| bool ok = levelDBTransaction->safeGet(leveldbKey, data, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetRecord); |
| return false; |
| } |
| if (!found) |
| return true; |
| |
| int64_t version; |
| const char* p = decodeVarInt(data.begin(), data.end(), version); |
| if (!p) { |
| INTERNAL_READ_ERROR(GetRecord); |
| return false; |
| } |
| |
| record.appendRange(p, static_cast<const char*>(data.end())); |
| return true; |
| } |
| |
| WARN_UNUSED_RETURN static bool getNewVersionNumber(LevelDBTransaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t& newVersionNumber) |
| { |
| const Vector<char> lastVersionKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::LastVersion); |
| |
| newVersionNumber = -1; |
| int64_t lastVersion = -1; |
| bool found = false; |
| bool ok = getInt(transaction, lastVersionKey, lastVersion, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetNewVersionNumber); |
| return false; |
| } |
| if (!found) |
| lastVersion = 0; |
| |
| ASSERT(lastVersion >= 0); |
| |
| int64_t version = lastVersion + 1; |
| putInt(transaction, lastVersionKey, version); |
| |
| ASSERT(version > lastVersion); // FIXME: Think about how we want to handle the overflow scenario. |
| |
| newVersionNumber = version; |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::putRecord(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, const IDBKey& key, PassRefPtr<SharedBuffer> prpValue, IDBRecordIdentifier* recordIdentifier) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::putRecord"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| ASSERT(key.isValid()); |
| |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| int64_t version = -1; |
| bool ok = getNewVersionNumber(levelDBTransaction, databaseId, objectStoreId, version); |
| if (!ok) |
| return false; |
| ASSERT(version >= 0); |
| const Vector<char> objectStoredataKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, key); |
| |
| Vector<char> v; |
| v.appendVector(encodeVarInt(version)); |
| RefPtr<SharedBuffer> value = prpValue; |
| ASSERT(value); |
| v.append(value->data(), value->size()); |
| |
| levelDBTransaction->put(objectStoredataKey, v); |
| |
| const Vector<char> existsEntryKey = ExistsEntryKey::encode(databaseId, objectStoreId, key); |
| levelDBTransaction->put(existsEntryKey, encodeInt(version)); |
| |
| recordIdentifier->reset(encodeIDBKey(key), version); |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::clearObjectStore(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::clearObjectStore"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| const Vector<char> startKey = KeyPrefix(databaseId, objectStoreId).encode(); |
| const Vector<char> stopKey = KeyPrefix(databaseId, objectStoreId + 1).encode(); |
| |
| deleteRange(levelDBTransaction, startKey, stopKey); |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::deleteRecord(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, const IDBRecordIdentifier& recordIdentifier) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::deleteRecord"); |
| |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| |
| const Vector<char> objectStoreDataKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, recordIdentifier.encodedPrimaryKey()); |
| levelDBTransaction->remove(objectStoreDataKey); |
| |
| const Vector<char> existsEntryKey = ExistsEntryKey::encode(databaseId, objectStoreId, recordIdentifier.encodedPrimaryKey()); |
| levelDBTransaction->remove(existsEntryKey); |
| return true; |
| } |
| |
| |
| bool IDBBackingStoreLevelDB::getKeyGeneratorCurrentNumber(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t& keyGeneratorCurrentNumber) |
| { |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| |
| const Vector<char> keyGeneratorCurrentNumberKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::KeyGeneratorCurrentNumber); |
| |
| keyGeneratorCurrentNumber = -1; |
| Vector<char> data; |
| |
| bool found = false; |
| bool ok = levelDBTransaction->safeGet(keyGeneratorCurrentNumberKey, data, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetKeyGeneratorCurrentNumber); |
| return false; |
| } |
| if (found) |
| keyGeneratorCurrentNumber = 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 Vector<char> startKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, minIDBKey()); |
| const Vector<char> stopKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, maxIDBKey()); |
| |
| OwnPtr<LevelDBIterator> it = levelDBTransaction->createIterator(); |
| int64_t maxNumericKey = 0; |
| |
| for (it->seek(startKey); it->isValid() && compareKeys(it->key(), stopKey) < 0; it->next()) { |
| const char* p = it->key().begin(); |
| const char* limit = it->key().end(); |
| |
| ObjectStoreDataKey dataKey; |
| p = ObjectStoreDataKey::decode(p, limit, &dataKey); |
| ASSERT(p); |
| |
| if (dataKey.userKey()->type() == IDBKey::NumberType) { |
| int64_t n = static_cast<int64_t>(dataKey.userKey()->number()); |
| if (n > maxNumericKey) |
| maxNumericKey = n; |
| } |
| } |
| |
| keyGeneratorCurrentNumber = maxNumericKey + 1; |
| } |
| |
| return keyGeneratorCurrentNumber; |
| } |
| |
| bool IDBBackingStoreLevelDB::maybeUpdateKeyGeneratorCurrentNumber(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t newNumber, bool checkCurrent) |
| { |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| |
| if (checkCurrent) { |
| int64_t currentNumber; |
| bool ok = getKeyGeneratorCurrentNumber(transaction, databaseId, objectStoreId, currentNumber); |
| if (!ok) |
| return false; |
| if (newNumber <= currentNumber) |
| return true; |
| } |
| |
| const Vector<char> keyGeneratorCurrentNumberKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::KeyGeneratorCurrentNumber); |
| putInt(levelDBTransaction, keyGeneratorCurrentNumberKey, newNumber); |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::keyExistsInObjectStore(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, const IDBKey& key, RefPtr<IDBRecordIdentifier>& foundIDBRecordIdentifier) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::keyExistsInObjectStore"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| bool found = false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| const Vector<char> leveldbKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, key); |
| Vector<char> data; |
| |
| bool ok = levelDBTransaction->safeGet(leveldbKey, data, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(KeyExistsInObjectStore); |
| return false; |
| } |
| if (!found) |
| return true; |
| |
| int64_t version; |
| if (!decodeVarInt(data.begin(), data.end(), version)) |
| return false; |
| |
| foundIDBRecordIdentifier = IDBRecordIdentifier::create(encodeIDBKey(key), version); |
| return true; |
| } |
| |
| static bool checkIndexAndMetaDataKey(const LevelDBIterator* it, const Vector<char>& stopKey, int64_t indexId, unsigned char metaDataType) |
| { |
| if (!it->isValid() || compareKeys(it->key(), stopKey) >= 0) |
| return false; |
| |
| IndexMetaDataKey metaDataKey; |
| const char* p = IndexMetaDataKey::decode(it->key().begin(), it->key().end(), &metaDataKey); |
| ASSERT_UNUSED(p, p); |
| if (metaDataKey.indexId() != indexId) |
| return false; |
| if (metaDataKey.metaDataType() != metaDataType) |
| return false; |
| return true; |
| } |
| |
| |
| // FIXME: This should do some error handling rather than plowing ahead when bad data is encountered. |
| bool IDBBackingStoreLevelDB::getIndexes(int64_t databaseId, int64_t objectStoreId, IDBObjectStoreMetadata::IndexMap* indexes) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::getIndexes"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId)) |
| return false; |
| const Vector<char> startKey = IndexMetaDataKey::encode(databaseId, objectStoreId, 0, 0); |
| const Vector<char> stopKey = IndexMetaDataKey::encode(databaseId, objectStoreId + 1, 0, 0); |
| |
| ASSERT(indexes->isEmpty()); |
| |
| OwnPtr<LevelDBIterator> it = m_db->createIterator(); |
| it->seek(startKey); |
| while (it->isValid() && compareKeys(it->key(), stopKey) < 0) { |
| const char* p = it->key().begin(); |
| const char* limit = it->key().end(); |
| |
| IndexMetaDataKey metaDataKey; |
| p = IndexMetaDataKey::decode(p, limit, &metaDataKey); |
| ASSERT(p); |
| if (metaDataKey.metaDataType() != IndexMetaDataKey::Name) { |
| INTERNAL_CONSISTENCY_ERROR(GetIndexes); |
| // Possible stale metadata due to http://webkit.org/b/85557 but don't fail the load. |
| it->next(); |
| continue; |
| } |
| |
| // FIXME: Do this by direct key lookup rather than iteration, to simplify. |
| int64_t indexId = metaDataKey.indexId(); |
| String indexName = decodeString(it->value().begin(), it->value().end()); |
| |
| it->next(); // unique flag |
| if (!checkIndexAndMetaDataKey(it.get(), stopKey, indexId, IndexMetaDataKey::Unique)) { |
| INTERNAL_CONSISTENCY_ERROR(GetIndexes); |
| break; |
| } |
| bool indexUnique = decodeBool(it->value().begin(), it->value().end()); |
| |
| it->next(); // keyPath |
| if (!checkIndexAndMetaDataKey(it.get(), stopKey, indexId, IndexMetaDataKey::KeyPath)) { |
| INTERNAL_CONSISTENCY_ERROR(GetIndexes); |
| break; |
| } |
| IDBKeyPath keyPath = decodeIDBKeyPath(it->value().begin(), it->value().end()); |
| |
| it->next(); // [optional] multiEntry flag |
| bool indexMultiEntry = false; |
| if (checkIndexAndMetaDataKey(it.get(), stopKey, indexId, IndexMetaDataKey::MultiEntry)) { |
| indexMultiEntry = decodeBool(it->value().begin(), it->value().end()); |
| it->next(); |
| } |
| |
| indexes->set(indexId, IDBIndexMetadata(indexName, indexId, keyPath, indexUnique, indexMultiEntry)); |
| } |
| return true; |
| } |
| |
| WARN_UNUSED_RETURN static bool setMaxIndexId(LevelDBTransaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId) |
| { |
| int64_t maxIndexId = -1; |
| const Vector<char> maxIndexIdKey = ObjectStoreMetaDataKey::encode(databaseId, objectStoreId, ObjectStoreMetaDataKey::MaxIndexId); |
| bool found = false; |
| bool ok = getInt(transaction, maxIndexIdKey, maxIndexId, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(SetMaxIndexId); |
| return false; |
| } |
| if (!found) |
| maxIndexId = MinimumIndexId; |
| |
| if (indexId <= maxIndexId) { |
| INTERNAL_CONSISTENCY_ERROR(SetMaxIndexId); |
| return false; |
| } |
| |
| putInt(transaction, maxIndexIdKey, indexId); |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::createIndex(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const String& name, const IDBKeyPath& keyPath, bool isUnique, bool isMultiEntry) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::createIndex"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId)) |
| return false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| if (!setMaxIndexId(levelDBTransaction, databaseId, objectStoreId, indexId)) |
| return false; |
| |
| const Vector<char> nameKey = IndexMetaDataKey::encode(databaseId, objectStoreId, indexId, IndexMetaDataKey::Name); |
| const Vector<char> uniqueKey = IndexMetaDataKey::encode(databaseId, objectStoreId, indexId, IndexMetaDataKey::Unique); |
| const Vector<char> keyPathKey = IndexMetaDataKey::encode(databaseId, objectStoreId, indexId, IndexMetaDataKey::KeyPath); |
| const Vector<char> multiEntryKey = IndexMetaDataKey::encode(databaseId, objectStoreId, indexId, IndexMetaDataKey::MultiEntry); |
| |
| putString(levelDBTransaction, nameKey, name); |
| putBool(levelDBTransaction, uniqueKey, isUnique); |
| putIDBKeyPath(levelDBTransaction, keyPathKey, keyPath); |
| putBool(levelDBTransaction, multiEntryKey, isMultiEntry); |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::deleteIndex(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::deleteIndex"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId)) |
| return false; |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| |
| const Vector<char> indexMetaDataStart = IndexMetaDataKey::encode(databaseId, objectStoreId, indexId, 0); |
| const Vector<char> indexMetaDataEnd = IndexMetaDataKey::encodeMaxKey(databaseId, objectStoreId, indexId); |
| deleteRange(levelDBTransaction, indexMetaDataStart, indexMetaDataEnd); |
| |
| const Vector<char> indexDataStart = IndexDataKey::encodeMinKey(databaseId, objectStoreId, indexId); |
| const Vector<char> indexDataEnd = IndexDataKey::encodeMaxKey(databaseId, objectStoreId, indexId); |
| deleteRange(levelDBTransaction, indexDataStart, indexDataEnd); |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::putIndexDataForRecord(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey& key, const IDBRecordIdentifier* recordIdentifier) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::putIndexDataForRecord"); |
| ASSERT(key.isValid()); |
| ASSERT(recordIdentifier); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId)) |
| return false; |
| |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| const Vector<char> indexDataKey = IndexDataKey::encode(databaseId, objectStoreId, indexId, encodeIDBKey(key), recordIdentifier->encodedPrimaryKey()); |
| |
| Vector<char> data; |
| data.appendVector(encodeVarInt(recordIdentifier->version())); |
| data.appendVector(recordIdentifier->encodedPrimaryKey()); |
| |
| levelDBTransaction->put(indexDataKey, data); |
| return true; |
| } |
| |
| static bool findGreatestKeyLessThanOrEqual(LevelDBTransaction* transaction, const Vector<char>& target, Vector<char>& foundKey) |
| { |
| OwnPtr<LevelDBIterator> it = transaction->createIterator(); |
| it->seek(target); |
| |
| if (!it->isValid()) { |
| it->seekToLast(); |
| if (!it->isValid()) |
| return false; |
| } |
| |
| while (IDBBackingStoreLevelDB::compareIndexKeys(it->key(), target) > 0) { |
| it->prev(); |
| if (!it->isValid()) |
| return false; |
| } |
| |
| do { |
| foundKey.clear(); |
| foundKey.append(it->key().begin(), it->key().end() - it->key().begin()); |
| |
| // There can be several index keys that compare equal. We want the last one. |
| it->next(); |
| } while (it->isValid() && !IDBBackingStoreLevelDB::compareIndexKeys(it->key(), target)); |
| |
| return true; |
| } |
| |
| static bool versionExists(LevelDBTransaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t version, const Vector<char>& encodedPrimaryKey, bool& exists) |
| { |
| const Vector<char> key = ExistsEntryKey::encode(databaseId, objectStoreId, encodedPrimaryKey); |
| Vector<char> data; |
| |
| bool ok = transaction->safeGet(key, data, exists); |
| if (!ok) { |
| INTERNAL_READ_ERROR(VersionExists); |
| return false; |
| } |
| if (!exists) |
| return true; |
| |
| exists = (decodeInt(data.begin(), data.end()) == version); |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::findKeyInIndex(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey& key, Vector<char>& foundEncodedPrimaryKey, bool& found) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::findKeyInIndex"); |
| ASSERT(KeyPrefix::validIds(databaseId, objectStoreId, indexId)); |
| |
| ASSERT(foundEncodedPrimaryKey.isEmpty()); |
| found = false; |
| |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| const Vector<char> leveldbKey = IndexDataKey::encode(databaseId, objectStoreId, indexId, key); |
| OwnPtr<LevelDBIterator> it = levelDBTransaction->createIterator(); |
| it->seek(leveldbKey); |
| |
| for (;;) { |
| if (!it->isValid()) |
| return true; |
| if (compareIndexKeys(it->key(), leveldbKey) > 0) |
| return true; |
| |
| int64_t version; |
| const char* p = decodeVarInt(it->value().begin(), it->value().end(), version); |
| if (!p) { |
| INTERNAL_READ_ERROR(FindKeyInIndex); |
| return false; |
| } |
| foundEncodedPrimaryKey.append(p, it->value().end() - p); |
| |
| bool exists = false; |
| bool ok = versionExists(levelDBTransaction, databaseId, objectStoreId, version, foundEncodedPrimaryKey, exists); |
| if (!ok) |
| return false; |
| if (!exists) { |
| // Delete stale index data entry and continue. |
| levelDBTransaction->remove(it->key()); |
| it->next(); |
| continue; |
| } |
| found = true; |
| return true; |
| } |
| } |
| |
| bool IDBBackingStoreLevelDB::getPrimaryKeyViaIndex(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey& key, RefPtr<IDBKey>& primaryKey) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::getPrimaryKeyViaIndex"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId)) |
| return false; |
| |
| bool found = false; |
| Vector<char> foundEncodedPrimaryKey; |
| bool ok = findKeyInIndex(transaction, databaseId, objectStoreId, indexId, key, foundEncodedPrimaryKey, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(GetPrimaryKeyViaIndex); |
| return false; |
| } |
| if (found) { |
| decodeIDBKey(foundEncodedPrimaryKey.begin(), foundEncodedPrimaryKey.end(), primaryKey); |
| return true; |
| } |
| |
| return true; |
| } |
| |
| bool IDBBackingStoreLevelDB::keyExistsInIndex(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey& indexKey, RefPtr<IDBKey>& foundPrimaryKey, bool& exists) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::keyExistsInIndex"); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId)) |
| return false; |
| |
| exists = false; |
| Vector<char> foundEncodedPrimaryKey; |
| bool ok = findKeyInIndex(transaction, databaseId, objectStoreId, indexId, indexKey, foundEncodedPrimaryKey, exists); |
| if (!ok) { |
| INTERNAL_READ_ERROR(KeyExistsInIndex); |
| return false; |
| } |
| if (!exists) |
| return true; |
| |
| decodeIDBKey(foundEncodedPrimaryKey.begin(), foundEncodedPrimaryKey.end(), foundPrimaryKey); |
| return true; |
| } |
| |
| |
| bool IDBBackingStoreLevelDB::makeIndexWriters(IDBTransactionBackendInterface& transaction, int64_t databaseId, const IDBObjectStoreMetadata& objectStore, IDBKey& primaryKey, bool keyWasGenerated, const Vector<int64_t>& indexIds, const Vector<IndexKeys>& indexKeys, Vector<RefPtr<IDBIndexWriter>>& indexWriters, String* errorMessage, bool& completed) |
| { |
| ASSERT(indexIds.size() == indexKeys.size()); |
| completed = false; |
| |
| HashMap<int64_t, IndexKeys> indexKeyMap; |
| for (size_t i = 0; i < indexIds.size(); ++i) |
| indexKeyMap.add(indexIds[i], indexKeys[i]); |
| |
| for (IDBObjectStoreMetadata::IndexMap::const_iterator it = objectStore.indexes.begin(); it != objectStore.indexes.end(); ++it) { |
| const IDBIndexMetadata& index = it->value; |
| |
| IndexKeys keys = indexKeyMap.get(it->key); |
| // If the objectStore is using autoIncrement, then any indexes with an identical keyPath need to also use the primary (generated) key as a key. |
| if (keyWasGenerated && (index.keyPath == objectStore.keyPath)) |
| keys.append(&primaryKey); |
| |
| RefPtr<IDBIndexWriter> indexWriter = IDBIndexWriter::create(index, keys); |
| bool canAddKeys = false; |
| bool backingStoreSuccess = indexWriter->verifyIndexKeys(*this, transaction.backingStoreTransaction(), databaseId, objectStore.id, index.id, canAddKeys, &primaryKey, errorMessage); |
| if (!backingStoreSuccess) |
| return false; |
| if (!canAddKeys) |
| return true; |
| |
| indexWriters.append(indexWriter.release()); |
| } |
| |
| completed = true; |
| return true; |
| } |
| |
| PassRefPtr<IDBKey> IDBBackingStoreLevelDB::generateKey(IDBTransactionBackendInterface& transaction, int64_t databaseId, int64_t objectStoreId) |
| { |
| const int64_t maxGeneratorValue = 9007199254740992LL; // Maximum integer storable as ECMAScript number. |
| int64_t currentNumber; |
| bool ok = getKeyGeneratorCurrentNumber(transaction.backingStoreTransaction(), databaseId, objectStoreId, currentNumber); |
| if (!ok) { |
| LOG_ERROR("Failed to getKeyGeneratorCurrentNumber"); |
| return IDBKey::createInvalid(); |
| } |
| if (currentNumber < 0 || currentNumber > maxGeneratorValue) |
| return IDBKey::createInvalid(); |
| |
| return IDBKey::createNumber(currentNumber); |
| } |
| |
| |
| bool IDBBackingStoreLevelDB::updateKeyGenerator(IDBTransactionBackendInterface& transaction, int64_t databaseId, int64_t objectStoreId, const IDBKey& key, bool checkCurrent) |
| { |
| ASSERT(key.type() == IDBKey::NumberType); |
| return maybeUpdateKeyGeneratorCurrentNumber(transaction.backingStoreTransaction(), databaseId, objectStoreId, static_cast<int64_t>(floor(key.number())) + 1, checkCurrent); |
| } |
| |
| class ObjectStoreKeyCursorImpl : public IDBBackingStoreCursorLevelDB { |
| public: |
| static PassRefPtr<ObjectStoreKeyCursorImpl> create(LevelDBTransaction* transaction, const IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| { |
| return adoptRef(new ObjectStoreKeyCursorImpl(transaction, cursorOptions)); |
| } |
| |
| virtual PassRefPtr<IDBBackingStoreCursorInterface> clone() |
| { |
| return adoptRef(new ObjectStoreKeyCursorImpl(this)); |
| } |
| |
| // IDBBackingStoreCursorLevelDB |
| virtual PassRefPtr<SharedBuffer> value() const OVERRIDE { ASSERT_NOT_REACHED(); return 0; } |
| virtual bool loadCurrentRow() OVERRIDE; |
| |
| protected: |
| virtual Vector<char> encodeKey(const IDBKey &key) |
| { |
| return ObjectStoreDataKey::encode(m_cursorOptions.databaseId, m_cursorOptions.objectStoreId, key); |
| } |
| |
| private: |
| ObjectStoreKeyCursorImpl(LevelDBTransaction* transaction, const IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| : IDBBackingStoreCursorLevelDB(transaction, cursorOptions) |
| { |
| } |
| |
| ObjectStoreKeyCursorImpl(const ObjectStoreKeyCursorImpl* other) |
| : IDBBackingStoreCursorLevelDB(other) |
| { |
| } |
| }; |
| |
| bool ObjectStoreKeyCursorImpl::loadCurrentRow() |
| { |
| const char* keyPosition = m_iterator->key().begin(); |
| const char* keyLimit = m_iterator->key().end(); |
| |
| ObjectStoreDataKey objectStoreDataKey; |
| keyPosition = ObjectStoreDataKey::decode(keyPosition, keyLimit, &objectStoreDataKey); |
| if (!keyPosition) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| |
| m_currentKey = objectStoreDataKey.userKey(); |
| |
| int64_t version; |
| const char* valuePosition = decodeVarInt(m_iterator->value().begin(), m_iterator->value().end(), version); |
| if (!valuePosition) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| |
| // FIXME: This re-encodes what was just decoded; try and optimize. |
| m_recordIdentifier->reset(encodeIDBKey(*m_currentKey), version); |
| |
| return true; |
| } |
| |
| class ObjectStoreCursorImpl : public IDBBackingStoreCursorLevelDB { |
| public: |
| static PassRefPtr<ObjectStoreCursorImpl> create(LevelDBTransaction* transaction, const IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| { |
| return adoptRef(new ObjectStoreCursorImpl(transaction, cursorOptions)); |
| } |
| |
| virtual PassRefPtr<IDBBackingStoreCursorInterface> clone() |
| { |
| return adoptRef(new ObjectStoreCursorImpl(this)); |
| } |
| |
| // IDBBackingStoreCursorLevelDB |
| virtual PassRefPtr<SharedBuffer> value() const OVERRIDE { return m_currentValue; } |
| virtual bool loadCurrentRow() OVERRIDE; |
| |
| protected: |
| virtual Vector<char> encodeKey(const IDBKey &key) |
| { |
| return ObjectStoreDataKey::encode(m_cursorOptions.databaseId, m_cursorOptions.objectStoreId, key); |
| } |
| |
| private: |
| ObjectStoreCursorImpl(LevelDBTransaction* transaction, const IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| : IDBBackingStoreCursorLevelDB(transaction, cursorOptions) |
| { |
| } |
| |
| ObjectStoreCursorImpl(const ObjectStoreCursorImpl* other) |
| : IDBBackingStoreCursorLevelDB(other) |
| , m_currentValue(other->m_currentValue) |
| { |
| } |
| |
| RefPtr<SharedBuffer> m_currentValue; |
| }; |
| |
| bool ObjectStoreCursorImpl::loadCurrentRow() |
| { |
| const char* keyPosition = m_iterator->key().begin(); |
| const char* keyLimit = m_iterator->key().end(); |
| |
| ObjectStoreDataKey objectStoreDataKey; |
| keyPosition = ObjectStoreDataKey::decode(keyPosition, keyLimit, &objectStoreDataKey); |
| if (!keyPosition) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| |
| m_currentKey = objectStoreDataKey.userKey(); |
| |
| int64_t version; |
| const char* valuePosition = decodeVarInt(m_iterator->value().begin(), m_iterator->value().end(), version); |
| if (!valuePosition) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| |
| // FIXME: This re-encodes what was just decoded; try and optimize. |
| m_recordIdentifier->reset(encodeIDBKey(*m_currentKey), version); |
| |
| Vector<char> value; |
| value.append(valuePosition, m_iterator->value().end() - valuePosition); |
| m_currentValue = SharedBuffer::adoptVector(value); |
| return true; |
| } |
| |
| class IndexKeyCursorImpl FINAL : public IDBBackingStoreCursorLevelDB { |
| public: |
| static PassRefPtr<IndexKeyCursorImpl> create(LevelDBTransaction* transaction, const IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| { |
| return adoptRef(new IndexKeyCursorImpl(transaction, cursorOptions)); |
| } |
| |
| virtual PassRefPtr<IDBBackingStoreCursorInterface> clone() |
| { |
| return adoptRef(new IndexKeyCursorImpl(this)); |
| } |
| |
| // IDBBackingStoreCursorLevelDB |
| virtual PassRefPtr<SharedBuffer> value() const OVERRIDE { ASSERT_NOT_REACHED(); return 0; } |
| virtual PassRefPtr<IDBKey> primaryKey() const OVERRIDE { return m_primaryKey; } |
| virtual const IDBRecordIdentifier& recordIdentifier() const OVERRIDE { ASSERT_NOT_REACHED(); return *m_recordIdentifier; } |
| virtual bool loadCurrentRow() OVERRIDE; |
| |
| protected: |
| virtual Vector<char> encodeKey(const IDBKey &key) |
| { |
| return IndexDataKey::encode(m_cursorOptions.databaseId, m_cursorOptions.objectStoreId, m_cursorOptions.indexId, key); |
| } |
| |
| private: |
| IndexKeyCursorImpl(LevelDBTransaction* transaction, const IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| : IDBBackingStoreCursorLevelDB(transaction, cursorOptions) |
| { |
| } |
| |
| IndexKeyCursorImpl(const IndexKeyCursorImpl* other) |
| : IDBBackingStoreCursorLevelDB(other) |
| , m_primaryKey(other->m_primaryKey) |
| { |
| } |
| |
| RefPtr<IDBKey> m_primaryKey; |
| }; |
| |
| bool IndexKeyCursorImpl::loadCurrentRow() |
| { |
| const char* keyPosition = m_iterator->key().begin(); |
| const char* keyLimit = m_iterator->key().end(); |
| |
| IndexDataKey indexDataKey; |
| keyPosition = IndexDataKey::decode(keyPosition, keyLimit, &indexDataKey); |
| |
| m_currentKey = indexDataKey.userKey(); |
| |
| int64_t indexDataVersion; |
| const char* valuePosition = decodeVarInt(m_iterator->value().begin(), m_iterator->value().end(), indexDataVersion); |
| if (!valuePosition) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| |
| valuePosition = decodeIDBKey(valuePosition, m_iterator->value().end(), m_primaryKey); |
| if (!valuePosition) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| |
| Vector<char> primaryLevelDBKey = ObjectStoreDataKey::encode(indexDataKey.databaseId(), indexDataKey.objectStoreId(), *m_primaryKey); |
| |
| Vector<char> result; |
| bool found = false; |
| bool ok = m_transaction->safeGet(primaryLevelDBKey, result, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| if (!found) { |
| m_transaction->remove(m_iterator->key()); |
| return false; |
| } |
| |
| int64_t objectStoreDataVersion; |
| const char* t = decodeVarInt(result.begin(), result.end(), objectStoreDataVersion); |
| if (!t) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| |
| if (objectStoreDataVersion != indexDataVersion) { |
| m_transaction->remove(m_iterator->key()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| class IndexCursorImpl FINAL : public IDBBackingStoreCursorLevelDB { |
| public: |
| static PassRefPtr<IndexCursorImpl> create(LevelDBTransaction* transaction, const IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| { |
| return adoptRef(new IndexCursorImpl(transaction, cursorOptions)); |
| } |
| |
| virtual PassRefPtr<IDBBackingStoreCursorInterface> clone() |
| { |
| return adoptRef(new IndexCursorImpl(this)); |
| } |
| |
| // IDBBackingStoreCursorLevelDB |
| virtual PassRefPtr<SharedBuffer> value() const OVERRIDE { return m_currentValue; } |
| virtual PassRefPtr<IDBKey> primaryKey() const OVERRIDE { return m_primaryKey; } |
| virtual const IDBRecordIdentifier& recordIdentifier() const OVERRIDE { ASSERT_NOT_REACHED(); return *m_recordIdentifier; } |
| virtual bool loadCurrentRow() OVERRIDE; |
| |
| protected: |
| virtual Vector<char> encodeKey(const IDBKey &key) |
| { |
| return IndexDataKey::encode(m_cursorOptions.databaseId, m_cursorOptions.objectStoreId, m_cursorOptions.indexId, key); |
| } |
| |
| private: |
| IndexCursorImpl(LevelDBTransaction* transaction, const IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| : IDBBackingStoreCursorLevelDB(transaction, cursorOptions) |
| { |
| } |
| |
| IndexCursorImpl(const IndexCursorImpl* other) |
| : IDBBackingStoreCursorLevelDB(other) |
| , m_primaryKey(other->m_primaryKey) |
| , m_currentValue(other->m_currentValue) |
| , m_primaryLevelDBKey(other->m_primaryLevelDBKey) |
| { |
| } |
| |
| RefPtr<IDBKey> m_primaryKey; |
| RefPtr<SharedBuffer> m_currentValue; |
| Vector<char> m_primaryLevelDBKey; |
| }; |
| |
| bool IndexCursorImpl::loadCurrentRow() |
| { |
| const char* keyPosition = m_iterator->key().begin(); |
| const char* keyLimit = m_iterator->key().end(); |
| |
| IndexDataKey indexDataKey; |
| keyPosition = IndexDataKey::decode(keyPosition, keyLimit, &indexDataKey); |
| |
| m_currentKey = indexDataKey.userKey(); |
| |
| const char* valuePosition = m_iterator->value().begin(); |
| const char* valueLimit = m_iterator->value().end(); |
| |
| int64_t indexDataVersion; |
| valuePosition = decodeVarInt(valuePosition, valueLimit, indexDataVersion); |
| if (!valuePosition) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| valuePosition = decodeIDBKey(valuePosition, valueLimit, m_primaryKey); |
| if (!valuePosition) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| |
| m_primaryLevelDBKey = ObjectStoreDataKey::encode(indexDataKey.databaseId(), indexDataKey.objectStoreId(), *m_primaryKey); |
| |
| Vector<char> result; |
| bool found = false; |
| bool ok = m_transaction->safeGet(m_primaryLevelDBKey, result, found); |
| if (!ok) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| if (!found) { |
| m_transaction->remove(m_iterator->key()); |
| return false; |
| } |
| |
| int64_t objectStoreDataVersion; |
| valuePosition = decodeVarInt(result.begin(), result.end(), objectStoreDataVersion); |
| if (!valuePosition) { |
| INTERNAL_READ_ERROR(LoadCurrentRow); |
| return false; |
| } |
| |
| if (objectStoreDataVersion != indexDataVersion) { |
| m_transaction->remove(m_iterator->key()); |
| return false; |
| } |
| |
| Vector<char> value; |
| value.append(valuePosition, result.end() - valuePosition); |
| m_currentValue = SharedBuffer::adoptVector(value); |
| return true; |
| } |
| |
| static bool objectStoreCursorOptions(LevelDBTransaction* transaction, int64_t databaseId, int64_t objectStoreId, const IDBKeyRange* range, IndexedDB::CursorDirection direction, IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| { |
| cursorOptions.databaseId = databaseId; |
| cursorOptions.objectStoreId = objectStoreId; |
| |
| bool lowerBound = range && range->lower(); |
| bool upperBound = range && range->upper(); |
| cursorOptions.forward = (direction == IndexedDB::CursorNextNoDuplicate || direction == IndexedDB::CursorNext); |
| cursorOptions.unique = (direction == IndexedDB::CursorNextNoDuplicate || direction == IndexedDB::CursorPrevNoDuplicate); |
| |
| if (!lowerBound) { |
| cursorOptions.lowKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, minIDBKey()); |
| cursorOptions.lowOpen = true; // Not included. |
| } else { |
| cursorOptions.lowKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, *range->lower()); |
| cursorOptions.lowOpen = range->lowerOpen(); |
| } |
| |
| if (!upperBound) { |
| cursorOptions.highKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, maxIDBKey()); |
| |
| if (cursorOptions.forward) |
| cursorOptions.highOpen = true; // Not included. |
| else { |
| // We need a key that exists. |
| if (!findGreatestKeyLessThanOrEqual(transaction, cursorOptions.highKey, cursorOptions.highKey)) |
| return false; |
| cursorOptions.highOpen = false; |
| } |
| } else { |
| cursorOptions.highKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, *range->upper()); |
| cursorOptions.highOpen = range->upperOpen(); |
| |
| if (!cursorOptions.forward) { |
| // For reverse cursors, we need a key that exists. |
| Vector<char> foundHighKey; |
| if (!findGreatestKeyLessThanOrEqual(transaction, cursorOptions.highKey, foundHighKey)) |
| return false; |
| |
| // If the target key should not be included, but we end up with a smaller key, we should include that. |
| if (cursorOptions.highOpen && IDBBackingStoreLevelDB::compareIndexKeys(foundHighKey, cursorOptions.highKey) < 0) |
| cursorOptions.highOpen = false; |
| |
| cursorOptions.highKey = foundHighKey; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool indexCursorOptions(LevelDBTransaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKeyRange* range, IndexedDB::CursorDirection direction, IDBBackingStoreCursorLevelDB::CursorOptions& cursorOptions) |
| { |
| ASSERT(transaction); |
| if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId)) |
| return false; |
| |
| cursorOptions.databaseId = databaseId; |
| cursorOptions.objectStoreId = objectStoreId; |
| cursorOptions.indexId = indexId; |
| |
| bool lowerBound = range && range->lower(); |
| bool upperBound = range && range->upper(); |
| cursorOptions.forward = (direction == IndexedDB::CursorNextNoDuplicate || direction == IndexedDB::CursorNext); |
| cursorOptions.unique = (direction == IndexedDB::CursorNextNoDuplicate || direction == IndexedDB::CursorPrevNoDuplicate); |
| |
| if (!lowerBound) { |
| cursorOptions.lowKey = IndexDataKey::encodeMinKey(databaseId, objectStoreId, indexId); |
| cursorOptions.lowOpen = false; // Included. |
| } else { |
| cursorOptions.lowKey = IndexDataKey::encode(databaseId, objectStoreId, indexId, *range->lower()); |
| cursorOptions.lowOpen = range->lowerOpen(); |
| } |
| |
| if (!upperBound) { |
| cursorOptions.highKey = IndexDataKey::encodeMaxKey(databaseId, objectStoreId, indexId); |
| cursorOptions.highOpen = false; // Included. |
| |
| if (!cursorOptions.forward) { // We need a key that exists. |
| if (!findGreatestKeyLessThanOrEqual(transaction, cursorOptions.highKey, cursorOptions.highKey)) |
| return false; |
| cursorOptions.highOpen = false; |
| } |
| } else { |
| cursorOptions.highKey = IndexDataKey::encode(databaseId, objectStoreId, indexId, *range->upper()); |
| cursorOptions.highOpen = range->upperOpen(); |
| |
| Vector<char> foundHighKey; |
| if (!findGreatestKeyLessThanOrEqual(transaction, cursorOptions.highKey, foundHighKey)) // 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 (cursorOptions.highOpen && IDBBackingStoreLevelDB::compareIndexKeys(foundHighKey, cursorOptions.highKey) < 0) |
| cursorOptions.highOpen = false; |
| |
| cursorOptions.highKey = foundHighKey; |
| } |
| |
| return true; |
| } |
| PassRefPtr<IDBBackingStoreCursorInterface> IDBBackingStoreLevelDB::openObjectStoreCursor(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, const IDBKeyRange* range, IndexedDB::CursorDirection direction) |
| |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::openObjectStoreCursor"); |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| IDBBackingStoreCursorLevelDB::CursorOptions cursorOptions; |
| if (!objectStoreCursorOptions(levelDBTransaction, databaseId, objectStoreId, range, direction, cursorOptions)) |
| return 0; |
| RefPtr<ObjectStoreCursorImpl> cursor = ObjectStoreCursorImpl::create(levelDBTransaction, cursorOptions); |
| if (!cursor->firstSeek()) |
| return 0; |
| |
| return cursor.release(); |
| } |
| |
| PassRefPtr<IDBBackingStoreCursorInterface> IDBBackingStoreLevelDB::openObjectStoreKeyCursor(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, const IDBKeyRange* range, IndexedDB::CursorDirection direction) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::openObjectStoreKeyCursor"); |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| IDBBackingStoreCursorLevelDB::CursorOptions cursorOptions; |
| if (!objectStoreCursorOptions(levelDBTransaction, databaseId, objectStoreId, range, direction, cursorOptions)) |
| return 0; |
| RefPtr<ObjectStoreKeyCursorImpl> cursor = ObjectStoreKeyCursorImpl::create(levelDBTransaction, cursorOptions); |
| if (!cursor->firstSeek()) |
| return 0; |
| |
| return cursor.release(); |
| } |
| |
| PassRefPtr<IDBBackingStoreCursorInterface> IDBBackingStoreLevelDB::openIndexKeyCursor(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKeyRange* range, IndexedDB::CursorDirection direction) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::openIndexKeyCursor"); |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| IDBBackingStoreCursorLevelDB::CursorOptions cursorOptions; |
| if (!indexCursorOptions(levelDBTransaction, databaseId, objectStoreId, indexId, range, direction, cursorOptions)) |
| return 0; |
| RefPtr<IndexKeyCursorImpl> cursor = IndexKeyCursorImpl::create(levelDBTransaction, cursorOptions); |
| if (!cursor->firstSeek()) |
| return 0; |
| |
| return cursor.release(); |
| } |
| |
| PassRefPtr<IDBBackingStoreCursorInterface> IDBBackingStoreLevelDB::openIndexCursor(IDBBackingStoreTransactionInterface& transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKeyRange* range, IndexedDB::CursorDirection direction) |
| { |
| LOG(StorageAPI, "IDBBackingStoreLevelDB::openIndexCursor"); |
| LevelDBTransaction* levelDBTransaction = IDBBackingStoreTransactionLevelDB::levelDBTransactionFrom(transaction); |
| IDBBackingStoreCursorLevelDB::CursorOptions cursorOptions; |
| if (!indexCursorOptions(levelDBTransaction, databaseId, objectStoreId, indexId, range, direction, cursorOptions)) |
| return 0; |
| RefPtr<IndexCursorImpl> cursor = IndexCursorImpl::create(levelDBTransaction, cursorOptions); |
| if (!cursor->firstSeek()) |
| return 0; |
| |
| return cursor.release(); |
| } |
| |
| std::unique_ptr<IDBBackingStoreTransactionInterface> IDBBackingStoreLevelDB::createBackingStoreTransaction() |
| { |
| return std::unique_ptr<IDBBackingStoreTransactionInterface>(new IDBBackingStoreTransactionLevelDB(this)); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(INDEXED_DATABASE) && USE(LEVELDB) |