/* * Copyright (c) 2025, stelar7 * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include namespace Web::IndexedDB { static int compare_index_records(IndexRecord const& a, IndexRecord const& b) { auto key_comparison = Key::compare_two_keys(a.key, b.key); if (key_comparison != 0) return key_comparison; return Key::compare_two_keys(a.value, b.value); } GC_DEFINE_ALLOCATOR(Index); Index::~Index() = default; GC::Ref Index::create(JS::Realm& realm, GC::Ref store, String const& name, KeyPath const& key_path, bool unique, bool multi_entry) { return realm.create(store, name, key_path, unique, multi_entry); } Index::Index(GC::Ref 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) { return AK::binary_search(m_records, key, nullptr, [](auto const& needle, auto const& record) { return Key::compare_two_keys(needle, record.key); }) != nullptr; } // 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 index’s referenced object store which has a key equal to the index’s record’s value. auto* store_record = AK::binary_search(m_object_store->records(), index_record.value, nullptr, [](auto const& needle, auto const& record) { return Key::compare_two_keys(needle, record.key); }); VERIFY(store_record); return *store_record->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 Index::first_in_range(GC::Ref range) { 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 Index::first_n_in_range(GC::Ref range, Optional count) { GC::ConservativeVector records; 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; } return records; } GC::ConservativeVector Index::last_n_in_range(GC::Ref range, Optional count) { GC::ConservativeVector records; 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; } return records; } u64 Index::count_records_in_range(GC::Ref range) { 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) { if (auto log = m_object_store->mutation_log()) log->note_index_record_stored(*this, record); // NOTE: The record is stored in index’s list of records such that the list is sorted primarily on the records keys, and secondarily on the records values, in ascending order. if (m_records.is_empty() || compare_index_records(m_records.last(), record) <= 0) { m_records.append(record); return; } m_records.insert(AK::lower_bound_index(m_records, record, compare_index_records), record); } void Index::remove_record(IndexRecord const& record) { auto index = AK::lower_bound_index(m_records, record, compare_index_records); if (index < m_records.size() && compare_index_records(m_records[index], record) == 0) m_records.remove(index); } void Index::remove_records_with_value_in_range(GC::Ref range) { auto log = m_object_store->mutation_log(); Vector 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)); } }