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