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