Skip to main content

icydb_core/db/query/intent/
query.rs

1//! Module: query::intent::query
2//! Responsibility: typed query-intent construction and planner handoff for entity queries.
3//! Does not own: runtime execution semantics or access-path execution behavior.
4//! Boundary: exposes query APIs and emits planner-owned compiled query contracts.
5
6use crate::{
7    db::{
8        executor::{
9            BytesByProjectionMode, PreparedExecutionPlan,
10            assemble_aggregate_terminal_execution_descriptor,
11            assemble_load_execution_node_descriptor, assemble_load_execution_verbose_diagnostics,
12            planning::route::AggregateRouteShape,
13        },
14        predicate::{CoercionId, CompareOp, MissingRowPolicy, Predicate},
15        query::{
16            builder::{
17                AggregateExpr, PreparedFluentAggregateExplainStrategy,
18                PreparedFluentProjectionStrategy,
19            },
20            explain::{
21                ExplainAccessPath, ExplainAggregateTerminalPlan, ExplainExecutionNodeDescriptor,
22                ExplainExecutionNodeType, ExplainOrderPushdown, ExplainPlan, ExplainPredicate,
23            },
24            expr::{FilterExpr, SortExpr},
25            intent::{QueryError, model::QueryModel},
26            plan::{AccessPlannedQuery, LoadSpec, QueryMode, VisibleIndexes},
27        },
28    },
29    traits::{EntityKind, EntityValue, FieldValue, SingletonEntity},
30    value::Value,
31};
32use core::marker::PhantomData;
33
34///
35/// StructuralQuery
36///
37/// Generic-free query-intent core shared by typed `Query<E>` wrappers.
38/// Stores model-level key access as `Value` so only typed key-entry helpers
39/// remain entity-specific at the outer API boundary.
40///
41
42#[derive(Clone, Debug)]
43pub(in crate::db) struct StructuralQuery {
44    intent: QueryModel<'static, Value>,
45}
46
47impl StructuralQuery {
48    #[must_use]
49    pub(in crate::db) const fn new(
50        model: &'static crate::model::entity::EntityModel,
51        consistency: MissingRowPolicy,
52    ) -> Self {
53        Self {
54            intent: QueryModel::new(model, consistency),
55        }
56    }
57
58    #[must_use]
59    const fn mode(&self) -> QueryMode {
60        self.intent.mode()
61    }
62
63    #[must_use]
64    fn has_explicit_order(&self) -> bool {
65        self.intent.has_explicit_order()
66    }
67
68    #[must_use]
69    const fn has_grouping(&self) -> bool {
70        self.intent.has_grouping()
71    }
72
73    #[must_use]
74    const fn load_spec(&self) -> Option<LoadSpec> {
75        match self.intent.mode() {
76            QueryMode::Load(spec) => Some(spec),
77            QueryMode::Delete(_) => None,
78        }
79    }
80
81    #[must_use]
82    pub(in crate::db) fn filter(mut self, predicate: Predicate) -> Self {
83        self.intent = self.intent.filter(predicate);
84        self
85    }
86
87    fn filter_expr(self, expr: FilterExpr) -> Result<Self, QueryError> {
88        let Self { intent } = self;
89        let intent = intent.filter_expr(expr)?;
90
91        Ok(Self { intent })
92    }
93
94    fn sort_expr(self, expr: SortExpr) -> Result<Self, QueryError> {
95        let Self { intent } = self;
96        let intent = intent.sort_expr(expr)?;
97
98        Ok(Self { intent })
99    }
100
101    #[must_use]
102    pub(in crate::db) fn order_by(mut self, field: impl AsRef<str>) -> Self {
103        self.intent = self.intent.order_by(field);
104        self
105    }
106
107    #[must_use]
108    pub(in crate::db) fn order_by_desc(mut self, field: impl AsRef<str>) -> Self {
109        self.intent = self.intent.order_by_desc(field);
110        self
111    }
112
113    #[must_use]
114    pub(in crate::db) fn distinct(mut self) -> Self {
115        self.intent = self.intent.distinct();
116        self
117    }
118
119    #[cfg(feature = "sql")]
120    #[must_use]
121    pub(in crate::db) fn select_fields<I, S>(mut self, fields: I) -> Self
122    where
123        I: IntoIterator<Item = S>,
124        S: Into<String>,
125    {
126        self.intent = self.intent.select_fields(fields);
127        self
128    }
129
130    pub(in crate::db) fn group_by(self, field: impl AsRef<str>) -> Result<Self, QueryError> {
131        let Self { intent } = self;
132        let intent = intent.push_group_field(field.as_ref())?;
133
134        Ok(Self { intent })
135    }
136
137    #[must_use]
138    pub(in crate::db) fn aggregate(mut self, aggregate: AggregateExpr) -> Self {
139        self.intent = self.intent.push_group_aggregate(aggregate);
140        self
141    }
142
143    #[must_use]
144    fn grouped_limits(mut self, max_groups: u64, max_group_bytes: u64) -> Self {
145        self.intent = self.intent.grouped_limits(max_groups, max_group_bytes);
146        self
147    }
148
149    pub(in crate::db) fn having_group(
150        self,
151        field: impl AsRef<str>,
152        op: CompareOp,
153        value: Value,
154    ) -> Result<Self, QueryError> {
155        let field = field.as_ref().to_owned();
156        let Self { intent } = self;
157        let intent = intent.push_having_group_clause(&field, op, value)?;
158
159        Ok(Self { intent })
160    }
161
162    pub(in crate::db) fn having_aggregate(
163        self,
164        aggregate_index: usize,
165        op: CompareOp,
166        value: Value,
167    ) -> Result<Self, QueryError> {
168        let Self { intent } = self;
169        let intent = intent.push_having_aggregate_clause(aggregate_index, op, value)?;
170
171        Ok(Self { intent })
172    }
173
174    #[must_use]
175    fn by_id(self, id: Value) -> Self {
176        let Self { intent } = self;
177        Self {
178            intent: intent.by_id(id),
179        }
180    }
181
182    #[must_use]
183    fn by_ids<I>(self, ids: I) -> Self
184    where
185        I: IntoIterator<Item = Value>,
186    {
187        let Self { intent } = self;
188        Self {
189            intent: intent.by_ids(ids),
190        }
191    }
192
193    #[must_use]
194    fn only(self, id: Value) -> Self {
195        let Self { intent } = self;
196
197        Self {
198            intent: intent.only(id),
199        }
200    }
201
202    #[must_use]
203    pub(in crate::db) fn delete(mut self) -> Self {
204        self.intent = self.intent.delete();
205        self
206    }
207
208    #[must_use]
209    pub(in crate::db) fn limit(mut self, limit: u32) -> Self {
210        self.intent = self.intent.limit(limit);
211        self
212    }
213
214    #[must_use]
215    pub(in crate::db) fn offset(mut self, offset: u32) -> Self {
216        self.intent = self.intent.offset(offset);
217        self
218    }
219
220    pub(in crate::db) fn build_plan(&self) -> Result<AccessPlannedQuery, QueryError> {
221        self.intent.build_plan_model()
222    }
223
224    pub(in crate::db) fn build_plan_with_visible_indexes(
225        &self,
226        visible_indexes: &VisibleIndexes<'_>,
227    ) -> Result<AccessPlannedQuery, QueryError> {
228        self.intent.build_plan_model_with_indexes(visible_indexes)
229    }
230
231    // Build one access plan using either schema-owned indexes or the session
232    // visibility slice already resolved at the caller boundary.
233    fn build_plan_for_visibility(
234        &self,
235        visible_indexes: Option<&VisibleIndexes<'_>>,
236    ) -> Result<AccessPlannedQuery, QueryError> {
237        match visible_indexes {
238            Some(visible_indexes) => self.build_plan_with_visible_indexes(visible_indexes),
239            None => self.build_plan(),
240        }
241    }
242
243    // Assemble one canonical execution descriptor from a previously built
244    // access plan so text/json/verbose explain surfaces do not each rebuild it.
245    fn explain_execution_descriptor_from_plan(
246        &self,
247        plan: &AccessPlannedQuery,
248    ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
249        assemble_load_execution_node_descriptor(
250            self.intent.model().fields(),
251            self.intent.model().primary_key().name(),
252            plan,
253        )
254        .map_err(QueryError::execute)
255    }
256
257    // Render one verbose execution explain payload from a single access plan.
258    fn explain_execution_verbose_from_plan(
259        &self,
260        plan: &AccessPlannedQuery,
261    ) -> Result<String, QueryError> {
262        let descriptor = self.explain_execution_descriptor_from_plan(plan)?;
263        let route_diagnostics = assemble_load_execution_verbose_diagnostics(
264            self.intent.model().fields(),
265            self.intent.model().primary_key().name(),
266            plan,
267        )
268        .map_err(QueryError::execute)?;
269        let explain = plan.explain_with_model(self.intent.model());
270
271        // Phase 1: render descriptor tree with node-local metadata.
272        let mut lines = vec![descriptor.render_text_tree_verbose()];
273        lines.extend(route_diagnostics);
274
275        // Phase 2: add descriptor-stage summaries for key execution operators.
276        lines.push(format!(
277            "diag.d.has_top_n_seek={}",
278            contains_execution_node_type(&descriptor, ExplainExecutionNodeType::TopNSeek)
279        ));
280        lines.push(format!(
281            "diag.d.has_index_range_limit_pushdown={}",
282            contains_execution_node_type(
283                &descriptor,
284                ExplainExecutionNodeType::IndexRangeLimitPushdown,
285            )
286        ));
287        lines.push(format!(
288            "diag.d.has_index_predicate_prefilter={}",
289            contains_execution_node_type(
290                &descriptor,
291                ExplainExecutionNodeType::IndexPredicatePrefilter,
292            )
293        ));
294        lines.push(format!(
295            "diag.d.has_residual_predicate_filter={}",
296            contains_execution_node_type(
297                &descriptor,
298                ExplainExecutionNodeType::ResidualPredicateFilter,
299            )
300        ));
301
302        // Phase 3: append logical-plan diagnostics relevant to verbose explain.
303        lines.push(format!("diag.p.mode={:?}", explain.mode()));
304        lines.push(format!(
305            "diag.p.order_pushdown={}",
306            plan_order_pushdown_label(explain.order_pushdown())
307        ));
308        lines.push(format!(
309            "diag.p.predicate_pushdown={}",
310            plan_predicate_pushdown_label(explain.predicate(), explain.access())
311        ));
312        lines.push(format!("diag.p.distinct={}", explain.distinct()));
313        lines.push(format!("diag.p.page={:?}", explain.page()));
314        lines.push(format!("diag.p.consistency={:?}", explain.consistency()));
315
316        Ok(lines.join("\n"))
317    }
318
319    #[cfg(feature = "sql")]
320    #[must_use]
321    pub(in crate::db) const fn model(&self) -> &'static crate::model::entity::EntityModel {
322        self.intent.model()
323    }
324
325    #[inline(never)]
326    pub(in crate::db) fn explain_execution_with_visible_indexes(
327        &self,
328        visible_indexes: &VisibleIndexes<'_>,
329    ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
330        let plan = self.build_plan_for_visibility(Some(visible_indexes))?;
331
332        self.explain_execution_descriptor_from_plan(&plan)
333    }
334
335    // Explain one load execution shape through the structural query core.
336    #[inline(never)]
337    pub(in crate::db) fn explain_execution(
338        &self,
339    ) -> Result<ExplainExecutionNodeDescriptor, QueryError> {
340        let plan = self.build_plan_for_visibility(None)?;
341
342        self.explain_execution_descriptor_from_plan(&plan)
343    }
344
345    // Render one deterministic scalar load execution tree through the shared
346    // structural descriptor path.
347    pub(in crate::db) fn explain_execution_text(&self) -> Result<String, QueryError> {
348        Ok(self.explain_execution()?.render_text_tree())
349    }
350
351    pub(in crate::db) fn explain_execution_text_with_visible_indexes(
352        &self,
353        visible_indexes: &VisibleIndexes<'_>,
354    ) -> Result<String, QueryError> {
355        Ok(self
356            .explain_execution_with_visible_indexes(visible_indexes)?
357            .render_text_tree())
358    }
359
360    // Render one canonical scalar load execution JSON payload through the shared
361    // structural descriptor path.
362    pub(in crate::db) fn explain_execution_json(&self) -> Result<String, QueryError> {
363        Ok(self.explain_execution()?.render_json_canonical())
364    }
365
366    pub(in crate::db) fn explain_execution_json_with_visible_indexes(
367        &self,
368        visible_indexes: &VisibleIndexes<'_>,
369    ) -> Result<String, QueryError> {
370        Ok(self
371            .explain_execution_with_visible_indexes(visible_indexes)?
372            .render_json_canonical())
373    }
374
375    // Render one verbose scalar load execution payload through the shared
376    // structural descriptor and route-diagnostics paths.
377    #[inline(never)]
378    pub(in crate::db) fn explain_execution_verbose(&self) -> Result<String, QueryError> {
379        let plan = self.build_plan_for_visibility(None)?;
380
381        self.explain_execution_verbose_from_plan(&plan)
382    }
383
384    #[inline(never)]
385    pub(in crate::db) fn explain_execution_verbose_with_visible_indexes(
386        &self,
387        visible_indexes: &VisibleIndexes<'_>,
388    ) -> Result<String, QueryError> {
389        let plan = self.build_plan_for_visibility(Some(visible_indexes))?;
390
391        self.explain_execution_verbose_from_plan(&plan)
392    }
393
394    #[inline(never)]
395    pub(in crate::db) fn explain_aggregate_terminal_with_visible_indexes(
396        &self,
397        visible_indexes: &VisibleIndexes<'_>,
398        aggregate: AggregateRouteShape<'_>,
399    ) -> Result<ExplainAggregateTerminalPlan, QueryError> {
400        let plan = self.build_plan_with_visible_indexes(visible_indexes)?;
401        let query_explain = plan.explain_with_model(self.intent.model());
402        let terminal = aggregate.kind();
403        let execution = assemble_aggregate_terminal_execution_descriptor(&plan, aggregate);
404
405        Ok(ExplainAggregateTerminalPlan::new(
406            query_explain,
407            terminal,
408            execution,
409        ))
410    }
411
412    #[inline(never)]
413    pub(in crate::db) fn explain_prepared_aggregate_terminal_with_visible_indexes<S>(
414        &self,
415        visible_indexes: &VisibleIndexes<'_>,
416        strategy: &S,
417    ) -> Result<ExplainAggregateTerminalPlan, QueryError>
418    where
419        S: PreparedFluentAggregateExplainStrategy,
420    {
421        let Some(kind) = strategy.explain_aggregate_kind() else {
422            return Err(QueryError::invariant(
423                "prepared fluent aggregate explain requires an explain-visible aggregate kind",
424            ));
425        };
426        let aggregate = AggregateRouteShape::new_from_fields(
427            kind,
428            strategy.explain_projected_field(),
429            self.intent.model().fields(),
430            self.intent.model().primary_key().name(),
431        );
432
433        self.explain_aggregate_terminal_with_visible_indexes(visible_indexes, aggregate)
434    }
435}
436
437///
438/// PlannedQueryCore
439///
440/// Generic-free planned-query payload shared by typed planned-query wrappers
441/// so explain and plan-hash logic stay structural while public callers retain
442/// entity-specific type inference.
443///
444
445#[derive(Debug)]
446struct PlannedQueryCore {
447    model: &'static crate::model::entity::EntityModel,
448    plan: AccessPlannedQuery,
449}
450
451impl PlannedQueryCore {
452    #[must_use]
453    const fn new(
454        model: &'static crate::model::entity::EntityModel,
455        plan: AccessPlannedQuery,
456    ) -> Self {
457        Self { model, plan }
458    }
459
460    #[must_use]
461    fn explain(&self) -> ExplainPlan {
462        self.plan.explain_with_model(self.model)
463    }
464
465    /// Return the stable plan hash for this planned query.
466    #[must_use]
467    fn plan_hash_hex(&self) -> String {
468        self.plan.fingerprint().to_string()
469    }
470}
471
472///
473/// PlannedQuery
474///
475/// Typed planned-query shell over one generic-free planner contract.
476/// This preserves caller-side entity inference while keeping the stored plan
477/// payload and explain/hash logic structural.
478///
479
480#[derive(Debug)]
481pub struct PlannedQuery<E: EntityKind> {
482    inner: PlannedQueryCore,
483    _marker: PhantomData<E>,
484}
485
486impl<E: EntityKind> PlannedQuery<E> {
487    #[must_use]
488    const fn from_inner(inner: PlannedQueryCore) -> Self {
489        Self {
490            inner,
491            _marker: PhantomData,
492        }
493    }
494
495    #[must_use]
496    pub fn explain(&self) -> ExplainPlan {
497        self.inner.explain()
498    }
499
500    /// Return the stable plan hash for this planned query.
501    #[must_use]
502    pub fn plan_hash_hex(&self) -> String {
503        self.inner.plan_hash_hex()
504    }
505}
506
507///
508/// CompiledQueryCore
509///
510/// Generic-free compiled-query payload shared by typed compiled-query wrappers
511/// so executor handoff state remains structural until the final typed adapter
512/// boundary.
513///
514
515#[derive(Clone, Debug)]
516struct CompiledQueryCore {
517    model: &'static crate::model::entity::EntityModel,
518    entity_path: &'static str,
519    plan: AccessPlannedQuery,
520}
521
522impl CompiledQueryCore {
523    #[must_use]
524    const fn new(
525        model: &'static crate::model::entity::EntityModel,
526        entity_path: &'static str,
527        plan: AccessPlannedQuery,
528    ) -> Self {
529        Self {
530            model,
531            entity_path,
532            plan,
533        }
534    }
535
536    #[must_use]
537    fn explain(&self) -> ExplainPlan {
538        self.plan.explain_with_model(self.model)
539    }
540
541    /// Return the stable plan hash for this compiled query.
542    #[must_use]
543    fn plan_hash_hex(&self) -> String {
544        self.plan.fingerprint().to_string()
545    }
546
547    #[must_use]
548    #[cfg(test)]
549    fn projection_spec(&self) -> crate::db::query::plan::expr::ProjectionSpec {
550        self.plan.projection_spec(self.model)
551    }
552
553    #[must_use]
554    fn into_inner(self) -> AccessPlannedQuery {
555        self.plan
556    }
557}
558
559///
560/// CompiledQuery
561///
562/// Typed compiled-query shell over one generic-free planner contract.
563/// The outer entity marker restores inference for executor handoff sites
564/// while the stored execution payload remains structural.
565///
566
567#[derive(Clone, Debug)]
568pub struct CompiledQuery<E: EntityKind> {
569    inner: CompiledQueryCore,
570    _marker: PhantomData<E>,
571}
572
573impl<E: EntityKind> CompiledQuery<E> {
574    #[must_use]
575    const fn from_inner(inner: CompiledQueryCore) -> Self {
576        Self {
577            inner,
578            _marker: PhantomData,
579        }
580    }
581
582    #[must_use]
583    pub fn explain(&self) -> ExplainPlan {
584        self.inner.explain()
585    }
586
587    /// Return the stable plan hash for this compiled query.
588    #[must_use]
589    pub fn plan_hash_hex(&self) -> String {
590        self.inner.plan_hash_hex()
591    }
592
593    #[must_use]
594    #[cfg(test)]
595    pub(in crate::db) fn projection_spec(&self) -> crate::db::query::plan::expr::ProjectionSpec {
596        self.inner.projection_spec()
597    }
598
599    /// Convert one structural compiled query into one prepared executor plan.
600    pub(in crate::db) fn into_prepared_execution_plan(
601        self,
602    ) -> crate::db::executor::PreparedExecutionPlan<E> {
603        assert!(
604            self.inner.entity_path == E::PATH,
605            "compiled query entity mismatch: compiled for '{}', requested '{}'",
606            self.inner.entity_path,
607            E::PATH,
608        );
609
610        crate::db::executor::PreparedExecutionPlan::new(self.into_inner())
611    }
612
613    #[must_use]
614    pub(in crate::db) fn into_inner(self) -> AccessPlannedQuery {
615        self.inner.into_inner()
616    }
617}
618
619///
620/// Query
621///
622/// Typed, declarative query intent for a specific entity type.
623///
624/// This intent is:
625/// - schema-agnostic at construction
626/// - normalized and validated only during planning
627/// - free of access-path decisions
628///
629
630#[derive(Debug)]
631pub struct Query<E: EntityKind> {
632    inner: StructuralQuery,
633    _marker: PhantomData<E>,
634}
635
636impl<E: EntityKind> Query<E> {
637    // Rebind one structural query core to the typed `Query<E>` surface.
638    pub(in crate::db) const fn from_inner(inner: StructuralQuery) -> Self {
639        Self {
640            inner,
641            _marker: PhantomData,
642        }
643    }
644
645    /// Create a new intent with an explicit missing-row policy.
646    /// Ignore favors idempotency and may mask index/data divergence on deletes.
647    /// Use Error to surface missing rows during scan/delete execution.
648    #[must_use]
649    pub const fn new(consistency: MissingRowPolicy) -> Self {
650        Self::from_inner(StructuralQuery::new(E::MODEL, consistency))
651    }
652
653    /// Return the intent mode (load vs delete).
654    #[must_use]
655    pub const fn mode(&self) -> QueryMode {
656        self.inner.mode()
657    }
658
659    pub(in crate::db) fn explain_with_visible_indexes(
660        &self,
661        visible_indexes: &VisibleIndexes<'_>,
662    ) -> Result<ExplainPlan, QueryError> {
663        let plan = self.build_plan_for_visibility(Some(visible_indexes))?;
664
665        Ok(plan.explain_with_model(E::MODEL))
666    }
667
668    pub(in crate::db) fn plan_hash_hex_with_visible_indexes(
669        &self,
670        visible_indexes: &VisibleIndexes<'_>,
671    ) -> Result<String, QueryError> {
672        let plan = self.build_plan_for_visibility(Some(visible_indexes))?;
673
674        Ok(plan.fingerprint().to_string())
675    }
676
677    // Build one typed access plan using either schema-owned indexes or the
678    // visibility slice already resolved at the session boundary.
679    fn build_plan_for_visibility(
680        &self,
681        visible_indexes: Option<&VisibleIndexes<'_>>,
682    ) -> Result<AccessPlannedQuery, QueryError> {
683        self.inner.build_plan_for_visibility(visible_indexes)
684    }
685
686    // Wrap one built plan as the typed planned-query DTO.
687    fn planned_query_from_plan(plan: AccessPlannedQuery) -> PlannedQuery<E> {
688        let _projection = plan.projection_spec(E::MODEL);
689
690        PlannedQuery::from_inner(PlannedQueryCore::new(E::MODEL, plan))
691    }
692
693    // Wrap one built plan as the typed compiled-query DTO.
694    fn compiled_query_from_plan(plan: AccessPlannedQuery) -> CompiledQuery<E> {
695        let _projection = plan.projection_spec(E::MODEL);
696
697        CompiledQuery::from_inner(CompiledQueryCore::new(E::MODEL, E::PATH, plan))
698    }
699
700    #[must_use]
701    pub(crate) fn has_explicit_order(&self) -> bool {
702        self.inner.has_explicit_order()
703    }
704
705    #[must_use]
706    pub(crate) const fn has_grouping(&self) -> bool {
707        self.inner.has_grouping()
708    }
709
710    #[must_use]
711    pub(crate) const fn load_spec(&self) -> Option<LoadSpec> {
712        self.inner.load_spec()
713    }
714
715    /// Add a predicate, implicitly AND-ing with any existing predicate.
716    #[must_use]
717    pub fn filter(mut self, predicate: Predicate) -> Self {
718        self.inner = self.inner.filter(predicate);
719        self
720    }
721
722    /// Apply a dynamic filter expression.
723    pub fn filter_expr(self, expr: FilterExpr) -> Result<Self, QueryError> {
724        let Self { inner, .. } = self;
725        let inner = inner.filter_expr(expr)?;
726
727        Ok(Self::from_inner(inner))
728    }
729
730    /// Apply a dynamic sort expression.
731    pub fn sort_expr(self, expr: SortExpr) -> Result<Self, QueryError> {
732        let Self { inner, .. } = self;
733        let inner = inner.sort_expr(expr)?;
734
735        Ok(Self::from_inner(inner))
736    }
737
738    /// Append an ascending sort key.
739    #[must_use]
740    pub fn order_by(mut self, field: impl AsRef<str>) -> Self {
741        self.inner = self.inner.order_by(field);
742        self
743    }
744
745    /// Append a descending sort key.
746    #[must_use]
747    pub fn order_by_desc(mut self, field: impl AsRef<str>) -> Self {
748        self.inner = self.inner.order_by_desc(field);
749        self
750    }
751
752    /// Enable DISTINCT semantics for this query.
753    #[must_use]
754    pub fn distinct(mut self) -> Self {
755        self.inner = self.inner.distinct();
756        self
757    }
758
759    // Keep the internal fluent SQL parity hook available for lowering tests
760    // without making generated SQL binding depend on the typed query shell.
761    #[cfg(all(test, feature = "sql"))]
762    #[must_use]
763    pub(in crate::db) fn select_fields<I, S>(mut self, fields: I) -> Self
764    where
765        I: IntoIterator<Item = S>,
766        S: Into<String>,
767    {
768        self.inner = self.inner.select_fields(fields);
769        self
770    }
771
772    /// Add one GROUP BY field.
773    pub fn group_by(self, field: impl AsRef<str>) -> Result<Self, QueryError> {
774        let Self { inner, .. } = self;
775        let inner = inner.group_by(field)?;
776
777        Ok(Self::from_inner(inner))
778    }
779
780    /// Add one aggregate terminal via composable aggregate expression.
781    #[must_use]
782    pub fn aggregate(mut self, aggregate: AggregateExpr) -> Self {
783        self.inner = self.inner.aggregate(aggregate);
784        self
785    }
786
787    /// Override grouped hard limits for grouped execution budget enforcement.
788    #[must_use]
789    pub fn grouped_limits(mut self, max_groups: u64, max_group_bytes: u64) -> Self {
790        self.inner = self.inner.grouped_limits(max_groups, max_group_bytes);
791        self
792    }
793
794    /// Add one grouped HAVING compare clause over one grouped key field.
795    pub fn having_group(
796        self,
797        field: impl AsRef<str>,
798        op: CompareOp,
799        value: Value,
800    ) -> Result<Self, QueryError> {
801        let Self { inner, .. } = self;
802        let inner = inner.having_group(field, op, value)?;
803
804        Ok(Self::from_inner(inner))
805    }
806
807    /// Add one grouped HAVING compare clause over one grouped aggregate output.
808    pub fn having_aggregate(
809        self,
810        aggregate_index: usize,
811        op: CompareOp,
812        value: Value,
813    ) -> Result<Self, QueryError> {
814        let Self { inner, .. } = self;
815        let inner = inner.having_aggregate(aggregate_index, op, value)?;
816
817        Ok(Self::from_inner(inner))
818    }
819
820    /// Set the access path to a single primary key lookup.
821    pub(crate) fn by_id(self, id: E::Key) -> Self {
822        let Self { inner, .. } = self;
823
824        Self::from_inner(inner.by_id(id.to_value()))
825    }
826
827    /// Set the access path to a primary key batch lookup.
828    pub(crate) fn by_ids<I>(self, ids: I) -> Self
829    where
830        I: IntoIterator<Item = E::Key>,
831    {
832        let Self { inner, .. } = self;
833
834        Self::from_inner(inner.by_ids(ids.into_iter().map(|id| id.to_value())))
835    }
836
837    /// Mark this intent as a delete query.
838    #[must_use]
839    pub fn delete(mut self) -> Self {
840        self.inner = self.inner.delete();
841        self
842    }
843
844    /// Apply a limit to the current mode.
845    ///
846    /// Load limits bound result size; delete limits bound mutation size.
847    /// For scalar load queries, any use of `limit` or `offset` requires an
848    /// explicit `order_by(...)` so pagination is deterministic.
849    /// GROUP BY queries use canonical grouped-key order by default.
850    #[must_use]
851    pub fn limit(mut self, limit: u32) -> Self {
852        self.inner = self.inner.limit(limit);
853        self
854    }
855
856    /// Apply an offset to the current mode.
857    ///
858    /// Scalar load pagination requires an explicit `order_by(...)`.
859    /// GROUP BY queries use canonical grouped-key order by default.
860    /// Delete mode applies this after ordering and predicate filtering.
861    #[must_use]
862    pub fn offset(mut self, offset: u32) -> Self {
863        self.inner = self.inner.offset(offset);
864        self
865    }
866
867    /// Explain this intent without executing it.
868    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
869        let plan = self.planned()?;
870
871        Ok(plan.explain())
872    }
873
874    /// Return a stable plan hash for this intent.
875    ///
876    /// The hash is derived from canonical planner contracts and is suitable
877    /// for diagnostics, explain diffing, and cache key construction.
878    pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
879        let plan = self.inner.build_plan()?;
880
881        Ok(plan.fingerprint().to_string())
882    }
883
884    /// Explain executor-selected load execution shape without running it.
885    pub fn explain_execution(&self) -> Result<ExplainExecutionNodeDescriptor, QueryError>
886    where
887        E: EntityValue,
888    {
889        self.inner.explain_execution()
890    }
891
892    pub(in crate::db) fn explain_execution_with_visible_indexes(
893        &self,
894        visible_indexes: &VisibleIndexes<'_>,
895    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
896    where
897        E: EntityValue,
898    {
899        self.inner
900            .explain_execution_with_visible_indexes(visible_indexes)
901    }
902
903    /// Explain executor-selected load execution shape as deterministic text.
904    pub fn explain_execution_text(&self) -> Result<String, QueryError>
905    where
906        E: EntityValue,
907    {
908        self.inner.explain_execution_text()
909    }
910
911    pub(in crate::db) fn explain_execution_text_with_visible_indexes(
912        &self,
913        visible_indexes: &VisibleIndexes<'_>,
914    ) -> Result<String, QueryError>
915    where
916        E: EntityValue,
917    {
918        self.inner
919            .explain_execution_text_with_visible_indexes(visible_indexes)
920    }
921
922    /// Explain executor-selected load execution shape as canonical JSON.
923    pub fn explain_execution_json(&self) -> Result<String, QueryError>
924    where
925        E: EntityValue,
926    {
927        self.inner.explain_execution_json()
928    }
929
930    pub(in crate::db) fn explain_execution_json_with_visible_indexes(
931        &self,
932        visible_indexes: &VisibleIndexes<'_>,
933    ) -> Result<String, QueryError>
934    where
935        E: EntityValue,
936    {
937        self.inner
938            .explain_execution_json_with_visible_indexes(visible_indexes)
939    }
940
941    /// Explain executor-selected load execution shape with route diagnostics.
942    #[inline(never)]
943    pub fn explain_execution_verbose(&self) -> Result<String, QueryError>
944    where
945        E: EntityValue,
946    {
947        self.inner.explain_execution_verbose()
948    }
949
950    // Build one aggregate-terminal explain payload without executing the query.
951    #[cfg(test)]
952    #[inline(never)]
953    pub(in crate::db) fn explain_aggregate_terminal(
954        &self,
955        aggregate: AggregateExpr,
956    ) -> Result<ExplainAggregateTerminalPlan, QueryError>
957    where
958        E: EntityValue,
959    {
960        self.inner.explain_aggregate_terminal_with_visible_indexes(
961            &VisibleIndexes::schema_owned(E::MODEL.indexes()),
962            AggregateRouteShape::new_from_fields(
963                aggregate.kind(),
964                aggregate.target_field(),
965                E::MODEL.fields(),
966                E::MODEL.primary_key().name(),
967            ),
968        )
969    }
970
971    pub(in crate::db) fn explain_execution_verbose_with_visible_indexes(
972        &self,
973        visible_indexes: &VisibleIndexes<'_>,
974    ) -> Result<String, QueryError>
975    where
976        E: EntityValue,
977    {
978        self.inner
979            .explain_execution_verbose_with_visible_indexes(visible_indexes)
980    }
981
982    pub(in crate::db) fn explain_prepared_aggregate_terminal_with_visible_indexes<S>(
983        &self,
984        visible_indexes: &VisibleIndexes<'_>,
985        strategy: &S,
986    ) -> Result<ExplainAggregateTerminalPlan, QueryError>
987    where
988        E: EntityValue,
989        S: PreparedFluentAggregateExplainStrategy,
990    {
991        self.inner
992            .explain_prepared_aggregate_terminal_with_visible_indexes(visible_indexes, strategy)
993    }
994
995    pub(in crate::db) fn explain_bytes_by_with_visible_indexes(
996        &self,
997        visible_indexes: &VisibleIndexes<'_>,
998        target_field: &str,
999    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
1000    where
1001        E: EntityValue,
1002    {
1003        let executable = self
1004            .plan_with_visible_indexes(visible_indexes)?
1005            .into_prepared_execution_plan();
1006        let mut descriptor = executable
1007            .explain_load_execution_node_descriptor()
1008            .map_err(QueryError::execute)?;
1009        let projection_mode = executable.bytes_by_projection_mode(target_field);
1010        let projection_mode_label =
1011            PreparedExecutionPlan::<E>::bytes_by_projection_mode_label(projection_mode);
1012
1013        descriptor
1014            .node_properties
1015            .insert("terminal", Value::from("bytes_by"));
1016        descriptor
1017            .node_properties
1018            .insert("terminal_field", Value::from(target_field.to_string()));
1019        descriptor.node_properties.insert(
1020            "terminal_projection_mode",
1021            Value::from(projection_mode_label),
1022        );
1023        descriptor.node_properties.insert(
1024            "terminal_index_only",
1025            Value::from(matches!(
1026                projection_mode,
1027                BytesByProjectionMode::CoveringIndex | BytesByProjectionMode::CoveringConstant
1028            )),
1029        );
1030
1031        Ok(descriptor)
1032    }
1033
1034    pub(in crate::db) fn explain_prepared_projection_terminal_with_visible_indexes(
1035        &self,
1036        visible_indexes: &VisibleIndexes<'_>,
1037        strategy: &PreparedFluentProjectionStrategy,
1038    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
1039    where
1040        E: EntityValue,
1041    {
1042        let executable = self
1043            .plan_with_visible_indexes(visible_indexes)?
1044            .into_prepared_execution_plan();
1045        let mut descriptor = executable
1046            .explain_load_execution_node_descriptor()
1047            .map_err(QueryError::execute)?;
1048        let projection_descriptor = strategy.explain_descriptor();
1049
1050        descriptor.node_properties.insert(
1051            "terminal",
1052            Value::from(projection_descriptor.terminal_label()),
1053        );
1054        descriptor.node_properties.insert(
1055            "terminal_field",
1056            Value::from(projection_descriptor.field_label().to_string()),
1057        );
1058        descriptor.node_properties.insert(
1059            "terminal_output",
1060            Value::from(projection_descriptor.output_label()),
1061        );
1062
1063        Ok(descriptor)
1064    }
1065
1066    /// Plan this intent into a neutral planned query contract.
1067    pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
1068        let plan = self.build_plan_for_visibility(None)?;
1069
1070        Ok(Self::planned_query_from_plan(plan))
1071    }
1072
1073    pub(in crate::db) fn planned_with_visible_indexes(
1074        &self,
1075        visible_indexes: &VisibleIndexes<'_>,
1076    ) -> Result<PlannedQuery<E>, QueryError> {
1077        let plan = self.build_plan_for_visibility(Some(visible_indexes))?;
1078
1079        Ok(Self::planned_query_from_plan(plan))
1080    }
1081
1082    /// Compile this intent into query-owned handoff state.
1083    ///
1084    /// This boundary intentionally does not expose executor runtime shape.
1085    pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
1086        let plan = self.build_plan_for_visibility(None)?;
1087
1088        Ok(Self::compiled_query_from_plan(plan))
1089    }
1090
1091    pub(in crate::db) fn plan_with_visible_indexes(
1092        &self,
1093        visible_indexes: &VisibleIndexes<'_>,
1094    ) -> Result<CompiledQuery<E>, QueryError> {
1095        let plan = self.build_plan_for_visibility(Some(visible_indexes))?;
1096
1097        Ok(Self::compiled_query_from_plan(plan))
1098    }
1099}
1100
1101fn contains_execution_node_type(
1102    descriptor: &ExplainExecutionNodeDescriptor,
1103    target: ExplainExecutionNodeType,
1104) -> bool {
1105    descriptor.node_type() == target
1106        || descriptor
1107            .children()
1108            .iter()
1109            .any(|child| contains_execution_node_type(child, target))
1110}
1111
1112fn plan_order_pushdown_label(order_pushdown: &ExplainOrderPushdown) -> String {
1113    match order_pushdown {
1114        ExplainOrderPushdown::MissingModelContext => "missing_model_context".to_string(),
1115        ExplainOrderPushdown::EligibleSecondaryIndex { index, prefix_len } => {
1116            format!("eligible(index={index},prefix_len={prefix_len})",)
1117        }
1118        ExplainOrderPushdown::Rejected(reason) => format!("rejected({reason:?})"),
1119    }
1120}
1121
1122fn plan_predicate_pushdown_label(
1123    predicate: &ExplainPredicate,
1124    access: &ExplainAccessPath,
1125) -> String {
1126    let access_label = match access {
1127        ExplainAccessPath::ByKey { .. } => "by_key",
1128        ExplainAccessPath::ByKeys { keys } if keys.is_empty() => "empty_access_contract",
1129        ExplainAccessPath::ByKeys { .. } => "by_keys",
1130        ExplainAccessPath::KeyRange { .. } => "key_range",
1131        ExplainAccessPath::IndexPrefix { .. } => "index_prefix",
1132        ExplainAccessPath::IndexMultiLookup { .. } => "index_multi_lookup",
1133        ExplainAccessPath::IndexRange { .. } => "index_range",
1134        ExplainAccessPath::FullScan => "full_scan",
1135        ExplainAccessPath::Union(_) => "union",
1136        ExplainAccessPath::Intersection(_) => "intersection",
1137    };
1138    if matches!(predicate, ExplainPredicate::None) {
1139        return "none".to_string();
1140    }
1141    if matches!(access, ExplainAccessPath::FullScan) {
1142        if explain_predicate_contains_non_strict_compare(predicate) {
1143            return "fallback(non_strict_compare_coercion)".to_string();
1144        }
1145        if explain_predicate_contains_empty_prefix_starts_with(predicate) {
1146            return "fallback(starts_with_empty_prefix)".to_string();
1147        }
1148        if explain_predicate_contains_is_null(predicate) {
1149            return "fallback(is_null_full_scan)".to_string();
1150        }
1151        if explain_predicate_contains_text_scan_operator(predicate) {
1152            return "fallback(text_operator_full_scan)".to_string();
1153        }
1154
1155        return format!("fallback({access_label})");
1156    }
1157
1158    format!("applied({access_label})")
1159}
1160
1161fn explain_predicate_contains_non_strict_compare(predicate: &ExplainPredicate) -> bool {
1162    match predicate {
1163        ExplainPredicate::Compare { coercion, .. } => coercion.id != CoercionId::Strict,
1164        ExplainPredicate::And(children) | ExplainPredicate::Or(children) => children
1165            .iter()
1166            .any(explain_predicate_contains_non_strict_compare),
1167        ExplainPredicate::Not(inner) => explain_predicate_contains_non_strict_compare(inner),
1168        ExplainPredicate::None
1169        | ExplainPredicate::True
1170        | ExplainPredicate::False
1171        | ExplainPredicate::IsNull { .. }
1172        | ExplainPredicate::IsNotNull { .. }
1173        | ExplainPredicate::IsMissing { .. }
1174        | ExplainPredicate::IsEmpty { .. }
1175        | ExplainPredicate::IsNotEmpty { .. }
1176        | ExplainPredicate::TextContains { .. }
1177        | ExplainPredicate::TextContainsCi { .. } => false,
1178    }
1179}
1180
1181fn explain_predicate_contains_is_null(predicate: &ExplainPredicate) -> bool {
1182    match predicate {
1183        ExplainPredicate::IsNull { .. } => true,
1184        ExplainPredicate::And(children) | ExplainPredicate::Or(children) => {
1185            children.iter().any(explain_predicate_contains_is_null)
1186        }
1187        ExplainPredicate::Not(inner) => explain_predicate_contains_is_null(inner),
1188        ExplainPredicate::None
1189        | ExplainPredicate::True
1190        | ExplainPredicate::False
1191        | ExplainPredicate::Compare { .. }
1192        | ExplainPredicate::IsNotNull { .. }
1193        | ExplainPredicate::IsMissing { .. }
1194        | ExplainPredicate::IsEmpty { .. }
1195        | ExplainPredicate::IsNotEmpty { .. }
1196        | ExplainPredicate::TextContains { .. }
1197        | ExplainPredicate::TextContainsCi { .. } => false,
1198    }
1199}
1200
1201fn explain_predicate_contains_empty_prefix_starts_with(predicate: &ExplainPredicate) -> bool {
1202    match predicate {
1203        ExplainPredicate::Compare {
1204            op: CompareOp::StartsWith,
1205            value: Value::Text(prefix),
1206            ..
1207        } => prefix.is_empty(),
1208        ExplainPredicate::And(children) | ExplainPredicate::Or(children) => children
1209            .iter()
1210            .any(explain_predicate_contains_empty_prefix_starts_with),
1211        ExplainPredicate::Not(inner) => explain_predicate_contains_empty_prefix_starts_with(inner),
1212        ExplainPredicate::None
1213        | ExplainPredicate::True
1214        | ExplainPredicate::False
1215        | ExplainPredicate::Compare { .. }
1216        | ExplainPredicate::IsNull { .. }
1217        | ExplainPredicate::IsNotNull { .. }
1218        | ExplainPredicate::IsMissing { .. }
1219        | ExplainPredicate::IsEmpty { .. }
1220        | ExplainPredicate::IsNotEmpty { .. }
1221        | ExplainPredicate::TextContains { .. }
1222        | ExplainPredicate::TextContainsCi { .. } => false,
1223    }
1224}
1225
1226fn explain_predicate_contains_text_scan_operator(predicate: &ExplainPredicate) -> bool {
1227    match predicate {
1228        ExplainPredicate::Compare {
1229            op: CompareOp::EndsWith,
1230            ..
1231        }
1232        | ExplainPredicate::TextContains { .. }
1233        | ExplainPredicate::TextContainsCi { .. } => true,
1234        ExplainPredicate::And(children) | ExplainPredicate::Or(children) => children
1235            .iter()
1236            .any(explain_predicate_contains_text_scan_operator),
1237        ExplainPredicate::Not(inner) => explain_predicate_contains_text_scan_operator(inner),
1238        ExplainPredicate::Compare { .. }
1239        | ExplainPredicate::None
1240        | ExplainPredicate::True
1241        | ExplainPredicate::False
1242        | ExplainPredicate::IsNull { .. }
1243        | ExplainPredicate::IsNotNull { .. }
1244        | ExplainPredicate::IsMissing { .. }
1245        | ExplainPredicate::IsEmpty { .. }
1246        | ExplainPredicate::IsNotEmpty { .. } => false,
1247    }
1248}
1249
1250impl<E> Query<E>
1251where
1252    E: EntityKind + SingletonEntity,
1253    E::Key: Default,
1254{
1255    /// Set the access path to the singleton primary key.
1256    pub(crate) fn only(self) -> Self {
1257        let Self { inner, .. } = self;
1258
1259        Self::from_inner(inner.only(E::Key::default().to_value()))
1260    }
1261}