Skip to main content

icydb_core/db/query/intent/
query.rs

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