Skip to main content

drive/query/drive_document_average_query/
mod.rs

1//! `DriveDocumentAverageQuery` — Drive's average-query surface.
2//!
3//! Parallels [`crate::query::drive_document_sum_query`] for the
4//! averaging surface. Averages are NOT computed server-side: every
5//! response carries a `(count, sum)` pair (atomic per group), and the
6//! client divides to obtain the average. This preserves full
7//! precision and lets callers pick their own representation
8//! (integer-truncated, floating-point, decimal); pre-dividing on the
9//! server would force a choice that loses information.
10//!
11//! The grovedb primitive backing this is `AggregateCountAndSumOnRange`
12//! (added in grovedb PR 670 alongside the `ProvableCountProvableSumTree`
13//! / PCPS element variant) — one root-hash-committed traversal returns
14//! both metrics together. See
15//! [`book/src/drive/average-index-examples.md`](../../../../../book/src/drive/average-index-examples.md)
16//! for the design and the grades-contract worked example.
17//!
18//! Wired end-to-end: the dispatcher routes prove-true requests to the
19//! PCPS / primary-key proof executors, and prove-false requests to the
20//! joint single-walk count-and-sum dispatcher at
21//! [`crate::query::drive_document_count_and_sum_query`]. Both paths
22//! read `(count, sum)` from each visited count-sum-bearing element in
23//! a single grovedb walk — see the
24//! [`drive_dispatcher`](drive_dispatcher) module docstring for the
25//! routing details and
26//! [`crate::query::drive_document_count_and_sum_query`] for the
27//! no-prove perf / atomicity contract.
28
29#[cfg(feature = "server")]
30pub mod drive_dispatcher;
31
32#[cfg(feature = "server")]
33use crate::query::{OrderClause, WhereClause};
34
35#[cfg(feature = "server")]
36use crate::config::DriveConfig;
37#[cfg(feature = "server")]
38use dpp::data_contract::document_type::DocumentTypeRef;
39#[cfg(feature = "server")]
40use dpp::data_contract::DataContract;
41
42/// What kind of average-query the dispatcher should run. Parallels
43/// [`crate::query::drive_document_sum_query::SumMode`].
44///
45/// The four variants correspond to the four response shapes:
46/// - `Aggregate` → `DocumentAverageResponse::Aggregate { count, sum }`
47///   (one pair across all matched docs)
48/// - `GroupByIn` → `DocumentAverageResponse::Entries(Vec<AverageEntry>)`
49///   (one entry per `In` value, each with its own `(count, sum)`)
50/// - `GroupByRange` → `Entries` with one entry per distinct in-range value
51/// - `GroupByCompound` → `Entries` with one entry per `(in_key, key)`
52///   pair (compound `In + range`)
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum AverageMode {
55    /// One `(count, sum)` pair across all matched documents.
56    Aggregate,
57    /// One pair per `In` value (cartesian fan-out at the `In`'s position).
58    GroupByIn,
59    /// One pair per distinct value in a range.
60    GroupByRange,
61    /// One pair per `(In-value, range-value)` pair.
62    GroupByCompound,
63}
64
65/// A single per-key average entry. Carries BOTH count and sum so the
66/// client can divide; the server intentionally doesn't pre-divide
67/// (see the module docstring).
68///
69/// - `in_key` carries the In value for compound `(In, range)` queries;
70///   `None` for flat queries.
71/// - `key` carries the terminator value (the range-key or the In
72///   single value, depending on shape).
73/// - `count` is the document count matching this key.
74/// - `sum` is the aggregated property value matching this key.
75///
76/// Both `count` and `sum` are `Option<_>` so the dispatcher can emit
77/// proven-absent entries when
78/// `absence_proofs_for_non_existing_searched_keys` is configured,
79/// mirroring the same three-valued pattern as `SumEntry` /
80/// `SplitCountEntry`.
81#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct AverageEntry {
83    /// In-prefix value when the query is compound (`In` on a prefix
84    /// property + range on the terminator). `None` for flat queries.
85    pub in_key: Option<Vec<u8>>,
86    /// The terminator key value (the value of the index's last covered
87    /// property within the query).
88    pub key: Vec<u8>,
89    /// Matched-document count for this key. `Some(n)` for matched
90    /// keys; `None` for keys proven absent.
91    pub count: Option<u64>,
92    /// Aggregated `sum_property` value for this key. `Some(n)` for
93    /// matched keys; `None` for keys proven absent.
94    pub sum: Option<i64>,
95}
96
97/// Server-side request input for the average dispatcher. Mirrors
98/// [`crate::query::drive_document_sum_query::DocumentSumRequest`]
99/// — same fields, same semantics; the response carries `(count, sum)`
100/// pairs instead of a single sum.
101#[cfg(feature = "server")]
102#[derive(Clone, Debug)]
103pub struct DocumentAverageRequest<'a> {
104    /// The data contract this document type belongs to.
105    pub contract: &'a DataContract,
106    /// The document type whose summable indexes will be picked from.
107    pub document_type: DocumentTypeRef<'a>,
108    /// The integer property to average. Must match the doctype-level
109    /// `documents_summable` (when set) and every covering index's
110    /// `summable: "<x>"` declaration; the dispatcher rejects mismatches
111    /// at parse time. Averages reuse the sum-tree index machinery —
112    /// no separate `averageable` flag exists or is needed (the same
113    /// `CountSumTree` / PCPS element backs both).
114    pub sum_property: String,
115    /// Structured where-clauses.
116    pub where_clauses: Vec<WhereClause>,
117    /// Structured order-clauses.
118    pub order_clauses: Vec<OrderClause>,
119    /// The average mode requested.
120    pub mode: AverageMode,
121    /// Optional cap on the number of entries returned in `Entries`-mode
122    /// responses.
123    ///
124    /// **Fallback differs between the no-proof and prove paths**:
125    ///
126    /// - **No-proof path**: unset `limit` falls back to
127    ///   [`crate::config::DriveConfig::default_query_limit`] (the
128    ///   operator-tunable runtime value); explicit `limit >
129    ///   max_query_limit` is clamped to `max_query_limit`. There's
130    ///   no consensus-verification step on no-proof responses, so
131    ///   operator-tunable defaults are safe here.
132    /// - **Prove path**: unset `limit` falls back to
133    ///   [`crate::config::DEFAULT_QUERY_LIMIT`] (the compile-time
134    ///   constant the SDK verifier also reads), explicitly NOT
135    ///   `drive_config.default_query_limit`. An explicit `limit >
136    ///   max_query_limit` is **rejected** with
137    ///   [`crate::error::query::QuerySyntaxError::InvalidLimit`]
138    ///   rather than clamped, so a tuned operator default or an
139    ///   over-max request can't byte-differ the
140    ///   `SizedQuery::limit` the SDK reconstructs for merk-root
141    ///   verification. See the
142    ///   [`drive_dispatcher`]'s `RangeDistinctProof` /
143    ///   `RangeAggregateCarrierProof` arms for the
144    ///   validate-don't-clamp policy, mirrored from count's
145    ///   prove-path arms.
146    pub limit: Option<u32>,
147    /// Whether to return a `Proof(Vec<u8>)` instead of materializing
148    /// the (count, sum) pairs server-side.
149    pub prove: bool,
150    /// Pointer to the drive config, used for limit defaults.
151    pub drive_config: &'a DriveConfig,
152}
153
154/// Server-side response from the average dispatcher. Parallels
155/// [`crate::query::drive_document_sum_query::DocumentSumResponse`]
156/// — same outer shape; the `Aggregate` and `Entries` payloads carry
157/// `(count, sum)` instead of just `sum`.
158#[cfg(feature = "server")]
159#[derive(Clone, Debug)]
160pub enum DocumentAverageResponse {
161    /// A single `(count, sum)` pair across all matched documents.
162    /// Client computes `avg = sum / count`.
163    Aggregate {
164        /// Total matched-document count.
165        count: u64,
166        /// Total aggregated value of `sum_property`.
167        sum: i64,
168    },
169    /// One entry per `In`-value or per distinct in-range value.
170    Entries(Vec<AverageEntry>),
171    /// Serialized grovedb proof bytes the client verifies with
172    /// `GroveDb::verify_aggregate_count_and_sum_query` (range path)
173    /// or the appropriate point-lookup verifier.
174    Proof(Vec<u8>),
175}