rushdb_query/planner/
explain.rs1use rushdb_lmpg::reader::TryRead;
5use rushdb_types::model::LogicalType;
6
7use super::{
8 collect_leaves, family_id, is_range_leaf, labels_for, leaf_estimate, normalize, property_id,
9};
10use crate::{Expr, FieldPred, QueryError};
11
12pub fn explain_cardinality<R: TryRead>(
20 snap: &R,
21 tenant: rushdb_types::ids::TenantId,
22 labels: &[String],
23 where_: &Expr,
24) -> Result<Option<u64>, QueryError> {
25 let mut leaves = Vec::new();
26 collect_leaves(where_, &mut leaves);
27 let mut best: Option<u64> = None;
28 for leaf in leaves {
29 if is_range_leaf(leaf) {
35 continue;
36 }
37 if let Some(estimate) = leaf_estimate(snap, tenant, leaf, labels)? {
38 if estimate != u64::MAX {
41 best = Some(best.map_or(estimate, |current| current.min(estimate)));
42 }
43 }
44 }
45 Ok(best)
46}
47
48pub fn explain_walk<R: TryRead>(
56 snap: &R,
57 tenant: rushdb_types::ids::TenantId,
58 labels: &[String],
59 where_: &Expr,
60) -> Result<Option<(String, u64, Vec<String>)>, QueryError> {
61 let mut leaves = Vec::new();
62 collect_leaves(where_, &mut leaves);
63 let mut eligible = Vec::new();
64 for leaf in leaves {
65 if let Some(estimate) = leaf_estimate(snap, tenant, leaf, labels)? {
66 eligible.push((estimate, leaf));
67 }
68 }
69 eligible.sort_by_key(|(estimate, _)| *estimate);
70 let Some((_, leaf)) = eligible.first() else {
71 return Ok(None);
72 };
73 if crate::index_access::answers(snap, tenant, leaf, labels)? {
74 return Ok(None);
75 }
76 let Expr::Field(
77 FieldPred::Eq(name, _)
78 | FieldPred::In(name, _)
79 | FieldPred::Gt(name, _)
80 | FieldPred::Gte(name, _)
81 | FieldPred::Lt(name, _)
82 | FieldPred::Lte(name, _),
83 ) = leaf
84 else {
85 return Ok(None);
86 };
87 let norm = normalize(name);
88 let Some(fid) = family_id(snap, tenant, &norm)? else {
89 return Ok(None);
90 };
91 let kind = rushdb_types::ids::OwnerKind::Record;
92 let mut owners = 0u64;
93 let mut walked_labels = std::collections::BTreeSet::new();
94 for ty in [
95 LogicalType::Utf8,
96 LogicalType::Int64,
97 LogicalType::Float64,
98 LogicalType::Boolean,
99 LogicalType::DateTime,
100 ] {
101 let Some(pid) = property_id(snap, tenant, fid, ty)? else {
102 continue;
103 };
104 for label in labels_for(snap, tenant, pid, labels)? {
105 owners = owners.saturating_add(rushdb_lmpg::derived::prop_support_label(
106 snap, tenant, kind, pid, &label,
107 )?);
108 walked_labels.insert(label);
109 }
110 }
111 Ok(Some((norm, owners, walked_labels.into_iter().collect())))
112}