LibWeb/IDB: Implement store_a_record_into_an_object_store

This commit is contained in:
stelar7 2025-04-11 11:38:29 +02:00
parent 432e2cf1e1
commit 80c36a90ee
6 changed files with 147 additions and 0 deletions

View file

@ -1312,4 +1312,117 @@ void delete_records_from_an_object_store(GC::Ref<ObjectStore> store, GC::Ref<IDB
// 3. Return undefined.
}
// https://w3c.github.io/IndexedDB/#store-a-record-into-an-object-store
WebIDL::ExceptionOr<GC::Ptr<Key>> store_a_record_into_an_object_store(JS::Realm& realm, GC::Ref<ObjectStore> store, JS::Value value, GC::Ptr<Key> key, bool no_overwrite)
{
// 1. If store uses a key generator, then:
if (store->key_generator().has_value()) {
// 1. If key is undefined, then:
if (key == nullptr) {
// 1. Let key be the result of generating a key for store.
auto maybe_key = generate_a_key(store);
// 2. If key is failure, then this operation failed with a "ConstraintError" DOMException. Abort this algorithm without taking any further steps.
if (maybe_key.is_error())
return WebIDL::ConstraintError::create(realm, String::from_utf8_without_validation(maybe_key.error().string_literal().bytes()));
key = Key::create_number(realm, static_cast<double>(maybe_key.value()));
// 3. If store also uses in-line keys, then run inject a key into a value using a key path with value, key and stores key path.
if (store->uses_inline_keys())
inject_a_key_into_a_value_using_a_key_path(realm, value, GC::Ref(*key), store->key_path().value());
}
// 2. Otherwise, run possibly update the key generator for store with key.
else {
possibly_update_the_key_generator(store, GC::Ref(*key));
}
}
// 2. If the no-overwrite flag was given to these steps and is true, and a record already exists in store with its key equal to key,
// then this operation failed with a "ConstraintError" DOMException. Abort this algorithm without taking any further steps.
auto has_record = store->has_record_with_key(*key);
if (no_overwrite && has_record)
return WebIDL::ConstraintError::create(realm, "Record already exists"_string);
// 3. If a record already exists in store with its key equal to key, then remove the record from store using delete records from an object store.
if (has_record) {
auto key_range = IDBKeyRange::create(realm, key, key, false, false);
delete_records_from_an_object_store(store, key_range);
}
// 4. Store a record in store containing key as its key and ! StructuredSerializeForStorage(value) as its value.
// The record is stored in the object stores list of records such that the list is sorted according to the key of the records in ascending order.
Record record = {
.key = *key,
.value = MUST(HTML::structured_serialize_for_storage(realm.vm(), value)),
};
store->store_a_record(record);
// 5. For each index which references store:
for (auto const& [name, index] : store->index_set()) {
// 1. Let index key be the result of extracting a key from a value using a key path with value, indexs key path, and indexs multiEntry flag.
auto index_key = TRY(extract_a_key_from_a_value_using_a_key_path(realm, value, index->key_path(), index->multi_entry()));
// 2. If index key is an exception, or invalid, or failure, take no further actions for index, and continue these steps for the next index.
if (index_key.is_error())
continue;
auto index_key_value = index_key.value();
auto index_multi_entry = index->multi_entry();
auto index_key_is_array = index_key_value->type() == Key::KeyType::Array;
auto index_is_unique = index->unique();
// 3. If indexs multiEntry flag is false, or if index key is not an array key,
// and if index already contains a record with key equal to index key,
// and indexs unique flag is true,
// then this operation failed with a "ConstraintError" DOMException.
// Abort this algorithm without taking any further steps.
if ((!index_multi_entry || !index_key_is_array) && index_is_unique && index->has_record_with_key(index_key_value))
return WebIDL::ConstraintError::create(realm, "Record already exists in index"_string);
// 4. If indexs multiEntry flag is true and index key is an array key,
// and if index already contains a record with key equal to any of the subkeys of index key,
// and indexs unique flag is true,
// then this operation failed with a "ConstraintError" DOMException.
// Abort this algorithm without taking any further steps.
if (index_multi_entry && index_key_is_array && index_is_unique) {
for (auto const& subkey : index_key_value->subkeys()) {
if (index->has_record_with_key(*subkey))
return WebIDL::ConstraintError::create(realm, "Record already exists in index"_string);
}
}
// 5. If indexs multiEntry flag is false, or if index key is not an array key
// then store a record in index containing index key as its key and key as its value.
// The record is stored in indexs list of records such that the list is sorted primarily on the records keys,
// and secondarily on the records values, in ascending order.
if (!index_multi_entry || !index_key_is_array) {
// FIXME:
// Record index_record = {
// .key = index_key_value,
// .value = MUST(HTML::structured_serialize_for_storage(realm.vm(), key)),
// };
// index->store_a_record(index_record);
}
// 6. If indexs multiEntry flag is true and index key is an array key,
// then for each subkey of the subkeys of index key store a record in index containing subkey as its key and key as its value.
if (index_multi_entry && index_key_is_array) {
for (auto const& subkey : index_key_value->subkeys()) {
(void)subkey;
// FIXME:
// Record index_record = {
// .key = *subkey,
// .value = MUST(HTML::structured_serialize_for_storage(realm.vm(), key)),
// };
// index->store_a_record(index_record);
}
}
}
// 6. Return key.
return key;
}
}

