icydb_core/db/query/intent/
query.rs1use crate::{
2 db::{
3 predicate::{CompareOp, MissingRowPolicy, Predicate},
4 query::{
5 builder::aggregate::AggregateExpr,
6 explain::{
7 ExplainExecutionNodeDescriptor, ExplainExecutionNodeType, ExplainOrderPushdown,
8 ExplainPlan,
9 },
10 expr::{FilterExpr, SortExpr},
11 intent::{QueryError, access_plan_to_entity_keys, model::QueryModel},
12 plan::{AccessPlannedQuery, LoadSpec, QueryMode},
13 },
14 },
15 traits::{EntityKind, EntityValue, SingletonEntity},
16 value::Value,
17};
18
19#[derive(Debug)]
31pub struct Query<E: EntityKind> {
32 intent: QueryModel<'static, E::Key>,
33}
34
35impl<E: EntityKind> Query<E> {
36 #[must_use]
40 pub const fn new(consistency: MissingRowPolicy) -> Self {
41 Self {
42 intent: QueryModel::new(E::MODEL, consistency),
43 }
44 }
45
46 #[must_use]
48 pub const fn mode(&self) -> QueryMode {
49 self.intent.mode()
50 }
51
52 #[must_use]
53 pub(crate) fn has_explicit_order(&self) -> bool {
54 self.intent.has_explicit_order()
55 }
56
57 #[must_use]
58 pub(crate) const fn has_grouping(&self) -> bool {
59 self.intent.has_grouping()
60 }
61
62 #[must_use]
63 pub(crate) const fn load_spec(&self) -> Option<LoadSpec> {
64 match self.intent.mode() {
65 QueryMode::Load(spec) => Some(spec),
66 QueryMode::Delete(_) => None,
67 }
68 }
69
70 #[must_use]
72 pub fn filter(mut self, predicate: Predicate) -> Self {
73 self.intent = self.intent.filter(predicate);
74 self
75 }
76
77 pub fn filter_expr(self, expr: FilterExpr) -> Result<Self, QueryError> {
79 let Self { intent } = self;
80 let intent = intent.filter_expr(expr)?;
81
82 Ok(Self { intent })
83 }
84
85 pub fn sort_expr(self, expr: SortExpr) -> Result<Self, QueryError> {
87 let Self { intent } = self;
88 let intent = intent.sort_expr(expr)?;
89
90 Ok(Self { intent })
91 }
92
93 #[must_use]
95 pub fn order_by(mut self, field: impl AsRef<str>) -> Self {
96 self.intent = self.intent.order_by(field);
97 self
98 }
99
100 #[must_use]
102 pub fn order_by_desc(mut self, field: impl AsRef<str>) -> Self {
103 self.intent = self.intent.order_by_desc(field);
104 self
105 }
106
107 #[must_use]
109 pub fn distinct(mut self) -> Self {
110 self.intent = self.intent.distinct();
111 self
112 }
113
114 pub fn group_by(self, field: impl AsRef<str>) -> Result<Self, QueryError> {
116 let Self { intent } = self;
117 let intent = intent.push_group_field(field.as_ref())?;
118
119 Ok(Self { intent })
120 }
121
122 #[must_use]
124 pub fn aggregate(mut self, aggregate: AggregateExpr) -> Self {
125 self.intent = self.intent.push_group_aggregate(aggregate);
126 self
127 }
128
129 #[must_use]
131 pub fn grouped_limits(mut self, max_groups: u64, max_group_bytes: u64) -> Self {
132 self.intent = self.intent.grouped_limits(max_groups, max_group_bytes);
133 self
134 }
135
136 pub fn having_group(
138 self,
139 field: impl AsRef<str>,
140 op: CompareOp,
141 value: Value,
142 ) -> Result<Self, QueryError> {
143 let field = field.as_ref().to_owned();
144 let Self { intent } = self;
145 let intent = intent.push_having_group_clause(&field, op, value)?;
146
147 Ok(Self { intent })
148 }
149
150 pub fn having_aggregate(
152 self,
153 aggregate_index: usize,
154 op: CompareOp,
155 value: Value,
156 ) -> Result<Self, QueryError> {
157 let Self { intent } = self;
158 let intent = intent.push_having_aggregate_clause(aggregate_index, op, value)?;
159
160 Ok(Self { intent })
161 }
162
163 pub(crate) fn by_id(self, id: E::Key) -> Self {
165 let Self { intent } = self;
166 Self {
167 intent: intent.by_id(id),
168 }
169 }
170
171 pub(crate) fn by_ids<I>(self, ids: I) -> Self
173 where
174 I: IntoIterator<Item = E::Key>,
175 {
176 let Self { intent } = self;
177 Self {
178 intent: intent.by_ids(ids),
179 }
180 }
181
182 #[must_use]
184 pub fn delete(mut self) -> Self {
185 self.intent = self.intent.delete();
186 self
187 }
188
189 #[must_use]
196 pub fn limit(mut self, limit: u32) -> Self {
197 self.intent = self.intent.limit(limit);
198 self
199 }
200
201 #[must_use]
207 pub fn offset(mut self, offset: u32) -> Self {
208 self.intent = self.intent.offset(offset);
209 self
210 }
211
212 pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
214 let plan = self.planned()?;
215
216 Ok(plan.explain())
217 }
218
219 pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
224 Ok(self.explain()?.fingerprint().to_string())
225 }
226
227 pub fn explain_execution(&self) -> Result<ExplainExecutionNodeDescriptor, QueryError>
229 where
230 E: EntityValue,
231 {
232 let executable = self.plan()?.into_executable();
233
234 executable
235 .explain_load_execution_node_descriptor()
236 .map_err(QueryError::execute)
237 }
238
239 pub fn explain_execution_text(&self) -> Result<String, QueryError>
241 where
242 E: EntityValue,
243 {
244 Ok(self.explain_execution()?.render_text_tree())
245 }
246
247 pub fn explain_execution_json(&self) -> Result<String, QueryError>
249 where
250 E: EntityValue,
251 {
252 Ok(self.explain_execution()?.render_json_canonical())
253 }
254
255 pub fn explain_execution_verbose(&self) -> Result<String, QueryError>
257 where
258 E: EntityValue,
259 {
260 let executable = self.plan()?.into_executable();
261 let descriptor = executable
262 .explain_load_execution_node_descriptor()
263 .map_err(QueryError::execute)?;
264 let route_diagnostics = executable
265 .explain_load_execution_verbose_diagnostics()
266 .map_err(QueryError::execute)?;
267 let explain = self.explain()?;
268
269 let mut lines = vec![descriptor.render_text_tree_verbose()];
271 lines.extend(route_diagnostics);
272
273 lines.push(format!(
275 "diagnostic.descriptor.has_top_n_seek={}",
276 contains_execution_node_type(&descriptor, ExplainExecutionNodeType::TopNSeek)
277 ));
278 lines.push(format!(
279 "diagnostic.descriptor.has_index_range_limit_pushdown={}",
280 contains_execution_node_type(
281 &descriptor,
282 ExplainExecutionNodeType::IndexRangeLimitPushdown,
283 )
284 ));
285 lines.push(format!(
286 "diagnostic.descriptor.has_index_predicate_prefilter={}",
287 contains_execution_node_type(
288 &descriptor,
289 ExplainExecutionNodeType::IndexPredicatePrefilter,
290 )
291 ));
292 lines.push(format!(
293 "diagnostic.descriptor.has_residual_predicate_filter={}",
294 contains_execution_node_type(
295 &descriptor,
296 ExplainExecutionNodeType::ResidualPredicateFilter,
297 )
298 ));
299
300 lines.push(format!("diagnostic.plan.mode={:?}", explain.mode));
302 lines.push(format!(
303 "diagnostic.plan.order_pushdown={}",
304 plan_order_pushdown_label(&explain.order_pushdown)
305 ));
306 lines.push(format!("diagnostic.plan.distinct={}", explain.distinct));
307 lines.push(format!("diagnostic.plan.page={:?}", explain.page));
308 lines.push(format!(
309 "diagnostic.plan.consistency={:?}",
310 explain.consistency
311 ));
312
313 Ok(lines.join("\n"))
314 }
315
316 pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
318 let plan = self.build_plan()?;
319 let _projection = plan.projection_spec(E::MODEL);
320
321 Ok(PlannedQuery::new(plan))
322 }
323
324 pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
328 let plan = self.build_plan()?;
329 let _projection = plan.projection_spec(E::MODEL);
330
331 Ok(CompiledQuery::new(plan))
332 }
333
334 fn build_plan(&self) -> Result<AccessPlannedQuery<E::Key>, QueryError> {
336 let plan_value = self.intent.build_plan_model()?;
337 let (logical, access) = plan_value.into_parts();
338 let access = access_plan_to_entity_keys::<E>(E::MODEL, access)?;
339 let plan = AccessPlannedQuery::from_parts(logical, access);
340
341 Ok(plan)
342 }
343}
344
345fn contains_execution_node_type(
346 descriptor: &ExplainExecutionNodeDescriptor,
347 target: ExplainExecutionNodeType,
348) -> bool {
349 descriptor.node_type == target
350 || descriptor
351 .children
352 .iter()
353 .any(|child| contains_execution_node_type(child, target))
354}
355
356fn plan_order_pushdown_label(order_pushdown: &ExplainOrderPushdown) -> String {
357 match order_pushdown {
358 ExplainOrderPushdown::MissingModelContext => "missing_model_context".to_string(),
359 ExplainOrderPushdown::EligibleSecondaryIndex { index, prefix_len } => {
360 format!("eligible(index={index},prefix_len={prefix_len})",)
361 }
362 ExplainOrderPushdown::Rejected(reason) => format!("rejected({reason:?})"),
363 }
364}
365
366impl<E> Query<E>
367where
368 E: EntityKind + SingletonEntity,
369 E::Key: Default,
370{
371 pub(crate) fn only(self) -> Self {
373 let Self { intent } = self;
374
375 Self {
376 intent: intent.only(E::Key::default()),
377 }
378 }
379}
380
381#[derive(Debug)]
389pub struct PlannedQuery<E: EntityKind> {
390 plan: AccessPlannedQuery<E::Key>,
391}
392
393impl<E: EntityKind> PlannedQuery<E> {
394 #[must_use]
395 pub(in crate::db) const fn new(plan: AccessPlannedQuery<E::Key>) -> Self {
396 Self { plan }
397 }
398
399 #[must_use]
400 pub fn explain(&self) -> ExplainPlan {
401 self.plan.explain_with_model(E::MODEL)
402 }
403
404 #[must_use]
406 pub fn plan_hash_hex(&self) -> String {
407 self.explain().fingerprint().to_string()
408 }
409}
410
411#[derive(Clone, Debug)]
420pub struct CompiledQuery<E: EntityKind> {
421 plan: AccessPlannedQuery<E::Key>,
422}
423
424impl<E: EntityKind> CompiledQuery<E> {
425 #[must_use]
426 pub(in crate::db) const fn new(plan: AccessPlannedQuery<E::Key>) -> Self {
427 Self { plan }
428 }
429
430 #[must_use]
431 pub fn explain(&self) -> ExplainPlan {
432 self.plan.explain_with_model(E::MODEL)
433 }
434
435 #[must_use]
437 pub fn plan_hash_hex(&self) -> String {
438 self.explain().fingerprint().to_string()
439 }
440
441 #[must_use]
443 #[cfg(test)]
444 pub(crate) fn projection_spec(&self) -> crate::db::query::plan::expr::ProjectionSpec {
445 self.plan.projection_spec(E::MODEL)
446 }
447
448 #[must_use]
449 pub(in crate::db) fn into_inner(self) -> AccessPlannedQuery<E::Key> {
450 self.plan
451 }
452}