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