icydb_core/db/query/
planner.rs1use crate::{
2 db::primitives::filter::{Cmp, FilterExpr},
3 obs::sink::{self, MetricsEvent, PlanKind},
4 prelude::*,
5 traits::EntityKind,
6};
7use std::fmt::{self, Display};
8
9#[derive(Debug)]
14pub enum QueryPlan {
15 FullScan,
16 Index(IndexPlan),
17 Keys(Vec<Key>),
18 Range(Key, Key),
20}
21
22impl fmt::Display for QueryPlan {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 match self {
25 Self::Index(plan) => write!(f, "Index({plan})"),
26
27 Self::Keys(keys) => {
28 let preview: Vec<String> = keys.iter().take(5).map(|k| format!("{k:?}")).collect();
30
31 if keys.len() > 5 {
32 write!(f, "Keys[{}… total {}]", preview.join(", "), keys.len())
33 } else {
34 write!(f, "Keys[{}]", preview.join(", "))
35 }
36 }
37
38 Self::Range(start, end) => {
39 write!(f, "Range({start:?} → {end:?})")
40 }
41
42 Self::FullScan => write!(f, "FullScan"),
43 }
44 }
45}
46
47#[derive(Debug)]
52pub struct IndexPlan {
53 pub index: &'static IndexModel,
54 pub values: Vec<Value>,
55}
56
57impl Display for IndexPlan {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 let values: Vec<String> = self.values.iter().map(|v| format!("{v:?}")).collect();
60 write!(f, "index={} values=[{}]", self.index, values.join(", "))
61 }
62}
63
64#[derive(Debug)]
69pub struct QueryPlanner {
70 pub filter: Option<FilterExpr>,
71}
72
73impl QueryPlanner {
74 #[must_use]
75 pub fn new(filter: Option<&FilterExpr>) -> Self {
77 Self {
78 filter: filter.cloned(),
79 }
80 }
81
82 #[must_use]
83 pub fn plan<E: EntityKind>(&self) -> QueryPlan {
88 if let Some(plan) = self.extract_from_filter::<E>() {
91 sink::record(MetricsEvent::Plan {
92 kind: match &plan {
93 QueryPlan::Keys(_) => PlanKind::Keys,
94 QueryPlan::Index(_) => PlanKind::Index,
95 QueryPlan::Range(_, _) => PlanKind::Range,
96 QueryPlan::FullScan => PlanKind::FullScan,
97 },
98 });
99 return plan;
100 }
101
102 if !E::INDEXES.is_empty()
105 && let Some(plan) = self.extract_from_index::<E>()
106 {
107 sink::record(MetricsEvent::Plan {
108 kind: PlanKind::Index,
109 });
110 return plan;
111 }
112
113 sink::record(MetricsEvent::Plan {
115 kind: PlanKind::FullScan,
116 });
117
118 QueryPlan::FullScan
119 }
120
121 fn extract_from_filter<E: EntityKind>(&self) -> Option<QueryPlan> {
123 let Some(filter) = &self.filter else {
124 return None;
125 };
126
127 match filter {
128 FilterExpr::Clause(clause) if clause.field == E::PRIMARY_KEY => match clause.cmp {
129 Cmp::Eq => clause.value.as_key().map(|key| QueryPlan::Keys(vec![key])),
130
131 Cmp::In => {
132 if let Value::List(values) = &clause.value {
133 let mut keys = values
134 .iter()
135 .filter_map(Value::as_key_coerced)
136 .collect::<Vec<_>>();
137 if keys.is_empty() {
138 return Some(QueryPlan::Keys(Vec::new()));
139 }
140 keys.sort_unstable();
141 keys.dedup();
142 Some(QueryPlan::Keys(keys))
143 } else {
144 None
145 }
146 }
147
148 _ => None,
149 },
150
151 _ => None,
152 }
153 }
154
155 fn extract_from_index<E: EntityKind>(&self) -> Option<QueryPlan> {
158 let Some(filter) = &self.filter else {
159 return None;
160 };
161
162 let mut best: Option<(usize, IndexPlan)> = None;
163
164 for index in E::INDEXES {
165 let mut values: Vec<Value> = Vec::with_capacity(index.fields.len());
167 let mut unusable = false;
168
169 for field in index.fields {
170 if let Some(v) = Self::find_eq_value(filter, field) {
171 if v.to_index_fingerprint().is_none() {
172 unusable = true;
173 break;
174 }
175 values.push(v);
176 } else {
177 break; }
179 }
180
181 if unusable || values.is_empty() {
183 continue;
184 }
185
186 let score = values.len();
187 let cand = (score, IndexPlan { index, values });
188
189 match &best {
190 Some((best_score, _)) if *best_score >= score => { }
191 _ => best = Some(cand),
192 }
193 }
194
195 best.map(|(_, plan)| QueryPlan::Index(plan))
196 }
197
198 fn find_eq_value(filter: &FilterExpr, field: &str) -> Option<Value> {
200 match filter {
201 FilterExpr::Clause(c) if c.field == field && matches!(c.cmp, Cmp::Eq) => {
202 Some(c.value.clone())
203 }
204 FilterExpr::And(list) => list.iter().find_map(|f| Self::find_eq_value(f, field)),
206 _ => None,
207 }
208 }
209}