| // Copyright 2017 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_metadata_coding.h" |
| |
| #include "base/strings/string_piece.h" |
| #include "content/browser/indexed_db/indexed_db_class_factory.h" |
| #include "content/browser/indexed_db/indexed_db_leveldb_coding.h" |
| #include "content/browser/indexed_db/indexed_db_leveldb_operations.h" |
| #include "content/browser/indexed_db/indexed_db_reporting.h" |
| #include "content/browser/indexed_db/indexed_db_tracing.h" |
| #include "content/browser/indexed_db/leveldb/leveldb_database.h" |
| #include "content/browser/indexed_db/leveldb/leveldb_transaction.h" |
| #include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h" |
| |
| using base::StringPiece; |
| using blink::IndexedDBDatabaseMetadata; |
| using blink::IndexedDBIndexMetadata; |
| using blink::IndexedDBKeyPath; |
| using blink::IndexedDBObjectStoreMetadata; |
| using leveldb::Status; |
| |
| namespace content { |
| using indexed_db::CheckIndexAndMetaDataKey; |
| using indexed_db::CheckObjectStoreAndMetaDataType; |
| using indexed_db::GetInt; |
| using indexed_db::GetVarInt; |
| using indexed_db::GetString; |
| using indexed_db::InternalInconsistencyStatus; |
| using indexed_db::InvalidDBKeyStatus; |
| using indexed_db::PutBool; |
| using indexed_db::PutIDBKeyPath; |
| using indexed_db::PutInt; |
| using indexed_db::PutString; |
| using indexed_db::PutVarInt; |
| |
| namespace { |
| |
| std::unique_ptr<LevelDBIterator> CreateIterator(LevelDBDatabase* database) { |
| return database->CreateIterator(database->DefaultReadOptions()); |
| } |
| |
| std::unique_ptr<LevelDBIterator> CreateIterator( |
| LevelDBTransaction* transaction) { |
| return transaction->CreateIterator(); |
| } |
| |
| // Reads all indexes for the given database and object store in |indexes|. |
| // TODO(jsbell): This should do some error handling rather than plowing ahead |
| // when bad data is encountered. |
| template <typename DatabaseOrTransaction> |
| Status ReadIndexes(DatabaseOrTransaction* db_or_transaction, |
| int64_t database_id, |
| int64_t object_store_id, |
| std::map<int64_t, IndexedDBIndexMetadata>* indexes) { |
| if (!KeyPrefix::ValidIds(database_id, object_store_id)) |
| return InvalidDBKeyStatus(); |
| const std::string start_key = |
| IndexMetaDataKey::Encode(database_id, object_store_id, 0, 0); |
| const std::string stop_key = |
| IndexMetaDataKey::Encode(database_id, object_store_id + 1, 0, 0); |
| |
| DCHECK(indexes->empty()); |
| |
| std::unique_ptr<LevelDBIterator> it = CreateIterator(db_or_transaction); |
| Status s = it->Seek(start_key); |
| while (s.ok() && it->IsValid() && CompareKeys(it->Key(), stop_key) < 0) { |
| IndexMetaDataKey meta_data_key; |
| { |
| StringPiece slice(it->Key()); |
| bool ok = IndexMetaDataKey::Decode(&slice, &meta_data_key); |
| DCHECK(ok); |
| } |
| if (meta_data_key.meta_data_type() != IndexMetaDataKey::NAME) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); |
| // Possible stale metadata due to http://webkit.org/b/85557 but don't fail |
| // the load. |
| s = it->Next(); |
| if (!s.ok()) |
| break; |
| continue; |
| } |
| |
| // TODO(jsbell): Do this by direct key lookup rather than iteration, to |
| // simplify. |
| int64_t index_id = meta_data_key.IndexId(); |
| base::string16 index_name; |
| { |
| StringPiece slice(it->Value()); |
| if (!DecodeString(&slice, &index_name) || !slice.empty()) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); |
| } |
| |
| s = it->Next(); // unique flag |
| if (!s.ok()) |
| break; |
| if (!CheckIndexAndMetaDataKey(it.get(), stop_key, index_id, |
| IndexMetaDataKey::UNIQUE)) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); |
| break; |
| } |
| bool index_unique; |
| { |
| StringPiece slice(it->Value()); |
| if (!DecodeBool(&slice, &index_unique) || !slice.empty()) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); |
| } |
| |
| s = it->Next(); // key_path |
| if (!s.ok()) |
| break; |
| if (!CheckIndexAndMetaDataKey(it.get(), stop_key, index_id, |
| IndexMetaDataKey::KEY_PATH)) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); |
| break; |
| } |
| IndexedDBKeyPath key_path; |
| { |
| StringPiece slice(it->Value()); |
| if (!DecodeIDBKeyPath(&slice, &key_path) || !slice.empty()) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); |
| } |
| |
| s = it->Next(); // [optional] multi_entry flag |
| if (!s.ok()) |
| break; |
| bool index_multi_entry = false; |
| if (CheckIndexAndMetaDataKey(it.get(), stop_key, index_id, |
| IndexMetaDataKey::MULTI_ENTRY)) { |
| StringPiece slice(it->Value()); |
| if (!DecodeBool(&slice, &index_multi_entry) || !slice.empty()) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); |
| |
| s = it->Next(); |
| if (!s.ok()) |
| break; |
| } |
| |
| (*indexes)[index_id] = IndexedDBIndexMetadata( |
| index_name, index_id, key_path, index_unique, index_multi_entry); |
| } |
| |
| if (!s.ok()) |
| INTERNAL_READ_ERROR_UNTESTED(GET_INDEXES); |
| |
| return s; |
| } |
| |
| // Reads all object stores and indexes for the given |database_id| into |
| // |object_stores|. |
| // TODO(jsbell): This should do some error handling rather than plowing ahead |
| // when bad data is encountered. |
| template <typename DatabaseOrTransaction> |
| Status ReadObjectStores( |
| DatabaseOrTransaction* db_or_transaction, |
| int64_t database_id, |
| std::map<int64_t, IndexedDBObjectStoreMetadata>* object_stores) { |
| if (!KeyPrefix::IsValidDatabaseId(database_id)) |
| return InvalidDBKeyStatus(); |
| const std::string start_key = |
| ObjectStoreMetaDataKey::Encode(database_id, 1, 0); |
| const std::string stop_key = |
| ObjectStoreMetaDataKey::EncodeMaxKey(database_id); |
| |
| DCHECK(object_stores->empty()); |
| |
| std::unique_ptr<LevelDBIterator> it = CreateIterator(db_or_transaction); |
| Status s = it->Seek(start_key); |
| while (s.ok() && it->IsValid() && CompareKeys(it->Key(), stop_key) < 0) { |
| ObjectStoreMetaDataKey meta_data_key; |
| { |
| StringPiece slice(it->Key()); |
| bool ok = ObjectStoreMetaDataKey::Decode(&slice, &meta_data_key) && |
| slice.empty(); |
| DCHECK(ok); |
| if (!ok || meta_data_key.MetaDataType() != ObjectStoreMetaDataKey::NAME) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| // Possible stale metadata, but don't fail the load. |
| s = it->Next(); |
| if (!s.ok()) |
| break; |
| continue; |
| } |
| } |
| |
| int64_t object_store_id = meta_data_key.ObjectStoreId(); |
| |
| // TODO(jsbell): Do this by direct key lookup rather than iteration, to |
| // simplify. |
| base::string16 object_store_name; |
| { |
| StringPiece slice(it->Value()); |
| if (!DecodeString(&slice, &object_store_name) || !slice.empty()) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| } |
| |
| s = it->Next(); |
| if (!s.ok()) |
| break; |
| if (!CheckObjectStoreAndMetaDataType(it.get(), stop_key, object_store_id, |
| ObjectStoreMetaDataKey::KEY_PATH)) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| break; |
| } |
| IndexedDBKeyPath key_path; |
| { |
| StringPiece slice(it->Value()); |
| if (!DecodeIDBKeyPath(&slice, &key_path) || !slice.empty()) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| } |
| |
| s = it->Next(); |
| if (!s.ok()) |
| break; |
| if (!CheckObjectStoreAndMetaDataType( |
| it.get(), stop_key, object_store_id, |
| ObjectStoreMetaDataKey::AUTO_INCREMENT)) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| break; |
| } |
| bool auto_increment; |
| { |
| StringPiece slice(it->Value()); |
| if (!DecodeBool(&slice, &auto_increment) || !slice.empty()) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| } |
| |
| s = it->Next(); // Is evictable. |
| if (!s.ok()) |
| break; |
| if (!CheckObjectStoreAndMetaDataType(it.get(), stop_key, object_store_id, |
| ObjectStoreMetaDataKey::EVICTABLE)) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| break; |
| } |
| |
| s = it->Next(); // Last version. |
| if (!s.ok()) |
| break; |
| if (!CheckObjectStoreAndMetaDataType( |
| it.get(), stop_key, object_store_id, |
| ObjectStoreMetaDataKey::LAST_VERSION)) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| break; |
| } |
| |
| s = it->Next(); // Maximum index id allocated. |
| if (!s.ok()) |
| break; |
| if (!CheckObjectStoreAndMetaDataType( |
| it.get(), stop_key, object_store_id, |
| ObjectStoreMetaDataKey::MAX_INDEX_ID)) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| break; |
| } |
| int64_t max_index_id; |
| { |
| StringPiece slice(it->Value()); |
| if (!DecodeInt(&slice, &max_index_id) || !slice.empty()) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| } |
| |
| s = it->Next(); // [optional] has key path (is not null) |
| if (!s.ok()) |
| break; |
| if (CheckObjectStoreAndMetaDataType(it.get(), stop_key, object_store_id, |
| ObjectStoreMetaDataKey::HAS_KEY_PATH)) { |
| bool has_key_path; |
| { |
| StringPiece slice(it->Value()); |
| if (!DecodeBool(&slice, &has_key_path)) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| } |
| // 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() == blink::kWebIDBKeyPathTypeString && |
| !key_path.string().empty())) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| break; |
| } |
| if (!has_key_path) |
| key_path = IndexedDBKeyPath(); |
| s = it->Next(); |
| if (!s.ok()) |
| break; |
| } |
| |
| int64_t key_generator_current_number = -1; |
| if (CheckObjectStoreAndMetaDataType( |
| it.get(), stop_key, object_store_id, |
| ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER)) { |
| StringPiece slice(it->Value()); |
| if (!DecodeInt(&slice, &key_generator_current_number) || !slice.empty()) |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); |
| |
| // TODO(jsbell): Return key_generator_current_number, cache in |
| // object store, and write lazily to backing store. For now, |
| // just assert that if it was written it was valid. |
| DCHECK_GE(key_generator_current_number, |
| ObjectStoreMetaDataKey::kKeyGeneratorInitialNumber); |
| s = it->Next(); |
| if (!s.ok()) |
| break; |
| } |
| |
| IndexedDBObjectStoreMetadata metadata(object_store_name, object_store_id, |
| key_path, auto_increment, |
| max_index_id); |
| s = ReadIndexes(db_or_transaction, database_id, object_store_id, |
| &metadata.indexes); |
| if (!s.ok()) |
| break; |
| (*object_stores)[object_store_id] = metadata; |
| } |
| |
| if (!s.ok()) |
| INTERNAL_READ_ERROR_UNTESTED(GET_OBJECT_STORES); |
| |
| return s; |
| } |
| |
| template <typename DatabaseOrTransaction> |
| Status ReadDatabaseNamesInternal(DatabaseOrTransaction* db_or_transaction, |
| const std::string& origin_identifier, |
| std::vector<base::string16>* names) { |
| const std::string start_key = |
| DatabaseNameKey::EncodeMinKeyForOrigin(origin_identifier); |
| const std::string stop_key = |
| DatabaseNameKey::EncodeStopKeyForOrigin(origin_identifier); |
| |
| DCHECK(names->empty()); |
| std::unique_ptr<LevelDBIterator> it = CreateIterator(db_or_transaction); |
| Status s; |
| for (s = it->Seek(start_key); |
| s.ok() && it->IsValid() && CompareKeys(it->Key(), stop_key) < 0; |
| s = it->Next()) { |
| // Decode database name (in iterator key). |
| StringPiece slice(it->Key()); |
| DatabaseNameKey database_name_key; |
| if (!DatabaseNameKey::Decode(&slice, &database_name_key) || |
| !slice.empty()) { |
| // TODO(dmurph): Change UMA name to ReadDatabaseNames. |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_DATABASE_NAMES); |
| continue; |
| } |
| |
| // Decode database id (in iterator value). |
| int64_t database_id = 0; |
| StringPiece value_slice(it->Value()); |
| if (!DecodeInt(&value_slice, &database_id) || !value_slice.empty()) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_DATABASE_NAMES); |
| continue; |
| } |
| |
| // Look up version by id. |
| bool found = false; |
| int64_t database_version = IndexedDBDatabaseMetadata::DEFAULT_VERSION; |
| s = GetVarInt(db_or_transaction, |
| DatabaseMetaDataKey::Encode( |
| database_id, DatabaseMetaDataKey::USER_VERSION), |
| &database_version, &found); |
| if (!s.ok() || !found) { |
| INTERNAL_READ_ERROR_UNTESTED(GET_DATABASE_NAMES); |
| continue; |
| } |
| |
| // Ignore stale metadata from failed initial opens. |
| if (database_version != IndexedDBDatabaseMetadata::DEFAULT_VERSION) |
| names->push_back(database_name_key.database_name()); |
| } |
| |
| if (!s.ok()) |
| INTERNAL_READ_ERROR(GET_DATABASE_NAMES); |
| |
| return s; |
| } |
| |
| // TODO(jsbell): This should do some error handling rather than |
| // plowing ahead when bad data is encountered. |
| template <typename DatabaseOrTransaction> |
| Status ReadMetadataForDatabaseNameInternal( |
| DatabaseOrTransaction* db_or_transaction, |
| const std::string& origin_identifier, |
| const base::string16& name, |
| IndexedDBDatabaseMetadata* metadata, |
| bool* found) { |
| IDB_TRACE("IndexedDBMetadataCoding::ReadMetadataForDatabaseName"); |
| const std::string key = DatabaseNameKey::Encode(origin_identifier, name); |
| *found = false; |
| |
| Status s = GetInt(db_or_transaction, key, &metadata->id, found); |
| if (!s.ok()) { |
| INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); |
| return s; |
| } |
| if (!*found) |
| return Status::OK(); |
| |
| s = GetVarInt(db_or_transaction, |
| DatabaseMetaDataKey::Encode(metadata->id, |
| DatabaseMetaDataKey::USER_VERSION), |
| &metadata->version, found); |
| if (!s.ok()) { |
| INTERNAL_READ_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); |
| return s; |
| } |
| if (!*found) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); |
| return InternalInconsistencyStatus(); |
| } |
| |
| if (metadata->version == IndexedDBDatabaseMetadata::DEFAULT_VERSION) |
| metadata->version = IndexedDBDatabaseMetadata::NO_VERSION; |
| |
| s = indexed_db::GetMaxObjectStoreId(db_or_transaction, metadata->id, |
| &metadata->max_object_store_id); |
| if (!s.ok()) |
| INTERNAL_READ_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); |
| |
| // We don't cache this, we just check it if it's there. |
| int64_t blob_key_generator_current_number = |
| DatabaseMetaDataKey::kInvalidBlobKey; |
| |
| s = GetVarInt( |
| db_or_transaction, |
| DatabaseMetaDataKey::Encode( |
| metadata->id, DatabaseMetaDataKey::BLOB_KEY_GENERATOR_CURRENT_NUMBER), |
| &blob_key_generator_current_number, found); |
| if (!s.ok()) { |
| INTERNAL_READ_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); |
| return s; |
| } |
| if (!*found) { |
| // This database predates blob support. |
| *found = true; |
| } else if (!DatabaseMetaDataKey::IsValidBlobKey( |
| blob_key_generator_current_number)) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); |
| return InternalInconsistencyStatus(); |
| } |
| |
| s = ReadObjectStores(db_or_transaction, metadata->id, |
| &metadata->object_stores); |
| |
| return s; |
| } |
| } // namespace |
| |
| IndexedDBMetadataCoding::IndexedDBMetadataCoding() = default; |
| IndexedDBMetadataCoding::~IndexedDBMetadataCoding() = default; |
| |
| Status IndexedDBMetadataCoding::ReadDatabaseNames( |
| LevelDBDatabase* db, |
| const std::string& origin_identifier, |
| std::vector<base::string16>* names) { |
| return ReadDatabaseNamesInternal(db, origin_identifier, names); |
| } |
| |
| Status IndexedDBMetadataCoding::ReadDatabaseNames( |
| LevelDBTransaction* transaction, |
| const std::string& origin_identifier, |
| std::vector<base::string16>* names) { |
| return ReadDatabaseNamesInternal(transaction, origin_identifier, names); |
| } |
| |
| Status IndexedDBMetadataCoding::ReadMetadataForDatabaseName( |
| LevelDBDatabase* db, |
| const std::string& origin_identifier, |
| const base::string16& name, |
| IndexedDBDatabaseMetadata* metadata, |
| bool* found) { |
| return ReadMetadataForDatabaseNameInternal(db, origin_identifier, name, |
| metadata, found); |
| } |
| |
| Status IndexedDBMetadataCoding::ReadMetadataForDatabaseName( |
| LevelDBTransaction* transaction, |
| const std::string& origin_identifier, |
| const base::string16& name, |
| IndexedDBDatabaseMetadata* metadata, |
| bool* found) { |
| return ReadMetadataForDatabaseNameInternal(transaction, origin_identifier, |
| name, metadata, found); |
| } |
| |
| Status IndexedDBMetadataCoding::CreateDatabase( |
| LevelDBDatabase* db, |
| const std::string& origin_identifier, |
| const base::string16& name, |
| int64_t version, |
| IndexedDBDatabaseMetadata* metadata) { |
| // TODO(jsbell): Don't persist metadata if open fails. http://crbug.com/395472 |
| scoped_refptr<LevelDBTransaction> transaction = |
| IndexedDBClassFactory::Get()->CreateLevelDBTransaction(db); |
| |
| int64_t row_id = 0; |
| Status s = indexed_db::GetNewDatabaseId(transaction.get(), &row_id); |
| if (!s.ok()) |
| return s; |
| DCHECK_GE(row_id, 0); |
| |
| if (version == IndexedDBDatabaseMetadata::NO_VERSION) |
| version = IndexedDBDatabaseMetadata::DEFAULT_VERSION; |
| |
| PutInt(transaction.get(), DatabaseNameKey::Encode(origin_identifier, name), |
| row_id); |
| PutVarInt( |
| transaction.get(), |
| DatabaseMetaDataKey::Encode(row_id, DatabaseMetaDataKey::USER_VERSION), |
| version); |
| PutVarInt(transaction.get(), |
| DatabaseMetaDataKey::Encode( |
| row_id, DatabaseMetaDataKey::BLOB_KEY_GENERATOR_CURRENT_NUMBER), |
| DatabaseMetaDataKey::kBlobKeyGeneratorInitialNumber); |
| |
| s = transaction->Commit(); |
| if (!s.ok()) { |
| INTERNAL_WRITE_ERROR_UNTESTED(CREATE_IDBDATABASE_METADATA); |
| return s; |
| } |
| |
| // Note: |version| is not stored on purpose. |
| metadata->name = name; |
| metadata->id = row_id; |
| |
| return s; |
| } |
| |
| void IndexedDBMetadataCoding::SetDatabaseVersion( |
| LevelDBTransaction* transaction, |
| int64_t row_id, |
| int64_t version, |
| IndexedDBDatabaseMetadata* c) { |
| if (version == IndexedDBDatabaseMetadata::NO_VERSION) |
| version = IndexedDBDatabaseMetadata::DEFAULT_VERSION; |
| DCHECK_GE(version, 0) << "version was " << version; |
| c->version = version; |
| PutVarInt( |
| transaction, |
| DatabaseMetaDataKey::Encode(row_id, DatabaseMetaDataKey::USER_VERSION), |
| version); |
| } |
| |
| Status IndexedDBMetadataCoding::FindDatabaseId( |
| LevelDBDatabase* db, |
| const std::string& origin_identifier, |
| const base::string16& name, |
| int64_t* id, |
| bool* found) { |
| const std::string key = DatabaseNameKey::Encode(origin_identifier, name); |
| |
| Status s = GetInt(db, key, id, found); |
| if (!s.ok()) |
| INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); |
| |
| return s; |
| } |
| |
| Status IndexedDBMetadataCoding::CreateObjectStore( |
| LevelDBTransaction* transaction, |
| int64_t database_id, |
| int64_t object_store_id, |
| base::string16 name, |
| IndexedDBKeyPath key_path, |
| bool auto_increment, |
| IndexedDBObjectStoreMetadata* metadata) { |
| if (!KeyPrefix::ValidIds(database_id, object_store_id)) |
| return InvalidDBKeyStatus(); |
| Status s = indexed_db::SetMaxObjectStoreId(transaction, database_id, |
| object_store_id); |
| if (!s.ok()) |
| return s; |
| |
| static const constexpr long long kInitialLastVersionNumber = 1; |
| const std::string name_key = ObjectStoreMetaDataKey::Encode( |
| database_id, object_store_id, ObjectStoreMetaDataKey::NAME); |
| const std::string key_path_key = ObjectStoreMetaDataKey::Encode( |
| database_id, object_store_id, ObjectStoreMetaDataKey::KEY_PATH); |
| const std::string auto_increment_key = ObjectStoreMetaDataKey::Encode( |
| database_id, object_store_id, ObjectStoreMetaDataKey::AUTO_INCREMENT); |
| const std::string evictable_key = ObjectStoreMetaDataKey::Encode( |
| database_id, object_store_id, ObjectStoreMetaDataKey::EVICTABLE); |
| const std::string last_version_key = ObjectStoreMetaDataKey::Encode( |
| database_id, object_store_id, ObjectStoreMetaDataKey::LAST_VERSION); |
| const std::string max_index_id_key = ObjectStoreMetaDataKey::Encode( |
| database_id, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID); |
| const std::string has_key_path_key = ObjectStoreMetaDataKey::Encode( |
| database_id, object_store_id, ObjectStoreMetaDataKey::HAS_KEY_PATH); |
| const std::string key_generator_current_number_key = |
| ObjectStoreMetaDataKey::Encode( |
| database_id, object_store_id, |
| ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER); |
| const std::string names_key = ObjectStoreNamesKey::Encode(database_id, name); |
| |
| PutString(transaction, name_key, name); |
| PutIDBKeyPath(transaction, key_path_key, key_path); |
| PutInt(transaction, auto_increment_key, auto_increment); |
| PutInt(transaction, evictable_key, false); |
| PutInt(transaction, last_version_key, kInitialLastVersionNumber); |
| PutInt(transaction, max_index_id_key, kMinimumIndexId); |
| PutBool(transaction, has_key_path_key, !key_path.IsNull()); |
| PutInt(transaction, key_generator_current_number_key, |
| ObjectStoreMetaDataKey::kKeyGeneratorInitialNumber); |
| PutInt(transaction, names_key, object_store_id); |
| |
| metadata->name = std::move(name); |
| metadata->id = object_store_id; |
| metadata->key_path = std::move(key_path); |
| metadata->auto_increment = auto_increment; |
| metadata->max_index_id = IndexedDBObjectStoreMetadata::kMinimumIndexId; |
| metadata->indexes.clear(); |
| return s; |
| } |
| |
| Status IndexedDBMetadataCoding::DeleteObjectStore( |
| LevelDBTransaction* transaction, |
| int64_t database_id, |
| const IndexedDBObjectStoreMetadata& object_store) { |
| if (!KeyPrefix::ValidIds(database_id, object_store.id)) |
| return InvalidDBKeyStatus(); |
| |
| base::string16 object_store_name; |
| bool found = false; |
| Status s = |
| GetString(transaction, |
| ObjectStoreMetaDataKey::Encode(database_id, object_store.id, |
| ObjectStoreMetaDataKey::NAME), |
| &object_store_name, &found); |
| if (!s.ok()) { |
| INTERNAL_READ_ERROR_UNTESTED(DELETE_OBJECT_STORE); |
| return s; |
| } |
| if (!found) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(DELETE_OBJECT_STORE); |
| return InternalInconsistencyStatus(); |
| } |
| |
| s = transaction->RemoveRange( |
| ObjectStoreMetaDataKey::Encode(database_id, object_store.id, 0), |
| ObjectStoreMetaDataKey::EncodeMaxKey(database_id, object_store.id), true); |
| |
| if (s.ok()) { |
| transaction->Remove( |
| ObjectStoreNamesKey::Encode(database_id, object_store_name)); |
| |
| s = transaction->RemoveRange( |
| IndexFreeListKey::Encode(database_id, object_store.id, 0), |
| IndexFreeListKey::EncodeMaxKey(database_id, object_store.id), true); |
| } |
| |
| if (s.ok()) { |
| s = transaction->RemoveRange( |
| IndexMetaDataKey::Encode(database_id, object_store.id, 0, 0), |
| IndexMetaDataKey::EncodeMaxKey(database_id, object_store.id), true); |
| } |
| |
| if (!s.ok()) |
| INTERNAL_WRITE_ERROR_UNTESTED(DELETE_OBJECT_STORE); |
| return s; |
| } |
| |
| Status IndexedDBMetadataCoding::RenameObjectStore( |
| LevelDBTransaction* transaction, |
| int64_t database_id, |
| base::string16 new_name, |
| base::string16* old_name, |
| IndexedDBObjectStoreMetadata* metadata) { |
| if (!KeyPrefix::ValidIds(database_id, metadata->id)) |
| return InvalidDBKeyStatus(); |
| |
| const std::string name_key = ObjectStoreMetaDataKey::Encode( |
| database_id, metadata->id, ObjectStoreMetaDataKey::NAME); |
| const std::string new_names_key = |
| ObjectStoreNamesKey::Encode(database_id, new_name); |
| |
| base::string16 old_name_check; |
| bool found = false; |
| Status s = GetString(transaction, name_key, &old_name_check, &found); |
| // TODO(dmurph): Change DELETE_OBJECT_STORE to RENAME_OBJECT_STORE & fix UMA. |
| if (!s.ok()) { |
| INTERNAL_READ_ERROR_UNTESTED(DELETE_OBJECT_STORE); |
| return s; |
| } |
| if (!found || old_name_check != metadata->name) { |
| INTERNAL_CONSISTENCY_ERROR_UNTESTED(DELETE_OBJECT_STORE); |
| return InternalInconsistencyStatus(); |
| } |
| const std::string old_names_key = |
| ObjectStoreNamesKey::Encode(database_id, metadata->name); |
| |
| PutString(transaction, name_key, new_name); |
| PutInt(transaction, new_names_key, metadata->id); |
| transaction->Remove(old_names_key); |
| *old_name = std::move(metadata->name); |
| metadata->name = std::move(new_name); |
| return s; |
| } |
| |
| Status IndexedDBMetadataCoding::CreateIndex(LevelDBTransaction* transaction, |
| int64_t database_id, |
| int64_t object_store_id, |
| int64_t index_id, |
| base::string16 name, |
| IndexedDBKeyPath key_path, |
| bool is_unique, |
| bool is_multi_entry, |
| IndexedDBIndexMetadata* metadata) { |
| if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id)) |
| return InvalidDBKeyStatus(); |
| Status s = indexed_db::SetMaxIndexId(transaction, database_id, |
| object_store_id, index_id); |
| |
| if (!s.ok()) |
| return s; |
| |
| const std::string name_key = IndexMetaDataKey::Encode( |
| database_id, object_store_id, index_id, IndexMetaDataKey::NAME); |
| const std::string unique_key = IndexMetaDataKey::Encode( |
| database_id, object_store_id, index_id, IndexMetaDataKey::UNIQUE); |
| const std::string key_path_key = IndexMetaDataKey::Encode( |
| database_id, object_store_id, index_id, IndexMetaDataKey::KEY_PATH); |
| const std::string multi_entry_key = IndexMetaDataKey::Encode( |
| database_id, object_store_id, index_id, IndexMetaDataKey::MULTI_ENTRY); |
| |
| PutString(transaction, name_key, name); |
| PutBool(transaction, unique_key, is_unique); |
| PutIDBKeyPath(transaction, key_path_key, key_path); |
| PutBool(transaction, multi_entry_key, is_multi_entry); |
| |
| metadata->name = std::move(name); |
| metadata->id = index_id; |
| metadata->key_path = std::move(key_path); |
| metadata->unique = is_unique; |
| metadata->multi_entry = is_multi_entry; |
| |
| return s; |
| } |
| |
| Status IndexedDBMetadataCoding::DeleteIndex( |
| LevelDBTransaction* transaction, |
| int64_t database_id, |
| int64_t object_store_id, |
| const IndexedDBIndexMetadata& metadata) { |
| if (!KeyPrefix::ValidIds(database_id, object_store_id, metadata.id)) |
| return InvalidDBKeyStatus(); |
| |
| const std::string index_meta_data_start = |
| IndexMetaDataKey::Encode(database_id, object_store_id, metadata.id, 0); |
| const std::string index_meta_data_end = |
| IndexMetaDataKey::EncodeMaxKey(database_id, object_store_id, metadata.id); |
| Status s = transaction->RemoveRange(index_meta_data_start, |
| index_meta_data_end, true); |
| return s; |
| } |
| |
| Status IndexedDBMetadataCoding::RenameIndex(LevelDBTransaction* transaction, |
| int64_t database_id, |
| int64_t object_store_id, |
| base::string16 new_name, |
| base::string16* old_name, |
| IndexedDBIndexMetadata* metadata) { |
| if (!KeyPrefix::ValidIds(database_id, object_store_id, metadata->id)) |
| return InvalidDBKeyStatus(); |
| |
| const std::string name_key = IndexMetaDataKey::Encode( |
| database_id, object_store_id, metadata->id, IndexMetaDataKey::NAME); |
| |
| // TODO(dmurph): Add consistancy checks & umas for old name. |
| |
| PutString(transaction, name_key, new_name); |
| *old_name = std::move(metadata->name); |
| metadata->name = std::move(new_name); |
| return Status::OK(); |
| } |
| |
| } // namespace content |