drive/query/projection.rs
1//! `SELECT` projection types for the v1 `getDocuments` surface.
2//!
3//! The projection determines what the response carries:
4//! - [`SelectFunction::Documents`]: matched rows (`ResultData.documents`).
5//! - [`SelectFunction::Count`]: row counts — single aggregate when
6//! `group_by` is empty, per-group entries otherwise.
7//! - [`SelectFunction::Sum`] / [`SelectFunction::Avg`]: numeric
8//! aggregate(s) of a named field — same single/per-group shape
9//! contract as `Count`.
10//!
11//! Per-function `field` requirements live in
12//! [`SelectProjection::field`]; the type itself just carries the
13//! `(function, field)` pair.
14//!
15//! Shared between the wire-decoding layer
16//! (`rs-drive-abci/src/query/document_query/v1/conversions.rs`)
17//! and the SDK's request builder
18//! (`rs-sdk/src/platform/documents/document_query.rs`). Server
19//! capability today: [`SelectFunction::Documents`],
20//! [`SelectFunction::Count`] with empty `field` (= `COUNT(*)`),
21//! [`SelectFunction::Sum`], and [`SelectFunction::Avg`] route
22//! through the drive count / sum / average dispatchers and are
23//! evaluated end-to-end (no-proof and proof paths).
24//! [`SelectFunction::Count`] with non-empty `field` (=
25//! `COUNT(field)`), [`SelectFunction::Min`], and
26//! [`SelectFunction::Max`] are wire-stable but rejected at routing
27//! time with `QuerySyntaxError::Unsupported("SELECT … is not yet
28//! implemented")` — the surface is shipped first so callers can
29//! encode against it, with execution landing later without
30//! another version bump.
31
32#[cfg(feature = "serde")]
33use serde::{Deserialize, Serialize};
34
35/// Projection function applied by `SELECT`. Distinct from
36/// [`crate::query::HavingAggregateFunction`] because this enum
37/// carries the document-fetch branch too — `SELECT` may return
38/// rows, not just aggregates — so the two can't share a type.
39#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41pub enum SelectFunction {
42 /// Return matched rows. The default so old fixtures /
43 /// builders that don't opt in get plain document-fetch
44 /// semantics.
45 #[default]
46 Documents,
47 /// `COUNT(*)` when [`SelectProjection::field`] is empty,
48 /// `COUNT(field)` (count of non-null `field` values)
49 /// otherwise. Empty-`field` form is currently the only
50 /// supported COUNT shape — the `field`-bearing form is
51 /// reserved for future server capability.
52 Count,
53 /// `SUM(field)`. Required field; numeric typed. Routed
54 /// end-to-end through the drive sum dispatcher (no-proof and
55 /// proof paths both terminate at grovedb's aggregate-sum /
56 /// sum-tree-walk primitives — see
57 /// `crate::query::drive_document_sum_query`).
58 Sum,
59 /// `AVG(field)`. Required field; numeric typed. Result is
60 /// `f64`. Routed end-to-end through the drive average
61 /// dispatcher, which composes count and sum walks under the
62 /// hood (no separate average primitive on the grovedb side) —
63 /// see `crate::query::drive_document_average_query`.
64 Avg,
65 /// `MIN(field)` — smallest value of `field` in each group
66 /// (or across all matching rows when `group_by` is empty).
67 /// Required field. Currently always rejected with "not yet
68 /// implemented"; **semantically distinct** from
69 /// [`crate::query::HavingRankingKind::Min`] which is a
70 /// cross-group ranking primitive on the HAVING right side.
71 Min,
72 /// `MAX(field)` — symmetric to [`Self::Min`] for the largest
73 /// value. Same not-yet-implemented contract; same caveat
74 /// versus [`crate::query::HavingRankingKind::Max`].
75 Max,
76}
77
78/// `(function, field)` projection. The `field` semantics depend
79/// on `function`:
80/// - [`SelectFunction::Documents`]: `field` must be empty.
81/// - [`SelectFunction::Count`]: empty `field` means `COUNT(*)`;
82/// non-empty means `COUNT(field)` (count of non-null values).
83/// - [`SelectFunction::Sum`] / [`SelectFunction::Avg`]: `field`
84/// is required and must be numeric-typed.
85#[derive(Clone, Debug, PartialEq, Eq, Default)]
86#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
87pub struct SelectProjection {
88 /// Projection function.
89 pub function: SelectFunction,
90 /// Field the function is applied to. See
91 /// [`SelectFunction`]'s per-variant docstring for the
92 /// per-function requirement.
93 pub field: String,
94}
95
96impl SelectProjection {
97 /// Plain document fetch — the default projection. `field` is
98 /// empty.
99 pub fn documents() -> Self {
100 Self {
101 function: SelectFunction::Documents,
102 field: String::new(),
103 }
104 }
105
106 /// `COUNT(*)` — empty `field`.
107 pub fn count_star() -> Self {
108 Self {
109 function: SelectFunction::Count,
110 field: String::new(),
111 }
112 }
113
114 /// `COUNT(field)` — count of non-null values of `field`.
115 pub fn count_field(field: impl Into<String>) -> Self {
116 Self {
117 function: SelectFunction::Count,
118 field: field.into(),
119 }
120 }
121
122 /// `SUM(field)`.
123 pub fn sum(field: impl Into<String>) -> Self {
124 Self {
125 function: SelectFunction::Sum,
126 field: field.into(),
127 }
128 }
129
130 /// `AVG(field)`.
131 pub fn avg(field: impl Into<String>) -> Self {
132 Self {
133 function: SelectFunction::Avg,
134 field: field.into(),
135 }
136 }
137
138 /// `MIN(field)` — per-group / global minimum.
139 pub fn min(field: impl Into<String>) -> Self {
140 Self {
141 function: SelectFunction::Min,
142 field: field.into(),
143 }
144 }
145
146 /// `MAX(field)` — per-group / global maximum.
147 pub fn max(field: impl Into<String>) -> Self {
148 Self {
149 function: SelectFunction::Max,
150 field: field.into(),
151 }
152 }
153}