Skip to main content

rushdb_query/
planned.rs

1//! Index candidates for an executor that checks every predicate itself.
2//!
3//! The SQL label scan filters rows with `DataFusion`, but it reads
4//! candidates the way records reads do: from the index plan when an index
5//! answers a condition, from a label walk otherwise, and it is refused the
6//! same way when a filter without a usable index would check more records
7//! than the unindexed-walk limit allows (QRY-3: none by default).
8
9use rushdb_lmpg::Lmpg;
10
11use crate::planner::OwnerSet;
12use crate::{
13    check_walk_budget, index_candidates_with_budget, working_set_budget, Expr, QueryError,
14    QuerySnapshot, SearchRequest,
15};
16
17/// The index plan's candidate ordinals, ascending. A superset of the
18/// records matching the conditions it was planned for, or exactly them.
19pub struct PlannedCandidates {
20    ords: roaring::treemap::IntoIter,
21    exact: bool,
22}
23
24impl PlannedCandidates {
25    /// Every candidate is a live record matching every condition: none
26    /// needs checking.
27    #[must_use]
28    pub fn exact(&self) -> bool {
29        self.exact
30    }
31}
32
33impl Iterator for PlannedCandidates {
34    type Item = u64;
35
36    fn next(&mut self) -> Option<u64> {
37        self.ords.next()
38    }
39}
40
41/// Candidates for records of `labels` matching every one of `conditions`.
42///
43/// `conditions` are the conjuncts of the caller's filter that it can state
44/// as native predicates; an empty slice is a filter none of whose
45/// conditions can be stated. `Ok(None)` means no index answers them and
46/// the caller walks the labels' records.
47///
48/// # Errors
49///
50/// Returns [`QueryError::UnindexedWalk`] when no index answers and the walk
51/// would check more records than the handle's unindexed-walk limit,
52/// [`QueryError::ResourceLimit`] when the candidate set exceeds
53/// `query-memory-max`, [`QueryError::Cancelled`] when the probe requests a
54/// stop, and storage read errors.
55pub fn planned_candidates(
56    lmpg: &Lmpg,
57    snap: &QuerySnapshot,
58    labels: &[String],
59    conditions: &[Expr],
60    is_cancelled: &dyn Fn() -> bool,
61) -> Result<Option<PlannedCandidates>, QueryError> {
62    let req = SearchRequest {
63        labels: labels.to_vec(),
64        where_: Some(Expr::And(conditions.to_vec())),
65        ..SearchRequest::default()
66    };
67    check_walk_budget(lmpg, snap, &req)?;
68    let is_cancelled: &dyn Fn() -> bool = &crate::cancel_probe(lmpg, is_cancelled);
69    let plan =
70        index_candidates_with_budget(lmpg, snap, &req, working_set_budget(lmpg), is_cancelled)?;
71    Ok(plan.map(|plan| {
72        let ords: OwnerSet = plan.ords;
73        PlannedCandidates {
74            ords: ords.into_iter(),
75            exact: plan.exact,
76        }
77    }))
78}