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