use std::sync::atomic::{AtomicBool, Ordering};
use rushdb_lmpg::reader::TryRead;
use super::{
labels_for, BoundedOwners, LeafScope, OwnerSet, PLANNER_MATERIALIZE_CAP, UNINDEXED_WALKS,
UNINDEXED_WALK_THRESHOLD,
};
use crate::{access, QueryError};
pub(super) const WALK_PART_OWNERS: usize = 4096;
pub(super) type Keep<'a> = &'a (dyn Fn(&access::Occurrence) -> bool + Sync);
pub(super) fn owners_matching<R: TryRead + Sync>(
snap: &R,
tenant: rushdb_types::ids::TenantId,
pid: u64,
scope: &LeafScope<'_>,
owners: &mut OwnerSet,
keep: Keep<'_>,
) -> Result<bool, QueryError> {
let kind = rushdb_types::ids::OwnerKind::Record;
let labels = labels_for(snap, tenant, pid, scope.labels)?;
let mut population = 0u64;
for label in &labels {
population = population.saturating_add(rushdb_lmpg::derived::prop_support_label(
snap, tenant, kind, pid, label,
)?);
}
let (_, array_labels) = rushdb_lmpg::derived::array_markers(snap, tenant, kind, pid)?;
let check = Check {
snap,
tenant,
pid,
keep,
};
let part_len = u64::try_from(WALK_PART_OWNERS).unwrap_or(u64::MAX);
if let Some(candidates) = scope.candidates.filter(|c| c.len() < population) {
let scalar = array_labels.is_empty();
let parts = usize::try_from(candidates.len().div_ceil(part_len)).unwrap_or(usize::MAX);
merge_parts(parts, scope, owners, |part, seen, stop| {
let first = u64::try_from(part)
.unwrap_or(u64::MAX)
.saturating_mul(part_len);
let Some(start) = candidates.select(first) else {
return Ok(Some(Vec::new()));
};
let mut iter = candidates.iter();
iter.advance_to(start);
check.part(iter.take(WALK_PART_OWNERS).map(|o| (o, scalar)), seen, stop)
})?;
return Ok(true);
}
if population > PLANNER_MATERIALIZE_CAP {
return Ok(false);
}
if population > UNINDEXED_WALK_THRESHOLD {
UNINDEXED_WALKS.fetch_add(1, Ordering::Relaxed);
}
let prefixes: Vec<(Vec<u8>, bool)> = labels
.iter()
.map(|label| {
let prefix = rushdb_types::keys::presence_nullness_prefix(
tenant,
kind,
label,
pid,
rushdb_types::keys::NULLNESS_VALUE,
);
(prefix, !array_labels.contains(label))
})
.collect();
let (width, parts) = match crate::scan::ordinal_end(snap, tenant)? {
Some(end) if end > 0 => {
let width = part_len.saturating_mul(end.div_ceil(population.max(1)).max(1));
(width, end.div_ceil(width))
}
_ => (u64::MAX, 1),
};
let parts = usize::try_from(parts).unwrap_or(usize::MAX);
merge_parts(parts, scope, owners, |part, seen, stop| {
let part_u64 = u64::try_from(part).unwrap_or(u64::MAX);
let from = (part > 0).then(|| part_u64.saturating_mul(width));
let to = (part + 1 < parts).then(|| part_u64.saturating_add(1).saturating_mul(width));
let mut ords = Vec::new();
for (prefix, scalar) in &prefixes {
each_presence_owner(snap, prefix, from, to, &mut |owner| {
ords.push((owner, *scalar));
Ok(())
})?;
}
check.part(ords.into_iter(), seen, stop)
})?;
Ok(true)
}
pub(super) fn each_presence_owner<R: TryRead + ?Sized>(
snap: &R,
prefix: &[u8],
from: Option<u64>,
to: Option<u64>,
visit: &mut dyn FnMut(u64) -> Result<(), QueryError>,
) -> Result<(), QueryError> {
let bound = |ord: Option<u64>| {
ord.map(|ord| {
let mut key = prefix.to_vec();
rushdb_types::keys::push_ord(&mut key, ord);
key
})
};
let (prefix_start, prefix_end) = rushdb_storage::prefix_range(prefix);
let start = bound(from).unwrap_or(prefix_start);
let end = bound(to).unwrap_or(prefix_end);
let mut failed = None;
snap.for_each_key(&start, &end, &mut |key| {
let result = rushdb_types::keys::read_ord_tail(key, prefix.len())
.ok_or_else(|| rushdb_storage::StorageError::CorruptLog.into())
.and_then(&mut *visit);
match result {
Ok(()) => true,
Err(error) => {
failed = Some(error);
false
}
}
})?;
failed.map_or(Ok(()), Err)
}
struct Check<'a, R> {
snap: &'a R,
tenant: rushdb_types::ids::TenantId,
pid: u64,
keep: Keep<'a>,
}
impl<R: TryRead> Check<'_, R> {
fn matches(&self, owner: u64, scalar: bool) -> Result<bool, QueryError> {
let kind = rushdb_types::ids::OwnerKind::Record;
Ok(if scalar {
access::occurrence(self.snap, self.tenant, kind, owner, self.pid, 0)?
.is_some_and(|occ| (self.keep)(&occ))
} else {
access::occurrences_of_pid(self.snap, self.tenant, kind, owner, self.pid)?
.iter()
.any(self.keep)
})
}
fn part(
&self,
owners: impl Iterator<Item = (u64, bool)>,
seen: &OwnerSet,
stop: &AtomicBool,
) -> Result<Option<Vec<u64>>, QueryError> {
let mut out = Vec::new();
for (i, (owner, scalar)) in owners.enumerate() {
if i % 256 == 0 && stop.load(Ordering::Relaxed) {
return Ok(None);
}
if !seen.contains(owner) && self.matches(owner, scalar)? {
out.push(owner);
}
}
Ok(Some(out))
}
}
fn merge_parts<F>(
parts: usize,
scope: &LeafScope<'_>,
owners: &mut OwnerSet,
part: F,
) -> Result<(), QueryError>
where
F: Fn(usize, &OwnerSet, &AtomicBool) -> Result<Option<Vec<u64>>, QueryError> + Sync,
{
let round = rushdb_storage::parallel::budget().saturating_add(1);
let stop = AtomicBool::new(false);
let mut start = 0;
while start < parts {
if (scope.is_cancelled)() {
return Err(QueryError::Cancelled);
}
let end = start.saturating_add(round).min(parts);
let seen: &OwnerSet = owners;
let outs = rushdb_storage::parallel::run_parts(end - start, |i| {
let out = part(start + i, seen, &stop);
if out.is_err() {
stop.store(true, Ordering::SeqCst);
}
Ok::<_, std::convert::Infallible>(out)
})
.unwrap_or_else(|never| match never {});
let mut matched = Vec::with_capacity(outs.len());
for out in outs {
matched.push(out?);
}
let mut bounded = BoundedOwners::new(owners, scope.owner_limit);
for part in matched {
let Some(part) = part else {
return Err(QueryError::Cancelled);
};
for owner in part {
bounded.insert(owner)?;
}
}
start = end;
}
Ok(())
}