Skip to main content

icydb_core/db/query/fluent/load/
terminals.rs

1//! Module: query::fluent::load::terminals
2//! Responsibility: fluent load terminal APIs and terminal-plan explanation entrypoints.
3//! Does not own: planner semantic validation or executor runtime routing decisions.
4//! Boundary: delegates to session planning/execution and returns typed query results.
5//!
6//! Terminal Execution Model
7//!
8//! Fluent terminals are concrete descriptors with a 1:1 mapping to session
9//! execution and explain entrypoints. This module may orchestrate descriptor
10//! construction, non-paged gating, and public output shaping, but it must not
11//! carry terminal-kind enums, transport output enums, or match-based execution
12//! dispatch. Adding a new terminal means adding a new descriptor type and one
13//! direct `TerminalStrategyDriver` implementation for that descriptor.
14
15use std::num::NonZeroU32;
16
17#[cfg(feature = "diagnostics")]
18use crate::db::FluentTerminalExecutionAttribution;
19#[cfg(feature = "diagnostics")]
20use crate::db::query::read_intent::ReadIntentKind;
21use crate::{
22    db::{
23        DbSession, PersistedRow, Query,
24        query::{
25            admission::QueryAdmissionPolicy,
26            api::ResponseCardinalityExt,
27            builder::{
28                AvgBySlotTerminal, AvgDistinctBySlotTerminal, CountDistinctBySlotTerminal,
29                CountRowsTerminal, DistinctValuesBySlotTerminal, ExistsRowsTerminal,
30                FirstIdTerminal, FirstValueBySlotTerminal, LastIdTerminal, LastValueBySlotTerminal,
31                MaxIdBySlotTerminal, MaxIdTerminal, MedianIdBySlotTerminal, MinIdBySlotTerminal,
32                MinIdTerminal, MinMaxIdBySlotTerminal, NthIdBySlotTerminal, SumBySlotTerminal,
33                SumDistinctBySlotTerminal, ValueProjectionExpr, ValuesBySlotTerminal,
34                ValuesBySlotWithIdsTerminal,
35            },
36            explain::{ExplainAggregateTerminalPlan, ExplainExecutionNodeDescriptor},
37            fluent::load::{FluentLoadQuery, LoadQueryResult},
38            intent::{IntentError, QueryError},
39            plan::AggregateKind,
40            read_intent::{
41                COMPLETE_SMALL_EXECUTION_LIMIT, COMPLETE_SMALL_MAX_ROWS,
42                PUBLIC_PAGE_MAX_RESPONSE_BYTES,
43            },
44        },
45        response::EntityResponse,
46    },
47    traits::EntityValue,
48    types::{Decimal, Id},
49    value::{OutputValue, Value},
50};
51
52type MinMaxByIds<E> = Option<(Id<E>, Id<E>)>;
53
54///
55/// TerminalStrategyDriver
56///
57/// TerminalStrategyDriver is the fluent terminal wiring adapter between a
58/// query-owned strategy object and the session-owned execution/explain
59/// boundary. Implementations are deliberately thin: they only choose the
60/// matching `DbSession` method for an existing strategy type.
61///
62
63trait TerminalStrategyDriver<E: PersistedRow + EntityValue> {
64    type Output;
65    type ExplainOutput;
66
67    fn execute(
68        self,
69        session: &DbSession<E::Canister>,
70        query: &Query<E>,
71    ) -> Result<Self::Output, QueryError>;
72
73    fn explain(
74        &self,
75        session: &DbSession<E::Canister>,
76        query: &Query<E>,
77    ) -> Result<Self::ExplainOutput, QueryError>;
78}
79
80// Define one aggregate-style terminal driver implementation. The macro keeps
81// terminal descriptors 1:1 with session methods while removing repeated explain
82// wiring that is identical for every aggregate terminal.
83macro_rules! impl_aggregate_terminal_driver {
84    ($terminal:ty, $output:ty, $execute:ident) => {
85        impl<E> TerminalStrategyDriver<E> for $terminal
86        where
87            E: PersistedRow + EntityValue,
88        {
89            type Output = $output;
90            type ExplainOutput = ExplainAggregateTerminalPlan;
91
92            fn execute(
93                self,
94                session: &DbSession<E::Canister>,
95                query: &Query<E>,
96            ) -> Result<Self::Output, QueryError> {
97                session.$execute(query, self)
98            }
99
100            fn explain(
101                &self,
102                session: &DbSession<E::Canister>,
103                query: &Query<E>,
104            ) -> Result<Self::ExplainOutput, QueryError> {
105                session.explain_query_prepared_aggregate_terminal_with_visible_indexes(query, self)
106            }
107        }
108    };
109}
110
111// Define one projection-style terminal driver implementation. Projection
112// terminals share the execution/explain shape but return execution descriptors
113// rather than aggregate-terminal plans.
114macro_rules! impl_projection_terminal_driver {
115    ($terminal:ty, $output:ty, $execute:ident) => {
116        impl<E> TerminalStrategyDriver<E> for $terminal
117        where
118            E: PersistedRow + EntityValue,
119        {
120            type Output = $output;
121            type ExplainOutput = ExplainExecutionNodeDescriptor;
122
123            fn execute(
124                self,
125                session: &DbSession<E::Canister>,
126                query: &Query<E>,
127            ) -> Result<Self::Output, QueryError> {
128                session.$execute(query, self)
129            }
130
131            fn explain(
132                &self,
133                session: &DbSession<E::Canister>,
134                query: &Query<E>,
135            ) -> Result<Self::ExplainOutput, QueryError> {
136                session.explain_query_prepared_projection_terminal_with_visible_indexes(query, self)
137            }
138        }
139    };
140}
141
142impl_aggregate_terminal_driver!(CountRowsTerminal, u32, execute_fluent_count_rows_terminal);
143impl_aggregate_terminal_driver!(
144    ExistsRowsTerminal,
145    bool,
146    execute_fluent_exists_rows_terminal
147);
148impl_aggregate_terminal_driver!(MinIdTerminal, Option<Id<E>>, execute_fluent_min_id_terminal);
149impl_aggregate_terminal_driver!(MaxIdTerminal, Option<Id<E>>, execute_fluent_max_id_terminal);
150impl_aggregate_terminal_driver!(
151    MinIdBySlotTerminal,
152    Option<Id<E>>,
153    execute_fluent_min_id_by_slot
154);
155impl_aggregate_terminal_driver!(
156    MaxIdBySlotTerminal,
157    Option<Id<E>>,
158    execute_fluent_max_id_by_slot
159);
160impl_aggregate_terminal_driver!(
161    SumBySlotTerminal,
162    Option<Decimal>,
163    execute_fluent_sum_by_slot
164);
165impl_aggregate_terminal_driver!(
166    SumDistinctBySlotTerminal,
167    Option<Decimal>,
168    execute_fluent_sum_distinct_by_slot
169);
170impl_aggregate_terminal_driver!(
171    AvgBySlotTerminal,
172    Option<Decimal>,
173    execute_fluent_avg_by_slot
174);
175impl_aggregate_terminal_driver!(
176    AvgDistinctBySlotTerminal,
177    Option<Decimal>,
178    execute_fluent_avg_distinct_by_slot
179);
180impl_aggregate_terminal_driver!(
181    FirstIdTerminal,
182    Option<Id<E>>,
183    execute_fluent_first_id_terminal
184);
185impl_aggregate_terminal_driver!(
186    LastIdTerminal,
187    Option<Id<E>>,
188    execute_fluent_last_id_terminal
189);
190impl_aggregate_terminal_driver!(
191    NthIdBySlotTerminal,
192    Option<Id<E>>,
193    execute_fluent_nth_id_by_slot
194);
195impl_aggregate_terminal_driver!(
196    MedianIdBySlotTerminal,
197    Option<Id<E>>,
198    execute_fluent_median_id_by_slot
199);
200impl_aggregate_terminal_driver!(
201    MinMaxIdBySlotTerminal,
202    MinMaxByIds<E>,
203    execute_fluent_min_max_id_by_slot
204);
205
206impl_projection_terminal_driver!(
207    ValuesBySlotTerminal,
208    Vec<Value>,
209    execute_fluent_values_by_slot
210);
211impl_projection_terminal_driver!(
212    DistinctValuesBySlotTerminal,
213    Vec<Value>,
214    execute_fluent_distinct_values_by_slot
215);
216impl_projection_terminal_driver!(
217    CountDistinctBySlotTerminal,
218    u32,
219    execute_fluent_count_distinct_by_slot
220);
221impl_projection_terminal_driver!(
222    ValuesBySlotWithIdsTerminal,
223    Vec<(Id<E>, Value)>,
224    execute_fluent_values_by_with_ids_slot
225);
226impl_projection_terminal_driver!(
227    FirstValueBySlotTerminal,
228    Option<Value>,
229    execute_fluent_first_value_by_slot
230);
231impl_projection_terminal_driver!(
232    LastValueBySlotTerminal,
233    Option<Value>,
234    execute_fluent_last_value_by_slot
235);
236
237// Convert one runtime projection value into the public output boundary type.
238fn output(value: Value) -> OutputValue {
239    OutputValue::from(value)
240}
241
242// Convert one ordered runtime projection vector into the public output form.
243fn output_values(values: Vec<Value>) -> Vec<OutputValue> {
244    values.into_iter().map(output).collect()
245}
246
247// Convert one ordered runtime `(id, value)` projection vector into the public output form.
248fn output_values_with_ids<E: PersistedRow>(
249    values: Vec<(Id<E>, Value)>,
250) -> Vec<(Id<E>, OutputValue)> {
251    values
252        .into_iter()
253        .map(|(id, value)| (id, output(value)))
254        .collect()
255}
256
257fn non_zero_u32(value: u32) -> Result<NonZeroU32, QueryError> {
258    NonZeroU32::new(value).ok_or_else(QueryError::invariant)
259}
260
261impl<E> FluentLoadQuery<'_, E>
262where
263    E: PersistedRow,
264{
265    // ------------------------------------------------------------------
266    // Execution (single semantic boundary)
267    // ------------------------------------------------------------------
268
269    /// Execute this query using the session's policy settings.
270    pub fn execute(&self) -> Result<LoadQueryResult<E>, QueryError>
271    where
272        E: EntityValue,
273    {
274        self.with_admitted_non_paged(DbSession::execute_query_result)
275    }
276
277    /// Execute this query without the default bounded read-admission gate.
278    ///
279    /// This is for trusted maintenance/admin code that has its own caller
280    /// authorization and resource policy. Application-facing reads should use
281    /// `execute`.
282    pub fn execute_trusted(&self) -> Result<LoadQueryResult<E>, QueryError>
283    where
284        E: EntityValue,
285    {
286        self.with_non_paged(DbSession::execute_query_result)
287    }
288
289    /// Execute this query through the scalar rows-only session boundary.
290    pub fn execute_rows(&self) -> Result<EntityResponse<E>, QueryError>
291    where
292        E: EntityValue,
293    {
294        self.with_admitted_non_paged(DbSession::execute_scalar_query_rows)
295    }
296
297    /// Execute scalar rows without the default bounded read-admission gate.
298    ///
299    /// This is for trusted maintenance/admin code that has its own caller
300    /// authorization and resource policy. Application-facing reads should use
301    /// `execute_rows`.
302    pub fn execute_rows_trusted(&self) -> Result<EntityResponse<E>, QueryError>
303    where
304        E: EntityValue,
305    {
306        self.with_non_paged(DbSession::execute_scalar_query_rows)
307    }
308
309    fn with_admitted_non_paged<T>(
310        &self,
311        map: impl FnOnce(&DbSession<E::Canister>, &Query<E>) -> Result<T, QueryError>,
312    ) -> Result<T, QueryError>
313    where
314        E: EntityValue,
315    {
316        self.ensure_non_paged_mode_ready()?;
317        self.ensure_default_read_admission()?;
318        map(self.session, self.query())
319    }
320
321    // Run one terminal operation through the canonical non-paged fluent policy
322    // gate so execution and explain helpers cannot drift on readiness checks.
323    fn with_non_paged<T>(
324        &self,
325        map: impl FnOnce(&DbSession<E::Canister>, &Query<E>) -> Result<T, QueryError>,
326    ) -> Result<T, QueryError>
327    where
328        E: EntityValue,
329    {
330        self.ensure_non_paged_mode_ready()?;
331        map(self.session, self.query())
332    }
333
334    // Resolve the structural execution descriptor for this fluent load query
335    // through the session-owned visible-index explain path once.
336    fn explain_execution_descriptor(&self) -> Result<ExplainExecutionNodeDescriptor, QueryError>
337    where
338        E: EntityValue,
339    {
340        self.with_non_paged(DbSession::explain_query_execution_with_visible_indexes)
341    }
342
343    // Render one descriptor-derived execution surface so text/json explain
344    // terminals do not each forward the same session explain call ad hoc.
345    fn render_execution_descriptor(
346        &self,
347        render: impl FnOnce(ExplainExecutionNodeDescriptor) -> String,
348    ) -> Result<String, QueryError>
349    where
350        E: EntityValue,
351    {
352        let descriptor = self.explain_execution_descriptor()?;
353
354        Ok(render(descriptor))
355    }
356
357    // Execute one prepared terminal descriptor through the canonical
358    // non-paged fluent policy gate.
359    fn execute_terminal<S>(&self, strategy: S) -> Result<S::Output, QueryError>
360    where
361        E: EntityValue,
362        S: TerminalStrategyDriver<E>,
363    {
364        self.with_admitted_non_paged(|session, query| strategy.execute(session, query))
365    }
366
367    // Explain one prepared terminal strategy through the same non-paged fluent
368    // policy gate used by execution.
369    fn explain_terminal<S>(&self, strategy: &S) -> Result<S::ExplainOutput, QueryError>
370    where
371        E: EntityValue,
372        S: TerminalStrategyDriver<E>,
373    {
374        self.with_non_paged(|session, query| strategy.explain(session, query))
375    }
376
377    fn ensure_exists_intent_owns_limit(&self) -> Result<(), QueryError> {
378        self.ensure_semantic_terminal_owns_limit(IntentError::raw_limit_before_exists_terminal())
379    }
380
381    fn ensure_count_exact_intent_owns_limit(&self) -> Result<(), QueryError> {
382        self.ensure_semantic_terminal_owns_limit(
383            IntentError::raw_limit_before_count_exact_terminal(),
384        )
385    }
386
387    fn ensure_sum_exact_intent_owns_limit(&self) -> Result<(), QueryError> {
388        self.ensure_semantic_terminal_owns_limit(IntentError::raw_limit_before_sum_exact_terminal())
389    }
390
391    fn ensure_collect_complete_intent_owns_limit(&self) -> Result<(), QueryError> {
392        self.ensure_semantic_terminal_owns_limit(
393            IntentError::raw_limit_before_collect_complete_terminal(),
394        )
395    }
396
397    // ------------------------------------------------------------------
398    // Execution terminals — semantic only
399    // ------------------------------------------------------------------
400
401    /// Execute and return whether the result set is empty.
402    pub fn is_empty(&self) -> Result<bool, QueryError>
403    where
404        E: EntityValue,
405    {
406        self.not_exists()
407    }
408
409    /// Execute and return whether no matching row exists.
410    pub fn not_exists(&self) -> Result<bool, QueryError>
411    where
412        E: EntityValue,
413    {
414        Ok(!self.exists()?)
415    }
416
417    /// Execute and return whether at least one matching row exists.
418    pub fn exists(&self) -> Result<bool, QueryError>
419    where
420        E: EntityValue,
421    {
422        self.ensure_exists_intent_owns_limit()?;
423
424        self.execute_terminal(ExistsRowsTerminal::new())
425    }
426
427    /// Execute and return whether at least one matching row exists with
428    /// terminal attribution.
429    #[cfg(feature = "diagnostics")]
430    #[doc(hidden)]
431    pub fn exists_with_attribution(
432        &self,
433    ) -> Result<(bool, FluentTerminalExecutionAttribution), QueryError>
434    where
435        E: EntityValue,
436    {
437        self.ensure_exists_intent_owns_limit()?;
438
439        let (exists, attribution) = self.with_admitted_non_paged(|session, query| {
440            session.execute_fluent_exists_rows_terminal_with_attribution(
441                query,
442                ExistsRowsTerminal::new(),
443            )
444        })?;
445
446        Ok((
447            exists,
448            attribution.with_read_intent(ReadIntentKind::ExistenceCheck),
449        ))
450    }
451
452    /// Explain scalar `exists()` routing without executing the terminal.
453    pub fn explain_exists(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
454    where
455        E: EntityValue,
456    {
457        self.ensure_exists_intent_owns_limit()?;
458
459        self.explain_terminal(&ExistsRowsTerminal::new())
460    }
461
462    /// Execute and return all matching rows if the complete result fits in
463    /// the default public-read small-set cap.
464    ///
465    /// This semantic terminal rejects a prior raw `limit(...)`. It owns an
466    /// internal lookahead limit so it can distinguish a complete small set
467    /// from a silently truncated row window.
468    pub fn collect_complete(&self) -> Result<Vec<E>, QueryError>
469    where
470        E: EntityValue,
471    {
472        self.ensure_collect_complete_intent_owns_limit()?;
473        self.ensure_non_paged_mode_ready()?;
474
475        let query = self.query().with_load_limit(COMPLETE_SMALL_EXECUTION_LIMIT);
476        let policy = QueryAdmissionPolicy::public_read(
477            non_zero_u32(COMPLETE_SMALL_EXECUTION_LIMIT)?,
478            non_zero_u32(PUBLIC_PAGE_MAX_RESPONSE_BYTES)?,
479        );
480
481        self.session
482            .ensure_query_read_admission_policy(&query, &policy)?;
483
484        let response = self.session.execute_scalar_query_rows(&query)?;
485        if response.count() > COMPLETE_SMALL_MAX_ROWS {
486            return Err(QueryError::intent(
487                IntentError::complete_read_too_many_rows(),
488            ));
489        }
490
491        Ok(response.entities())
492    }
493
494    /// Explain scalar `not_exists()` routing without executing the terminal.
495    ///
496    /// This remains an `exists()` execution plan with negated boolean semantics.
497    pub fn explain_not_exists(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
498    where
499        E: EntityValue,
500    {
501        self.explain_exists()
502    }
503
504    /// Explain scalar load execution shape without executing the query.
505    pub fn explain_execution(&self) -> Result<ExplainExecutionNodeDescriptor, QueryError>
506    where
507        E: EntityValue,
508    {
509        self.explain_execution_descriptor()
510    }
511
512    /// Explain scalar load execution shape as deterministic text.
513    pub fn explain_execution_text(&self) -> Result<String, QueryError>
514    where
515        E: EntityValue,
516    {
517        self.render_execution_descriptor(|descriptor| descriptor.render_text_tree())
518    }
519
520    /// Explain scalar load execution shape as canonical JSON.
521    pub fn explain_execution_json(&self) -> Result<String, QueryError>
522    where
523        E: EntityValue,
524    {
525        self.with_non_paged(DbSession::explain_query_execution_json_with_visible_indexes)
526    }
527
528    /// Explain scalar load execution shape as verbose text with diagnostics.
529    pub fn explain_execution_verbose(&self) -> Result<String, QueryError>
530    where
531        E: EntityValue,
532    {
533        self.with_non_paged(DbSession::explain_query_execution_verbose_with_visible_indexes)
534    }
535
536    /// Execute and return the number of matching rows.
537    pub fn count(&self) -> Result<u32, QueryError>
538    where
539        E: EntityValue,
540    {
541        self.execute_terminal(CountRowsTerminal::new())
542    }
543
544    /// Execute and return the exact number of matching rows.
545    ///
546    /// Unlike `count()`, this semantic aggregate rejects a prior raw
547    /// `limit(...)` so exact counts cannot accidentally mean "count the first
548    /// N rows."
549    pub fn count_exact(&self) -> Result<u32, QueryError>
550    where
551        E: EntityValue,
552    {
553        self.ensure_count_exact_intent_owns_limit()?;
554
555        self.execute_terminal(CountRowsTerminal::new())
556    }
557
558    /// Execute and return the number of matching rows with terminal attribution.
559    #[cfg(feature = "diagnostics")]
560    #[doc(hidden)]
561    pub fn count_with_attribution(
562        &self,
563    ) -> Result<(u32, FluentTerminalExecutionAttribution), QueryError>
564    where
565        E: EntityValue,
566    {
567        let (count, attribution) = self.with_admitted_non_paged(|session, query| {
568            session.execute_fluent_count_rows_terminal_with_attribution(
569                query,
570                CountRowsTerminal::new(),
571            )
572        })?;
573
574        Ok((
575            count,
576            attribution.with_read_intent(ReadIntentKind::BoundedRowWindow),
577        ))
578    }
579
580    /// Execute and return the exact number of matching rows with terminal attribution.
581    #[cfg(feature = "diagnostics")]
582    #[doc(hidden)]
583    pub fn count_exact_with_attribution(
584        &self,
585    ) -> Result<(u32, FluentTerminalExecutionAttribution), QueryError>
586    where
587        E: EntityValue,
588    {
589        self.ensure_count_exact_intent_owns_limit()?;
590
591        let (count, attribution) = self.with_admitted_non_paged(|session, query| {
592            session.execute_fluent_count_rows_terminal_with_attribution(
593                query,
594                CountRowsTerminal::new(),
595            )
596        })?;
597
598        Ok((
599            count,
600            attribution.with_read_intent(ReadIntentKind::ExactAggregate),
601        ))
602    }
603
604    /// Execute and return the total persisted payload bytes for the effective
605    /// result window.
606    pub fn bytes(&self) -> Result<u64, QueryError>
607    where
608        E: EntityValue,
609    {
610        self.with_admitted_non_paged(DbSession::execute_fluent_bytes)
611    }
612
613    /// Execute and return the total serialized bytes for `field` over the
614    /// effective result window.
615    pub fn bytes_by(&self, field: impl AsRef<str>) -> Result<u64, QueryError>
616    where
617        E: EntityValue,
618    {
619        let target_slot = self.resolve_non_paged_slot(field)?;
620
621        self.with_admitted_non_paged(|session, query| {
622            session.execute_fluent_bytes_by_slot(query, target_slot)
623        })
624    }
625
626    /// Explain `bytes_by(field)` routing without executing the terminal.
627    pub fn explain_bytes_by(
628        &self,
629        field: impl AsRef<str>,
630    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
631    where
632        E: EntityValue,
633    {
634        let target_slot = self.resolve_non_paged_slot(field)?;
635
636        self.with_non_paged(|session, query| {
637            session.explain_query_bytes_by_with_visible_indexes(query, target_slot.field())
638        })
639    }
640
641    /// Execute and return the smallest matching identifier, if any.
642    pub fn min(&self) -> Result<Option<Id<E>>, QueryError>
643    where
644        E: EntityValue,
645    {
646        self.execute_terminal(MinIdTerminal::new())
647    }
648
649    /// Explain scalar `min()` routing without executing the terminal.
650    pub fn explain_min(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
651    where
652        E: EntityValue,
653    {
654        self.explain_terminal(&MinIdTerminal::new())
655    }
656
657    /// Execute and return the id of the row with the smallest value for `field`.
658    ///
659    /// Ties are deterministic: equal field values resolve by primary key ascending.
660    pub fn min_by(&self, field: impl AsRef<str>) -> Result<Option<Id<E>>, QueryError>
661    where
662        E: EntityValue,
663    {
664        let target_slot = self.resolve_non_paged_slot(field)?;
665
666        self.execute_terminal(MinIdBySlotTerminal::new(target_slot))
667    }
668
669    /// Execute and return the largest matching identifier, if any.
670    pub fn max(&self) -> Result<Option<Id<E>>, QueryError>
671    where
672        E: EntityValue,
673    {
674        self.execute_terminal(MaxIdTerminal::new())
675    }
676
677    /// Explain scalar `max()` routing without executing the terminal.
678    pub fn explain_max(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
679    where
680        E: EntityValue,
681    {
682        self.explain_terminal(&MaxIdTerminal::new())
683    }
684
685    /// Execute and return the id of the row with the largest value for `field`.
686    ///
687    /// Ties are deterministic: equal field values resolve by primary key ascending.
688    pub fn max_by(&self, field: impl AsRef<str>) -> Result<Option<Id<E>>, QueryError>
689    where
690        E: EntityValue,
691    {
692        let target_slot = self.resolve_non_paged_slot(field)?;
693
694        self.execute_terminal(MaxIdBySlotTerminal::new(target_slot))
695    }
696
697    /// Execute and return the id at zero-based ordinal `nth` when rows are
698    /// ordered by `field` ascending, with primary-key ascending tie-breaks.
699    pub fn nth_by(&self, field: impl AsRef<str>, nth: usize) -> Result<Option<Id<E>>, QueryError>
700    where
701        E: EntityValue,
702    {
703        let target_slot = self.resolve_non_paged_slot(field)?;
704
705        self.execute_terminal(NthIdBySlotTerminal::new(target_slot, nth))
706    }
707
708    /// Execute and return the sum of `field` over matching rows.
709    pub fn sum_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
710    where
711        E: EntityValue,
712    {
713        let target_slot = self.resolve_non_paged_slot(field)?;
714
715        self.execute_terminal(SumBySlotTerminal::new(target_slot))
716    }
717
718    /// Execute and return the exact sum of `field` over matching rows.
719    ///
720    /// Unlike `sum_by(...)`, this semantic aggregate rejects a prior raw
721    /// `limit(...)` so exact sums cannot accidentally mean "sum the first N
722    /// rows."
723    pub fn sum_exact(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
724    where
725        E: EntityValue,
726    {
727        self.ensure_sum_exact_intent_owns_limit()?;
728
729        let target_slot = self.resolve_non_paged_slot(field)?;
730
731        self.execute_terminal(SumBySlotTerminal::new(target_slot))
732    }
733
734    /// Explain scalar `sum_by(field)` routing without executing the terminal.
735    pub fn explain_sum_by(
736        &self,
737        field: impl AsRef<str>,
738    ) -> Result<ExplainAggregateTerminalPlan, QueryError>
739    where
740        E: EntityValue,
741    {
742        let target_slot = self.resolve_non_paged_slot(field)?;
743
744        self.explain_terminal(&SumBySlotTerminal::new(target_slot))
745    }
746
747    /// Execute and return the sum of distinct `field` values.
748    pub fn sum_distinct_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
749    where
750        E: EntityValue,
751    {
752        let target_slot = self.resolve_non_paged_slot(field)?;
753
754        self.execute_terminal(SumDistinctBySlotTerminal::new(target_slot))
755    }
756
757    /// Explain scalar `sum(distinct field)` routing without executing the terminal.
758    pub fn explain_sum_distinct_by(
759        &self,
760        field: impl AsRef<str>,
761    ) -> Result<ExplainAggregateTerminalPlan, QueryError>
762    where
763        E: EntityValue,
764    {
765        let target_slot = self.resolve_non_paged_slot(field)?;
766
767        self.explain_terminal(&SumDistinctBySlotTerminal::new(target_slot))
768    }
769
770    /// Execute and return the average of `field` over matching rows.
771    pub fn avg_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
772    where
773        E: EntityValue,
774    {
775        let target_slot = self.resolve_non_paged_slot(field)?;
776
777        self.execute_terminal(AvgBySlotTerminal::new(target_slot))
778    }
779
780    /// Explain scalar `avg_by(field)` routing without executing the terminal.
781    pub fn explain_avg_by(
782        &self,
783        field: impl AsRef<str>,
784    ) -> Result<ExplainAggregateTerminalPlan, QueryError>
785    where
786        E: EntityValue,
787    {
788        let target_slot = self.resolve_non_paged_slot(field)?;
789
790        self.explain_terminal(&AvgBySlotTerminal::new(target_slot))
791    }
792
793    /// Execute and return the average of distinct `field` values.
794    pub fn avg_distinct_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
795    where
796        E: EntityValue,
797    {
798        let target_slot = self.resolve_non_paged_slot(field)?;
799
800        self.execute_terminal(AvgDistinctBySlotTerminal::new(target_slot))
801    }
802
803    /// Explain scalar `avg(distinct field)` routing without executing the terminal.
804    pub fn explain_avg_distinct_by(
805        &self,
806        field: impl AsRef<str>,
807    ) -> Result<ExplainAggregateTerminalPlan, QueryError>
808    where
809        E: EntityValue,
810    {
811        let target_slot = self.resolve_non_paged_slot(field)?;
812
813        self.explain_terminal(&AvgDistinctBySlotTerminal::new(target_slot))
814    }
815
816    /// Execute and return the median id by `field` using deterministic ordering
817    /// `(field asc, primary key asc)`.
818    ///
819    /// Even-length windows select the lower median.
820    pub fn median_by(&self, field: impl AsRef<str>) -> Result<Option<Id<E>>, QueryError>
821    where
822        E: EntityValue,
823    {
824        let target_slot = self.resolve_non_paged_slot(field)?;
825
826        self.execute_terminal(MedianIdBySlotTerminal::new(target_slot))
827    }
828
829    /// Execute and return the number of distinct values for `field` over the
830    /// effective result window.
831    pub fn count_distinct_by(&self, field: impl AsRef<str>) -> Result<u32, QueryError>
832    where
833        E: EntityValue,
834    {
835        let target_slot = self.resolve_non_paged_slot(field)?;
836
837        self.execute_terminal(CountDistinctBySlotTerminal::new(target_slot))
838    }
839
840    /// Explain `count_distinct_by(field)` routing without executing the terminal.
841    pub fn explain_count_distinct_by(
842        &self,
843        field: impl AsRef<str>,
844    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
845    where
846        E: EntityValue,
847    {
848        let target_slot = self.resolve_non_paged_slot(field)?;
849
850        self.explain_terminal(&CountDistinctBySlotTerminal::new(target_slot))
851    }
852
853    /// Execute and return both `(min_by(field), max_by(field))` in one terminal.
854    ///
855    /// Tie handling is deterministic for both extrema: primary key ascending.
856    pub fn min_max_by(&self, field: impl AsRef<str>) -> Result<MinMaxByIds<E>, QueryError>
857    where
858        E: EntityValue,
859    {
860        let target_slot = self.resolve_non_paged_slot(field)?;
861
862        self.execute_terminal(MinMaxIdBySlotTerminal::new(target_slot))
863    }
864
865    /// Execute and return projected field values for the effective result window.
866    pub fn values_by(&self, field: impl AsRef<str>) -> Result<Vec<OutputValue>, QueryError>
867    where
868        E: EntityValue,
869    {
870        let target_slot = self.resolve_non_paged_slot(field)?;
871
872        self.execute_terminal(ValuesBySlotTerminal::new(target_slot))
873            .map(output_values)
874    }
875
876    /// Execute and return projected values for one shared bounded projection
877    /// over the effective response window.
878    pub fn project_values<P>(&self, projection: &P) -> Result<Vec<OutputValue>, QueryError>
879    where
880        E: EntityValue,
881        P: ValueProjectionExpr,
882    {
883        let target_slot = self.resolve_non_paged_slot(projection.field())?;
884
885        self.with_admitted_non_paged(|session, query| {
886            session.execute_fluent_project_values_by_slot(
887                query,
888                target_slot,
889                projection.projection_plan().into_expr(),
890            )
891        })
892        .map(output_values)
893    }
894
895    /// Explain `project_values(projection)` routing without executing it.
896    pub fn explain_project_values<P>(
897        &self,
898        projection: &P,
899    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
900    where
901        E: EntityValue,
902        P: ValueProjectionExpr,
903    {
904        let target_slot = self.resolve_non_paged_slot(projection.field())?;
905
906        self.explain_terminal(&ValuesBySlotTerminal::new(target_slot))
907    }
908
909    /// Explain `values_by(field)` routing without executing the terminal.
910    pub fn explain_values_by(
911        &self,
912        field: impl AsRef<str>,
913    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
914    where
915        E: EntityValue,
916    {
917        let target_slot = self.resolve_non_paged_slot(field)?;
918
919        self.explain_terminal(&ValuesBySlotTerminal::new(target_slot))
920    }
921
922    /// Execute and return the first `k` rows from the effective response window.
923    pub fn take(&self, take_count: u32) -> Result<EntityResponse<E>, QueryError>
924    where
925        E: EntityValue,
926    {
927        self.with_admitted_non_paged(|session, query| {
928            session.execute_fluent_take(query, take_count)
929        })
930    }
931
932    /// Execute and return the top `k` rows by `field` under deterministic
933    /// ordering `(field desc, primary_key asc)` over the effective response
934    /// window.
935    ///
936    /// This terminal applies its own ordering and does not preserve query
937    /// `order_term(...)` row order in the returned rows. For `k = 1`, this
938    /// matches `max_by(field)` selection semantics.
939    pub fn top_k_by(
940        &self,
941        field: impl AsRef<str>,
942        take_count: u32,
943    ) -> Result<EntityResponse<E>, QueryError>
944    where
945        E: EntityValue,
946    {
947        let target_slot = self.resolve_non_paged_slot(field)?;
948
949        self.with_admitted_non_paged(|session, query| {
950            session.execute_fluent_top_k_rows_by_slot(query, target_slot, take_count)
951        })
952    }
953
954    /// Execute and return the bottom `k` rows by `field` under deterministic
955    /// ordering `(field asc, primary_key asc)` over the effective response
956    /// window.
957    ///
958    /// This terminal applies its own ordering and does not preserve query
959    /// `order_term(...)` row order in the returned rows. For `k = 1`, this
960    /// matches `min_by(field)` selection semantics.
961    pub fn bottom_k_by(
962        &self,
963        field: impl AsRef<str>,
964        take_count: u32,
965    ) -> Result<EntityResponse<E>, QueryError>
966    where
967        E: EntityValue,
968    {
969        let target_slot = self.resolve_non_paged_slot(field)?;
970
971        self.with_admitted_non_paged(|session, query| {
972            session.execute_fluent_bottom_k_rows_by_slot(query, target_slot, take_count)
973        })
974    }
975
976    /// Execute and return projected values for the top `k` rows by `field`
977    /// under deterministic ordering `(field desc, primary_key asc)` over the
978    /// effective response window.
979    ///
980    /// Ranking is applied before projection and does not preserve query
981    /// `order_term(...)` row order in the returned values. For `k = 1`, this
982    /// matches `max_by(field)` projected to one value.
983    pub fn top_k_by_values(
984        &self,
985        field: impl AsRef<str>,
986        take_count: u32,
987    ) -> Result<Vec<OutputValue>, QueryError>
988    where
989        E: EntityValue,
990    {
991        let target_slot = self.resolve_non_paged_slot(field)?;
992
993        self.with_admitted_non_paged(|session, query| {
994            session
995                .execute_fluent_top_k_values_by_slot(query, target_slot, take_count)
996                .map(output_values)
997        })
998    }
999
1000    /// Execute and return projected values for the bottom `k` rows by `field`
1001    /// under deterministic ordering `(field asc, primary_key asc)` over the
1002    /// effective response window.
1003    ///
1004    /// Ranking is applied before projection and does not preserve query
1005    /// `order_term(...)` row order in the returned values. For `k = 1`, this
1006    /// matches `min_by(field)` projected to one value.
1007    pub fn bottom_k_by_values(
1008        &self,
1009        field: impl AsRef<str>,
1010        take_count: u32,
1011    ) -> Result<Vec<OutputValue>, QueryError>
1012    where
1013        E: EntityValue,
1014    {
1015        let target_slot = self.resolve_non_paged_slot(field)?;
1016
1017        self.with_admitted_non_paged(|session, query| {
1018            session
1019                .execute_fluent_bottom_k_values_by_slot(query, target_slot, take_count)
1020                .map(output_values)
1021        })
1022    }
1023
1024    /// Execute and return projected id/value pairs for the top `k` rows by
1025    /// `field` under deterministic ordering `(field desc, primary_key asc)`
1026    /// over the effective response window.
1027    ///
1028    /// Ranking is applied before projection and does not preserve query
1029    /// `order_term(...)` row order in the returned values. For `k = 1`, this
1030    /// matches `max_by(field)` projected to one `(id, value)` pair.
1031    pub fn top_k_by_with_ids(
1032        &self,
1033        field: impl AsRef<str>,
1034        take_count: u32,
1035    ) -> Result<Vec<(Id<E>, OutputValue)>, QueryError>
1036    where
1037        E: EntityValue,
1038    {
1039        let target_slot = self.resolve_non_paged_slot(field)?;
1040
1041        self.with_admitted_non_paged(|session, query| {
1042            session
1043                .execute_fluent_top_k_values_with_ids_by_slot(query, target_slot, take_count)
1044                .map(output_values_with_ids)
1045        })
1046    }
1047
1048    /// Execute and return projected id/value pairs for the bottom `k` rows by
1049    /// `field` under deterministic ordering `(field asc, primary_key asc)`
1050    /// over the effective response window.
1051    ///
1052    /// Ranking is applied before projection and does not preserve query
1053    /// `order_term(...)` row order in the returned values. For `k = 1`, this
1054    /// matches `min_by(field)` projected to one `(id, value)` pair.
1055    pub fn bottom_k_by_with_ids(
1056        &self,
1057        field: impl AsRef<str>,
1058        take_count: u32,
1059    ) -> Result<Vec<(Id<E>, OutputValue)>, QueryError>
1060    where
1061        E: EntityValue,
1062    {
1063        let target_slot = self.resolve_non_paged_slot(field)?;
1064
1065        self.with_admitted_non_paged(|session, query| {
1066            session
1067                .execute_fluent_bottom_k_values_with_ids_by_slot(query, target_slot, take_count)
1068                .map(output_values_with_ids)
1069        })
1070    }
1071
1072    /// Execute and return distinct projected field values for the effective
1073    /// result window, preserving first-observed value order.
1074    pub fn distinct_values_by(&self, field: impl AsRef<str>) -> Result<Vec<OutputValue>, QueryError>
1075    where
1076        E: EntityValue,
1077    {
1078        let target_slot = self.resolve_non_paged_slot(field)?;
1079
1080        self.execute_terminal(DistinctValuesBySlotTerminal::new(target_slot))
1081            .map(output_values)
1082    }
1083
1084    /// Explain `distinct_values_by(field)` routing without executing the terminal.
1085    pub fn explain_distinct_values_by(
1086        &self,
1087        field: impl AsRef<str>,
1088    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
1089    where
1090        E: EntityValue,
1091    {
1092        let target_slot = self.resolve_non_paged_slot(field)?;
1093
1094        self.explain_terminal(&DistinctValuesBySlotTerminal::new(target_slot))
1095    }
1096
1097    /// Execute and return projected field values paired with row ids for the
1098    /// effective result window.
1099    pub fn values_by_with_ids(
1100        &self,
1101        field: impl AsRef<str>,
1102    ) -> Result<Vec<(Id<E>, OutputValue)>, QueryError>
1103    where
1104        E: EntityValue,
1105    {
1106        let target_slot = self.resolve_non_paged_slot(field)?;
1107
1108        self.execute_terminal(ValuesBySlotWithIdsTerminal::new(target_slot))
1109            .map(output_values_with_ids)
1110    }
1111
1112    /// Execute and return projected id/value pairs for one shared bounded
1113    /// projection over the effective response window.
1114    pub fn project_values_with_ids<P>(
1115        &self,
1116        projection: &P,
1117    ) -> Result<Vec<(Id<E>, OutputValue)>, QueryError>
1118    where
1119        E: EntityValue,
1120        P: ValueProjectionExpr,
1121    {
1122        let target_slot = self.resolve_non_paged_slot(projection.field())?;
1123
1124        self.with_admitted_non_paged(|session, query| {
1125            session.execute_fluent_project_values_with_ids_by_slot(
1126                query,
1127                target_slot,
1128                projection.projection_plan().into_expr(),
1129            )
1130        })
1131        .map(output_values_with_ids)
1132    }
1133
1134    /// Explain `values_by_with_ids(field)` routing without executing the terminal.
1135    pub fn explain_values_by_with_ids(
1136        &self,
1137        field: impl AsRef<str>,
1138    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
1139    where
1140        E: EntityValue,
1141    {
1142        let target_slot = self.resolve_non_paged_slot(field)?;
1143
1144        self.explain_terminal(&ValuesBySlotWithIdsTerminal::new(target_slot))
1145    }
1146
1147    /// Execute and return the first projected field value in effective response
1148    /// order, if any.
1149    pub fn first_value_by(&self, field: impl AsRef<str>) -> Result<Option<OutputValue>, QueryError>
1150    where
1151        E: EntityValue,
1152    {
1153        let target_slot = self.resolve_non_paged_slot(field)?;
1154
1155        self.execute_terminal(FirstValueBySlotTerminal::new(target_slot))
1156            .map(|value| value.map(output))
1157    }
1158
1159    /// Execute and return the first projected value for one shared bounded
1160    /// projection in effective response order, if any.
1161    pub fn project_first_value<P>(&self, projection: &P) -> Result<Option<OutputValue>, QueryError>
1162    where
1163        E: EntityValue,
1164        P: ValueProjectionExpr,
1165    {
1166        let target_slot = self.resolve_non_paged_slot(projection.field())?;
1167
1168        self.with_admitted_non_paged(|session, query| {
1169            session.execute_fluent_project_terminal_value_by_slot(
1170                query,
1171                target_slot,
1172                AggregateKind::First,
1173                projection.projection_plan().into_expr(),
1174            )
1175        })
1176        .map(|value| value.map(output))
1177    }
1178
1179    /// Explain `first_value_by(field)` routing without executing the terminal.
1180    pub fn explain_first_value_by(
1181        &self,
1182        field: impl AsRef<str>,
1183    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
1184    where
1185        E: EntityValue,
1186    {
1187        let target_slot = self.resolve_non_paged_slot(field)?;
1188
1189        self.explain_terminal(&FirstValueBySlotTerminal::new(target_slot))
1190    }
1191
1192    /// Execute and return the last projected field value in effective response
1193    /// order, if any.
1194    pub fn last_value_by(&self, field: impl AsRef<str>) -> Result<Option<OutputValue>, QueryError>
1195    where
1196        E: EntityValue,
1197    {
1198        let target_slot = self.resolve_non_paged_slot(field)?;
1199
1200        self.execute_terminal(LastValueBySlotTerminal::new(target_slot))
1201            .map(|value| value.map(output))
1202    }
1203
1204    /// Execute and return the last projected value for one shared bounded
1205    /// projection in effective response order, if any.
1206    pub fn project_last_value<P>(&self, projection: &P) -> Result<Option<OutputValue>, QueryError>
1207    where
1208        E: EntityValue,
1209        P: ValueProjectionExpr,
1210    {
1211        let target_slot = self.resolve_non_paged_slot(projection.field())?;
1212
1213        self.with_admitted_non_paged(|session, query| {
1214            session.execute_fluent_project_terminal_value_by_slot(
1215                query,
1216                target_slot,
1217                AggregateKind::Last,
1218                projection.projection_plan().into_expr(),
1219            )
1220        })
1221        .map(|value| value.map(output))
1222    }
1223
1224    /// Explain `last_value_by(field)` routing without executing the terminal.
1225    pub fn explain_last_value_by(
1226        &self,
1227        field: impl AsRef<str>,
1228    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
1229    where
1230        E: EntityValue,
1231    {
1232        let target_slot = self.resolve_non_paged_slot(field)?;
1233
1234        self.explain_terminal(&LastValueBySlotTerminal::new(target_slot))
1235    }
1236
1237    /// Execute and return the first matching identifier in response order, if any.
1238    pub fn first(&self) -> Result<Option<Id<E>>, QueryError>
1239    where
1240        E: EntityValue,
1241    {
1242        self.execute_terminal(FirstIdTerminal::new())
1243    }
1244
1245    /// Explain scalar `first()` routing without executing the terminal.
1246    pub fn explain_first(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
1247    where
1248        E: EntityValue,
1249    {
1250        self.explain_terminal(&FirstIdTerminal::new())
1251    }
1252
1253    /// Execute and return the last matching identifier in response order, if any.
1254    pub fn last(&self) -> Result<Option<Id<E>>, QueryError>
1255    where
1256        E: EntityValue,
1257    {
1258        self.execute_terminal(LastIdTerminal::new())
1259    }
1260
1261    /// Explain scalar `last()` routing without executing the terminal.
1262    pub fn explain_last(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
1263    where
1264        E: EntityValue,
1265    {
1266        self.explain_terminal(&LastIdTerminal::new())
1267    }
1268
1269    /// Execute and require exactly one matching row.
1270    pub fn require_one(&self) -> Result<(), QueryError>
1271    where
1272        E: EntityValue,
1273    {
1274        self.execute_rows()?.require_one()?;
1275        Ok(())
1276    }
1277
1278    /// Execute and require at least one matching row.
1279    pub fn require_some(&self) -> Result<(), QueryError>
1280    where
1281        E: EntityValue,
1282    {
1283        self.execute_rows()?.require_some()?;
1284        Ok(())
1285    }
1286}