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
6use crate::{
7    db::{
8        PersistedRow,
9        executor::{
10            ExecutablePlan, LoadExecutor, ScalarNumericFieldBoundaryRequest,
11            ScalarProjectionBoundaryRequest, ScalarTerminalBoundaryOutput,
12            ScalarTerminalBoundaryRequest,
13        },
14        query::{
15            api::ResponseCardinalityExt,
16            builder::{
17                PreparedFluentNumericFieldRuntimeRequest, PreparedFluentNumericFieldStrategy,
18                PreparedFluentOrderSensitiveTerminalRuntimeRequest,
19                PreparedFluentOrderSensitiveTerminalStrategy,
20                PreparedFluentProjectionRuntimeRequest, PreparedFluentProjectionStrategy,
21                PreparedFluentScalarTerminalRuntimeRequest, PreparedFluentScalarTerminalStrategy,
22            },
23            explain::{ExplainAggregateTerminalPlan, ExplainExecutionNodeDescriptor},
24            fluent::load::FluentLoadQuery,
25            intent::QueryError,
26            plan::AggregateKind,
27        },
28        response::EntityResponse,
29    },
30    error::InternalError,
31    traits::EntityValue,
32    types::{Decimal, Id},
33    value::Value,
34};
35
36type MinMaxByIds<E> = Option<(Id<E>, Id<E>)>;
37
38impl<E> FluentLoadQuery<'_, E>
39where
40    E: PersistedRow,
41{
42    // ------------------------------------------------------------------
43    // Execution (single semantic boundary)
44    // ------------------------------------------------------------------
45
46    /// Execute this query using the session's policy settings.
47    pub fn execute(&self) -> Result<EntityResponse<E>, QueryError>
48    where
49        E: EntityValue,
50    {
51        self.ensure_non_paged_mode_ready()?;
52
53        self.session.execute_query(self.query())
54    }
55
56    // Run one scalar terminal through the canonical non-paged fluent policy
57    // gate before handing execution to the session load-query adapter.
58    fn execute_scalar_non_paged_terminal<T, F>(&self, execute: F) -> Result<T, QueryError>
59    where
60        E: EntityValue,
61        F: FnOnce(LoadExecutor<E>, ExecutablePlan<E>) -> Result<T, InternalError>,
62    {
63        self.ensure_non_paged_mode_ready()?;
64
65        self.session.execute_load_query_with(self.query(), execute)
66    }
67
68    // Run one scalar aggregate EXPLAIN terminal through the canonical
69    // non-paged fluent policy gate.
70    fn explain_prepared_scalar_non_paged_terminal(
71        &self,
72        strategy: &PreparedFluentScalarTerminalStrategy,
73    ) -> Result<ExplainAggregateTerminalPlan, QueryError>
74    where
75        E: EntityValue,
76    {
77        self.ensure_non_paged_mode_ready()?;
78
79        self.session
80            .explain_query_prepared_scalar_terminal_with_visible_indexes(self.query(), strategy)
81    }
82
83    // Execute one prepared fluent scalar terminal through the canonical
84    // non-paged fluent policy gate using the prepared runtime request as the
85    // single execution source.
86    fn execute_prepared_scalar_terminal_output(
87        &self,
88        strategy: PreparedFluentScalarTerminalStrategy,
89    ) -> Result<ScalarTerminalBoundaryOutput, QueryError>
90    where
91        E: EntityValue,
92    {
93        self.execute_scalar_non_paged_terminal(move |load, plan| {
94            load.execute_scalar_terminal_request(
95                plan,
96                scalar_terminal_boundary_request_from_prepared(strategy.runtime_request().clone()),
97            )
98        })
99    }
100
101    // Execute one prepared fluent numeric-field terminal through the canonical
102    // non-paged fluent policy gate using the prepared numeric strategy as the
103    // single runtime source.
104    fn execute_prepared_numeric_field_terminal(
105        &self,
106        strategy: PreparedFluentNumericFieldStrategy,
107    ) -> Result<Option<Decimal>, QueryError>
108    where
109        E: EntityValue,
110    {
111        self.execute_scalar_non_paged_terminal(move |load, plan| {
112            load.execute_numeric_field_boundary(
113                plan,
114                strategy.target_field().clone(),
115                numeric_field_boundary_request_from_prepared(strategy.runtime_request()),
116            )
117        })
118    }
119
120    // Execute one prepared fluent order-sensitive terminal through the
121    // canonical non-paged fluent policy gate using the prepared order-sensitive
122    // strategy as the single runtime source.
123    fn execute_prepared_order_sensitive_terminal_output(
124        &self,
125        strategy: PreparedFluentOrderSensitiveTerminalStrategy,
126    ) -> Result<ScalarTerminalBoundaryOutput, QueryError>
127    where
128        E: EntityValue,
129    {
130        self.execute_scalar_non_paged_terminal(move |load, plan| {
131            load.execute_scalar_terminal_request(
132                plan,
133                order_sensitive_terminal_boundary_request_from_prepared(
134                    strategy.runtime_request().clone(),
135                ),
136            )
137        })
138    }
139
140    // Execute one prepared fluent projection/distinct terminal through the
141    // canonical non-paged fluent policy gate using the prepared projection
142    // strategy as the single runtime source.
143    fn execute_prepared_projection_terminal_output(
144        &self,
145        strategy: PreparedFluentProjectionStrategy,
146    ) -> Result<crate::db::executor::ScalarProjectionBoundaryOutput, QueryError>
147    where
148        E: EntityValue,
149    {
150        self.execute_scalar_non_paged_terminal(move |load, plan| {
151            load.execute_scalar_projection_boundary(
152                plan,
153                strategy.target_field().clone(),
154                projection_boundary_request_from_prepared(strategy.runtime_request()),
155            )
156        })
157    }
158
159    // Run one order-sensitive aggregate EXPLAIN terminal through the canonical
160    // non-paged fluent policy gate.
161    fn explain_prepared_order_sensitive_non_paged_terminal(
162        &self,
163        strategy: &PreparedFluentOrderSensitiveTerminalStrategy,
164    ) -> Result<ExplainAggregateTerminalPlan, QueryError>
165    where
166        E: EntityValue,
167    {
168        self.ensure_non_paged_mode_ready()?;
169
170        self.session
171            .explain_query_prepared_order_sensitive_terminal_with_visible_indexes(
172                self.query(),
173                strategy,
174            )
175    }
176
177    // ------------------------------------------------------------------
178    // Execution terminals — semantic only
179    // ------------------------------------------------------------------
180
181    /// Execute and return whether the result set is empty.
182    pub fn is_empty(&self) -> Result<bool, QueryError>
183    where
184        E: EntityValue,
185    {
186        self.not_exists()
187    }
188
189    /// Execute and return whether no matching row exists.
190    pub fn not_exists(&self) -> Result<bool, QueryError>
191    where
192        E: EntityValue,
193    {
194        Ok(!self.exists()?)
195    }
196
197    /// Execute and return whether at least one matching row exists.
198    pub fn exists(&self) -> Result<bool, QueryError>
199    where
200        E: EntityValue,
201    {
202        self.execute_prepared_scalar_terminal_output(
203            PreparedFluentScalarTerminalStrategy::exists_rows(),
204        )?
205        .into_exists()
206        .map_err(QueryError::execute)
207    }
208
209    /// Explain scalar `exists()` routing without executing the terminal.
210    pub fn explain_exists(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
211    where
212        E: EntityValue,
213    {
214        self.explain_prepared_scalar_non_paged_terminal(
215            &PreparedFluentScalarTerminalStrategy::exists_rows(),
216        )
217    }
218
219    /// Explain scalar `not_exists()` routing without executing the terminal.
220    ///
221    /// This remains an `exists()` execution plan with negated boolean semantics.
222    pub fn explain_not_exists(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
223    where
224        E: EntityValue,
225    {
226        self.explain_exists()
227    }
228
229    /// Explain scalar load execution shape without executing the query.
230    pub fn explain_execution(&self) -> Result<ExplainExecutionNodeDescriptor, QueryError>
231    where
232        E: EntityValue,
233    {
234        self.session
235            .explain_query_execution_with_visible_indexes(self.query())
236    }
237
238    /// Explain scalar load execution shape as deterministic text.
239    pub fn explain_execution_text(&self) -> Result<String, QueryError>
240    where
241        E: EntityValue,
242    {
243        self.session
244            .explain_query_execution_text_with_visible_indexes(self.query())
245    }
246
247    /// Explain scalar load execution shape as canonical JSON.
248    pub fn explain_execution_json(&self) -> Result<String, QueryError>
249    where
250        E: EntityValue,
251    {
252        self.session
253            .explain_query_execution_json_with_visible_indexes(self.query())
254    }
255
256    /// Explain scalar load execution shape as verbose text with diagnostics.
257    pub fn explain_execution_verbose(&self) -> Result<String, QueryError>
258    where
259        E: EntityValue,
260    {
261        self.session
262            .explain_query_execution_verbose_with_visible_indexes(self.query())
263    }
264
265    /// Execute and return the number of matching rows.
266    pub fn count(&self) -> Result<u32, QueryError>
267    where
268        E: EntityValue,
269    {
270        self.execute_prepared_scalar_terminal_output(
271            PreparedFluentScalarTerminalStrategy::count_rows(),
272        )?
273        .into_count()
274        .map_err(QueryError::execute)
275    }
276
277    /// Execute and return the total persisted payload bytes for the effective
278    /// result window.
279    pub fn bytes(&self) -> Result<u64, QueryError>
280    where
281        E: EntityValue,
282    {
283        self.execute_scalar_non_paged_terminal(|load, plan| load.bytes(plan))
284    }
285
286    /// Execute and return the total serialized bytes for `field` over the
287    /// effective result window.
288    pub fn bytes_by(&self, field: impl AsRef<str>) -> Result<u64, QueryError>
289    where
290        E: EntityValue,
291    {
292        self.ensure_non_paged_mode_ready()?;
293
294        Self::with_slot(field, |target_slot| {
295            self.session
296                .execute_load_query_with(self.query(), move |load, plan| {
297                    load.bytes_by_slot(plan, target_slot)
298                })
299        })
300    }
301
302    /// Explain `bytes_by(field)` routing without executing the terminal.
303    pub fn explain_bytes_by(
304        &self,
305        field: impl AsRef<str>,
306    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
307    where
308        E: EntityValue,
309    {
310        self.ensure_non_paged_mode_ready()?;
311
312        Self::with_slot(field, |target_slot| {
313            self.session
314                .explain_query_bytes_by_with_visible_indexes(self.query(), target_slot.field())
315        })
316    }
317
318    /// Execute and return the smallest matching identifier, if any.
319    pub fn min(&self) -> Result<Option<Id<E>>, QueryError>
320    where
321        E: EntityValue,
322    {
323        self.execute_prepared_scalar_terminal_output(
324            PreparedFluentScalarTerminalStrategy::id_terminal(AggregateKind::Min),
325        )?
326        .into_id()
327        .map_err(QueryError::execute)
328    }
329
330    /// Explain scalar `min()` routing without executing the terminal.
331    pub fn explain_min(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
332    where
333        E: EntityValue,
334    {
335        self.explain_prepared_scalar_non_paged_terminal(
336            &PreparedFluentScalarTerminalStrategy::id_terminal(AggregateKind::Min),
337        )
338    }
339
340    /// Execute and return the id of the row with the smallest value for `field`.
341    ///
342    /// Ties are deterministic: equal field values resolve by primary key ascending.
343    pub fn min_by(&self, field: impl AsRef<str>) -> Result<Option<Id<E>>, QueryError>
344    where
345        E: EntityValue,
346    {
347        self.ensure_non_paged_mode_ready()?;
348
349        Self::with_slot(field, |target_slot| {
350            self.execute_prepared_scalar_terminal_output(
351                PreparedFluentScalarTerminalStrategy::id_by_slot(AggregateKind::Min, target_slot),
352            )?
353            .into_id()
354            .map_err(QueryError::execute)
355        })
356    }
357
358    /// Execute and return the largest matching identifier, if any.
359    pub fn max(&self) -> Result<Option<Id<E>>, QueryError>
360    where
361        E: EntityValue,
362    {
363        self.execute_prepared_scalar_terminal_output(
364            PreparedFluentScalarTerminalStrategy::id_terminal(AggregateKind::Max),
365        )?
366        .into_id()
367        .map_err(QueryError::execute)
368    }
369
370    /// Explain scalar `max()` routing without executing the terminal.
371    pub fn explain_max(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
372    where
373        E: EntityValue,
374    {
375        self.explain_prepared_scalar_non_paged_terminal(
376            &PreparedFluentScalarTerminalStrategy::id_terminal(AggregateKind::Max),
377        )
378    }
379
380    /// Execute and return the id of the row with the largest value for `field`.
381    ///
382    /// Ties are deterministic: equal field values resolve by primary key ascending.
383    pub fn max_by(&self, field: impl AsRef<str>) -> Result<Option<Id<E>>, QueryError>
384    where
385        E: EntityValue,
386    {
387        self.ensure_non_paged_mode_ready()?;
388
389        Self::with_slot(field, |target_slot| {
390            self.execute_prepared_scalar_terminal_output(
391                PreparedFluentScalarTerminalStrategy::id_by_slot(AggregateKind::Max, target_slot),
392            )?
393            .into_id()
394            .map_err(QueryError::execute)
395        })
396    }
397
398    /// Execute and return the id at zero-based ordinal `nth` when rows are
399    /// ordered by `field` ascending, with primary-key ascending tie-breaks.
400    pub fn nth_by(&self, field: impl AsRef<str>, nth: usize) -> Result<Option<Id<E>>, QueryError>
401    where
402        E: EntityValue,
403    {
404        self.ensure_non_paged_mode_ready()?;
405
406        Self::with_slot(field, |target_slot| {
407            self.execute_prepared_order_sensitive_terminal_output(
408                PreparedFluentOrderSensitiveTerminalStrategy::nth_by_slot(target_slot, nth),
409            )?
410            .into_id()
411            .map_err(QueryError::execute)
412        })
413    }
414
415    /// Execute and return the sum of `field` over matching rows.
416    pub fn sum_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
417    where
418        E: EntityValue,
419    {
420        self.ensure_non_paged_mode_ready()?;
421
422        Self::with_slot(field, |target_slot| {
423            self.execute_prepared_numeric_field_terminal(
424                PreparedFluentNumericFieldStrategy::sum_by_slot(target_slot),
425            )
426        })
427    }
428
429    /// Execute and return the sum of distinct `field` values.
430    pub fn sum_distinct_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
431    where
432        E: EntityValue,
433    {
434        self.ensure_non_paged_mode_ready()?;
435
436        Self::with_slot(field, |target_slot| {
437            self.execute_prepared_numeric_field_terminal(
438                PreparedFluentNumericFieldStrategy::sum_distinct_by_slot(target_slot),
439            )
440        })
441    }
442
443    /// Execute and return the average of `field` over matching rows.
444    pub fn avg_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
445    where
446        E: EntityValue,
447    {
448        self.ensure_non_paged_mode_ready()?;
449
450        Self::with_slot(field, |target_slot| {
451            self.execute_prepared_numeric_field_terminal(
452                PreparedFluentNumericFieldStrategy::avg_by_slot(target_slot),
453            )
454        })
455    }
456
457    /// Execute and return the average of distinct `field` values.
458    pub fn avg_distinct_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
459    where
460        E: EntityValue,
461    {
462        self.ensure_non_paged_mode_ready()?;
463
464        Self::with_slot(field, |target_slot| {
465            self.execute_prepared_numeric_field_terminal(
466                PreparedFluentNumericFieldStrategy::avg_distinct_by_slot(target_slot),
467            )
468        })
469    }
470
471    /// Execute and return the median id by `field` using deterministic ordering
472    /// `(field asc, primary key asc)`.
473    ///
474    /// Even-length windows select the lower median.
475    pub fn median_by(&self, field: impl AsRef<str>) -> Result<Option<Id<E>>, QueryError>
476    where
477        E: EntityValue,
478    {
479        self.ensure_non_paged_mode_ready()?;
480
481        Self::with_slot(field, |target_slot| {
482            self.execute_prepared_order_sensitive_terminal_output(
483                PreparedFluentOrderSensitiveTerminalStrategy::median_by_slot(target_slot),
484            )?
485            .into_id()
486            .map_err(QueryError::execute)
487        })
488    }
489
490    /// Execute and return the number of distinct values for `field` over the
491    /// effective result window.
492    pub fn count_distinct_by(&self, field: impl AsRef<str>) -> Result<u32, QueryError>
493    where
494        E: EntityValue,
495    {
496        self.ensure_non_paged_mode_ready()?;
497
498        Self::with_slot(field, |target_slot| {
499            self.execute_prepared_projection_terminal_output(
500                PreparedFluentProjectionStrategy::count_distinct_by_slot(target_slot),
501            )?
502            .into_count()
503            .map_err(QueryError::execute)
504        })
505    }
506
507    /// Execute and return both `(min_by(field), max_by(field))` in one terminal.
508    ///
509    /// Tie handling is deterministic for both extrema: primary key ascending.
510    pub fn min_max_by(&self, field: impl AsRef<str>) -> Result<MinMaxByIds<E>, QueryError>
511    where
512        E: EntityValue,
513    {
514        self.ensure_non_paged_mode_ready()?;
515
516        Self::with_slot(field, |target_slot| {
517            self.execute_prepared_order_sensitive_terminal_output(
518                PreparedFluentOrderSensitiveTerminalStrategy::min_max_by_slot(target_slot),
519            )?
520            .into_id_pair()
521            .map_err(QueryError::execute)
522        })
523    }
524
525    /// Execute and return projected field values for the effective result window.
526    pub fn values_by(&self, field: impl AsRef<str>) -> Result<Vec<Value>, QueryError>
527    where
528        E: EntityValue,
529    {
530        self.ensure_non_paged_mode_ready()?;
531
532        Self::with_slot(field, |target_slot| {
533            self.execute_prepared_projection_terminal_output(
534                PreparedFluentProjectionStrategy::values_by_slot(target_slot),
535            )?
536            .into_values()
537            .map_err(QueryError::execute)
538        })
539    }
540
541    /// Execute and return the first `k` rows from the effective response window.
542    pub fn take(&self, take_count: u32) -> Result<EntityResponse<E>, QueryError>
543    where
544        E: EntityValue,
545    {
546        self.execute_scalar_non_paged_terminal(|load, plan| load.take(plan, take_count))
547    }
548
549    /// Execute and return the top `k` rows by `field` under deterministic
550    /// ordering `(field desc, primary_key asc)` over the effective response
551    /// window.
552    ///
553    /// This terminal applies its own ordering and does not preserve query
554    /// `order_by(...)` row order in the returned rows. For `k = 1`, this
555    /// matches `max_by(field)` selection semantics.
556    pub fn top_k_by(
557        &self,
558        field: impl AsRef<str>,
559        take_count: u32,
560    ) -> Result<EntityResponse<E>, QueryError>
561    where
562        E: EntityValue,
563    {
564        self.ensure_non_paged_mode_ready()?;
565
566        Self::with_slot(field, |target_slot| {
567            self.session
568                .execute_load_query_with(self.query(), move |load, plan| {
569                    load.top_k_by_slot(plan, target_slot, take_count)
570                })
571        })
572    }
573
574    /// Execute and return the bottom `k` rows by `field` under deterministic
575    /// ordering `(field asc, primary_key asc)` over the effective response
576    /// window.
577    ///
578    /// This terminal applies its own ordering and does not preserve query
579    /// `order_by(...)` row order in the returned rows. For `k = 1`, this
580    /// matches `min_by(field)` selection semantics.
581    pub fn bottom_k_by(
582        &self,
583        field: impl AsRef<str>,
584        take_count: u32,
585    ) -> Result<EntityResponse<E>, QueryError>
586    where
587        E: EntityValue,
588    {
589        self.ensure_non_paged_mode_ready()?;
590
591        Self::with_slot(field, |target_slot| {
592            self.session
593                .execute_load_query_with(self.query(), move |load, plan| {
594                    load.bottom_k_by_slot(plan, target_slot, take_count)
595                })
596        })
597    }
598
599    /// Execute and return projected values for the top `k` rows by `field`
600    /// under deterministic ordering `(field desc, primary_key asc)` over the
601    /// effective response window.
602    ///
603    /// Ranking is applied before projection and does not preserve query
604    /// `order_by(...)` row order in the returned values. For `k = 1`, this
605    /// matches `max_by(field)` projected to one value.
606    pub fn top_k_by_values(
607        &self,
608        field: impl AsRef<str>,
609        take_count: u32,
610    ) -> Result<Vec<Value>, QueryError>
611    where
612        E: EntityValue,
613    {
614        self.ensure_non_paged_mode_ready()?;
615
616        Self::with_slot(field, |target_slot| {
617            self.session
618                .execute_load_query_with(self.query(), move |load, plan| {
619                    load.top_k_by_values_slot(plan, target_slot, take_count)
620                })
621        })
622    }
623
624    /// Execute and return projected values for the bottom `k` rows by `field`
625    /// under deterministic ordering `(field asc, primary_key asc)` over the
626    /// effective response window.
627    ///
628    /// Ranking is applied before projection and does not preserve query
629    /// `order_by(...)` row order in the returned values. For `k = 1`, this
630    /// matches `min_by(field)` projected to one value.
631    pub fn bottom_k_by_values(
632        &self,
633        field: impl AsRef<str>,
634        take_count: u32,
635    ) -> Result<Vec<Value>, QueryError>
636    where
637        E: EntityValue,
638    {
639        self.ensure_non_paged_mode_ready()?;
640
641        Self::with_slot(field, |target_slot| {
642            self.session
643                .execute_load_query_with(self.query(), move |load, plan| {
644                    load.bottom_k_by_values_slot(plan, target_slot, take_count)
645                })
646        })
647    }
648
649    /// Execute and return projected id/value pairs for the top `k` rows by
650    /// `field` under deterministic ordering `(field desc, primary_key asc)`
651    /// over the effective response window.
652    ///
653    /// Ranking is applied before projection and does not preserve query
654    /// `order_by(...)` row order in the returned values. For `k = 1`, this
655    /// matches `max_by(field)` projected to one `(id, value)` pair.
656    pub fn top_k_by_with_ids(
657        &self,
658        field: impl AsRef<str>,
659        take_count: u32,
660    ) -> Result<Vec<(Id<E>, Value)>, QueryError>
661    where
662        E: EntityValue,
663    {
664        self.ensure_non_paged_mode_ready()?;
665
666        Self::with_slot(field, |target_slot| {
667            self.session
668                .execute_load_query_with(self.query(), move |load, plan| {
669                    load.top_k_by_with_ids_slot(plan, target_slot, take_count)
670                })
671        })
672    }
673
674    /// Execute and return projected id/value pairs for the bottom `k` rows by
675    /// `field` under deterministic ordering `(field asc, primary_key asc)`
676    /// over the effective response window.
677    ///
678    /// Ranking is applied before projection and does not preserve query
679    /// `order_by(...)` row order in the returned values. For `k = 1`, this
680    /// matches `min_by(field)` projected to one `(id, value)` pair.
681    pub fn bottom_k_by_with_ids(
682        &self,
683        field: impl AsRef<str>,
684        take_count: u32,
685    ) -> Result<Vec<(Id<E>, Value)>, QueryError>
686    where
687        E: EntityValue,
688    {
689        self.ensure_non_paged_mode_ready()?;
690
691        Self::with_slot(field, |target_slot| {
692            self.session
693                .execute_load_query_with(self.query(), move |load, plan| {
694                    load.bottom_k_by_with_ids_slot(plan, target_slot, take_count)
695                })
696        })
697    }
698
699    /// Execute and return distinct projected field values for the effective
700    /// result window, preserving first-observed value order.
701    pub fn distinct_values_by(&self, field: impl AsRef<str>) -> Result<Vec<Value>, QueryError>
702    where
703        E: EntityValue,
704    {
705        self.ensure_non_paged_mode_ready()?;
706
707        Self::with_slot(field, |target_slot| {
708            self.execute_prepared_projection_terminal_output(
709                PreparedFluentProjectionStrategy::distinct_values_by_slot(target_slot),
710            )?
711            .into_values()
712            .map_err(QueryError::execute)
713        })
714    }
715
716    /// Execute and return projected field values paired with row ids for the
717    /// effective result window.
718    pub fn values_by_with_ids(
719        &self,
720        field: impl AsRef<str>,
721    ) -> Result<Vec<(Id<E>, Value)>, QueryError>
722    where
723        E: EntityValue,
724    {
725        self.ensure_non_paged_mode_ready()?;
726
727        Self::with_slot(field, |target_slot| {
728            self.execute_prepared_projection_terminal_output(
729                PreparedFluentProjectionStrategy::values_by_with_ids_slot(target_slot),
730            )?
731            .into_values_with_ids()
732            .map_err(QueryError::execute)
733        })
734    }
735
736    /// Execute and return the first projected field value in effective response
737    /// order, if any.
738    pub fn first_value_by(&self, field: impl AsRef<str>) -> Result<Option<Value>, QueryError>
739    where
740        E: EntityValue,
741    {
742        self.ensure_non_paged_mode_ready()?;
743
744        Self::with_slot(field, |target_slot| {
745            self.execute_prepared_projection_terminal_output(
746                PreparedFluentProjectionStrategy::first_value_by_slot(target_slot),
747            )?
748            .into_terminal_value()
749            .map_err(QueryError::execute)
750        })
751    }
752
753    /// Execute and return the last projected field value in effective response
754    /// order, if any.
755    pub fn last_value_by(&self, field: impl AsRef<str>) -> Result<Option<Value>, QueryError>
756    where
757        E: EntityValue,
758    {
759        self.ensure_non_paged_mode_ready()?;
760
761        Self::with_slot(field, |target_slot| {
762            self.execute_prepared_projection_terminal_output(
763                PreparedFluentProjectionStrategy::last_value_by_slot(target_slot),
764            )?
765            .into_terminal_value()
766            .map_err(QueryError::execute)
767        })
768    }
769
770    /// Execute and return the first matching identifier in response order, if any.
771    pub fn first(&self) -> Result<Option<Id<E>>, QueryError>
772    where
773        E: EntityValue,
774    {
775        self.execute_prepared_order_sensitive_terminal_output(
776            PreparedFluentOrderSensitiveTerminalStrategy::first(),
777        )?
778        .into_id()
779        .map_err(QueryError::execute)
780    }
781
782    /// Explain scalar `first()` routing without executing the terminal.
783    pub fn explain_first(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
784    where
785        E: EntityValue,
786    {
787        self.explain_prepared_order_sensitive_non_paged_terminal(
788            &PreparedFluentOrderSensitiveTerminalStrategy::first(),
789        )
790    }
791
792    /// Execute and return the last matching identifier in response order, if any.
793    pub fn last(&self) -> Result<Option<Id<E>>, QueryError>
794    where
795        E: EntityValue,
796    {
797        self.execute_prepared_order_sensitive_terminal_output(
798            PreparedFluentOrderSensitiveTerminalStrategy::last(),
799        )?
800        .into_id()
801        .map_err(QueryError::execute)
802    }
803
804    /// Explain scalar `last()` routing without executing the terminal.
805    pub fn explain_last(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
806    where
807        E: EntityValue,
808    {
809        self.explain_prepared_order_sensitive_non_paged_terminal(
810            &PreparedFluentOrderSensitiveTerminalStrategy::last(),
811        )
812    }
813
814    /// Execute and require exactly one matching row.
815    pub fn require_one(&self) -> Result<(), QueryError>
816    where
817        E: EntityValue,
818    {
819        self.execute()?.require_one()?;
820        Ok(())
821    }
822
823    /// Execute and require at least one matching row.
824    pub fn require_some(&self) -> Result<(), QueryError>
825    where
826        E: EntityValue,
827    {
828        self.execute()?.require_some()?;
829        Ok(())
830    }
831}
832
833fn scalar_terminal_boundary_request_from_prepared(
834    request: PreparedFluentScalarTerminalRuntimeRequest,
835) -> ScalarTerminalBoundaryRequest {
836    match request {
837        PreparedFluentScalarTerminalRuntimeRequest::CountRows => {
838            ScalarTerminalBoundaryRequest::Count
839        }
840        PreparedFluentScalarTerminalRuntimeRequest::ExistsRows => {
841            ScalarTerminalBoundaryRequest::Exists
842        }
843        PreparedFluentScalarTerminalRuntimeRequest::IdTerminal { kind } => {
844            ScalarTerminalBoundaryRequest::IdTerminal { kind }
845        }
846        PreparedFluentScalarTerminalRuntimeRequest::IdBySlot { kind, target_field } => {
847            ScalarTerminalBoundaryRequest::IdBySlot { kind, target_field }
848        }
849    }
850}
851
852const fn numeric_field_boundary_request_from_prepared(
853    request: PreparedFluentNumericFieldRuntimeRequest,
854) -> ScalarNumericFieldBoundaryRequest {
855    match request {
856        PreparedFluentNumericFieldRuntimeRequest::Sum => ScalarNumericFieldBoundaryRequest::Sum,
857        PreparedFluentNumericFieldRuntimeRequest::SumDistinct => {
858            ScalarNumericFieldBoundaryRequest::SumDistinct
859        }
860        PreparedFluentNumericFieldRuntimeRequest::Avg => ScalarNumericFieldBoundaryRequest::Avg,
861        PreparedFluentNumericFieldRuntimeRequest::AvgDistinct => {
862            ScalarNumericFieldBoundaryRequest::AvgDistinct
863        }
864    }
865}
866
867fn order_sensitive_terminal_boundary_request_from_prepared(
868    request: PreparedFluentOrderSensitiveTerminalRuntimeRequest,
869) -> ScalarTerminalBoundaryRequest {
870    match request {
871        PreparedFluentOrderSensitiveTerminalRuntimeRequest::ResponseOrder { kind } => {
872            ScalarTerminalBoundaryRequest::IdTerminal { kind }
873        }
874        PreparedFluentOrderSensitiveTerminalRuntimeRequest::NthBySlot { target_field, nth } => {
875            ScalarTerminalBoundaryRequest::NthBySlot { target_field, nth }
876        }
877        PreparedFluentOrderSensitiveTerminalRuntimeRequest::MedianBySlot { target_field } => {
878            ScalarTerminalBoundaryRequest::MedianBySlot { target_field }
879        }
880        PreparedFluentOrderSensitiveTerminalRuntimeRequest::MinMaxBySlot { target_field } => {
881            ScalarTerminalBoundaryRequest::MinMaxBySlot { target_field }
882        }
883    }
884}
885
886const fn projection_boundary_request_from_prepared(
887    request: PreparedFluentProjectionRuntimeRequest,
888) -> ScalarProjectionBoundaryRequest {
889    match request {
890        PreparedFluentProjectionRuntimeRequest::Values => ScalarProjectionBoundaryRequest::Values,
891        PreparedFluentProjectionRuntimeRequest::DistinctValues => {
892            ScalarProjectionBoundaryRequest::DistinctValues
893        }
894        PreparedFluentProjectionRuntimeRequest::CountDistinct => {
895            ScalarProjectionBoundaryRequest::CountDistinct
896        }
897        PreparedFluentProjectionRuntimeRequest::ValuesWithIds => {
898            ScalarProjectionBoundaryRequest::ValuesWithIds
899        }
900        PreparedFluentProjectionRuntimeRequest::TerminalValue { terminal_kind } => {
901            ScalarProjectionBoundaryRequest::TerminalValue { terminal_kind }
902        }
903    }
904}