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