Files
ladybird/Libraries/LibWeb/IndexedDB/Internal/Index.cpp
Andreas Kling 6564eff91c LibWeb: Heap-allocate SerializationRecord in IndexedDB ObjectStoreRecord
Wrap the SerializationRecord (Vector<u8, 1024>) in an OwnPtr so that
each ObjectStoreRecord is only ~16 bytes instead of ~1040+ bytes.
This makes Vector operations on the records list dramatically cheaper
since memmove now shifts pointers instead of kilobyte-sized buffers.
2026-03-21 08:41:13 -05:00

171 lines
5.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (c) 2025, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/QuickSort.h>
#include <LibWeb/IndexedDB/Internal/Index.h>
#include <LibWeb/IndexedDB/Internal/MutationLog.h>
#include <LibWeb/IndexedDB/Internal/ObjectStore.h>
namespace Web::IndexedDB {
GC_DEFINE_ALLOCATOR(Index);
Index::~Index() = default;
GC::Ref<Index> Index::create(JS::Realm& realm, GC::Ref<ObjectStore> store, String const& name, KeyPath const& key_path, bool unique, bool multi_entry)
{
return realm.create<Index>(store, name, key_path, unique, multi_entry);
}
Index::Index(GC::Ref<ObjectStore> store, String const& name, KeyPath const& key_path, bool unique, bool multi_entry)
: m_object_store(store)
, m_name(name)
, m_unique(unique)
, m_multi_entry(multi_entry)
, m_key_path(key_path)
{
store->index_set().set(name, *this);
}
void Index::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_object_store);
for (auto& record : m_records) {
visitor.visit(record.key);
visitor.visit(record.value);
}
}
void Index::set_name(String name)
{
// NOTE: Update the key in the map so it still matches the name
auto old_value = m_object_store->index_set().take(m_name).release_value();
m_object_store->index_set().set(name, old_value);
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 Key::equals(record.key, key);
});
return index != m_records.end();
}
// https://w3c.github.io/IndexedDB/#index-referenced-value
HTML::SerializationRecord const& Index::referenced_value(IndexRecord const& index_record) const
{
// Records in an index are said to have a referenced value.
// This is the value of the record in the indexs referenced object store which has a key equal to the indexs records value.
return *m_object_store
->records()
.first_matching([&](auto const& store_record) {
return Key::equals(store_record.key, index_record.value);
})
.value()
.value;
}
void Index::clear_records()
{
auto deleted = move(m_records);
if (auto log = m_object_store->mutation_log(); log && !deleted.is_empty())
log->note_index_records_deleted(*this, move(deleted));
}
Optional<IndexRecord&> Index::first_in_range(GC::Ref<IDBKeyRange> range)
{
return m_records.first_matching([&](auto const& record) {
return range->is_in_range(record.key);
});
}
GC::ConservativeVector<IndexRecord> Index::first_n_in_range(GC::Ref<IDBKeyRange> range, Optional<WebIDL::UnsignedLong> count)
{
GC::ConservativeVector<IndexRecord> records(range->heap());
for (auto const& record : m_records) {
if (range->is_in_range(record.key))
records.append(record);
if (count.has_value() && records.size() >= *count)
break;
}
return records;
}
GC::ConservativeVector<IndexRecord> Index::last_n_in_range(GC::Ref<IDBKeyRange> range, Optional<WebIDL::UnsignedLong> count)
{
GC::ConservativeVector<IndexRecord> records(range->heap());
for (auto const& record : m_records.in_reverse()) {
if (range->is_in_range(record.key))
records.append(record);
if (count.has_value() && records.size() >= *count)
break;
}
return records;
}
u64 Index::count_records_in_range(GC::Ref<IDBKeyRange> range)
{
u64 count = 0;
for (auto const& record : m_records) {
if (range->is_in_range(record.key))
++count;
}
return count;
}
void Index::store_a_record(IndexRecord const& record)
{
if (auto log = m_object_store->mutation_log())
log->note_index_record_stored(*this, record);
m_records.append(record);
// NOTE: 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.
AK::quick_sort(m_records, [](auto const& a, auto const& b) {
auto key_comparison = Key::compare_two_keys(a.key, b.key);
if (key_comparison != 0)
return key_comparison < 0;
return Key::compare_two_keys(a.value, b.value) < 0;
});
}
void Index::remove_record(IndexRecord const& record)
{
m_records.remove_first_matching([&](auto const& existing) {
return Key::equals(existing.key, record.key) && Key::equals(existing.value, record.value);
});
}
void Index::remove_records_with_value_in_range(GC::Ref<IDBKeyRange> range)
{
auto log = m_object_store->mutation_log();
Vector<IndexRecord> removed_records;
for (size_t i = 0; i < m_records.size();) {
auto const& record = m_records[i];
if (range->is_in_range(record.value)) {
auto removed_record = m_records.take(i);
if (log)
removed_records.append(removed_record);
continue;
}
i++;
}
if (!removed_records.is_empty())
log->note_index_records_deleted(*this, move(removed_records));
}
}