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