Skip to main content

icydb_core/db/query/intent/
query.rs

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