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