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