Skip to main content

icydb_core/db/query/intent/
query.rs

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