| /* |
| * Copyright (C) 2015 Apple 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 INC. ``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 INC. OR |
| * 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 "MemoryObjectStore.h" |
| |
| #if ENABLE(INDEXED_DATABASE) |
| |
| #include "IDBBindingUtilities.h" |
| #include "IDBError.h" |
| #include "IDBGetAllResult.h" |
| #include "IDBKeyRangeData.h" |
| #include "IDBSerializationContext.h" |
| #include "IDBValue.h" |
| #include "IndexKey.h" |
| #include "Logging.h" |
| #include "MemoryBackingStoreTransaction.h" |
| #include "UniqueIDBDatabase.h" |
| #include <JavaScriptCore/JSCJSValue.h> |
| #include <JavaScriptCore/JSCJSValueInlines.h> |
| #include <JavaScriptCore/JSLock.h> |
| |
| namespace WebCore { |
| using namespace JSC; |
| namespace IDBServer { |
| |
| Ref<MemoryObjectStore> MemoryObjectStore::create(PAL::SessionID sessionID, const IDBObjectStoreInfo& info) |
| { |
| return adoptRef(*new MemoryObjectStore(sessionID, info)); |
| } |
| |
| MemoryObjectStore::MemoryObjectStore(PAL::SessionID sessionID, const IDBObjectStoreInfo& info) |
| : m_info(info) |
| , m_serializationContext(IDBSerializationContext::getOrCreateIDBSerializationContext(sessionID)) |
| { |
| } |
| |
| MemoryObjectStore::~MemoryObjectStore() |
| { |
| m_writeTransaction = nullptr; |
| } |
| |
| MemoryIndex* MemoryObjectStore::indexForIdentifier(uint64_t identifier) |
| { |
| ASSERT(identifier); |
| return m_indexesByIdentifier.get(identifier); |
| } |
| |
| void MemoryObjectStore::writeTransactionStarted(MemoryBackingStoreTransaction& transaction) |
| { |
| LOG(IndexedDB, "MemoryObjectStore::writeTransactionStarted"); |
| |
| ASSERT(!m_writeTransaction); |
| m_writeTransaction = &transaction; |
| } |
| |
| void MemoryObjectStore::writeTransactionFinished(MemoryBackingStoreTransaction& transaction) |
| { |
| LOG(IndexedDB, "MemoryObjectStore::writeTransactionFinished"); |
| |
| ASSERT_UNUSED(transaction, m_writeTransaction == &transaction); |
| m_writeTransaction = nullptr; |
| } |
| |
| IDBError MemoryObjectStore::createIndex(MemoryBackingStoreTransaction& transaction, const IDBIndexInfo& info) |
| { |
| LOG(IndexedDB, "MemoryObjectStore::createIndex"); |
| |
| if (!m_writeTransaction || !m_writeTransaction->isVersionChange() || m_writeTransaction != &transaction) |
| return IDBError(ConstraintError); |
| |
| ASSERT(!m_indexesByIdentifier.contains(info.identifier())); |
| auto index = MemoryIndex::create(info, *this); |
| |
| // If there was an error populating the new index, then the current records in the object store violate its contraints |
| auto error = populateIndexWithExistingRecords(index.get()); |
| if (!error.isNull()) |
| return error; |
| |
| m_info.addExistingIndex(info); |
| transaction.addNewIndex(index.get()); |
| registerIndex(WTFMove(index)); |
| |
| return IDBError { }; |
| } |
| |
| void MemoryObjectStore::maybeRestoreDeletedIndex(Ref<MemoryIndex>&& index) |
| { |
| LOG(IndexedDB, "MemoryObjectStore::maybeRestoreDeletedIndex"); |
| |
| if (m_info.hasIndex(index->info().name())) |
| return; |
| |
| m_info.addExistingIndex(index->info()); |
| |
| ASSERT(!m_indexesByIdentifier.contains(index->info().identifier())); |
| index->clearIndexValueStore(); |
| auto error = populateIndexWithExistingRecords(index.get()); |
| |
| // Since this index was installed in the object store before this transaction started, |
| // assuming things were in a valid state then, we should definitely be able to successfully |
| // repopulate the index with the object store's pre-transaction records. |
| ASSERT_UNUSED(error, error.isNull()); |
| |
| registerIndex(WTFMove(index)); |
| } |
| |
| RefPtr<MemoryIndex> MemoryObjectStore::takeIndexByIdentifier(uint64_t indexIdentifier) |
| { |
| auto indexByIdentifier = m_indexesByIdentifier.take(indexIdentifier); |
| if (!indexByIdentifier) |
| return nullptr; |
| |
| auto index = m_indexesByName.take(indexByIdentifier->info().name()); |
| ASSERT(index); |
| |
| return index; |
| } |
| |
| IDBError MemoryObjectStore::deleteIndex(MemoryBackingStoreTransaction& transaction, uint64_t indexIdentifier) |
| { |
| LOG(IndexedDB, "MemoryObjectStore::deleteIndex"); |
| |
| if (!m_writeTransaction || !m_writeTransaction->isVersionChange() || m_writeTransaction != &transaction) |
| return IDBError(ConstraintError); |
| |
| auto index = takeIndexByIdentifier(indexIdentifier); |
| ASSERT(index); |
| if (!index) |
| return IDBError(ConstraintError); |
| |
| m_info.deleteIndex(indexIdentifier); |
| transaction.indexDeleted(*index); |
| |
| return IDBError { }; |
| } |
| |
| void MemoryObjectStore::deleteAllIndexes(MemoryBackingStoreTransaction& transaction) |
| { |
| Vector<uint64_t> indexIdentifiers; |
| indexIdentifiers.reserveInitialCapacity(m_indexesByName.size()); |
| |
| for (auto& index : m_indexesByName.values()) |
| indexIdentifiers.uncheckedAppend(index->info().identifier()); |
| |
| for (auto identifier : indexIdentifiers) |
| deleteIndex(transaction, identifier); |
| } |
| |
| bool MemoryObjectStore::containsRecord(const IDBKeyData& key) |
| { |
| if (!m_keyValueStore) |
| return false; |
| |
| return m_keyValueStore->contains(key); |
| } |
| |
| void MemoryObjectStore::clear() |
| { |
| LOG(IndexedDB, "MemoryObjectStore::clear"); |
| ASSERT(m_writeTransaction); |
| |
| m_writeTransaction->objectStoreCleared(*this, WTFMove(m_keyValueStore), WTFMove(m_orderedKeys)); |
| for (auto& index : m_indexesByIdentifier.values()) |
| index->objectStoreCleared(); |
| |
| for (auto& cursor : m_cursors.values()) |
| cursor->objectStoreCleared(); |
| } |
| |
| void MemoryObjectStore::replaceKeyValueStore(std::unique_ptr<KeyValueMap>&& store, std::unique_ptr<IDBKeyDataSet>&& orderedKeys) |
| { |
| ASSERT(m_writeTransaction); |
| ASSERT(m_writeTransaction->isAborting()); |
| |
| m_keyValueStore = WTFMove(store); |
| m_orderedKeys = WTFMove(orderedKeys); |
| } |
| |
| void MemoryObjectStore::deleteRecord(const IDBKeyData& key) |
| { |
| LOG(IndexedDB, "MemoryObjectStore::deleteRecord"); |
| |
| ASSERT(m_writeTransaction); |
| |
| if (!m_keyValueStore) { |
| m_writeTransaction->recordValueChanged(*this, key, nullptr); |
| return; |
| } |
| |
| ASSERT(m_orderedKeys); |
| |
| auto iterator = m_keyValueStore->find(key); |
| if (iterator == m_keyValueStore->end()) { |
| m_writeTransaction->recordValueChanged(*this, key, nullptr); |
| return; |
| } |
| |
| m_writeTransaction->recordValueChanged(*this, key, &iterator->value); |
| m_keyValueStore->remove(iterator); |
| m_orderedKeys->erase(key); |
| |
| updateIndexesForDeleteRecord(key); |
| updateCursorsForDeleteRecord(key); |
| } |
| |
| void MemoryObjectStore::deleteRange(const IDBKeyRangeData& inputRange) |
| { |
| LOG(IndexedDB, "MemoryObjectStore::deleteRange"); |
| |
| ASSERT(m_writeTransaction); |
| |
| if (inputRange.isExactlyOneKey()) { |
| deleteRecord(inputRange.lowerKey); |
| return; |
| } |
| |
| IDBKeyRangeData range = inputRange; |
| while (true) { |
| auto key = lowestKeyWithRecordInRange(range); |
| if (key.isNull()) |
| break; |
| |
| deleteRecord(key); |
| |
| range.lowerKey = key; |
| range.lowerOpen = true; |
| } |
| } |
| |
| IDBError MemoryObjectStore::addRecord(MemoryBackingStoreTransaction& transaction, const IDBKeyData& keyData, const IDBValue& value) |
| { |
| LOG(IndexedDB, "MemoryObjectStore::addRecord"); |
| |
| ASSERT(m_writeTransaction); |
| ASSERT_UNUSED(transaction, m_writeTransaction == &transaction); |
| ASSERT(!m_keyValueStore || !m_keyValueStore->contains(keyData)); |
| ASSERT(!m_orderedKeys || m_orderedKeys->find(keyData) == m_orderedKeys->end()); |
| |
| if (!m_keyValueStore) { |
| ASSERT(!m_orderedKeys); |
| m_keyValueStore = makeUnique<KeyValueMap>(); |
| m_orderedKeys = makeUniqueWithoutFastMallocCheck<IDBKeyDataSet>(); |
| } |
| |
| auto mapResult = m_keyValueStore->set(keyData, value.data()); |
| ASSERT(mapResult.isNewEntry); |
| auto listResult = m_orderedKeys->insert(keyData); |
| ASSERT(listResult.second); |
| |
| // If there was an error indexing this addition, then revert it. |
| auto error = updateIndexesForPutRecord(keyData, value.data()); |
| if (!error.isNull()) { |
| m_keyValueStore->remove(mapResult.iterator); |
| m_orderedKeys->erase(listResult.first); |
| } else |
| updateCursorsForPutRecord(listResult.first); |
| |
| return error; |
| } |
| |
| void MemoryObjectStore::updateCursorsForPutRecord(IDBKeyDataSet::iterator iterator) |
| { |
| for (auto& cursor : m_cursors.values()) |
| cursor->keyAdded(iterator); |
| } |
| |
| void MemoryObjectStore::updateCursorsForDeleteRecord(const IDBKeyData& key) |
| { |
| for (auto& cursor : m_cursors.values()) |
| cursor->keyDeleted(key); |
| } |
| |
| void MemoryObjectStore::updateIndexesForDeleteRecord(const IDBKeyData& value) |
| { |
| for (auto& index : m_indexesByName.values()) |
| index->removeEntriesWithValueKey(value); |
| } |
| |
| IDBError MemoryObjectStore::updateIndexesForPutRecord(const IDBKeyData& key, const ThreadSafeDataBuffer& value) |
| { |
| JSLockHolder locker(m_serializationContext->vm()); |
| |
| auto jsValue = deserializeIDBValueToJSValue(m_serializationContext->execState(), value); |
| if (jsValue.isUndefinedOrNull()) |
| return IDBError { }; |
| |
| IDBError error; |
| Vector<std::pair<MemoryIndex*, IndexKey>> changedIndexRecords; |
| |
| for (auto& index : m_indexesByName.values()) { |
| IndexKey indexKey; |
| generateIndexKeyForValue(m_serializationContext->execState(), index->info(), jsValue, indexKey, m_info.keyPath(), key); |
| |
| if (indexKey.isNull()) |
| continue; |
| |
| error = index->putIndexKey(key, indexKey); |
| if (!error.isNull()) |
| break; |
| |
| changedIndexRecords.append(std::make_pair(index.get(), indexKey)); |
| } |
| |
| // If any of the index puts failed, revert all of the ones that went through. |
| if (!error.isNull()) { |
| for (auto& record : changedIndexRecords) |
| record.first->removeRecord(key, record.second); |
| } |
| |
| return error; |
| } |
| |
| IDBError MemoryObjectStore::populateIndexWithExistingRecords(MemoryIndex& index) |
| { |
| if (!m_keyValueStore) |
| return IDBError { }; |
| |
| JSLockHolder locker(m_serializationContext->vm()); |
| |
| for (const auto& iterator : *m_keyValueStore) { |
| auto jsValue = deserializeIDBValueToJSValue(m_serializationContext->execState(), iterator.value); |
| if (jsValue.isUndefinedOrNull()) |
| return IDBError { }; |
| |
| IndexKey indexKey; |
| generateIndexKeyForValue(m_serializationContext->execState(), index.info(), jsValue, indexKey, m_info.keyPath(), iterator.key); |
| |
| if (indexKey.isNull()) |
| continue; |
| |
| IDBError error = index.putIndexKey(iterator.key, indexKey); |
| if (!error.isNull()) |
| return error; |
| } |
| |
| return IDBError { }; |
| } |
| |
| uint64_t MemoryObjectStore::countForKeyRange(uint64_t indexIdentifier, const IDBKeyRangeData& inRange) const |
| { |
| LOG(IndexedDB, "MemoryObjectStore::countForKeyRange"); |
| |
| if (indexIdentifier) { |
| auto* index = m_indexesByIdentifier.get(indexIdentifier); |
| ASSERT(index); |
| return index->countForKeyRange(inRange); |
| } |
| |
| if (!m_keyValueStore) |
| return 0; |
| |
| uint64_t count = 0; |
| IDBKeyRangeData range = inRange; |
| while (true) { |
| auto key = lowestKeyWithRecordInRange(range); |
| if (key.isNull()) |
| break; |
| |
| ++count; |
| range.lowerKey = key; |
| range.lowerOpen = true; |
| } |
| |
| return count; |
| } |
| |
| ThreadSafeDataBuffer MemoryObjectStore::valueForKey(const IDBKeyData& key) const |
| { |
| if (!m_keyValueStore) |
| return { }; |
| |
| return m_keyValueStore->get(key); |
| } |
| |
| ThreadSafeDataBuffer MemoryObjectStore::valueForKeyRange(const IDBKeyRangeData& keyRangeData) const |
| { |
| LOG(IndexedDB, "MemoryObjectStore::valueForKey"); |
| |
| IDBKeyData key = lowestKeyWithRecordInRange(keyRangeData); |
| if (key.isNull()) |
| return ThreadSafeDataBuffer(); |
| |
| ASSERT(m_keyValueStore); |
| return m_keyValueStore->get(key); |
| } |
| |
| void MemoryObjectStore::getAllRecords(const IDBKeyRangeData& keyRangeData, Optional<uint32_t> count, IndexedDB::GetAllType type, IDBGetAllResult& result) const |
| { |
| result = { type, m_info.keyPath() }; |
| |
| uint32_t targetCount; |
| if (count && count.value()) |
| targetCount = count.value(); |
| else |
| targetCount = std::numeric_limits<uint32_t>::max(); |
| |
| IDBKeyRangeData range = keyRangeData; |
| uint32_t currentCount = 0; |
| while (currentCount < targetCount) { |
| IDBKeyData key = lowestKeyWithRecordInRange(range); |
| if (key.isNull()) |
| return; |
| |
| range.lowerKey = key; |
| range.lowerOpen = true; |
| if (type == IndexedDB::GetAllType::Values) |
| result.addValue(valueForKey(key)); |
| result.addKey(WTFMove(key)); |
| |
| ++currentCount; |
| } |
| } |
| |
| IDBGetResult MemoryObjectStore::indexValueForKeyRange(uint64_t indexIdentifier, IndexedDB::IndexRecordType recordType, const IDBKeyRangeData& range) const |
| { |
| LOG(IndexedDB, "MemoryObjectStore::indexValueForKeyRange"); |
| |
| auto* index = m_indexesByIdentifier.get(indexIdentifier); |
| ASSERT(index); |
| return index->getResultForKeyRange(recordType, range); |
| } |
| |
| IDBKeyData MemoryObjectStore::lowestKeyWithRecordInRange(const IDBKeyRangeData& keyRangeData) const |
| { |
| if (!m_keyValueStore) |
| return { }; |
| |
| if (keyRangeData.isExactlyOneKey() && m_keyValueStore->contains(keyRangeData.lowerKey)) |
| return keyRangeData.lowerKey; |
| |
| ASSERT(m_orderedKeys); |
| |
| auto lowestInRange = m_orderedKeys->lower_bound(keyRangeData.lowerKey); |
| |
| if (lowestInRange == m_orderedKeys->end()) |
| return { }; |
| |
| if (keyRangeData.lowerOpen && *lowestInRange == keyRangeData.lowerKey) |
| ++lowestInRange; |
| |
| if (lowestInRange == m_orderedKeys->end()) |
| return { }; |
| |
| if (!keyRangeData.upperKey.isNull()) { |
| if (lowestInRange->compare(keyRangeData.upperKey) > 0) |
| return { }; |
| if (keyRangeData.upperOpen && *lowestInRange == keyRangeData.upperKey) |
| return { }; |
| } |
| |
| return *lowestInRange; |
| } |
| |
| void MemoryObjectStore::registerIndex(Ref<MemoryIndex>&& index) |
| { |
| ASSERT(!m_indexesByIdentifier.contains(index->info().identifier())); |
| ASSERT(!m_indexesByName.contains(index->info().name())); |
| |
| auto identifier = index->info().identifier(); |
| m_indexesByName.set(index->info().name(), &index.get()); |
| m_indexesByIdentifier.set(identifier, WTFMove(index)); |
| } |
| |
| void MemoryObjectStore::unregisterIndex(MemoryIndex& index) |
| { |
| ASSERT(m_indexesByIdentifier.contains(index.info().identifier())); |
| ASSERT(m_indexesByName.contains(index.info().name())); |
| |
| m_indexesByName.remove(index.info().name()); |
| m_indexesByIdentifier.remove(index.info().identifier()); |
| } |
| |
| MemoryObjectStoreCursor* MemoryObjectStore::maybeOpenCursor(const IDBCursorInfo& info) |
| { |
| auto result = m_cursors.add(info.identifier(), nullptr); |
| if (!result.isNewEntry) |
| return nullptr; |
| |
| result.iterator->value = makeUnique<MemoryObjectStoreCursor>(*this, info); |
| return result.iterator->value.get(); |
| } |
| |
| void MemoryObjectStore::renameIndex(MemoryIndex& index, const String& newName) |
| { |
| ASSERT(m_indexesByName.get(index.info().name()) == &index); |
| ASSERT(!m_indexesByName.contains(newName)); |
| ASSERT(m_info.infoForExistingIndex(index.info().name())); |
| |
| m_info.infoForExistingIndex(index.info().name())->rename(newName); |
| m_indexesByName.set(newName, m_indexesByName.take(index.info().name())); |
| index.rename(newName); |
| } |
| |
| } // namespace IDBServer |
| } // namespace WebCore |
| |
| #endif // ENABLE(INDEXED_DATABASE) |