Skip to main content

drive/query/drive_document_sum_query/
drive_dispatcher.rs

1//! Sum-query dispatcher entry point.
2//!
3//! Parallels [`crate::query::drive_document_count_query::drive_dispatcher`]
4//! for the sum surface. Routes a parsed [`DocumentSumRequest`] to one of
5//! the per-mode executors based on the (where × mode × prove) triple,
6//! exactly the way count's dispatcher does.
7//!
8//! `where_clauses_from_value` / `order_clauses_from_value` are wire-shape
9//! adapters that the bench and the gRPC handler both use to convert the
10//! CBOR-decoded `Value::Array` input into structured `Vec<WhereClause>` /
11//! `Vec<OrderClause>`. Identical input contract to count.
12
13use crate::drive::Drive;
14use crate::error::Error;
15use crate::query::drive_document_sum_query::{
16    DocumentSumMode, DocumentSumRequest, DocumentSumResponse, RangeSumOptions, SumMode,
17};
18use crate::query::{OrderClause, WhereClause};
19use dpp::data_contract::accessors::v0::DataContractV0Getters;
20use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
21use dpp::platform_value::Value;
22use dpp::version::PlatformVersion;
23use grovedb::TransactionArg;
24
25#[cfg(feature = "server")]
26impl Drive {
27    /// Server-side entry point for the sum surface. Routes a
28    /// [`DocumentSumRequest`] to the appropriate executor based on the
29    /// where-shape, requested mode, and `prove` flag.
30    ///
31    /// Mirrors [`Drive::execute_document_count_request`].
32    pub fn execute_document_sum_request(
33        &self,
34        request: DocumentSumRequest,
35        transaction: TransactionArg,
36        platform_version: &PlatformVersion,
37    ) -> Result<DocumentSumResponse, Error> {
38        let resolved_mode = super::mode_detection::detect_sum_mode(&request, platform_version)?;
39
40        let contract_id = request.contract.id().to_buffer();
41        let document_type_name = request.document_type.name().to_string();
42        let where_clauses = request.where_clauses;
43        let sum_property = request.sum_property;
44        // Default direction is ascending; the first order clause's
45        // direction (if any) wins. Mirrors count's analog.
46        let order_by_ascending = request
47            .order_clauses
48            .first()
49            .map(|c| c.ascending)
50            .unwrap_or(true);
51
52        match resolved_mode {
53            DocumentSumMode::Total => {
54                let entries = self.execute_document_sum_total_no_proof(
55                    contract_id,
56                    request.document_type,
57                    document_type_name,
58                    where_clauses,
59                    sum_property,
60                    transaction,
61                    platform_version,
62                )?;
63                let total = entries.first().and_then(|e| e.sum).unwrap_or(0);
64                Ok(DocumentSumResponse::Aggregate(total))
65            }
66            DocumentSumMode::PerInValue => {
67                let options = RangeSumOptions {
68                    return_distinct_sums_in_range: false,
69                    carrier_outer_limit: None,
70                    left_to_right: order_by_ascending,
71                };
72                Ok(DocumentSumResponse::Entries(
73                    self.execute_document_sum_per_in_value_no_proof(
74                        contract_id,
75                        request.document_type,
76                        document_type_name,
77                        where_clauses,
78                        sum_property,
79                        options,
80                        transaction,
81                        platform_version,
82                    )?,
83                ))
84            }
85            DocumentSumMode::RangeNoProof => {
86                let return_distinct = matches!(
87                    request.mode,
88                    SumMode::GroupByRange | SumMode::GroupByCompound
89                );
90                let options = RangeSumOptions {
91                    return_distinct_sums_in_range: return_distinct,
92                    carrier_outer_limit: None,
93                    left_to_right: order_by_ascending,
94                };
95                let entries = self.execute_document_sum_range_no_proof(
96                    contract_id,
97                    request.document_type,
98                    document_type_name,
99                    where_clauses,
100                    sum_property,
101                    options,
102                    transaction,
103                    platform_version,
104                )?;
105                if matches!(request.mode, SumMode::Aggregate) {
106                    let total = entries.first().and_then(|e| e.sum).unwrap_or(0);
107                    Ok(DocumentSumResponse::Aggregate(total))
108                } else {
109                    Ok(DocumentSumResponse::Entries(entries))
110                }
111            }
112            DocumentSumMode::RangeProof => Ok(DocumentSumResponse::Proof(
113                self.execute_document_sum_range_proof(
114                    contract_id,
115                    request.document_type,
116                    document_type_name,
117                    where_clauses,
118                    sum_property,
119                    transaction,
120                    platform_version,
121                )?,
122            )),
123            DocumentSumMode::RangeDistinctProof => {
124                // Validate-don't-clamp limit policy on the prove path:
125                // client-side proof reconstruction needs the EXACT
126                // limit value the server applied to the path query
127                // (the SDK rebuilds the same `SizedQuery::limit` for
128                // merk-root recomputation). Silent clamping or a
129                // tuned `default_query_limit` would byte-differ the
130                // reconstructed path query and break verification.
131                //
132                // Limit fallback uses [`crate::config::DEFAULT_QUERY_LIMIT`]
133                // (compile-time constant), NOT
134                // `drive_config.default_query_limit` (operator-tunable
135                // runtime value). `max_query_limit` still gates the
136                // request as a DoS-protection knob — proofs never
137                // cross the operator-set ceiling, but the ceiling
138                // itself doesn't shape proof bytes; it only decides
139                // whether the request gets served.
140                //
141                // Mirrors count's policy at
142                // `drive_document_count_query::drive_dispatcher`
143                // `DocumentCountMode::RangeDistinctProof`.
144                let effective_limit = request
145                    .limit
146                    .unwrap_or(crate::config::DEFAULT_QUERY_LIMIT as u32);
147                if effective_limit > request.drive_config.max_query_limit as u32 {
148                    return Err(Error::Query(
149                        crate::error::query::QuerySyntaxError::InvalidLimit(format!(
150                            "limit {} exceeds max_query_limit {} on the prove + \
151                             distinct-walk path (GROUP BY a range field, SUM); \
152                             reduce the requested limit or use prove = false",
153                            effective_limit, request.drive_config.max_query_limit
154                        )),
155                    ));
156                }
157                let limit_u16 = u16::try_from(effective_limit).map_err(|_| {
158                    Error::Query(crate::error::query::QuerySyntaxError::Unsupported(format!(
159                        "limit {} exceeds u16::MAX for range-distinct sum proof",
160                        effective_limit
161                    )))
162                })?;
163                Ok(DocumentSumResponse::Proof(
164                    self.execute_document_sum_range_distinct_proof(
165                        contract_id,
166                        request.document_type,
167                        document_type_name,
168                        where_clauses,
169                        sum_property,
170                        limit_u16,
171                        order_by_ascending,
172                        transaction,
173                        platform_version,
174                    )?,
175                ))
176            }
177            DocumentSumMode::PointLookupProof => Ok(DocumentSumResponse::Proof(
178                self.execute_document_sum_point_lookup_proof(
179                    contract_id,
180                    request.document_type,
181                    document_type_name,
182                    where_clauses,
183                    sum_property,
184                    transaction,
185                    platform_version,
186                )?,
187            )),
188            DocumentSumMode::RangeAggregateCarrierProof => {
189                // Validate-don't-clamp limit policy on the prove path
190                // — same contract as RangeDistinctProof above. The
191                // carrier proof's outer-walk cap is `SizedQuery::limit`
192                // bytes-of-proof material; a silent clamp would
193                // byte-differ the SDK's reconstruction and break
194                // verification. Unlike the distinct arm, the carrier
195                // arm passes `Option<u16>` (None = unbounded outer
196                // walk), so the request's `None` stays `None` instead
197                // of falling back to a default.
198                let limit_u16 = request
199                    .limit
200                    .map(|l| {
201                        if l > request.drive_config.max_query_limit as u32 {
202                            return Err(Error::Query(
203                                crate::error::query::QuerySyntaxError::InvalidLimit(format!(
204                                    "limit {} exceeds max_query_limit {} on the prove + \
205                                     carrier-aggregate path (GROUP BY In + range, SUM); \
206                                     reduce the requested limit or use prove = false",
207                                    l, request.drive_config.max_query_limit
208                                )),
209                            ));
210                        }
211                        u16::try_from(l).map_err(|_| {
212                            Error::Query(crate::error::query::QuerySyntaxError::Unsupported(
213                                format!(
214                                    "limit {} exceeds u16::MAX for carrier-aggregate sum proof",
215                                    l
216                                ),
217                            ))
218                        })
219                    })
220                    .transpose()?;
221                Ok(DocumentSumResponse::Proof(
222                    self.execute_document_sum_range_aggregate_carrier_proof(
223                        contract_id,
224                        request.document_type,
225                        document_type_name,
226                        where_clauses,
227                        sum_property,
228                        limit_u16,
229                        order_by_ascending,
230                        transaction,
231                        platform_version,
232                    )?,
233                ))
234            }
235        }
236    }
237}
238
239// `detect_sum_mode` lives in the versioned
240// [`mode_detection`](super::mode_detection) module — the routing
241// table is consensus-relevant on the query surface and protocol
242// versions that change it must do so behind a method-version bump.
243
244/// Parse the wire-CBOR `Value::Array` shape into structured
245/// `Vec<WhereClause>`. Delegates to count's parser.
246pub fn where_clauses_from_value(value: &Value) -> Result<Vec<WhereClause>, Error> {
247    crate::query::drive_document_count_query::drive_dispatcher::where_clauses_from_value(value)
248}
249
250/// Parse the wire-CBOR `Value::Array` shape into structured
251/// `Vec<OrderClause>`. Delegates to count's parser.
252pub fn order_clauses_from_value(value: &Value) -> Result<Vec<OrderClause>, Error> {
253    crate::query::drive_document_count_query::drive_dispatcher::order_clauses_from_value(value)
254}