View file

@ -40,5 +40,6 @@ ErrorOr<u64> generate_a_key(GC::Ref<ObjectStore>);
void possibly_update_the_key_generator(GC::Ref<ObjectStore>, GC::Ref<Key>);
void inject_a_key_into_a_value_using_a_key_path(JS::Realm&, JS::Value, GC::Ref<Key>, KeyPath const&);
void delete_records_from_an_object_store(GC::Ref<ObjectStore>, GC::Ref<IDBKeyRange>);
WebIDL::ExceptionOr<GC::Ptr<Key>> store_a_record_into_an_object_store(JS::Realm&, GC::Ref<ObjectStore>, JS::Value, GC::Ptr<Key>, bool);
}

View file

@ -48,4 +48,13 @@ void Index::set_name(String name)
m_name = move(name);
}
bool Index::has_record_with_key(GC::Ref<Key> key)
{
auto index = m_records.find_if([&key](auto const& record) {
return record.key == key;
});
return index != m_records.end();
}
}

View file

@ -39,6 +39,8 @@ public:
[[nodiscard]] AK::ReadonlySpan<IndexRecord> records() const { return m_records; }
[[nodiscard]] KeyPath const& key_path() const { return m_key_path; }
[[nodiscard]] bool has_record_with_key(GC::Ref<Key> key);
protected:
virtual void visit_edges(Visitor&) override;

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/QuickSort.h>
#include <LibWeb/IndexedDB/IDBKeyRange.h>
#include <LibWeb/IndexedDB/Internal/ObjectStore.h>
@ -47,4 +48,23 @@ void ObjectStore::remove_records_in_range(GC::Ref<IDBKeyRange> range)
});
}
bool ObjectStore::has_record_with_key(GC::Ref<Key> key)
{
auto index = m_records.find_if([&key](auto const& record) {
return record.key == key;
});
return index != m_records.end();
}
void ObjectStore::store_a_record(Record const& record)
{
m_records.append(record);
// NOTE: The record is stored in the object stores list of records such that the list is sorted according to the key of the records in ascending order.
AK::quick_sort(m_records, [](auto const& a, auto const& b) {
return Key::compare_two_keys(a.key, b.key) < 0;
});
}
}

View file

@ -49,6 +49,8 @@ public:
GC::Ref<Database> database() const { return m_database; }
void remove_records_in_range(GC::Ref<IDBKeyRange> range);
bool has_record_with_key(GC::Ref<Key> key);
void store_a_record(Record const& record);
protected:
virtual void visit_edges(Visitor&) override;