Skip to main content

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}