Skip to main content

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

1use crate::{
2    db::{
3        query::fluent::load::FluentLoadQuery,
4        query::{api::ResponseCardinalityExt, intent::QueryError},
5        response::EntityResponse,
6    },
7    traits::{EntityKind, EntityValue},
8    types::{Decimal, Id},
9    value::Value,
10};
11
12type MinMaxByIds<E> = Option<(Id<E>, Id<E>)>;
13
14impl<E> FluentLoadQuery<'_, E>
15where
16    E: EntityKind,
17{
18    // ------------------------------------------------------------------
19    // Execution (single semantic boundary)
20    // ------------------------------------------------------------------
21
22    /// Execute this query using the session's policy settings.
23    pub fn execute(&self) -> Result<EntityResponse<E>, QueryError>
24    where
25        E: EntityValue,
26    {
27        self.ensure_non_paged_mode_ready()?;
28
29        self.session.execute_query(self.query())
30    }
31
32    // ------------------------------------------------------------------
33    // Execution terminals — semantic only
34    // ------------------------------------------------------------------
35
36    /// Execute and return whether the result set is empty.
37    pub fn is_empty(&self) -> Result<bool, QueryError>
38    where
39        E: EntityValue,
40    {
41        Ok(!self.exists()?)
42    }
43
44    /// Execute and return whether at least one matching row exists.
45    pub fn exists(&self) -> Result<bool, QueryError>
46    where
47        E: EntityValue,
48    {
49        self.ensure_non_paged_mode_ready()?;
50
51        self.session
52            .execute_load_query_with(self.query(), |load, plan| load.aggregate_exists(plan))
53    }
54
55    /// Execute and return the number of matching rows.
56    pub fn count(&self) -> Result<u32, QueryError>
57    where
58        E: EntityValue,
59    {
60        self.ensure_non_paged_mode_ready()?;
61
62        self.session
63            .execute_load_query_with(self.query(), |load, plan| load.aggregate_count(plan))
64    }
65
66    /// Execute and return the smallest matching identifier, if any.
67    pub fn min(&self) -> Result<Option<Id<E>>, QueryError>
68    where
69        E: EntityValue,
70    {
71        self.ensure_non_paged_mode_ready()?;
72
73        self.session
74            .execute_load_query_with(self.query(), |load, plan| load.aggregate_min(plan))
75    }
76
77    /// Execute and return the id of the row with the smallest value for `field`.
78    ///
79    /// Ties are deterministic: equal field values resolve by primary key ascending.
80    pub fn min_by(&self, field: impl AsRef<str>) -> Result<Option<Id<E>>, QueryError>
81    where
82        E: EntityValue,
83    {
84        self.ensure_non_paged_mode_ready()?;
85
86        Self::with_slot(field, |target_slot| {
87            self.session
88                .execute_load_query_with(self.query(), move |load, plan| {
89                    load.aggregate_min_by_slot(plan, target_slot)
90                })
91        })
92    }
93
94    /// Execute and return the largest matching identifier, if any.
95    pub fn max(&self) -> Result<Option<Id<E>>, QueryError>
96    where
97        E: EntityValue,
98    {
99        self.ensure_non_paged_mode_ready()?;
100
101        self.session
102            .execute_load_query_with(self.query(), |load, plan| load.aggregate_max(plan))
103    }
104
105    /// Execute and return the id of the row with the largest value for `field`.
106    ///
107    /// Ties are deterministic: equal field values resolve by primary key ascending.
108    pub fn max_by(&self, field: impl AsRef<str>) -> Result<Option<Id<E>>, QueryError>
109    where
110        E: EntityValue,
111    {
112        self.ensure_non_paged_mode_ready()?;
113
114        Self::with_slot(field, |target_slot| {
115            self.session
116                .execute_load_query_with(self.query(), move |load, plan| {
117                    load.aggregate_max_by_slot(plan, target_slot)
118                })
119        })
120    }
121
122    /// Execute and return the id at zero-based ordinal `nth` when rows are
123    /// ordered by `field` ascending, with primary-key ascending tie-breaks.
124    pub fn nth_by(&self, field: impl AsRef<str>, nth: usize) -> Result<Option<Id<E>>, QueryError>
125    where
126        E: EntityValue,
127    {
128        self.ensure_non_paged_mode_ready()?;
129
130        Self::with_slot(field, |target_slot| {
131            self.session
132                .execute_load_query_with(self.query(), move |load, plan| {
133                    load.aggregate_nth_by_slot(plan, target_slot, nth)
134                })
135        })
136    }
137
138    /// Execute and return the sum of `field` over matching rows.
139    pub fn sum_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
140    where
141        E: EntityValue,
142    {
143        self.ensure_non_paged_mode_ready()?;
144
145        Self::with_slot(field, |target_slot| {
146            self.session
147                .execute_load_query_with(self.query(), move |load, plan| {
148                    load.aggregate_sum_by_slot(plan, target_slot)
149                })
150        })
151    }
152
153    /// Execute and return the sum of distinct `field` values.
154    pub fn sum_distinct_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
155    where
156        E: EntityValue,
157    {
158        self.ensure_non_paged_mode_ready()?;
159
160        Self::with_slot(field, |target_slot| {
161            self.session
162                .execute_load_query_with(self.query(), move |load, plan| {
163                    load.aggregate_sum_distinct_by_slot(plan, target_slot)
164                })
165        })
166    }
167
168    /// Execute and return the average of `field` over matching rows.
169    pub fn avg_by(&self, field: impl AsRef<str>) -> Result<Option<Decimal>, QueryError>
170    where
171        E: EntityValue,
172    {
173        self.ensure_non_paged_mode_ready()?;
174
175        Self::with_slot(field, |target_slot| {
176            self.session
177                .execute_load_query_with(self.query(), move |load, plan| {
178                    load.aggregate_avg_by_slot(plan, target_slot)
179                })
180        })
181    }
182
183    /// Execute and return the median id by `field` using deterministic ordering
184    /// `(field asc, primary key asc)`.
185    ///
186    /// Even-length windows select the lower median.
187    pub fn median_by(&self, field: impl AsRef<str>) -> Result<Option<Id<E>>, QueryError>
188    where
189        E: EntityValue,
190    {
191        self.ensure_non_paged_mode_ready()?;
192
193        Self::with_slot(field, |target_slot| {
194            self.session
195                .execute_load_query_with(self.query(), move |load, plan| {
196                    load.aggregate_median_by_slot(plan, target_slot)
197                })
198        })
199    }
200
201    /// Execute and return the number of distinct values for `field` over the
202    /// effective result window.
203    pub fn count_distinct_by(&self, field: impl AsRef<str>) -> Result<u32, QueryError>
204    where
205        E: EntityValue,
206    {
207        self.ensure_non_paged_mode_ready()?;
208
209        Self::with_slot(field, |target_slot| {
210            self.session
211                .execute_load_query_with(self.query(), move |load, plan| {
212                    load.aggregate_count_distinct_by_slot(plan, target_slot)
213                })
214        })
215    }
216
217    /// Execute and return both `(min_by(field), max_by(field))` in one terminal.
218    ///
219    /// Tie handling is deterministic for both extrema: primary key ascending.
220    pub fn min_max_by(&self, field: impl AsRef<str>) -> Result<MinMaxByIds<E>, QueryError>
221    where
222        E: EntityValue,
223    {
224        self.ensure_non_paged_mode_ready()?;
225
226        Self::with_slot(field, |target_slot| {
227            self.session
228                .execute_load_query_with(self.query(), move |load, plan| {
229                    load.aggregate_min_max_by_slot(plan, target_slot)
230                })
231        })
232    }
233
234    /// Execute and return projected field values for the effective result window.
235    pub fn values_by(&self, field: impl AsRef<str>) -> Result<Vec<Value>, QueryError>
236    where
237        E: EntityValue,
238    {
239        self.ensure_non_paged_mode_ready()?;
240
241        Self::with_slot(field, |target_slot| {
242            self.session
243                .execute_load_query_with(self.query(), move |load, plan| {
244                    load.values_by_slot(plan, target_slot)
245                })
246        })
247    }
248
249    /// Execute and return the first `k` rows from the effective response window.
250    pub fn take(&self, take_count: u32) -> Result<EntityResponse<E>, QueryError>
251    where
252        E: EntityValue,
253    {
254        self.ensure_non_paged_mode_ready()?;
255
256        self.session
257            .execute_load_query_with(self.query(), |load, plan| load.take(plan, take_count))
258    }
259
260    /// Execute and return the top `k` rows by `field` under deterministic
261    /// ordering `(field desc, primary_key asc)` over the effective response
262    /// window.
263    ///
264    /// This terminal applies its own ordering and does not preserve query
265    /// `order_by(...)` row order in the returned rows. For `k = 1`, this
266    /// matches `max_by(field)` selection semantics.
267    pub fn top_k_by(
268        &self,
269        field: impl AsRef<str>,
270        take_count: u32,
271    ) -> Result<EntityResponse<E>, QueryError>
272    where
273        E: EntityValue,
274    {
275        self.ensure_non_paged_mode_ready()?;
276
277        Self::with_slot(field, |target_slot| {
278            self.session
279                .execute_load_query_with(self.query(), move |load, plan| {
280                    load.top_k_by_slot(plan, target_slot, take_count)
281                })
282        })
283    }
284
285    /// Execute and return the bottom `k` rows by `field` under deterministic
286    /// ordering `(field asc, primary_key asc)` over the effective response
287    /// window.
288    ///
289    /// This terminal applies its own ordering and does not preserve query
290    /// `order_by(...)` row order in the returned rows. For `k = 1`, this
291    /// matches `min_by(field)` selection semantics.
292    pub fn bottom_k_by(
293        &self,
294        field: impl AsRef<str>,
295        take_count: u32,
296    ) -> Result<EntityResponse<E>, QueryError>
297    where
298        E: EntityValue,
299    {
300        self.ensure_non_paged_mode_ready()?;
301
302        Self::with_slot(field, |target_slot| {
303            self.session
304                .execute_load_query_with(self.query(), move |load, plan| {
305                    load.bottom_k_by_slot(plan, target_slot, take_count)
306                })
307        })
308    }
309
310    /// Execute and return projected values for the top `k` rows by `field`
311    /// under deterministic ordering `(field desc, primary_key asc)` over the
312    /// effective response window.
313    ///
314    /// Ranking is applied before projection and does not preserve query
315    /// `order_by(...)` row order in the returned values. For `k = 1`, this
316    /// matches `max_by(field)` projected to one value.
317    pub fn top_k_by_values(
318        &self,
319        field: impl AsRef<str>,
320        take_count: u32,
321    ) -> Result<Vec<Value>, QueryError>
322    where
323        E: EntityValue,
324    {
325        self.ensure_non_paged_mode_ready()?;
326
327        Self::with_slot(field, |target_slot| {
328            self.session
329                .execute_load_query_with(self.query(), move |load, plan| {
330                    load.top_k_by_values_slot(plan, target_slot, take_count)
331                })
332        })
333    }
334
335    /// Execute and return projected values for the bottom `k` rows by `field`
336    /// under deterministic ordering `(field asc, primary_key asc)` over the
337    /// effective response window.
338    ///
339    /// Ranking is applied before projection and does not preserve query
340    /// `order_by(...)` row order in the returned values. For `k = 1`, this
341    /// matches `min_by(field)` projected to one value.
342    pub fn bottom_k_by_values(
343        &self,
344        field: impl AsRef<str>,
345        take_count: u32,
346    ) -> Result<Vec<Value>, QueryError>
347    where
348        E: EntityValue,
349    {
350        self.ensure_non_paged_mode_ready()?;
351
352        Self::with_slot(field, |target_slot| {
353            self.session
354                .execute_load_query_with(self.query(), move |load, plan| {
355                    load.bottom_k_by_values_slot(plan, target_slot, take_count)
356                })
357        })
358    }
359
360    /// Execute and return projected id/value pairs for the top `k` rows by
361    /// `field` under deterministic ordering `(field desc, primary_key asc)`
362    /// over the effective response window.
363    ///
364    /// Ranking is applied before projection and does not preserve query
365    /// `order_by(...)` row order in the returned values. For `k = 1`, this
366    /// matches `max_by(field)` projected to one `(id, value)` pair.
367    pub fn top_k_by_with_ids(
368        &self,
369        field: impl AsRef<str>,
370        take_count: u32,
371    ) -> Result<Vec<(Id<E>, Value)>, QueryError>
372    where
373        E: EntityValue,
374    {
375        self.ensure_non_paged_mode_ready()?;
376
377        Self::with_slot(field, |target_slot| {
378            self.session
379                .execute_load_query_with(self.query(), move |load, plan| {
380                    load.top_k_by_with_ids_slot(plan, target_slot, take_count)
381                })
382        })
383    }
384
385    /// Execute and return projected id/value pairs for the bottom `k` rows by
386    /// `field` under deterministic ordering `(field asc, primary_key asc)`
387    /// over the effective response window.
388    ///
389    /// Ranking is applied before projection and does not preserve query
390    /// `order_by(...)` row order in the returned values. For `k = 1`, this
391    /// matches `min_by(field)` projected to one `(id, value)` pair.
392    pub fn bottom_k_by_with_ids(
393        &self,
394        field: impl AsRef<str>,
395        take_count: u32,
396    ) -> Result<Vec<(Id<E>, Value)>, QueryError>
397    where
398        E: EntityValue,
399    {
400        self.ensure_non_paged_mode_ready()?;
401
402        Self::with_slot(field, |target_slot| {
403            self.session
404                .execute_load_query_with(self.query(), move |load, plan| {
405                    load.bottom_k_by_with_ids_slot(plan, target_slot, take_count)
406                })
407        })
408    }
409
410    /// Execute and return distinct projected field values for the effective
411    /// result window, preserving first-observed value order.
412    pub fn distinct_values_by(&self, field: impl AsRef<str>) -> Result<Vec<Value>, QueryError>
413    where
414        E: EntityValue,
415    {
416        self.ensure_non_paged_mode_ready()?;
417
418        Self::with_slot(field, |target_slot| {
419            self.session
420                .execute_load_query_with(self.query(), move |load, plan| {
421                    load.distinct_values_by_slot(plan, target_slot)
422                })
423        })
424    }
425
426    /// Execute and return projected field values paired with row ids for the
427    /// effective result window.
428    pub fn values_by_with_ids(
429        &self,
430        field: impl AsRef<str>,
431    ) -> Result<Vec<(Id<E>, Value)>, QueryError>
432    where
433        E: EntityValue,
434    {
435        self.ensure_non_paged_mode_ready()?;
436
437        Self::with_slot(field, |target_slot| {
438            self.session
439                .execute_load_query_with(self.query(), move |load, plan| {
440                    load.values_by_with_ids_slot(plan, target_slot)
441                })
442        })
443    }
444
445    /// Execute and return the first projected field value in effective response
446    /// order, if any.
447    pub fn first_value_by(&self, field: impl AsRef<str>) -> Result<Option<Value>, QueryError>
448    where
449        E: EntityValue,
450    {
451        self.ensure_non_paged_mode_ready()?;
452
453        Self::with_slot(field, |target_slot| {
454            self.session
455                .execute_load_query_with(self.query(), move |load, plan| {
456                    load.first_value_by_slot(plan, target_slot)
457                })
458        })
459    }
460
461    /// Execute and return the last projected field value in effective response
462    /// order, if any.
463    pub fn last_value_by(&self, field: impl AsRef<str>) -> Result<Option<Value>, QueryError>
464    where
465        E: EntityValue,
466    {
467        self.ensure_non_paged_mode_ready()?;
468
469        Self::with_slot(field, |target_slot| {
470            self.session
471                .execute_load_query_with(self.query(), move |load, plan| {
472                    load.last_value_by_slot(plan, target_slot)
473                })
474        })
475    }
476
477    /// Execute and return the first matching identifier in response order, if any.
478    pub fn first(&self) -> Result<Option<Id<E>>, QueryError>
479    where
480        E: EntityValue,
481    {
482        self.ensure_non_paged_mode_ready()?;
483
484        self.session
485            .execute_load_query_with(self.query(), |load, plan| load.aggregate_first(plan))
486    }
487
488    /// Execute and return the last matching identifier in response order, if any.
489    pub fn last(&self) -> Result<Option<Id<E>>, QueryError>
490    where
491        E: EntityValue,
492    {
493        self.ensure_non_paged_mode_ready()?;
494
495        self.session
496            .execute_load_query_with(self.query(), |load, plan| load.aggregate_last(plan))
497    }
498
499    /// Execute and require exactly one matching row.
500    pub fn require_one(&self) -> Result<(), QueryError>
501    where
502        E: EntityValue,
503    {
504        self.execute()?.require_one()?;
505        Ok(())
506    }
507
508    /// Execute and require at least one matching row.
509    pub fn require_some(&self) -> Result<(), QueryError>
510    where
511        E: EntityValue,
512    {
513        self.execute()?.require_some()?;
514        Ok(())
515    }
516}