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