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