LibWeb: Seek IndexedDB record ranges
Add a helper for finding key-range slices in sorted record vectors. Use it for object-store and index range operations.
This commit is contained in:
parent
41c3989433
commit
2fda5ffc9b
3 changed files with 109 additions and 77 deletions
|
|
@ -8,6 +8,7 @@
|
|||
#include <LibWeb/IndexedDB/Internal/Index.h>
|
||||
#include <LibWeb/IndexedDB/Internal/MutationLog.h>
|
||||
#include <LibWeb/IndexedDB/Internal/ObjectStore.h>
|
||||
#include <LibWeb/IndexedDB/Internal/RecordRange.h>
|
||||
|
||||
namespace Web::IndexedDB {
|
||||
|
||||
|
|
@ -87,17 +88,18 @@ void Index::clear_records()
|
|||
|
||||
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);
|
||||
});
|
||||
auto record_range = record_range_for_key_range(m_records, range);
|
||||
if (record_range.start == record_range.end)
|
||||
return {};
|
||||
return m_records[record_range.start];
|
||||
}
|
||||
|
||||
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);
|
||||
auto record_range = record_range_for_key_range(m_records, range);
|
||||
for (size_t i = record_range.start; i < record_range.end; ++i) {
|
||||
records.append(m_records[i]);
|
||||
|
||||
if (count.has_value() && records.size() >= *count)
|
||||
break;
|
||||
|
|
@ -109,9 +111,10 @@ GC::ConservativeVector<IndexRecord> Index::first_n_in_range(GC::Ref<IDBKeyRange>
|
|||
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);
|
||||
auto record_range = record_range_for_key_range(m_records, range);
|
||||
for (size_t i = record_range.end; i > record_range.start;) {
|
||||
--i;
|
||||
records.append(m_records[i]);
|
||||
|
||||
if (count.has_value() && records.size() >= *count)
|
||||
break;
|
||||
|
|
@ -122,12 +125,8 @@ GC::ConservativeVector<IndexRecord> Index::last_n_in_range(GC::Ref<IDBKeyRange>
|
|||
|
||||
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;
|
||||
auto record_range = record_range_for_key_range(m_records, range);
|
||||
return record_range.end - record_range.start;
|
||||
}
|
||||
|
||||
void Index::store_a_record(IndexRecord const& record)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <LibWeb/IndexedDB/IDBKeyRange.h>
|
||||
#include <LibWeb/IndexedDB/Internal/MutationLog.h>
|
||||
#include <LibWeb/IndexedDB/Internal/ObjectStore.h>
|
||||
#include <LibWeb/IndexedDB/Internal/RecordRange.h>
|
||||
|
||||
namespace Web::IndexedDB {
|
||||
|
||||
|
|
@ -63,49 +64,17 @@ void ObjectStore::remove_records_in_range(GC::Ref<IDBKeyRange> range)
|
|||
return;
|
||||
|
||||
// Since records are sorted by key, records in range form a contiguous block.
|
||||
// Binary search for the first record in range.
|
||||
size_t lo = 0;
|
||||
size_t hi = m_records.size();
|
||||
auto record_range = record_range_for_key_range(m_records, range);
|
||||
|
||||
auto lower = range->lower_key();
|
||||
if (lower) {
|
||||
// Find the first record with key >= lower (or > lower if lower_open).
|
||||
size_t l = 0, h = m_records.size();
|
||||
while (l < h) {
|
||||
size_t mid = l + (h - l) / 2;
|
||||
auto cmp = Key::compare_two_keys(m_records[mid].key, *lower);
|
||||
if (cmp < 0 || (cmp == 0 && range->lower_open()))
|
||||
l = mid + 1;
|
||||
else
|
||||
h = mid;
|
||||
}
|
||||
lo = l;
|
||||
}
|
||||
|
||||
auto upper = range->upper_key();
|
||||
if (upper) {
|
||||
// Find the first record with key > upper (or >= upper if upper_open).
|
||||
size_t l = lo, h = m_records.size();
|
||||
while (l < h) {
|
||||
size_t mid = l + (h - l) / 2;
|
||||
auto cmp = Key::compare_two_keys(m_records[mid].key, *upper);
|
||||
if (cmp < 0 || (cmp == 0 && !range->upper_open()))
|
||||
l = mid + 1;
|
||||
else
|
||||
h = mid;
|
||||
}
|
||||
hi = l;
|
||||
}
|
||||
|
||||
if (lo < hi) {
|
||||
if (record_range.start < record_range.end) {
|
||||
if (m_mutation_log) {
|
||||
Vector<ObjectStoreRecord> deleted;
|
||||
deleted.ensure_capacity(hi - lo);
|
||||
for (size_t i = lo; i < hi; ++i)
|
||||
deleted.ensure_capacity(record_range.end - record_range.start);
|
||||
for (size_t i = record_range.start; i < record_range.end; ++i)
|
||||
deleted.append(move(m_records[i]));
|
||||
m_mutation_log->note_records_deleted(move(deleted));
|
||||
}
|
||||
m_records.remove(lo, hi - lo);
|
||||
m_records.remove(record_range.start, record_range.end - record_range.start);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,34 +101,26 @@ void ObjectStore::store_a_record(ObjectStoreRecord record)
|
|||
m_mutation_log->note_record_stored(record.key);
|
||||
|
||||
// NOTE: The record is stored in the object store’s list of records such that the list is sorted according to the key of the records in ascending order.
|
||||
// We use binary search to find the correct insertion position.
|
||||
size_t lo = 0;
|
||||
size_t hi = m_records.size();
|
||||
while (lo < hi) {
|
||||
size_t mid = lo + (hi - lo) / 2;
|
||||
if (Key::compare_two_keys(m_records[mid].key, record.key) < 0)
|
||||
lo = mid + 1;
|
||||
else
|
||||
hi = mid;
|
||||
if (m_records.is_empty() || Key::compare_two_keys(m_records.last().key, record.key) <= 0) {
|
||||
m_records.append(move(record));
|
||||
return;
|
||||
}
|
||||
m_records.insert(lo, move(record));
|
||||
|
||||
m_records.insert(first_record_index_with_key_at_or_after(m_records, record.key, false), move(record));
|
||||
}
|
||||
|
||||
u64 ObjectStore::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;
|
||||
auto record_range = record_range_for_key_range(m_records, range);
|
||||
return record_range.end - record_range.start;
|
||||
}
|
||||
|
||||
Optional<ObjectStoreRecord&> ObjectStore::first_in_range(GC::Ref<IDBKeyRange> range)
|
||||
{
|
||||
return m_records.first_matching([&](auto const& record) {
|
||||
return range->is_in_range(record.key);
|
||||
});
|
||||
auto record_range = record_range_for_key_range(m_records, range);
|
||||
if (record_range.start == record_range.end)
|
||||
return {};
|
||||
return m_records[record_range.start];
|
||||
}
|
||||
|
||||
void ObjectStore::clear_records()
|
||||
|
|
@ -221,9 +182,9 @@ void ObjectStore::possibly_update_the_key_generator(GC::Ref<Key> key)
|
|||
GC::ConservativeVector<ObjectStoreRecord> ObjectStore::first_n_in_range(GC::Ref<IDBKeyRange> range, Optional<WebIDL::UnsignedLong> count)
|
||||
{
|
||||
GC::ConservativeVector<ObjectStoreRecord> records(range->heap());
|
||||
for (auto const& record : m_records) {
|
||||
if (range->is_in_range(record.key))
|
||||
records.append(record);
|
||||
auto record_range = record_range_for_key_range(m_records, range);
|
||||
for (size_t i = record_range.start; i < record_range.end; ++i) {
|
||||
records.append(m_records[i]);
|
||||
|
||||
if (count.has_value() && records.size() >= *count)
|
||||
break;
|
||||
|
|
@ -235,9 +196,10 @@ GC::ConservativeVector<ObjectStoreRecord> ObjectStore::first_n_in_range(GC::Ref<
|
|||
GC::ConservativeVector<ObjectStoreRecord> ObjectStore::last_n_in_range(GC::Ref<IDBKeyRange> range, Optional<WebIDL::UnsignedLong> count)
|
||||
{
|
||||
GC::ConservativeVector<ObjectStoreRecord> records(range->heap());
|
||||
for (auto const& record : m_records.in_reverse()) {
|
||||
if (range->is_in_range(record.key))
|
||||
records.append(record);
|
||||
auto record_range = record_range_for_key_range(m_records, range);
|
||||
for (size_t i = record_range.end; i > record_range.start;) {
|
||||
--i;
|
||||
records.append(m_records[i]);
|
||||
|
||||
if (count.has_value() && records.size() >= *count)
|
||||
break;
|
||||
|
|
|
|||
71
Libraries/LibWeb/IndexedDB/Internal/RecordRange.h
Normal file
71
Libraries/LibWeb/IndexedDB/Internal/RecordRange.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <LibGC/Ptr.h>
|
||||
#include <LibWeb/IndexedDB/IDBKeyRange.h>
|
||||
|
||||
namespace Web::IndexedDB {
|
||||
|
||||
struct RecordRange {
|
||||
size_t start { 0 };
|
||||
size_t end { 0 };
|
||||
};
|
||||
|
||||
template<typename Records>
|
||||
static size_t first_record_index_with_key_at_or_after(Records const& records, GC::Ref<Key> key, bool open)
|
||||
{
|
||||
size_t low = 0;
|
||||
size_t high = records.size();
|
||||
|
||||
while (low < high) {
|
||||
size_t middle = low + (high - low) / 2;
|
||||
auto comparison = Key::compare_two_keys(records[middle].key, key);
|
||||
if (comparison < 0 || (comparison == 0 && open))
|
||||
low = middle + 1;
|
||||
else
|
||||
high = middle;
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
|
||||
template<typename Records>
|
||||
static size_t first_record_index_with_key_after(Records const& records, GC::Ref<Key> key, bool open, size_t start = 0)
|
||||
{
|
||||
size_t low = start;
|
||||
size_t high = records.size();
|
||||
|
||||
while (low < high) {
|
||||
size_t middle = low + (high - low) / 2;
|
||||
auto comparison = Key::compare_two_keys(records[middle].key, key);
|
||||
if (comparison < 0 || (comparison == 0 && !open))
|
||||
low = middle + 1;
|
||||
else
|
||||
high = middle;
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
|
||||
template<typename Records>
|
||||
static RecordRange record_range_for_key_range(Records const& records, GC::Ref<IDBKeyRange> range)
|
||||
{
|
||||
size_t start = 0;
|
||||
size_t end = records.size();
|
||||
|
||||
if (auto lower = range->lower_key())
|
||||
start = first_record_index_with_key_at_or_after(records, *lower, range->lower_open());
|
||||
|
||||
if (auto upper = range->upper_key())
|
||||
end = first_record_index_with_key_after(records, *upper, range->upper_open(), start);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue