use std::collections::{BTreeMap, BTreeSet, btree_map};
use zerocopy::byteorder::{LE, U64};
use crate::{
DbError, ElementId, IncidenceId, LabelId, PropertyKeyId, PropertySubject, RelationId,
RelationTypeId,
state::{ElementRecord, IncidenceRecord, RelationRecord},
value::{PropertyType, PropertyValue},
wire,
};
type EqualityKey = (PropertyKeyId, PropertyValue);
enum Posting<'a, T> {
Owned(&'a BTreeSet<T>),
Borrowed(&'a [U64<LE>]),
}
impl<T: Copy + Ord + FromU64> Posting<'_, T> {
fn iter(&self) -> impl Iterator<Item = T> + '_ {
let owned = match self {
Self::Owned(set) => Some(set.iter().copied()),
Self::Borrowed(_run) => None,
};
let borrowed = match self {
Self::Owned(_set) => None,
Self::Borrowed(run) => Some(run.iter().map(|word| T::from_u64(word.get()))),
};
owned
.into_iter()
.flatten()
.chain(borrowed.into_iter().flatten())
}
}
trait FromU64 {
fn from_u64(value: u64) -> Self;
}
impl FromU64 for ElementId {
fn from_u64(value: u64) -> Self {
Self::new(value)
}
}
impl FromU64 for RelationId {
fn from_u64(value: u64) -> Self {
Self::new(value)
}
}
impl FromU64 for IncidenceId {
fn from_u64(value: u64) -> Self {
Self::new(value)
}
}
impl FromU64 for LabelId {
fn from_u64(value: u64) -> Self {
Self::new(value)
}
}
impl FromU64 for RelationTypeId {
fn from_u64(value: u64) -> Self {
Self::new(value)
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum BaseIndex<'a> {
Owned(&'a OwnedBaseIndex),
Borrowed(BorrowedBaseIndex<'a>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct OwnedBaseIndex {
label_members: BTreeMap<LabelId, BTreeSet<ElementId>>,
relation_type_members: BTreeMap<RelationTypeId, BTreeSet<RelationId>>,
property_equality: BTreeMap<EqualityKey, BTreeSet<PropertySubject>>,
element_incidences: BTreeMap<ElementId, BTreeSet<IncidenceId>>,
relation_incidences: BTreeMap<RelationId, BTreeSet<IncidenceId>>,
}
#[derive(Clone, Copy, Debug, yoke::Yokeable)]
#[yoke(prove_covariant)]
pub(crate) struct BorrowedBaseIndex<'a> {
label_dir: &'a [wire::PostingDirEntry],
label_pool: &'a [U64<LE>],
relation_type_dir: &'a [wire::PostingDirEntry],
relation_type_pool: &'a [U64<LE>],
equality_dir: &'a [wire::EqualityDirEntry],
equality_pool: &'a [U64<LE>],
equality_text: &'a [u8],
element_incidence_dir: &'a [wire::PostingDirEntry],
element_incidence_pool: &'a [U64<LE>],
relation_incidence_dir: &'a [wire::PostingDirEntry],
relation_incidence_pool: &'a [U64<LE>],
}
impl OwnedBaseIndex {
pub(crate) const fn empty() -> Self {
Self {
label_members: BTreeMap::new(),
relation_type_members: BTreeMap::new(),
property_equality: BTreeMap::new(),
element_incidences: BTreeMap::new(),
relation_incidences: BTreeMap::new(),
}
}
pub(crate) fn from_records(
elements: &BTreeMap<ElementId, ElementRecord>,
relations: &BTreeMap<RelationId, RelationRecord>,
incidences: &BTreeMap<IncidenceId, IncidenceRecord>,
properties: &BTreeMap<PropertySubject, BTreeMap<PropertyKeyId, PropertyValue>>,
) -> Self {
let mut label_members: BTreeMap<LabelId, BTreeSet<ElementId>> = BTreeMap::new();
for record in elements.values() {
for label in &record.labels {
label_members.entry(*label).or_default().insert(record.id);
}
}
let mut relation_type_members: BTreeMap<RelationTypeId, BTreeSet<RelationId>> =
BTreeMap::new();
for record in relations.values() {
if let Some(relation_type) = record.relation_type {
relation_type_members
.entry(relation_type)
.or_default()
.insert(record.id);
}
}
let mut property_equality: BTreeMap<EqualityKey, BTreeSet<PropertySubject>> =
BTreeMap::new();
for (subject, keys) in properties {
for (key, value) in keys {
property_equality
.entry((*key, value.clone()))
.or_default()
.insert(*subject);
}
}
let mut element_incidences: BTreeMap<ElementId, BTreeSet<IncidenceId>> = BTreeMap::new();
let mut relation_incidences: BTreeMap<RelationId, BTreeSet<IncidenceId>> = BTreeMap::new();
for record in incidences.values() {
element_incidences
.entry(record.element)
.or_default()
.insert(record.id);
relation_incidences
.entry(record.relation)
.or_default()
.insert(record.id);
}
Self {
label_members,
relation_type_members,
property_equality,
element_incidences,
relation_incidences,
}
}
#[expect(
clippy::type_complexity,
reason = "the five posting maps are returned together so freeze serializes them in one pass"
)]
pub(crate) const fn maps(
&self,
) -> (
&BTreeMap<LabelId, BTreeSet<ElementId>>,
&BTreeMap<RelationTypeId, BTreeSet<RelationId>>,
&BTreeMap<EqualityKey, BTreeSet<PropertySubject>>,
&BTreeMap<ElementId, BTreeSet<IncidenceId>>,
&BTreeMap<RelationId, BTreeSet<IncidenceId>>,
) {
(
&self.label_members,
&self.relation_type_members,
&self.property_equality,
&self.element_incidences,
&self.relation_incidences,
)
}
}
fn equality_entry_key(entry: &wire::EqualityDirEntry, text: &[u8]) -> Result<EqualityKey, DbError> {
let key = PropertyKeyId::new(entry.key_id.get());
let value_type = wire::property_type_from_tag(entry.value_tag.get())
.ok_or_else(|| DbError::invalid_store("equality index value tag out of range"))?;
let value = match value_type {
PropertyType::Boolean => PropertyValue::Boolean(entry.value_scalar.get() != 0),
PropertyType::Integer => PropertyValue::Integer(entry.value_scalar.get().cast_signed()),
PropertyType::Text => {
let start = usize::try_from(entry.text_off.get()).map_err(|_overflow| {
DbError::invalid_store("equality index text offset overflow")
})?;
let len = usize::try_from(entry.text_len.get()).map_err(|_overflow| {
DbError::invalid_store("equality index text length overflow")
})?;
let end = start
.checked_add(len)
.ok_or_else(|| DbError::invalid_store("equality index text slice overflow"))?;
let bytes = text
.get(start..end)
.ok_or_else(|| DbError::invalid_store("equality index text out of bounds"))?;
let string = core::str::from_utf8(bytes)
.map_err(|_error| DbError::invalid_store("equality index text is not UTF-8"))?;
PropertyValue::Text(string.to_owned())
}
};
Ok((key, value))
}
impl<'a> BorrowedBaseIndex<'a> {
#[expect(
clippy::too_many_arguments,
reason = "each of the five posting sections contributes its directory and pool slices"
)]
pub(crate) fn from_sections(
label_dir: &'a [wire::PostingDirEntry],
label_pool: &'a [U64<LE>],
relation_type_dir: &'a [wire::PostingDirEntry],
relation_type_pool: &'a [U64<LE>],
equality_dir: &'a [wire::EqualityDirEntry],
equality_pool: &'a [U64<LE>],
equality_text: &'a [u8],
element_incidence_dir: &'a [wire::PostingDirEntry],
element_incidence_pool: &'a [U64<LE>],
relation_incidence_dir: &'a [wire::PostingDirEntry],
relation_incidence_pool: &'a [U64<LE>],
) -> Result<Self, DbError> {
validate_simple_dir(label_dir, label_pool, "label")?;
validate_simple_dir(relation_type_dir, relation_type_pool, "relation-type")?;
validate_simple_dir(
element_incidence_dir,
element_incidence_pool,
"element-incidence",
)?;
validate_simple_dir(
relation_incidence_dir,
relation_incidence_pool,
"relation-incidence",
)?;
validate_equality_dir(equality_dir, equality_pool, equality_text)?;
Ok(Self {
label_dir,
label_pool,
relation_type_dir,
relation_type_pool,
equality_dir,
equality_pool,
equality_text,
element_incidence_dir,
element_incidence_pool,
relation_incidence_dir,
relation_incidence_pool,
})
}
}
fn validate_simple_dir(
dir: &[wire::PostingDirEntry],
pool: &[U64<LE>],
label: &'static str,
) -> Result<(), DbError> {
for entry in dir {
slice_pool(pool, entry.members_off.get(), entry.members_len.get())
.ok_or_else(|| DbError::invalid_store(format!("{label} posting run out of bounds")))?;
}
Ok(())
}
fn validate_equality_dir(
dir: &[wire::EqualityDirEntry],
pool: &[U64<LE>],
text: &[u8],
) -> Result<(), DbError> {
for entry in dir {
let _key = equality_entry_key(entry, text)?;
let run = slice_pool(pool, entry.members_off.get(), entry.members_len.get())
.ok_or_else(|| DbError::invalid_store("equality posting run out of bounds"))?;
if run.len() % 2 != 0 {
return Err(DbError::invalid_store(
"equality posting subject run is not a whole number of (kind, id) pairs",
));
}
for pair in run.chunks_exact(2) {
let kind = u32::try_from(pair[0].get())
.map_err(|_overflow| DbError::invalid_store("equality subject kind overflow"))?;
wire::decode_subject(kind, pair[1].get())
.ok_or_else(|| DbError::invalid_store("equality subject kind out of range"))?;
}
}
Ok(())
}
fn slice_pool(pool: &[U64<LE>], offset: u64, len: u64) -> Option<&[U64<LE>]> {
let start = usize::try_from(offset).ok()?;
let length = usize::try_from(len).ok()?;
let end = start.checked_add(length)?;
pool.get(start..end)
}
fn simple_lookup<'a>(
dir: &[wire::PostingDirEntry],
pool: &'a [U64<LE>],
key: u64,
) -> Option<&'a [U64<LE>]> {
let index = dir
.binary_search_by(|entry| entry.key.get().cmp(&key))
.ok()?;
let entry = dir.get(index)?;
slice_pool(pool, entry.members_off.get(), entry.members_len.get())
}
impl BaseIndex<'_> {
fn label_posting(&self, label: LabelId) -> Option<Posting<'_, ElementId>> {
match self {
Self::Owned(owned) => owned.label_members.get(&label).map(Posting::Owned),
Self::Borrowed(borrowed) => {
simple_lookup(borrowed.label_dir, borrowed.label_pool, label.get())
.map(Posting::Borrowed)
}
}
}
fn relation_type_posting(
&self,
relation_type: RelationTypeId,
) -> Option<Posting<'_, RelationId>> {
match self {
Self::Owned(owned) => owned
.relation_type_members
.get(&relation_type)
.map(Posting::Owned),
Self::Borrowed(borrowed) => simple_lookup(
borrowed.relation_type_dir,
borrowed.relation_type_pool,
relation_type.get(),
)
.map(Posting::Borrowed),
}
}
fn element_incidence_posting(&self, element: ElementId) -> Option<Posting<'_, IncidenceId>> {
match self {
Self::Owned(owned) => owned.element_incidences.get(&element).map(Posting::Owned),
Self::Borrowed(borrowed) => simple_lookup(
borrowed.element_incidence_dir,
borrowed.element_incidence_pool,
element.get(),
)
.map(Posting::Borrowed),
}
}
fn relation_incidence_posting(&self, relation: RelationId) -> Option<Posting<'_, IncidenceId>> {
match self {
Self::Owned(owned) => owned.relation_incidences.get(&relation).map(Posting::Owned),
Self::Borrowed(borrowed) => simple_lookup(
borrowed.relation_incidence_dir,
borrowed.relation_incidence_pool,
relation.get(),
)
.map(Posting::Borrowed),
}
}
fn equality_subjects(
&self,
key: PropertyKeyId,
value: &PropertyValue,
) -> Option<Vec<PropertySubject>> {
match self {
Self::Owned(owned) => owned
.property_equality
.get(&(key, value.clone()))
.map(|set| set.iter().copied().collect()),
Self::Borrowed(borrowed) => {
let probe = (key, value.clone());
let index = borrowed
.equality_dir
.binary_search_by(|entry| {
equality_entry_key(entry, borrowed.equality_text)
.map_or(std::cmp::Ordering::Less, |candidate| candidate.cmp(&probe))
})
.ok()?;
let entry = borrowed.equality_dir.get(index)?;
Some(borrowed.decode_subjects(entry))
}
}
}
fn range_postings(
&self,
key: PropertyKeyId,
min: &PropertyValue,
max: &PropertyValue,
) -> Vec<(EqualityKey, Vec<PropertySubject>)> {
if min > max {
return Vec::new();
}
match self {
Self::Owned(owned) => owned
.property_equality
.range((key, min.clone())..=(key, max.clone()))
.map(|(pair, subjects)| (pair.clone(), subjects.iter().copied().collect()))
.collect(),
Self::Borrowed(borrowed) => borrowed.range_postings(key, min, max),
}
}
fn key_postings(&self, key: PropertyKeyId) -> Vec<(PropertyValue, Vec<PropertySubject>)> {
let start = (key, PropertyValue::Boolean(false));
match self {
Self::Owned(owned) => owned
.property_equality
.range(start..)
.take_while(|((entry_key, _value), _subjects)| *entry_key == key)
.map(|((_key, value), subjects)| {
(value.clone(), subjects.iter().copied().collect())
})
.collect(),
Self::Borrowed(borrowed) => borrowed.key_postings(key),
}
}
}
impl BorrowedBaseIndex<'_> {
fn decode_subjects(&self, entry: &wire::EqualityDirEntry) -> Vec<PropertySubject> {
let run = slice_pool(
self.equality_pool,
entry.members_off.get(),
entry.members_len.get(),
)
.unwrap_or(&[]);
run.chunks_exact(2)
.filter_map(|pair| {
let kind = u32::try_from(pair[0].get()).ok()?;
wire::decode_subject(kind, pair[1].get())
})
.collect()
}
fn key_lower_bound(&self, key: PropertyKeyId) -> usize {
self.equality_dir
.partition_point(|entry| entry.key_id.get() < key.get())
}
fn range_postings(
&self,
key: PropertyKeyId,
min: &PropertyValue,
max: &PropertyValue,
) -> Vec<(EqualityKey, Vec<PropertySubject>)> {
let mut out = Vec::new();
for entry in self
.equality_dir
.get(self.key_lower_bound(key)..)
.unwrap_or(&[])
{
if entry.key_id.get() != key.get() {
break;
}
let Ok((entry_key, value)) = equality_entry_key(entry, self.equality_text) else {
continue;
};
if value < *min {
continue;
}
if value > *max {
break;
}
out.push(((entry_key, value), self.decode_subjects(entry)));
}
out
}
fn key_postings(&self, key: PropertyKeyId) -> Vec<(PropertyValue, Vec<PropertySubject>)> {
let mut out = Vec::new();
for entry in self
.equality_dir
.get(self.key_lower_bound(key)..)
.unwrap_or(&[])
{
if entry.key_id.get() != key.get() {
break;
}
let Ok((_key, value)) = equality_entry_key(entry, self.equality_text) else {
continue;
};
out.push((value, self.decode_subjects(entry)));
}
out
}
}
#[cfg(any(debug_assertions, test))]
impl BaseIndex<'_> {
pub(crate) fn to_owned_snapshot(self) -> OwnedBaseIndex {
match self {
Self::Owned(owned) => owned.clone(),
Self::Borrowed(borrowed) => borrowed.to_owned_snapshot(),
}
}
}
#[cfg(any(debug_assertions, test))]
impl BorrowedBaseIndex<'_> {
fn to_owned_snapshot(self) -> OwnedBaseIndex {
let label_members = materialize_simple(self.label_dir, self.label_pool);
let relation_type_members =
materialize_simple(self.relation_type_dir, self.relation_type_pool);
let element_incidences =
materialize_simple(self.element_incidence_dir, self.element_incidence_pool);
let relation_incidences =
materialize_simple(self.relation_incidence_dir, self.relation_incidence_pool);
let mut property_equality: BTreeMap<EqualityKey, BTreeSet<PropertySubject>> =
BTreeMap::new();
for entry in self.equality_dir {
if let Ok(key) = equality_entry_key(entry, self.equality_text) {
property_equality.insert(key, self.decode_subjects(entry).into_iter().collect());
}
}
OwnedBaseIndex {
label_members,
relation_type_members,
property_equality,
element_incidences,
relation_incidences,
}
}
}
#[cfg(any(debug_assertions, test))]
fn materialize_simple<K, M>(
dir: &[wire::PostingDirEntry],
pool: &[U64<LE>],
) -> BTreeMap<K, BTreeSet<M>>
where
K: Ord + FromU64,
M: Ord + Copy + FromU64,
{
let mut map: BTreeMap<K, BTreeSet<M>> = BTreeMap::new();
for entry in dir {
let run = slice_pool(pool, entry.members_off.get(), entry.members_len.get()).unwrap_or(&[]);
let members = run.iter().map(|word| M::from_u64(word.get())).collect();
map.insert(K::from_u64(entry.key.get()), members);
}
map
}
#[cfg(any(debug_assertions, test))]
pub(crate) fn indexes_agree(left: BaseIndex<'_>, right: BaseIndex<'_>) -> bool {
left.to_owned_snapshot() == right.to_owned_snapshot()
}
#[derive(Clone, Debug, Default)]
pub(crate) struct OverlayIndex {
label_added: BTreeMap<LabelId, BTreeSet<ElementId>>,
label_removed: BTreeMap<LabelId, BTreeSet<ElementId>>,
relation_type_added: BTreeMap<RelationTypeId, BTreeSet<RelationId>>,
relation_type_removed: BTreeMap<RelationTypeId, BTreeSet<RelationId>>,
equality_added: BTreeMap<EqualityKey, BTreeSet<PropertySubject>>,
equality_removed: BTreeMap<EqualityKey, BTreeSet<PropertySubject>>,
element_incidence_added: BTreeMap<ElementId, BTreeSet<IncidenceId>>,
element_incidence_removed: BTreeMap<ElementId, BTreeSet<IncidenceId>>,
relation_incidence_added: BTreeMap<RelationId, BTreeSet<IncidenceId>>,
relation_incidence_removed: BTreeMap<RelationId, BTreeSet<IncidenceId>>,
}
impl OverlayIndex {
pub(crate) const fn new() -> Self {
Self {
label_added: BTreeMap::new(),
label_removed: BTreeMap::new(),
relation_type_added: BTreeMap::new(),
relation_type_removed: BTreeMap::new(),
equality_added: BTreeMap::new(),
equality_removed: BTreeMap::new(),
element_incidence_added: BTreeMap::new(),
element_incidence_removed: BTreeMap::new(),
relation_incidence_added: BTreeMap::new(),
relation_incidence_removed: BTreeMap::new(),
}
}
fn add_equality(&mut self, subject: PropertySubject, key: PropertyKeyId, value: PropertyValue) {
remove_posting(&mut self.equality_removed, (key, value.clone()), &subject);
self.equality_added
.entry((key, value))
.or_default()
.insert(subject);
}
fn remove_equality(
&mut self,
subject: PropertySubject,
key: PropertyKeyId,
value: PropertyValue,
) {
remove_posting(&mut self.equality_added, (key, value.clone()), &subject);
self.equality_removed
.entry((key, value))
.or_default()
.insert(subject);
}
pub(crate) fn on_set_property(
&mut self,
subject: PropertySubject,
key: PropertyKeyId,
previous: Option<&PropertyValue>,
new_value: &PropertyValue,
) {
if let Some(previous) = previous
&& previous != new_value
{
self.remove_equality(subject, key, previous.clone());
}
self.add_equality(subject, key, new_value.clone());
}
pub(crate) fn on_remove_property(
&mut self,
subject: PropertySubject,
key: PropertyKeyId,
previous: Option<&PropertyValue>,
) {
if let Some(previous) = previous {
self.remove_equality(subject, key, previous.clone());
}
}
pub(crate) fn on_add_element_label(&mut self, element: ElementId, label: LabelId) {
remove_posting(&mut self.label_removed, label, &element);
self.label_added.entry(label).or_default().insert(element);
}
pub(crate) fn on_create_incidence(
&mut self,
incidence: IncidenceId,
relation: RelationId,
element: ElementId,
) {
remove_posting(&mut self.element_incidence_removed, element, &incidence);
self.element_incidence_added
.entry(element)
.or_default()
.insert(incidence);
remove_posting(&mut self.relation_incidence_removed, relation, &incidence);
self.relation_incidence_added
.entry(relation)
.or_default()
.insert(incidence);
}
pub(crate) fn on_set_relation_type(
&mut self,
relation: RelationId,
previous: Option<RelationTypeId>,
new_type: RelationTypeId,
) {
if let Some(previous) = previous
&& previous != new_type
{
self.remove_relation_type(relation, previous);
}
remove_posting(&mut self.relation_type_removed, new_type, &relation);
self.relation_type_added
.entry(new_type)
.or_default()
.insert(relation);
}
fn remove_relation_type(&mut self, relation: RelationId, relation_type: RelationTypeId) {
remove_posting(&mut self.relation_type_added, relation_type, &relation);
self.relation_type_removed
.entry(relation_type)
.or_default()
.insert(relation);
}
pub(crate) fn on_tombstone_element(
&mut self,
element: ElementId,
labels: &BTreeSet<LabelId>,
properties: &BTreeMap<PropertyKeyId, PropertyValue>,
) {
for label in labels {
remove_posting(&mut self.label_added, *label, &element);
self.label_removed
.entry(*label)
.or_default()
.insert(element);
}
self.withdraw_subject_properties(PropertySubject::Element(element), properties);
}
pub(crate) fn on_tombstone_relation(
&mut self,
relation: RelationId,
relation_type: Option<RelationTypeId>,
properties: &BTreeMap<PropertyKeyId, PropertyValue>,
) {
if let Some(relation_type) = relation_type {
self.remove_relation_type(relation, relation_type);
}
self.withdraw_subject_properties(PropertySubject::Relation(relation), properties);
}
pub(crate) fn on_tombstone_incidence(
&mut self,
incidence: IncidenceId,
relation: RelationId,
element: ElementId,
properties: &BTreeMap<PropertyKeyId, PropertyValue>,
) {
remove_posting(&mut self.element_incidence_added, element, &incidence);
self.element_incidence_removed
.entry(element)
.or_default()
.insert(incidence);
remove_posting(&mut self.relation_incidence_added, relation, &incidence);
self.relation_incidence_removed
.entry(relation)
.or_default()
.insert(incidence);
self.withdraw_subject_properties(PropertySubject::Incidence(incidence), properties);
}
fn withdraw_subject_properties(
&mut self,
subject: PropertySubject,
properties: &BTreeMap<PropertyKeyId, PropertyValue>,
) {
for (key, value) in properties {
self.remove_equality(subject, *key, value.clone());
}
}
pub(crate) fn elements_with_label(
&self,
base: BaseIndex<'_>,
label: LabelId,
) -> Vec<ElementId> {
merge_posting(
base.label_posting(label),
self.label_added.get(&label),
self.label_removed.get(&label),
)
}
pub(crate) fn relations_with_type(
&self,
base: BaseIndex<'_>,
relation_type: RelationTypeId,
) -> Vec<RelationId> {
merge_posting(
base.relation_type_posting(relation_type),
self.relation_type_added.get(&relation_type),
self.relation_type_removed.get(&relation_type),
)
}
pub(crate) fn element_incidences(
&self,
base: BaseIndex<'_>,
element: ElementId,
) -> Vec<IncidenceId> {
merge_posting(
base.element_incidence_posting(element),
self.element_incidence_added.get(&element),
self.element_incidence_removed.get(&element),
)
}
pub(crate) fn relation_incidences(
&self,
base: BaseIndex<'_>,
relation: RelationId,
) -> Vec<IncidenceId> {
merge_posting(
base.relation_incidence_posting(relation),
self.relation_incidence_added.get(&relation),
self.relation_incidence_removed.get(&relation),
)
}
pub(crate) fn property_key_subjects(
&self,
base: BaseIndex<'_>,
key: PropertyKeyId,
) -> Vec<(PropertySubject, PropertyValue)> {
let start = (key, PropertyValue::Boolean(false));
let mut visible: BTreeMap<PropertySubject, PropertyValue> = BTreeMap::new();
for (value, subjects) in base.key_postings(key) {
let removed = self.equality_removed.get(&(key, value.clone()));
let kept = subjects
.into_iter()
.filter(|subject| removed.is_none_or(|set| !set.contains(subject)))
.map(|subject| (subject, value.clone()));
visible.extend(kept);
}
for ((entry_key, value), subjects) in self.equality_added.range(start..) {
if *entry_key != key {
break;
}
visible.extend(subjects.iter().map(|subject| (*subject, value.clone())));
}
visible.into_iter().collect()
}
pub(crate) fn property_equal(
&self,
base: BaseIndex<'_>,
key: PropertyKeyId,
value: &PropertyValue,
) -> Vec<PropertySubject> {
let pair = (key, value.clone());
let base_subjects = base.equality_subjects(key, value).unwrap_or_default();
merge_posting_seq(
base_subjects,
self.equality_added.get(&pair),
self.equality_removed.get(&pair),
)
}
pub(crate) fn property_range(
&self,
base: BaseIndex<'_>,
key: PropertyKeyId,
min: &PropertyValue,
max: &PropertyValue,
) -> Vec<PropertySubject> {
if min > max {
return Vec::new();
}
let mut visible: BTreeSet<PropertySubject> = BTreeSet::new();
for (pair, subjects) in base.range_postings(key, min, max) {
let removed = self.equality_removed.get(&pair);
let kept = subjects
.into_iter()
.filter(|subject| removed.is_none_or(|set| !set.contains(subject)));
visible.extend(kept);
}
for (_pair, subjects) in self
.equality_added
.range((key, min.clone())..=(key, max.clone()))
{
visible.extend(subjects.iter().copied());
}
visible.into_iter().collect()
}
pub(crate) fn property_composite_equal(
&self,
base: BaseIndex<'_>,
pairs: &[(PropertyKeyId, PropertyValue)],
) -> Vec<PropertySubject> {
let mut per_key: Vec<BTreeSet<PropertySubject>> = pairs
.iter()
.map(|(key, value)| {
self.property_equal(base, *key, value)
.into_iter()
.collect::<BTreeSet<_>>()
})
.collect();
if per_key.is_empty() {
return Vec::new();
}
per_key.sort_by_key(BTreeSet::len);
let mut iter = per_key.into_iter();
let Some(mut accumulator) = iter.next() else {
return Vec::new();
};
for posting in iter {
if accumulator.is_empty() {
break;
}
accumulator.retain(|subject| posting.contains(subject));
}
accumulator.into_iter().collect()
}
}
fn remove_posting<K: Ord, M: Ord>(map: &mut BTreeMap<K, BTreeSet<M>>, key: K, member: &M) {
if let btree_map::Entry::Occupied(mut entry) = map.entry(key) {
entry.get_mut().remove(member);
if entry.get().is_empty() {
entry.remove();
}
}
}
fn merge_posting<M: Copy + Ord + FromU64>(
base: Option<Posting<'_, M>>,
added: Option<&BTreeSet<M>>,
removed: Option<&BTreeSet<M>>,
) -> Vec<M> {
let mut visible: BTreeSet<M> = BTreeSet::new();
if let Some(base) = base {
visible.extend(base.iter());
}
if let Some(added) = added {
visible.extend(added.iter().copied());
}
if let Some(removed) = removed {
for member in removed {
visible.remove(member);
}
}
visible.into_iter().collect()
}
fn merge_posting_seq(
base: Vec<PropertySubject>,
added: Option<&BTreeSet<PropertySubject>>,
removed: Option<&BTreeSet<PropertySubject>>,
) -> Vec<PropertySubject> {
let mut visible: BTreeSet<PropertySubject> = base.into_iter().collect();
if let Some(added) = added {
visible.extend(added.iter().copied());
}
if let Some(removed) = removed {
for member in removed {
visible.remove(member);
}
}
visible.into_iter().collect()
}