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