use super::*;
use hawdb_plan_cypher::{membership_conjunct, predicate_cannot_fail};
use std::cell::RefCell;
use std::collections::HashSet;
pub(super) struct MembershipSet {
ids: Option<HashSet<i64>>,
unknown: bool,
_lease: Option<crate::QueryMemoryLease>,
}
#[derive(Default)]
pub(super) struct MembershipSets {
sets: RefCell<BTreeMap<String, MembershipSet>>,
}
impl MembershipSets {
pub(super) fn reset(&self) {
self.sets.borrow_mut().clear();
}
}
const SET_ENTRY_BYTES: usize = 32;
impl MatchRuntime<'_> {
pub(super) fn membership_rejects(
&self,
index: usize,
target: &str,
neighbor: NodeId,
row: &Binding,
) -> Result<bool> {
if self.deferred.get() || self.program.id_set_conjuncts.is_empty() {
return Ok(false);
}
let all = self.program.predicate_conjuncts();
let planned = |conjunct: &Predicate| {
self.program.id_set_conjuncts.iter().any(|index| {
all.get(*index)
.is_some_and(|planned| std::ptr::eq(planned, conjunct))
})
};
let group: &[&Predicate] = if index + 1 < self.program.steps.len() {
self.placed
.early
.get(index + 1)
.map(Vec::as_slice)
.unwrap_or_default()
} else {
&self.placed.last
};
let last = index + 1 >= self.program.steps.len();
let mut conjuncts = Vec::new();
for conjunct in group {
flatten(conjunct, &mut conjuncts);
}
for (position, conjunct) in conjuncts.iter().enumerate() {
if let Some((variable, column)) = membership_conjunct(conjunct)
&& variable == target
&& planned(conjunct)
{
match self.membership(column, neighbor, row)? {
Membership::NotList => return Ok(false),
Membership::Present => continue,
Membership::Absent { unknown } => {
return Ok(!last
|| !unknown
|| conjuncts[position + 1..]
.iter()
.all(|later| predicate_cannot_fail(later)));
}
}
}
if !predicate_cannot_fail(conjunct) {
return Ok(false);
}
}
Ok(false)
}
fn membership(&self, column: &str, neighbor: NodeId, row: &Binding) -> Result<Membership> {
let mut sets = self.memberships.sets.borrow_mut();
if !sets.contains_key(column) {
let set = match row.values.get(column) {
Some(Value::Null) => MembershipSet {
ids: Some(HashSet::new()),
unknown: true,
_lease: None,
},
Some(Value::List(values)) => {
let lease = self
.account
.reserve(values.len().saturating_mul(SET_ENTRY_BYTES))?;
let ids = values
.iter()
.filter_map(|value| match value {
Value::Map(entries) => match entries.get("_id") {
Some(Value::Int(id)) => Some(*id),
_ => None,
},
_ => None,
})
.collect();
MembershipSet {
ids: Some(ids),
unknown: values.contains(&Value::Null),
_lease: Some(lease),
}
}
_ => MembershipSet {
ids: None,
unknown: false,
_lease: None,
},
};
sets.insert(column.to_string(), set);
}
let set = &sets[column];
Ok(match &set.ids {
None => Membership::NotList,
Some(ids) if ids.contains(&(neighbor.0 as i64)) => Membership::Present,
Some(_) => Membership::Absent {
unknown: set.unknown,
},
})
}
}
enum Membership {
NotList,
Present,
Absent { unknown: bool },
}
fn flatten<'p>(predicate: &'p Predicate, output: &mut Vec<&'p Predicate>) {
match predicate {
Predicate::And(children) => {
for child in children {
flatten(child, output);
}
}
predicate => output.push(predicate),
}
}