Skip to main content

drive/query/drive_document_average_query/
drive_dispatcher.rs

1//! Average-query dispatcher entry point.
2//!
3//! Routes a [`DocumentAverageRequest`] to one of two backends:
4//! - **No-prove path** → delegates to the joint count-and-sum
5//!   dispatcher
6//!   [`Drive::execute_document_count_and_sum_request`], which walks
7//!   grovedb ONCE and reads both metrics from each visited
8//!   count-sum-bearing element via
9//!   [`grovedb::Element::count_sum_value_or_default`]. See its module
10//!   docstring for the routing / atomicity contract.
11//! - **Prove path** → dispatched to
12//!   [`Drive::execute_document_average_prove`] (defined below), which
13//!   routes to one of the PCPS / direct-read prove executors based on
14//!   `(mode, where_clauses)`. The prove path's per-shape rules are
15//!   unchanged.
16//!
17//! ## Joint dispatch
18//!
19//! The no-prove dispatcher at
20//! [`crate::query::drive_document_count_and_sum_query`] reads
21//! `(count, sum)` together — via grovedb's combined
22//! `query_aggregate_count_and_sum` accumulator on the aggregate range
23//! branch, and via a single PCPS walk on the distinct-grouped branch.
24//! Routing reuses sum's versioned mode-detection table so the
25//! `(where_clauses × mode)` → executor decision has a single source
26//! of truth shared with the count and sum surfaces.
27//!
28//! ## Prove path shapes (unchanged)
29//!
30//! The prove-path routing table at
31//! [`Self::execute_document_average_prove`] picks one of:
32//!     - empty-where + `documentsCountable + documentsSummable`
33//!       doctype → primary-key count-sum tree direct read
34//!     - range AVG on a `rangeAverageable` index → PCPS
35//!       `AggregateCountAndSumOnRange` proof
36//!     - In + range AVG on a `rangeAverageable` index → carrier-PCPS
37//!       proof
38//!     - GroupByRange / GroupByCompound + range on a
39//!       `rangeAverageable` index → per-distinct-key
40//!       count-and-sum proof (walks `ProvableCountProvableSumTree`
41//!       terminators)
42//!     - Equal/In + no range on a summable + countable index →
43//!       point-lookup count-and-sum proof (walks count-sum-bearing
44//!       terminator elements)
45//!   The client verifies with the matching
46//!   `verify_*_count_and_sum_proof` helpers in `drive-proof-verifier`.
47
48use crate::drive::Drive;
49use crate::error::query::QuerySyntaxError;
50use crate::error::Error;
51use crate::query::drive_document_average_query::{
52    AverageMode, DocumentAverageRequest, DocumentAverageResponse,
53};
54use crate::query::drive_document_sum_query::index_picker::{
55    find_range_summable_index_for_where_clauses, find_summable_index_for_where_clauses,
56};
57use crate::query::drive_document_sum_query::{is_range_operator, DriveDocumentSumQuery};
58use dpp::data_contract::accessors::v0::DataContractV0Getters;
59use dpp::data_contract::document_type::accessors::{DocumentTypeV0Getters, DocumentTypeV2Getters};
60use dpp::version::PlatformVersion;
61use grovedb::TransactionArg;
62
63#[cfg(feature = "server")]
64impl Drive {
65    /// Server-side entry point for the average surface.
66    ///
67    /// Splits prove vs. no-prove at the top level:
68    /// - `prove = true` → routes to
69    ///   [`Self::execute_document_average_prove`].
70    /// - `prove = false` → routes to
71    ///   [`Self::execute_document_count_and_sum_request`], the joint
72    ///   dispatcher that reads `(count, sum)` together.
73    pub fn execute_document_average_request(
74        &self,
75        request: DocumentAverageRequest,
76        transaction: TransactionArg,
77        platform_version: &PlatformVersion,
78    ) -> Result<DocumentAverageResponse, Error> {
79        if request.prove {
80            return self.execute_document_average_prove(request, transaction, platform_version);
81        }
82        self.execute_document_count_and_sum_request(request, transaction, platform_version)
83    }
84
85    /// Prove path of [`Self::execute_document_average_request`].
86    ///
87    /// Routes the `(where_clauses × mode)` pair to one of the
88    /// available PCPS / direct-read prove executors and returns
89    /// proof bytes the client verifies with the matching
90    /// `verify_*_count_and_sum_proof` helper.
91    ///
92    /// Supported prove shapes:
93    /// - `Aggregate` + empty where + doctype's primary key tree is a
94    ///   count-sum-bearing variant (`CountSumTree` /
95    ///   `ProvableCountSumTree` /
96    ///   `ProvableCountProvableSumTree`) — proves the primary-key
97    ///   element directly via `primary_key_sum_path_query`. Client
98    ///   verifies with `verify_primary_key_count_sum_tree_proof`.
99    /// - `Aggregate` + range clause on a PCPS-eligible index
100    ///   (`rangeCountable: true` AND `rangeSummable: true`) — proves
101    ///   via `execute_aggregate_count_and_sum_with_proof`. Client
102    ///   verifies with `verify_aggregate_count_and_sum_proof`.
103    /// - `Aggregate` + Equal/In, no range, on a count+sum index
104    ///   (or doctype's count-sum primary key) — proves via
105    ///   `execute_point_lookup_sum_with_proof`. Client verifies
106    ///   with `verify_point_lookup_count_and_sum_proof`.
107    /// - `GroupByIn` + In + range on a PCPS-eligible index — proves
108    ///   via `execute_carrier_aggregate_count_and_sum_with_proof`.
109    ///   Client verifies with
110    ///   `verify_carrier_aggregate_count_and_sum_proof`.
111    /// - `GroupByRange` / `GroupByCompound` + range on a PCPS-
112    ///   eligible index — proves via
113    ///   `execute_distinct_sum_with_proof` against a path query
114    ///   whose terminator value trees are
115    ///   `ProvableCountProvableSumTree`. Client verifies with
116    ///   `verify_distinct_count_and_sum_proof`.
117    fn execute_document_average_prove(
118        &self,
119        request: DocumentAverageRequest,
120        transaction: TransactionArg,
121        platform_version: &PlatformVersion,
122    ) -> Result<DocumentAverageResponse, Error> {
123        let contract_id = request.contract.id().to_buffer();
124        let document_type_name = request.document_type.name().to_string();
125        let has_range = request
126            .where_clauses
127            .iter()
128            .any(|wc| is_range_operator(wc.operator));
129        let order_by_ascending = request
130            .order_clauses
131            .first()
132            .map(|c| c.ascending)
133            .unwrap_or(true);
134
135        // Empty-where AVG fast path: prove the primary-key
136        // count-sum-bearing element directly when the doctype
137        // declares both `documents_countable: true` (implied by
138        // having a CountSumTree primary key) and a matching
139        // `documents_summable`. The verifier extracts `(count,
140        // sum)` from one element.
141        if matches!(request.mode, AverageMode::Aggregate)
142            && request.where_clauses.is_empty()
143            && request.document_type.documents_countable()
144            && request
145                .document_type
146                .documents_summable()
147                .map(|p| p == request.sum_property)
148                .unwrap_or(false)
149        {
150            let path_query =
151                DriveDocumentSumQuery::primary_key_sum_path_query(contract_id, &document_type_name);
152            let proof = self
153                .grove
154                .get_proved_path_query(
155                    &path_query,
156                    None,
157                    transaction,
158                    &platform_version.drive.grove_version,
159                )
160                .unwrap()
161                .map_err(|e| Error::GroveDB(Box::new(e)))?;
162            return Ok(DocumentAverageResponse::Proof(proof));
163        }
164
165        // Range AVG: pick a PCPS-eligible index (range_countable
166        // AND range_summable) covering the where clauses. Mirror of
167        // sum's `find_range_summable_index_for_where_clauses` with
168        // an additional `range_countable` filter.
169        if has_range
170            && matches!(
171                request.mode,
172                AverageMode::Aggregate | AverageMode::GroupByIn
173            )
174        {
175            let index = find_range_summable_index_for_where_clauses(
176                request.document_type.indexes(),
177                &request.where_clauses,
178                &request.sum_property,
179            )
180            .filter(|idx| idx.range_countable)
181            .ok_or_else(|| {
182                Error::Query(QuerySyntaxError::WhereClauseOnNonIndexedProperty(
183                    "prove AVG requires an index that declares BOTH `rangeCountable: \
184                     true` AND `rangeSummable: true` (a `rangeAverageable: true` \
185                     index is the shorthand) whose last property matches the range \
186                     field and whose summable property matches the request's \
187                     `sum_property`"
188                        .to_string(),
189                ))
190            })?;
191            let sum_query = DriveDocumentSumQuery {
192                document_type: request.document_type,
193                contract_id,
194                document_type_name,
195                index,
196                where_clauses: request.where_clauses.clone(),
197                sum_property: request.sum_property.clone(),
198            };
199
200            let proof = match request.mode {
201                AverageMode::Aggregate => sum_query.execute_aggregate_count_and_sum_with_proof(
202                    self,
203                    transaction,
204                    platform_version,
205                )?,
206                AverageMode::GroupByIn => {
207                    // Carrier-PCPS: one (count, sum) per In branch.
208                    // Validate-don't-clamp limit policy on the prove
209                    // path — `SizedQuery::limit` is bytes-of-proof
210                    // material; silent clamping would byte-differ the
211                    // SDK's reconstruction and break verification.
212                    // Same contract as sum's `RangeAggregateCarrierProof`
213                    // arm. `None` stays `None` (unbounded outer walk).
214                    let limit_u16 = request
215                        .limit
216                        .map(|l| {
217                            if l > request.drive_config.max_query_limit as u32 {
218                                return Err(Error::Query(QuerySyntaxError::InvalidLimit(format!(
219                                    "limit {} exceeds max_query_limit {} on the prove + \
220                                         carrier-aggregate path (GROUP BY In + range, AVG); \
221                                         reduce the requested limit or use prove = false",
222                                    l, request.drive_config.max_query_limit
223                                ))));
224                            }
225                            u16::try_from(l).map_err(|_| {
226                                Error::Query(QuerySyntaxError::Unsupported(format!(
227                                    "limit {} exceeds u16::MAX for carrier-aggregate \
228                                     count+sum (AVG) proof",
229                                    l
230                                )))
231                            })
232                        })
233                        .transpose()?;
234                    sum_query.execute_carrier_aggregate_count_and_sum_with_proof(
235                        self,
236                        limit_u16,
237                        order_by_ascending,
238                        transaction,
239                        platform_version,
240                    )?
241                }
242                _ => unreachable!("outer matches! gate filters out non-Aggregate/GroupByIn"),
243            };
244            return Ok(DocumentAverageResponse::Proof(proof));
245        }
246
247        // Distinct AVG (GroupByRange / GroupByCompound + range) —
248        // per-distinct-key (count, sum) proof against a PCPS-
249        // eligible index (rangeCountable + rangeSummable, i.e. a
250        // `rangeAverageable: true` index). The prover uses sum's
251        // `execute_distinct_sum_with_proof` against a path query
252        // whose terminators are `ProvableCountProvableSumTree`; the
253        // verifier extracts `count_sum_value_or_default()` from
254        // each emitted element.
255        if has_range
256            && matches!(
257                request.mode,
258                AverageMode::GroupByRange | AverageMode::GroupByCompound
259            )
260        {
261            let index = find_range_summable_index_for_where_clauses(
262                request.document_type.indexes(),
263                &request.where_clauses,
264                &request.sum_property,
265            )
266            .filter(|idx| idx.range_countable)
267            .ok_or_else(|| {
268                Error::Query(QuerySyntaxError::WhereClauseOnNonIndexedProperty(
269                    "prove distinct AVG requires an index that declares BOTH \
270                     `rangeCountable: true` AND `rangeSummable: true` (a \
271                     `rangeAverageable: true` index is the shorthand) whose last \
272                     property matches the range field and whose summable property \
273                     matches the request's `sum_property`"
274                        .to_string(),
275                ))
276            })?;
277            // Validate-don't-clamp limit policy on the prove path —
278            // see sum's `RangeDistinctProof` arm for the full
279            // rationale. Limit fallback uses
280            // [`crate::config::DEFAULT_QUERY_LIMIT`] (compile-time
281            // constant) so the SDK's reconstruction lands on the same
282            // `SizedQuery::limit` value; `max_query_limit` still
283            // gates as a DoS ceiling.
284            let effective_limit = request
285                .limit
286                .unwrap_or(crate::config::DEFAULT_QUERY_LIMIT as u32);
287            if effective_limit > request.drive_config.max_query_limit as u32 {
288                return Err(Error::Query(QuerySyntaxError::InvalidLimit(format!(
289                    "limit {} exceeds max_query_limit {} on the prove + distinct-walk \
290                     path (GROUP BY a range field, AVG); reduce the requested limit \
291                     or use prove = false",
292                    effective_limit, request.drive_config.max_query_limit
293                ))));
294            }
295            let limit_u16 = u16::try_from(effective_limit).map_err(|_| {
296                Error::Query(QuerySyntaxError::Unsupported(format!(
297                    "limit {} exceeds u16::MAX for distinct AVG proof",
298                    effective_limit
299                )))
300            })?;
301            let sum_query = DriveDocumentSumQuery {
302                document_type: request.document_type,
303                contract_id,
304                document_type_name,
305                index,
306                where_clauses: request.where_clauses.clone(),
307                sum_property: request.sum_property.clone(),
308            };
309            let proof = sum_query.execute_distinct_sum_with_proof(
310                self,
311                limit_u16,
312                order_by_ascending,
313                transaction,
314                platform_version,
315            )?;
316            return Ok(DocumentAverageResponse::Proof(proof));
317        }
318
319        // Point-lookup AVG: Equal/In on a count+sum index (whose
320        // `summable.is_some()` AND `countable.is_countable()`) OR
321        // doctype-level documentsSummable + documentsCountable for
322        // the empty-where case (handled by the fast path above —
323        // this arm handles the non-empty-where Equal/In shape).
324        //
325        // Accepts both `Aggregate` (caller wants one aggregate row
326        // collapsed across all matched In branches — folded
327        // client-side by `DocumentAverage`) and `GroupByIn` (caller
328        // wants per-In-branch entries — `DocumentSplitAverages`
329        // shape). The grovedb-side proof is identical: one walk
330        // through the point-lookup `subquery` per In key emits one
331        // count-sum-bearing element per branch.
332        //
333        // Mirrors the sum router's resolved-mode table
334        // (`mode_detection/v0/mod.rs`) which maps both
335        // `(SumMode::Aggregate, !range, _, true)` and
336        // `(SumMode::GroupByIn, !range, _, true)` to
337        // `DocumentSumMode::PointLookupProof`. Before adding
338        // `GroupByIn` here the SDK could ask drive for a no-range
339        // GroupByIn AVG proof, drive would 500 with `Unsupported`,
340        // and the SDK's `verify_point_lookup_count_and_sum_proof`
341        // arm (gated on the same resolved mode) would never get
342        // proof bytes to verify.
343        if !has_range
344            && matches!(
345                request.mode,
346                AverageMode::Aggregate | AverageMode::GroupByIn
347            )
348        {
349            let index = find_summable_index_for_where_clauses(
350                request.document_type.indexes(),
351                &request.where_clauses,
352                &request.sum_property,
353            )
354            .filter(|idx| idx.countable.is_countable())
355            .ok_or_else(|| {
356                Error::Query(QuerySyntaxError::WhereClauseOnNonIndexedProperty(
357                    "prove point-lookup AVG requires an index that declares BOTH \
358                     `summable: \"<prop>\"` AND a countable terminator (`countable: \
359                     \"countable\"` or `\"countableAllowingOffset\"`) whose properties \
360                     exactly match the where clause fields"
361                        .to_string(),
362                ))
363            })?;
364            let sum_query = DriveDocumentSumQuery {
365                document_type: request.document_type,
366                contract_id,
367                document_type_name,
368                index,
369                where_clauses: request.where_clauses.clone(),
370                sum_property: request.sum_property.clone(),
371            };
372            let proof = sum_query.execute_point_lookup_sum_with_proof(
373                self,
374                transaction,
375                platform_version,
376            )?;
377            return Ok(DocumentAverageResponse::Proof(proof));
378        }
379
380        // Unreachable in practice — the matches!() gates above
381        // cover every (mode × has_range) combination today. Kept as
382        // a typed error in case a future AverageMode variant lands
383        // without a corresponding prove arm.
384        Err(Error::Query(QuerySyntaxError::Unsupported(format!(
385            "execute_document_average_request prove=true: the (mode = {:?}, has_range \
386             = {}) combination is not yet supported on the prove path. \
387             This is likely a new AverageMode variant that hasn't been wired \
388             into the prove dispatcher.",
389            request.mode, has_range,
390        ))))
391    }
392}
393
394#[cfg(all(test, feature = "server"))]
395mod tests {
396    use super::*;
397
398    // ── Dispatcher limit-policy regression tests ───────────────────
399    //
400    // AVG-side analogs of count's
401    // `test_range_distinct_proof_uses_compile_time_default_query_limit_not_operator_config`
402    // and the sum-side tests in `drive_document_sum_query/tests.rs`'s
403    // `limit_policy_regression` module. The AVG dispatcher's
404    // `RangeDistinctProof` arm mirrors the same validate-don't-clamp
405    // policy on the prove path; these tests pin that the dispatcher
406    // uses [`crate::config::DEFAULT_QUERY_LIMIT`] (compile-time
407    // constant) rather than the operator-tunable
408    // `drive_config.default_query_limit`, AND that an explicit
409    // `limit > max_query_limit` returns a typed
410    // `QuerySyntaxError::InvalidLimit` instead of silently clamping.
411    //
412    // The AVG distinct path internally calls
413    // `execute_distinct_sum_with_proof` (the same primitive sum's
414    // RangeDistinctProof uses — see `drive_document_average_query/
415    // drive_dispatcher.rs::execute_document_average_prove`); the
416    // distinction is the index requirement (`rangeCountable +
417    // rangeSummable`, i.e. PCPS / `rangeAverageable`) and the
418    // verifier helper (`verify_aggregate_count_and_sum_query`).
419
420    use crate::config::{DriveConfig, DEFAULT_QUERY_LIMIT};
421    use crate::drive::Drive;
422    use crate::error::query::QuerySyntaxError;
423    use crate::query::drive_document_average_query::{
424        AverageMode, DocumentAverageRequest, DocumentAverageResponse,
425    };
426    use crate::query::{WhereClause, WhereOperator};
427    use crate::util::object_size_info::DocumentInfo::DocumentRefInfo;
428    use crate::util::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo};
429    use crate::util::storage_flags::StorageFlags;
430    use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure;
431    use dpp::block::block_info::BlockInfo;
432    use dpp::data_contract::accessors::v0::DataContractV0Getters;
433    use dpp::data_contract::DataContractFactory;
434    use dpp::document::{Document, DocumentV0};
435    use dpp::identifier::Identifier;
436    use dpp::platform_value::{platform_value, Value};
437    use grovedb::GroveDb;
438    use std::borrow::Cow;
439    use std::collections::BTreeMap as StdBTreeMap;
440
441    const PROTOCOL_VERSION_V12: u32 = 12;
442
443    /// v12 contract with a `widget` doctype carrying a single
444    /// `(color, amount)` `rangeAverageable: true` (= `rangeCountable +
445    /// rangeSummable`) index. The PCPS combined `byColor` index is
446    /// what the AVG `RangeDistinctProof` arm walks.
447    fn build_widget_contract_pcps() -> dpp::data_contract::DataContract {
448        let factory = DataContractFactory::new(PROTOCOL_VERSION_V12).expect("create factory");
449        let document_schema = platform_value!({
450            "type": "object",
451            "properties": {
452                "color":  {"type": "string",  "position": 0, "maxLength": 32},
453                "amount": {"type": "integer", "position": 1, "minimum": 0, "maximum": 1000},
454            },
455            "required": ["color", "amount"],
456            "indices": [{
457                "name": "byColor",
458                "properties": [{"color": "asc"}],
459                // rangeAverageable is shorthand for rangeCountable +
460                // rangeSummable on the same summable property. The
461                // DPP parser desugars it into both flags; the picker
462                // routes it through the PCPS path.
463                "summable":        "amount",
464                "rangeSummable":   true,
465                "countable":       "countable",
466                "rangeCountable":  true,
467            }],
468            "additionalProperties": false,
469        });
470        let schemas = platform_value!({ "widget": document_schema });
471        factory
472            .create_with_value_config(
473                dpp::tests::utils::generate_random_identifier_struct(),
474                0,
475                schemas,
476                None,
477                None,
478            )
479            .expect("create data contract")
480            .data_contract_owned()
481    }
482
483    fn insert_widget(
484        drive: &Drive,
485        contract: &dpp::data_contract::DataContract,
486        i: usize,
487        color: &str,
488        amount: u64,
489    ) {
490        let platform_version = PlatformVersion::latest();
491        let document_type = contract
492            .document_type_for_name("widget")
493            .expect("widget type exists");
494        let mut properties = StdBTreeMap::new();
495        properties.insert("color".to_string(), Value::Text(color.to_string()));
496        properties.insert("amount".to_string(), Value::U64(amount));
497        let document: Document = DocumentV0 {
498            id: Identifier::from([(i + 1) as u8; 32]),
499            owner_id: Identifier::from([0u8; 32]),
500            properties,
501            revision: None,
502            created_at: None,
503            updated_at: None,
504            transferred_at: None,
505            created_at_block_height: None,
506            updated_at_block_height: None,
507            transferred_at_block_height: None,
508            created_at_core_block_height: None,
509            updated_at_core_block_height: None,
510            transferred_at_core_block_height: None,
511            creator_id: None,
512        }
513        .into();
514        let storage_flags = Some(Cow::Owned(StorageFlags::SingleEpoch(0)));
515        drive
516            .add_document_for_contract(
517                DocumentAndContractInfo {
518                    owned_document_info: OwnedDocumentInfo {
519                        document_info: DocumentRefInfo((&document, storage_flags)),
520                        owner_id: None,
521                    },
522                    contract,
523                    document_type,
524                },
525                false,
526                BlockInfo::default(),
527                true,
528                None,
529                platform_version,
530                None,
531            )
532            .expect("insert widget");
533    }
534
535    /// AVG mirror of the SUM/count regression: with
536    /// `drive_config.default_query_limit = 1` and a `limit = None`
537    /// request, the dispatcher must use `DEFAULT_QUERY_LIMIT` (= 100)
538    /// for the prove path's `SizedQuery::limit`. If it regressed to
539    /// using the runtime `default_query_limit`, the reconstructed
540    /// path query would byte-differ and `verify_aggregate_count_and_sum_query`
541    /// would return Err — exactly the silent-verify-failure surface
542    /// this test guards.
543    #[test]
544    fn range_distinct_avg_proof_uses_compile_time_default_query_limit_not_operator_config() {
545        const OPERATOR_TUNED_LIMIT: u16 = 1;
546        assert_ne!(
547            DEFAULT_QUERY_LIMIT, OPERATOR_TUNED_LIMIT,
548            "test invariant: OPERATOR_TUNED_LIMIT must differ from DEFAULT_QUERY_LIMIT"
549        );
550
551        let drive = setup_drive_with_initial_state_structure(None);
552        let platform_version = PlatformVersion::latest();
553        let data_contract = build_widget_contract_pcps();
554        drive
555            .apply_contract(
556                &data_contract,
557                BlockInfo::default(),
558                true,
559                StorageFlags::optional_default_as_cow(),
560                None,
561                platform_version,
562            )
563            .expect("apply contract");
564
565        let docs = [
566            ("red", 5u64),
567            ("red", 5),
568            ("green", 7),
569            ("green", 7),
570            ("green", 7),
571            ("blue", 2),
572        ];
573        for (i, (color, amount)) in docs.iter().enumerate() {
574            insert_widget(&drive, &data_contract, i, color, *amount);
575        }
576
577        let document_type = data_contract
578            .document_type_for_name("widget")
579            .expect("widget");
580
581        let drive_config = DriveConfig {
582            default_query_limit: OPERATOR_TUNED_LIMIT,
583            ..Default::default()
584        };
585
586        let color_gt_blue = WhereClause {
587            field: "color".to_string(),
588            operator: WhereOperator::GreaterThan,
589            value: Value::Text("blue".to_string()),
590        };
591        let request = DocumentAverageRequest {
592            contract: &data_contract,
593            document_type,
594            sum_property: "amount".to_string(),
595            where_clauses: vec![color_gt_blue.clone()],
596            order_clauses: Vec::new(),
597            mode: AverageMode::GroupByRange,
598            limit: None,
599            prove: true,
600            drive_config: &drive_config,
601        };
602
603        let response = drive
604            .execute_document_average_request(request, None, platform_version)
605            .expect("dispatcher should succeed on distinct AVG path");
606        let proof_bytes = match response {
607            DocumentAverageResponse::Proof(p) => p,
608            other => panic!("expected Proof response, got {:?}", other),
609        };
610        assert!(!proof_bytes.is_empty(), "non-empty proof bytes expected");
611
612        // Reconstruct the path query the way the SDK verifier does
613        // — anchored to DEFAULT_QUERY_LIMIT.
614        let index = find_range_summable_index_for_where_clauses(
615            document_type.indexes(),
616            std::slice::from_ref(&color_gt_blue),
617            "amount",
618        )
619        .filter(|idx| idx.range_countable)
620        .expect("byColor rangeAverageable index covers `color > blue`");
621        let sum_query = DriveDocumentSumQuery {
622            document_type,
623            contract_id: data_contract.id().to_buffer(),
624            document_type_name: "widget".to_string(),
625            index,
626            where_clauses: vec![color_gt_blue],
627            sum_property: "amount".to_string(),
628        };
629        let verifier_path_query = sum_query
630            .distinct_sum_path_query(Some(DEFAULT_QUERY_LIMIT), true, platform_version)
631            .expect("path query builder accepts the same shape the prover used");
632
633        // AVG distinct path's proof verifies via the same
634        // `GroveDb::verify_query` shape sum uses — the difference is
635        // the PCPS terminator the proof commits, and the SDK extracts
636        // (count, sum) from each via `count_sum_value_or_default()`.
637        // For this regression test we only need to confirm root-hash
638        // recomputation succeeds against the DEFAULT_QUERY_LIMIT-anchored
639        // path query; any limit mismatch surfaces as Err here.
640        let (_root_hash, _elements) = GroveDb::verify_query(
641            &proof_bytes,
642            &verifier_path_query,
643            &platform_version.drive.grove_version,
644        )
645        .expect(
646            "expected proof to verify against a path query rebuilt with DEFAULT_QUERY_LIMIT; \
647             a failure here means the dispatcher signed the AVG proof with the \
648             operator-tunable default_query_limit — a consensus-adjacent silent-verify \
649             regression",
650        );
651    }
652
653    /// AVG `RangeDistinctProof` over-max rejection: explicit
654    /// `limit > max_query_limit` MUST surface as `InvalidLimit`,
655    /// not a silent clamp.
656    #[test]
657    fn range_distinct_avg_proof_rejects_limit_over_max() {
658        let drive = setup_drive_with_initial_state_structure(None);
659        let platform_version = PlatformVersion::latest();
660        let data_contract = build_widget_contract_pcps();
661        drive
662            .apply_contract(
663                &data_contract,
664                BlockInfo::default(),
665                true,
666                StorageFlags::optional_default_as_cow(),
667                None,
668                platform_version,
669            )
670            .expect("apply contract");
671
672        insert_widget(&drive, &data_contract, 0, "red", 5);
673
674        let document_type = data_contract
675            .document_type_for_name("widget")
676            .expect("widget");
677        let drive_config = DriveConfig::default();
678        let over_max = drive_config.max_query_limit as u32 + 1;
679
680        let color_gt_blue = WhereClause {
681            field: "color".to_string(),
682            operator: WhereOperator::GreaterThan,
683            value: Value::Text("blue".to_string()),
684        };
685        let request = DocumentAverageRequest {
686            contract: &data_contract,
687            document_type,
688            sum_property: "amount".to_string(),
689            where_clauses: vec![color_gt_blue],
690            order_clauses: Vec::new(),
691            mode: AverageMode::GroupByRange,
692            limit: Some(over_max),
693            prove: true,
694            drive_config: &drive_config,
695        };
696
697        let err = drive
698            .execute_document_average_request(request, None, platform_version)
699            .expect_err("limit > max_query_limit must reject, not clamp");
700
701        assert!(
702            matches!(err, Error::Query(QuerySyntaxError::InvalidLimit(_))),
703            "expected QuerySyntaxError::InvalidLimit, got {err:?}"
704        );
705        let msg = err.to_string();
706        assert!(
707            msg.contains("exceeds max_query_limit"),
708            "error must name the rejected limit; got: {msg}"
709        );
710    }
711
712    /// AVG no-range `GroupByIn` + prove MUST hit the point-lookup
713    /// arm and emit proof bytes — the sum router resolves this
714    /// shape to `DocumentSumMode::PointLookupProof` and the SDK
715    /// helper at `verify_point_lookup_count_and_sum_proof` is the
716    /// matching verifier. Before the fix this fell through every
717    /// arm in `execute_document_average_prove` and returned
718    /// `QuerySyntaxError::Unsupported`, leaving the SDK unable to
719    /// finish what it had already started: encode + dispatch a
720    /// valid AVG `GroupByIn` request.
721    ///
722    /// This regression test pins both halves of the contract:
723    ///   1. The server returns proof bytes (no fallthrough error).
724    ///   2. The proof bytes are bincode-decodable as a `GroveDBProof`
725    ///      (sanity-check that it's a real point-lookup payload
726    ///      rather than an empty placeholder).
727    #[test]
728    fn no_range_group_by_in_avg_prove_routes_to_point_lookup() {
729        use grovedb::operations::proof::GroveDBProof;
730
731        let drive = setup_drive_with_initial_state_structure(None);
732        let platform_version = PlatformVersion::latest();
733
734        // A `summable + countable` (non-range) index is what the
735        // point-lookup AVG arm walks. Build a `widget` doctype with
736        // `byColor` index: `summable: "amount" + countable:
737        // "countable"`. (No rangeSummable / rangeCountable — those
738        // are for the range arms.)
739        let factory = DataContractFactory::new(PROTOCOL_VERSION_V12).expect("create factory");
740        let document_schema = platform_value!({
741            "type": "object",
742            "properties": {
743                "color":  {"type": "string",  "position": 0, "maxLength": 32},
744                "amount": {"type": "integer", "position": 1, "minimum": 0, "maximum": 1000},
745            },
746            "required": ["color", "amount"],
747            "indices": [{
748                "name": "byColor",
749                "properties": [{"color": "asc"}],
750                "summable":  "amount",
751                "countable": "countable",
752            }],
753            "additionalProperties": false,
754        });
755        let schemas = platform_value!({ "widget": document_schema });
756        let data_contract = factory
757            .create_with_value_config(
758                dpp::tests::utils::generate_random_identifier_struct(),
759                0,
760                schemas,
761                None,
762                None,
763            )
764            .expect("create data contract")
765            .data_contract_owned();
766
767        drive
768            .apply_contract(
769                &data_contract,
770                BlockInfo::default(),
771                true,
772                StorageFlags::optional_default_as_cow(),
773                None,
774                platform_version,
775            )
776            .expect("apply contract");
777
778        insert_widget(&drive, &data_contract, 0, "red", 5);
779        insert_widget(&drive, &data_contract, 1, "red", 7);
780        insert_widget(&drive, &data_contract, 2, "green", 3);
781
782        let document_type = data_contract
783            .document_type_for_name("widget")
784            .expect("widget");
785        let drive_config = DriveConfig::default();
786
787        // GroupByIn shape: `color IN ["red", "green"]`, no range,
788        // no order. The router maps this to PointLookupProof and
789        // the dispatcher must hand back proof bytes (NOT
790        // QuerySyntaxError::Unsupported).
791        let color_in = WhereClause {
792            field: "color".to_string(),
793            operator: WhereOperator::In,
794            value: Value::Array(vec![
795                Value::Text("red".to_string()),
796                Value::Text("green".to_string()),
797            ]),
798        };
799        let request = DocumentAverageRequest {
800            contract: &data_contract,
801            document_type,
802            sum_property: "amount".to_string(),
803            where_clauses: vec![color_in],
804            order_clauses: Vec::new(),
805            mode: AverageMode::GroupByIn,
806            limit: None,
807            prove: true,
808            drive_config: &drive_config,
809        };
810
811        let response = drive
812            .execute_document_average_request(request, None, platform_version)
813            .expect(
814                "no-range GroupByIn AVG + prove must hit the point-lookup arm \
815                 (router resolves this shape to DocumentSumMode::PointLookupProof); \
816                 a failure here means execute_document_average_prove regressed to \
817                 the pre-fix gap that rejected this combination with Unsupported",
818            );
819        let proof_bytes = match response {
820            DocumentAverageResponse::Proof(p) => p,
821            other => panic!("expected Proof response, got {:?}", other),
822        };
823        assert!(
824            !proof_bytes.is_empty(),
825            "non-empty proof bytes expected from point-lookup AVG path"
826        );
827
828        // Decode as a GroveDBProof — sanity-checks that it's a real
829        // payload rather than a placeholder. Verification (root-hash
830        // recomputation) is exercised end-to-end in the SDK
831        // FromProof tests; the dispatcher-level test here just pins
832        // the routing decision.
833        let bincode_config = bincode::config::standard()
834            .with_big_endian()
835            .with_no_limit();
836        let _: (GroveDBProof, _) = bincode::decode_from_slice(&proof_bytes, bincode_config)
837            .expect("proof bytes must bincode-decode as a GroveDBProof");
838    }
839
840    // ── Joint count-and-sum no-prove executor cross-checks ────────
841    //
842    // Acceptance criterion 4 of issue #3687: "one [test] per joint
843    // executor confirming `(count, sum)` match what the current
844    // double-dispatch produces, against the same grades-contract
845    // fixture."
846    //
847    // Strategy: for each joint executor (Total / PerInValue /
848    // RangeNoProof — and RangeNoProof's distinct branch), issue the
849    // AVG no-prove request via `execute_document_average_request`
850    // AND independently issue separate count + sum requests under
851    // the same transaction. Assert the joint executor's
852    // `(count, sum)` matches the zipped pair from the independent
853    // count + sum surfaces — a cross-check the joint and per-surface
854    // dispatchers cannot silently disagree.
855
856    use crate::query::drive_document_average_query::AverageEntry;
857    use crate::query::drive_document_count_query::{
858        CountMode, DocumentCountRequest, DocumentCountResponse,
859    };
860    use crate::query::drive_document_sum_query::{
861        DocumentSumRequest, DocumentSumResponse, SumMode,
862    };
863
864    /// Issue an independent count + sum pair via the per-surface
865    /// dispatchers and return the zipped `(count, sum)` aggregate.
866    /// Used as the source of truth for cross-checking the joint
867    /// executor's output.
868    fn independent_count_sum_aggregate(
869        drive: &Drive,
870        contract: &dpp::data_contract::DataContract,
871        document_type: dpp::data_contract::document_type::DocumentTypeRef,
872        sum_property: &str,
873        where_clauses: Vec<WhereClause>,
874        drive_config: &DriveConfig,
875        platform_version: &PlatformVersion,
876    ) -> (u64, i64) {
877        let count_request = DocumentCountRequest {
878            contract,
879            document_type,
880            where_clauses: where_clauses.clone(),
881            order_clauses: Vec::new(),
882            mode: CountMode::Aggregate,
883            limit: None,
884            prove: false,
885            drive_config,
886        };
887        let sum_request = DocumentSumRequest {
888            contract,
889            document_type,
890            sum_property: sum_property.to_string(),
891            where_clauses,
892            order_clauses: Vec::new(),
893            mode: SumMode::Aggregate,
894            limit: None,
895            prove: false,
896            drive_config,
897        };
898        let count_resp = drive
899            .execute_document_count_request(count_request, None, platform_version)
900            .expect("independent count");
901        let sum_resp = drive
902            .execute_document_sum_request(sum_request, None, platform_version)
903            .expect("independent sum");
904        let count = match count_resp {
905            DocumentCountResponse::Aggregate(c) => c,
906            other => panic!("expected count Aggregate, got {:?}", other),
907        };
908        let sum = match sum_resp {
909            DocumentSumResponse::Aggregate(s) => s,
910            other => panic!("expected sum Aggregate, got {:?}", other),
911        };
912        (count, sum)
913    }
914
915    /// `execute_document_count_and_sum_total_no_proof` cross-check:
916    /// empty-where total on a doctype with `documents_summable +
917    /// documents_countable`. Goes through the primary-key fast path.
918    #[test]
919    fn joint_total_executor_matches_independent_count_plus_sum() {
920        let drive = setup_drive_with_initial_state_structure(None);
921        let platform_version = PlatformVersion::latest();
922
923        // The empty-where Total path requires the doctype's
924        // documents_summable + documents_countable to be set, but a
925        // covering `summable + countable` byColor index also works
926        // for the Equal-only-fully-covered sub-path. Use the latter
927        // since the test factory above doesn't easily produce
928        // doctype-level summable+countable. The Equal-only branch
929        // of execute_document_count_and_sum_total_no_proof still
930        // routes through `DocumentSumMode::Total` per sum's table.
931        let factory = DataContractFactory::new(PROTOCOL_VERSION_V12).expect("create factory");
932        let document_schema = platform_value!({
933            "type": "object",
934            "properties": {
935                "color":  {"type": "string",  "position": 0, "maxLength": 32},
936                "amount": {"type": "integer", "position": 1, "minimum": 0, "maximum": 1000},
937            },
938            "required": ["color", "amount"],
939            "indices": [{
940                "name": "byColor",
941                "properties": [{"color": "asc"}],
942                "summable":  "amount",
943                "countable": "countable",
944            }],
945            "additionalProperties": false,
946        });
947        let schemas = platform_value!({ "widget": document_schema });
948        let data_contract = factory
949            .create_with_value_config(
950                dpp::tests::utils::generate_random_identifier_struct(),
951                0,
952                schemas,
953                None,
954                None,
955            )
956            .expect("create data contract")
957            .data_contract_owned();
958        drive
959            .apply_contract(
960                &data_contract,
961                BlockInfo::default(),
962                true,
963                StorageFlags::optional_default_as_cow(),
964                None,
965                platform_version,
966            )
967            .expect("apply contract");
968
969        let docs = [
970            ("red", 5u64),
971            ("red", 5),
972            ("red", 7),
973            ("green", 3),
974            ("green", 4),
975            ("blue", 1),
976        ];
977        for (i, (color, amount)) in docs.iter().enumerate() {
978            insert_widget(&drive, &data_contract, i, color, *amount);
979        }
980
981        let document_type = data_contract
982            .document_type_for_name("widget")
983            .expect("widget");
984        let drive_config = DriveConfig::default();
985
986        // Aggregate, no where → empty-where Total path. The doctype
987        // doesn't declare documents_summable here so the executor
988        // fall-through is the picker path on the byColor index. But
989        // the empty-where branch requires documents_summable; if the
990        // doctype lacks it, the picker is invoked with empty where,
991        // which `find_summable_index_for_where_clauses` rejects
992        // (zero indexable fields). So we test Equal-only-fully-
993        // covered instead — same `DocumentSumMode::Total`
994        // resolution.
995        let where_clauses = vec![WhereClause {
996            field: "color".to_string(),
997            operator: WhereOperator::Equal,
998            value: Value::Text("red".to_string()),
999        }];
1000
1001        let request = DocumentAverageRequest {
1002            contract: &data_contract,
1003            document_type,
1004            sum_property: "amount".to_string(),
1005            where_clauses: where_clauses.clone(),
1006            order_clauses: Vec::new(),
1007            mode: AverageMode::Aggregate,
1008            limit: None,
1009            prove: false,
1010            drive_config: &drive_config,
1011        };
1012
1013        let joint_response = drive
1014            .execute_document_average_request(request, None, platform_version)
1015            .expect("joint total dispatch");
1016        let (joint_count, joint_sum) = match joint_response {
1017            DocumentAverageResponse::Aggregate { count, sum } => (count, sum),
1018            other => panic!("expected Aggregate, got {:?}", other),
1019        };
1020
1021        let (indep_count, indep_sum) = independent_count_sum_aggregate(
1022            &drive,
1023            &data_contract,
1024            document_type,
1025            "amount",
1026            where_clauses,
1027            &drive_config,
1028            platform_version,
1029        );
1030
1031        assert_eq!(
1032            (joint_count, joint_sum),
1033            (indep_count, indep_sum),
1034            "joint total executor must produce the same (count, sum) as \
1035             independent count + sum dispatch (red == 3 docs / sum 17)"
1036        );
1037        // Sanity check against the fixture: red docs are 5+5+7 = 17 / count 3.
1038        assert_eq!((joint_count, joint_sum), (3, 17));
1039    }
1040
1041    /// `execute_document_count_and_sum_per_in_value_no_proof`
1042    /// cross-check: In on a `summable + countable` index.
1043    #[test]
1044    fn joint_per_in_value_executor_matches_independent_count_plus_sum() {
1045        let drive = setup_drive_with_initial_state_structure(None);
1046        let platform_version = PlatformVersion::latest();
1047
1048        let factory = DataContractFactory::new(PROTOCOL_VERSION_V12).expect("create factory");
1049        let document_schema = platform_value!({
1050            "type": "object",
1051            "properties": {
1052                "color":  {"type": "string",  "position": 0, "maxLength": 32},
1053                "amount": {"type": "integer", "position": 1, "minimum": 0, "maximum": 1000},
1054            },
1055            "required": ["color", "amount"],
1056            "indices": [{
1057                "name": "byColor",
1058                "properties": [{"color": "asc"}],
1059                "summable":  "amount",
1060                "countable": "countable",
1061            }],
1062            "additionalProperties": false,
1063        });
1064        let schemas = platform_value!({ "widget": document_schema });
1065        let data_contract = factory
1066            .create_with_value_config(
1067                dpp::tests::utils::generate_random_identifier_struct(),
1068                0,
1069                schemas,
1070                None,
1071                None,
1072            )
1073            .expect("create data contract")
1074            .data_contract_owned();
1075        drive
1076            .apply_contract(
1077                &data_contract,
1078                BlockInfo::default(),
1079                true,
1080                StorageFlags::optional_default_as_cow(),
1081                None,
1082                platform_version,
1083            )
1084            .expect("apply contract");
1085
1086        let docs = [
1087            ("red", 5u64),
1088            ("red", 7),
1089            ("green", 3),
1090            ("green", 4),
1091            ("blue", 1),
1092            ("blue", 2),
1093        ];
1094        for (i, (color, amount)) in docs.iter().enumerate() {
1095            insert_widget(&drive, &data_contract, i, color, *amount);
1096        }
1097
1098        let document_type = data_contract
1099            .document_type_for_name("widget")
1100            .expect("widget");
1101        let drive_config = DriveConfig::default();
1102
1103        let color_in = WhereClause {
1104            field: "color".to_string(),
1105            operator: WhereOperator::In,
1106            value: Value::Array(vec![
1107                Value::Text("red".to_string()),
1108                Value::Text("green".to_string()),
1109            ]),
1110        };
1111
1112        let request = DocumentAverageRequest {
1113            contract: &data_contract,
1114            document_type,
1115            sum_property: "amount".to_string(),
1116            where_clauses: vec![color_in.clone()],
1117            order_clauses: Vec::new(),
1118            mode: AverageMode::GroupByIn,
1119            limit: None,
1120            prove: false,
1121            drive_config: &drive_config,
1122        };
1123
1124        let joint_response = drive
1125            .execute_document_average_request(request, None, platform_version)
1126            .expect("joint per-in-value dispatch");
1127        let joint_entries = match joint_response {
1128            DocumentAverageResponse::Entries(e) => e,
1129            other => panic!("expected Entries, got {:?}", other),
1130        };
1131
1132        // Cross-check via independent count + sum per-In dispatch.
1133        let count_request = DocumentCountRequest {
1134            contract: &data_contract,
1135            document_type,
1136            where_clauses: vec![color_in.clone()],
1137            order_clauses: Vec::new(),
1138            mode: CountMode::GroupByIn,
1139            limit: None,
1140            prove: false,
1141            drive_config: &drive_config,
1142        };
1143        let sum_request = DocumentSumRequest {
1144            contract: &data_contract,
1145            document_type,
1146            sum_property: "amount".to_string(),
1147            where_clauses: vec![color_in],
1148            order_clauses: Vec::new(),
1149            mode: SumMode::GroupByIn,
1150            limit: None,
1151            prove: false,
1152            drive_config: &drive_config,
1153        };
1154        let count_resp = drive
1155            .execute_document_count_request(count_request, None, platform_version)
1156            .expect("independent count");
1157        let sum_resp = drive
1158            .execute_document_sum_request(sum_request, None, platform_version)
1159            .expect("independent sum");
1160        let count_entries = match count_resp {
1161            DocumentCountResponse::Entries(e) => e,
1162            other => panic!("expected count Entries, got {:?}", other),
1163        };
1164        let sum_entries = match sum_resp {
1165            DocumentSumResponse::Entries(e) => e,
1166            other => panic!("expected sum Entries, got {:?}", other),
1167        };
1168
1169        // Zip by key and assert joint matches.
1170        assert_eq!(joint_entries.len(), count_entries.len());
1171        assert_eq!(joint_entries.len(), sum_entries.len());
1172        for ((joint, count), sum) in joint_entries
1173            .iter()
1174            .zip(count_entries.iter())
1175            .zip(sum_entries.iter())
1176        {
1177            assert_eq!(joint.key, count.key);
1178            assert_eq!(joint.key, sum.key);
1179            assert_eq!(joint.count, count.count);
1180            assert_eq!(joint.sum, sum.sum);
1181        }
1182        // Two entries — red and green.
1183        assert_eq!(joint_entries.len(), 2);
1184        // red: 2 docs, sum = 12.
1185        // green: 2 docs, sum = 7.
1186        // BTreeMap orders by serialized key bytes (lex on string
1187        // bytes since color is Text). "green" < "red" lex.
1188        let mut by_key: Vec<&AverageEntry> = joint_entries.iter().collect();
1189        by_key.sort_by(|a, b| a.key.cmp(&b.key));
1190        let red_entry = by_key
1191            .iter()
1192            .find(|e| e.key.windows(3).any(|w| w == b"red"))
1193            .expect("red entry");
1194        let green_entry = by_key
1195            .iter()
1196            .find(|e| e.key.windows(5).any(|w| w == b"green"))
1197            .expect("green entry");
1198        assert_eq!(red_entry.count, Some(2));
1199        assert_eq!(red_entry.sum, Some(12));
1200        assert_eq!(green_entry.count, Some(2));
1201        assert_eq!(green_entry.sum, Some(7));
1202    }
1203
1204    /// `execute_document_count_and_sum_range_no_proof` cross-check:
1205    /// distinct GroupByRange on a `rangeAverageable` (PCPS) index.
1206    #[test]
1207    fn joint_range_no_proof_executor_matches_independent_count_plus_sum() {
1208        let drive = setup_drive_with_initial_state_structure(None);
1209        let platform_version = PlatformVersion::latest();
1210        let data_contract = build_widget_contract_pcps();
1211        drive
1212            .apply_contract(
1213                &data_contract,
1214                BlockInfo::default(),
1215                true,
1216                StorageFlags::optional_default_as_cow(),
1217                None,
1218                platform_version,
1219            )
1220            .expect("apply contract");
1221
1222        let docs = [
1223            ("red", 5u64),
1224            ("red", 7),
1225            ("green", 3),
1226            ("green", 4),
1227            ("green", 6),
1228            ("blue", 2),
1229        ];
1230        for (i, (color, amount)) in docs.iter().enumerate() {
1231            insert_widget(&drive, &data_contract, i, color, *amount);
1232        }
1233
1234        let document_type = data_contract
1235            .document_type_for_name("widget")
1236            .expect("widget");
1237        let drive_config = DriveConfig::default();
1238
1239        // `color > "blue"` on the byColor rangeAverageable index.
1240        let color_gt_blue = WhereClause {
1241            field: "color".to_string(),
1242            operator: WhereOperator::GreaterThan,
1243            value: Value::Text("blue".to_string()),
1244        };
1245
1246        let request = DocumentAverageRequest {
1247            contract: &data_contract,
1248            document_type,
1249            sum_property: "amount".to_string(),
1250            where_clauses: vec![color_gt_blue.clone()],
1251            order_clauses: Vec::new(),
1252            mode: AverageMode::GroupByRange,
1253            limit: None,
1254            prove: false,
1255            drive_config: &drive_config,
1256        };
1257
1258        let joint_response = drive
1259            .execute_document_average_request(request, None, platform_version)
1260            .expect("joint range distinct dispatch");
1261        let joint_entries = match joint_response {
1262            DocumentAverageResponse::Entries(e) => e,
1263            other => panic!("expected Entries, got {:?}", other),
1264        };
1265
1266        // Cross-check via independent count + sum distinct dispatch.
1267        let count_request = DocumentCountRequest {
1268            contract: &data_contract,
1269            document_type,
1270            where_clauses: vec![color_gt_blue.clone()],
1271            order_clauses: Vec::new(),
1272            mode: CountMode::GroupByRange,
1273            limit: None,
1274            prove: false,
1275            drive_config: &drive_config,
1276        };
1277        let sum_request = DocumentSumRequest {
1278            contract: &data_contract,
1279            document_type,
1280            sum_property: "amount".to_string(),
1281            where_clauses: vec![color_gt_blue],
1282            order_clauses: Vec::new(),
1283            mode: SumMode::GroupByRange,
1284            limit: None,
1285            prove: false,
1286            drive_config: &drive_config,
1287        };
1288        let count_resp = drive
1289            .execute_document_count_request(count_request, None, platform_version)
1290            .expect("independent count");
1291        let sum_resp = drive
1292            .execute_document_sum_request(sum_request, None, platform_version)
1293            .expect("independent sum");
1294        let count_entries = match count_resp {
1295            DocumentCountResponse::Entries(e) => e,
1296            other => panic!("expected count Entries, got {:?}", other),
1297        };
1298        let sum_entries = match sum_resp {
1299            DocumentSumResponse::Entries(e) => e,
1300            other => panic!("expected sum Entries, got {:?}", other),
1301        };
1302
1303        // Both executors emit per-distinct-key entries in ascending
1304        // serialized-key order; the lengths must match and per-key
1305        // (count, sum) must zip to the same values.
1306        assert_eq!(joint_entries.len(), count_entries.len());
1307        assert_eq!(joint_entries.len(), sum_entries.len());
1308        for ((joint, count), sum) in joint_entries
1309            .iter()
1310            .zip(count_entries.iter())
1311            .zip(sum_entries.iter())
1312        {
1313            assert_eq!(joint.key, count.key);
1314            assert_eq!(joint.key, sum.key);
1315            assert_eq!(joint.count, count.count);
1316            assert_eq!(joint.sum, sum.sum);
1317        }
1318        // Two distinct keys (green, red); blue is filtered out by
1319        // the range. green: 3 docs, sum=13; red: 2 docs, sum=12.
1320        assert_eq!(joint_entries.len(), 2);
1321    }
1322
1323    /// Flat-summed range cross-check: `Aggregate + range` on a PCPS
1324    /// index resolves to `DocumentSumMode::RangeNoProof` with
1325    /// `return_distinct_sums_in_range = false`. The joint executor
1326    /// folds visited PCPS elements via `count_sum_value_or_default()`
1327    /// in Rust (no engine-side combined accumulator exists). Pin parity
1328    /// vs. the independent count + sum aggregate dispatch — this is the
1329    /// path where the issue's perf win lands.
1330    #[test]
1331    fn joint_range_aggregate_executor_matches_independent_count_plus_sum() {
1332        let drive = setup_drive_with_initial_state_structure(None);
1333        let platform_version = PlatformVersion::latest();
1334        let data_contract = build_widget_contract_pcps();
1335        drive
1336            .apply_contract(
1337                &data_contract,
1338                BlockInfo::default(),
1339                true,
1340                StorageFlags::optional_default_as_cow(),
1341                None,
1342                platform_version,
1343            )
1344            .expect("apply contract");
1345
1346        let docs = [
1347            ("red", 5u64),
1348            ("red", 7),
1349            ("green", 3),
1350            ("green", 4),
1351            ("green", 6),
1352            ("blue", 2),
1353        ];
1354        for (i, (color, amount)) in docs.iter().enumerate() {
1355            insert_widget(&drive, &data_contract, i, color, *amount);
1356        }
1357
1358        let document_type = data_contract
1359            .document_type_for_name("widget")
1360            .expect("widget");
1361        let drive_config = DriveConfig::default();
1362
1363        let color_gt_blue = WhereClause {
1364            field: "color".to_string(),
1365            operator: WhereOperator::GreaterThan,
1366            value: Value::Text("blue".to_string()),
1367        };
1368
1369        let request = DocumentAverageRequest {
1370            contract: &data_contract,
1371            document_type,
1372            sum_property: "amount".to_string(),
1373            where_clauses: vec![color_gt_blue.clone()],
1374            order_clauses: Vec::new(),
1375            mode: AverageMode::Aggregate,
1376            limit: None,
1377            prove: false,
1378            drive_config: &drive_config,
1379        };
1380
1381        let joint_response = drive
1382            .execute_document_average_request(request, None, platform_version)
1383            .expect("joint range aggregate dispatch");
1384        let (joint_count, joint_sum) = match joint_response {
1385            DocumentAverageResponse::Aggregate { count, sum } => (count, sum),
1386            other => panic!("expected Aggregate, got {:?}", other),
1387        };
1388
1389        let (indep_count, indep_sum) = independent_count_sum_aggregate(
1390            &drive,
1391            &data_contract,
1392            document_type,
1393            "amount",
1394            vec![color_gt_blue],
1395            &drive_config,
1396            platform_version,
1397        );
1398
1399        assert_eq!(
1400            (joint_count, joint_sum),
1401            (indep_count, indep_sum),
1402            "joint range-aggregate executor must produce the same (count, sum) \
1403             as independent count + sum range dispatch"
1404        );
1405        // Sanity check: color > "blue" matches green (3,4,6 = sum 13)
1406        // + red (5,7 = sum 12); total 5 docs / sum 25.
1407        assert_eq!((joint_count, joint_sum), (5, 25));
1408    }
1409
1410    /// Compound-summed range cross-check: `GroupByIn + In + range` on
1411    /// a PCPS index resolves to `DocumentSumMode::RangeNoProof` with
1412    /// `return_distinct_sums_in_range = false`. The joint executor's
1413    /// distinct path query expresses the multi-In outer walk as a
1414    /// single grovedb call (atomicity inherent) and folds each
1415    /// In-branch's PCPS elements into one `(count, sum)` pair via
1416    /// `count_sum_value_or_default()`.
1417    ///
1418    /// Pin parity vs. the independent count + sum dispatch. This is
1419    /// the second untested-flat-summed branch the agent's three tests
1420    /// don't cover.
1421    #[test]
1422    fn joint_range_group_by_in_executor_matches_independent_count_plus_sum() {
1423        let drive = setup_drive_with_initial_state_structure(None);
1424        let platform_version = PlatformVersion::latest();
1425
1426        // PCPS index keyed on (color, amount) so In on color + range
1427        // on amount fits the rangeCountable + rangeSummable shape.
1428        let factory = DataContractFactory::new(PROTOCOL_VERSION_V12).expect("create factory");
1429        let document_schema = platform_value!({
1430            "type": "object",
1431            "properties": {
1432                "color":  {"type": "string",  "position": 0, "maxLength": 32},
1433                "amount": {"type": "integer", "position": 1, "minimum": 0, "maximum": 1000},
1434            },
1435            "required": ["color", "amount"],
1436            "indices": [{
1437                "name": "byColorAmount",
1438                "properties": [{"color": "asc"}, {"amount": "asc"}],
1439                "summable":        "amount",
1440                "rangeSummable":   true,
1441                "countable":       "countable",
1442                "rangeCountable":  true,
1443            }],
1444            "additionalProperties": false,
1445        });
1446        let schemas = platform_value!({ "widget": document_schema });
1447        let data_contract = factory
1448            .create_with_value_config(
1449                dpp::tests::utils::generate_random_identifier_struct(),
1450                0,
1451                schemas,
1452                None,
1453                None,
1454            )
1455            .expect("create data contract")
1456            .data_contract_owned();
1457        drive
1458            .apply_contract(
1459                &data_contract,
1460                BlockInfo::default(),
1461                true,
1462                StorageFlags::optional_default_as_cow(),
1463                None,
1464                platform_version,
1465            )
1466            .expect("apply contract");
1467
1468        let docs = [
1469            ("red", 5u64),
1470            ("red", 7),
1471            ("red", 9),
1472            ("green", 3),
1473            ("green", 4),
1474            ("blue", 8),
1475            ("blue", 9),
1476        ];
1477        for (i, (color, amount)) in docs.iter().enumerate() {
1478            insert_widget(&drive, &data_contract, i, color, *amount);
1479        }
1480
1481        let document_type = data_contract
1482            .document_type_for_name("widget")
1483            .expect("widget");
1484        let drive_config = DriveConfig::default();
1485
1486        // In on color (red, green) + range on amount (≥ 4).
1487        let color_in = WhereClause {
1488            field: "color".to_string(),
1489            operator: WhereOperator::In,
1490            value: Value::Array(vec![
1491                Value::Text("red".to_string()),
1492                Value::Text("green".to_string()),
1493            ]),
1494        };
1495        let amount_ge_4 = WhereClause {
1496            field: "amount".to_string(),
1497            operator: WhereOperator::GreaterThanOrEquals,
1498            value: Value::U64(4),
1499        };
1500
1501        let request = DocumentAverageRequest {
1502            contract: &data_contract,
1503            document_type,
1504            sum_property: "amount".to_string(),
1505            where_clauses: vec![color_in.clone(), amount_ge_4.clone()],
1506            order_clauses: Vec::new(),
1507            mode: AverageMode::GroupByIn,
1508            limit: None,
1509            prove: false,
1510            drive_config: &drive_config,
1511        };
1512
1513        let joint_response = drive
1514            .execute_document_average_request(request, None, platform_version)
1515            .expect("joint range GroupByIn dispatch");
1516        let joint_entries = match joint_response {
1517            DocumentAverageResponse::Entries(e) => e,
1518            other => panic!("expected Entries, got {:?}", other),
1519        };
1520
1521        // Independent count + sum GroupByIn dispatch.
1522        let count_request = DocumentCountRequest {
1523            contract: &data_contract,
1524            document_type,
1525            where_clauses: vec![color_in.clone(), amount_ge_4.clone()],
1526            order_clauses: Vec::new(),
1527            mode: CountMode::GroupByIn,
1528            limit: None,
1529            prove: false,
1530            drive_config: &drive_config,
1531        };
1532        let sum_request = DocumentSumRequest {
1533            contract: &data_contract,
1534            document_type,
1535            sum_property: "amount".to_string(),
1536            where_clauses: vec![color_in, amount_ge_4],
1537            order_clauses: Vec::new(),
1538            mode: SumMode::GroupByIn,
1539            limit: None,
1540            prove: false,
1541            drive_config: &drive_config,
1542        };
1543        let count_resp = drive
1544            .execute_document_count_request(count_request, None, platform_version)
1545            .expect("independent count");
1546        let sum_resp = drive
1547            .execute_document_sum_request(sum_request, None, platform_version)
1548            .expect("independent sum");
1549        let count_entries = match count_resp {
1550            DocumentCountResponse::Entries(e) => e,
1551            other => panic!("expected count Entries, got {:?}", other),
1552        };
1553        let sum_entries = match sum_resp {
1554            DocumentSumResponse::Entries(e) => e,
1555            other => panic!("expected sum Entries, got {:?}", other),
1556        };
1557
1558        // The independent count and sum dispatches both produce entries
1559        // for every In branch (with `count`/`sum` reflecting the In
1560        // branch's value); the joint executor must produce the same
1561        // shape. Build a key-keyed map for each and assert pairwise
1562        // equality on the (count, sum) pair.
1563        use std::collections::BTreeMap;
1564        let count_by_key: BTreeMap<Vec<u8>, Option<u64>> = count_entries
1565            .iter()
1566            .map(|e| (e.key.clone(), e.count))
1567            .collect();
1568        let sum_by_key: BTreeMap<Vec<u8>, Option<i64>> =
1569            sum_entries.iter().map(|e| (e.key.clone(), e.sum)).collect();
1570        let joint_by_key: BTreeMap<Vec<u8>, (Option<u64>, Option<i64>)> = joint_entries
1571            .iter()
1572            .map(|e| (e.key.clone(), (e.count, e.sum)))
1573            .collect();
1574
1575        assert_eq!(
1576            count_by_key.keys().collect::<Vec<_>>(),
1577            joint_by_key.keys().collect::<Vec<_>>(),
1578            "joint executor must emit the same In-branch keys as independent count"
1579        );
1580        for (key, (joint_count, joint_sum)) in joint_by_key.iter() {
1581            assert_eq!(joint_count, count_by_key.get(key).unwrap());
1582            assert_eq!(joint_sum, sum_by_key.get(key).unwrap());
1583        }
1584    }
1585
1586    /// Distinct AVG no-proof MUST honor the request's `limit` —
1587    /// `GroupByRange` over a wide range should truncate to the
1588    /// caller's `limit` rather than enumerate every distinct in-range
1589    /// terminator. Regression test for the joint dispatcher's
1590    /// `RangeNoProof` distinct branch: prior to the P2 fix the
1591    /// dispatcher hard-coded `None` into `distinct_sum_path_query`,
1592    /// silently returning every matching key.
1593    #[test]
1594    fn distinct_avg_no_proof_honors_explicit_limit() {
1595        let drive = setup_drive_with_initial_state_structure(None);
1596        let platform_version = PlatformVersion::latest();
1597        let data_contract = build_widget_contract_pcps();
1598        drive
1599            .apply_contract(
1600                &data_contract,
1601                BlockInfo::default(),
1602                true,
1603                StorageFlags::optional_default_as_cow(),
1604                None,
1605                platform_version,
1606            )
1607            .expect("apply contract");
1608
1609        // Five distinct color buckets so a `limit = 2` request must
1610        // truncate the result set; otherwise the executor would emit
1611        // all five.
1612        let docs = [
1613            ("red", 5u64),
1614            ("green", 7),
1615            ("blue", 2),
1616            ("yellow", 4),
1617            ("purple", 9),
1618        ];
1619        for (i, (color, amount)) in docs.iter().enumerate() {
1620            insert_widget(&drive, &data_contract, i, color, *amount);
1621        }
1622
1623        let document_type = data_contract
1624            .document_type_for_name("widget")
1625            .expect("widget");
1626        let drive_config = DriveConfig::default();
1627
1628        let color_ge_a = WhereClause {
1629            field: "color".to_string(),
1630            operator: WhereOperator::GreaterThanOrEquals,
1631            value: Value::Text("a".to_string()),
1632        };
1633
1634        let request = DocumentAverageRequest {
1635            contract: &data_contract,
1636            document_type,
1637            sum_property: "amount".to_string(),
1638            where_clauses: vec![color_ge_a],
1639            order_clauses: Vec::new(),
1640            mode: AverageMode::GroupByRange,
1641            limit: Some(2),
1642            prove: false,
1643            drive_config: &drive_config,
1644        };
1645
1646        let response = drive
1647            .execute_document_average_request(request, None, platform_version)
1648            .expect("dispatcher should succeed");
1649        let entries = match response {
1650            DocumentAverageResponse::Entries(e) => e,
1651            other => panic!("expected Entries, got {:?}", other),
1652        };
1653        assert_eq!(
1654            entries.len(),
1655            2,
1656            "distinct AVG no-proof must apply the request's `limit = 2` and \
1657             return exactly 2 entries; got {entries:?}"
1658        );
1659    }
1660
1661    /// Distinct AVG no-proof with `limit = None` must default to
1662    /// `drive_config.default_query_limit`, not enumerate every
1663    /// distinct key. Regression test for the same hard-coded `None`
1664    /// the prior implementation passed.
1665    #[test]
1666    fn distinct_avg_no_proof_defaults_limit_to_operator_default_query_limit() {
1667        let drive = setup_drive_with_initial_state_structure(None);
1668        let platform_version = PlatformVersion::latest();
1669        let data_contract = build_widget_contract_pcps();
1670        drive
1671            .apply_contract(
1672                &data_contract,
1673                BlockInfo::default(),
1674                true,
1675                StorageFlags::optional_default_as_cow(),
1676                None,
1677                platform_version,
1678            )
1679            .expect("apply contract");
1680
1681        // Five distinct buckets and an operator-tuned
1682        // `default_query_limit = 3`. The dispatcher must honor the
1683        // operator's runtime default on the no-proof path (this is
1684        // explicitly documented as DIFFERENT from the prove path,
1685        // which uses the compile-time constant for byte-stability of
1686        // proof reconstruction). A regression that leaves limit as
1687        // `None` would emit all 5 entries.
1688        let docs = [
1689            ("red", 5u64),
1690            ("green", 7),
1691            ("blue", 2),
1692            ("yellow", 4),
1693            ("purple", 9),
1694        ];
1695        for (i, (color, amount)) in docs.iter().enumerate() {
1696            insert_widget(&drive, &data_contract, i, color, *amount);
1697        }
1698
1699        let document_type = data_contract
1700            .document_type_for_name("widget")
1701            .expect("widget");
1702        let drive_config = DriveConfig {
1703            default_query_limit: 3,
1704            ..Default::default()
1705        };
1706
1707        let color_ge_a = WhereClause {
1708            field: "color".to_string(),
1709            operator: WhereOperator::GreaterThanOrEquals,
1710            value: Value::Text("a".to_string()),
1711        };
1712        let request = DocumentAverageRequest {
1713            contract: &data_contract,
1714            document_type,
1715            sum_property: "amount".to_string(),
1716            where_clauses: vec![color_ge_a],
1717            order_clauses: Vec::new(),
1718            mode: AverageMode::GroupByRange,
1719            limit: None,
1720            prove: false,
1721            drive_config: &drive_config,
1722        };
1723
1724        let response = drive
1725            .execute_document_average_request(request, None, platform_version)
1726            .expect("dispatcher should succeed");
1727        let entries = match response {
1728            DocumentAverageResponse::Entries(e) => e,
1729            other => panic!("expected Entries, got {:?}", other),
1730        };
1731        assert_eq!(
1732            entries.len(),
1733            3,
1734            "distinct AVG no-proof with `limit = None` must default to \
1735             `drive_config.default_query_limit` (= 3 here) rather than \
1736             enumerating all 5 distinct keys; got {entries:?}"
1737        );
1738    }
1739
1740    /// Distinct AVG no-proof with `limit > max_query_limit` must
1741    /// clamp to `max_query_limit`, not return an error. Mirrors
1742    /// count's no-proof distinct-walk clamp policy (documented in
1743    /// `DocumentAverageRequest::limit`).
1744    #[test]
1745    fn distinct_avg_no_proof_clamps_limit_to_max_query_limit() {
1746        let drive = setup_drive_with_initial_state_structure(None);
1747        let platform_version = PlatformVersion::latest();
1748        let data_contract = build_widget_contract_pcps();
1749        drive
1750            .apply_contract(
1751                &data_contract,
1752                BlockInfo::default(),
1753                true,
1754                StorageFlags::optional_default_as_cow(),
1755                None,
1756                platform_version,
1757            )
1758            .expect("apply contract");
1759
1760        let docs = [
1761            ("red", 5u64),
1762            ("green", 7),
1763            ("blue", 2),
1764            ("yellow", 4),
1765            ("purple", 9),
1766        ];
1767        for (i, (color, amount)) in docs.iter().enumerate() {
1768            insert_widget(&drive, &data_contract, i, color, *amount);
1769        }
1770
1771        let document_type = data_contract
1772            .document_type_for_name("widget")
1773            .expect("widget");
1774        // Operator-tuned `max_query_limit = 2`. An explicit `limit =
1775        // 4` MUST clamp to 2 (no-proof policy; the prove path errors
1776        // on this combination instead — see the
1777        // `range_distinct_avg_proof_rejects_limit_over_max` test
1778        // above for the prove counterpart).
1779        let drive_config = DriveConfig {
1780            default_query_limit: 100,
1781            max_query_limit: 2,
1782            ..Default::default()
1783        };
1784
1785        let color_ge_a = WhereClause {
1786            field: "color".to_string(),
1787            operator: WhereOperator::GreaterThanOrEquals,
1788            value: Value::Text("a".to_string()),
1789        };
1790        let request = DocumentAverageRequest {
1791            contract: &data_contract,
1792            document_type,
1793            sum_property: "amount".to_string(),
1794            where_clauses: vec![color_ge_a],
1795            order_clauses: Vec::new(),
1796            mode: AverageMode::GroupByRange,
1797            limit: Some(4),
1798            prove: false,
1799            drive_config: &drive_config,
1800        };
1801
1802        let response = drive
1803            .execute_document_average_request(request, None, platform_version)
1804            .expect("dispatcher should succeed (no-proof clamps, never errors)");
1805        let entries = match response {
1806            DocumentAverageResponse::Entries(e) => e,
1807            other => panic!("expected Entries, got {:?}", other),
1808        };
1809        assert_eq!(
1810            entries.len(),
1811            2,
1812            "distinct AVG no-proof must clamp `limit = 4` to \
1813             `max_query_limit = 2`; got {entries:?}"
1814        );
1815    }
1816
1817    /// `execute_document_count_and_sum_request` must reject a direct
1818    /// caller passing `prove = true`. The wrapper
1819    /// `execute_document_average_request` is the only legitimate entry
1820    /// that routes prove requests (to the prove-side dispatcher);
1821    /// reaching the joint dispatcher with `prove = true` would
1822    /// otherwise silently produce a no-proof response. Regression for
1823    /// the CodeRabbit "enforce no-prove precondition" finding.
1824    #[test]
1825    fn joint_dispatcher_rejects_prove_true_request() {
1826        let drive = setup_drive_with_initial_state_structure(None);
1827        let platform_version = PlatformVersion::latest();
1828        let data_contract = build_widget_contract_pcps();
1829        drive
1830            .apply_contract(
1831                &data_contract,
1832                BlockInfo::default(),
1833                true,
1834                StorageFlags::optional_default_as_cow(),
1835                None,
1836                platform_version,
1837            )
1838            .expect("apply contract");
1839
1840        let document_type = data_contract
1841            .document_type_for_name("widget")
1842            .expect("widget");
1843        let drive_config = DriveConfig::default();
1844
1845        let request = DocumentAverageRequest {
1846            contract: &data_contract,
1847            document_type,
1848            sum_property: "amount".to_string(),
1849            where_clauses: Vec::new(),
1850            order_clauses: Vec::new(),
1851            mode: AverageMode::Aggregate,
1852            limit: None,
1853            prove: true,
1854            drive_config: &drive_config,
1855        };
1856
1857        let err = drive
1858            .execute_document_count_and_sum_request(request, None, platform_version)
1859            .expect_err("prove=true direct call must reject");
1860        let msg = format!("{err:?}");
1861        assert!(
1862            msg.contains("no-prove"),
1863            "expected the prove=true guard to fire; got: {msg}"
1864        );
1865    }
1866
1867    /// AVG no-proof dispatcher must run
1868    /// `validate_and_canonicalize_where_clauses` so it shares the same
1869    /// accept/reject contract as the count and document-query
1870    /// surfaces. Pin a representative rejection: a duplicate Equal on
1871    /// the same field. Without the validator the executor would
1872    /// either succeed with a silently-collapsed shape or fail
1873    /// downstream with a less precise error.
1874    #[test]
1875    fn joint_dispatcher_runs_validate_and_canonicalize_where_clauses() {
1876        let drive = setup_drive_with_initial_state_structure(None);
1877        let platform_version = PlatformVersion::latest();
1878        let data_contract = build_widget_contract_pcps();
1879        drive
1880            .apply_contract(
1881                &data_contract,
1882                BlockInfo::default(),
1883                true,
1884                StorageFlags::optional_default_as_cow(),
1885                None,
1886                platform_version,
1887            )
1888            .expect("apply contract");
1889
1890        let document_type = data_contract
1891            .document_type_for_name("widget")
1892            .expect("widget");
1893        let drive_config = DriveConfig::default();
1894
1895        // Duplicate Equal on `color` — validator rejects via
1896        // `WhereClause::group_clauses`.
1897        let dup_color_a = WhereClause {
1898            field: "color".to_string(),
1899            operator: WhereOperator::Equal,
1900            value: Value::Text("red".to_string()),
1901        };
1902        let dup_color_b = WhereClause {
1903            field: "color".to_string(),
1904            operator: WhereOperator::Equal,
1905            value: Value::Text("green".to_string()),
1906        };
1907        let request = DocumentAverageRequest {
1908            contract: &data_contract,
1909            document_type,
1910            sum_property: "amount".to_string(),
1911            where_clauses: vec![dup_color_a, dup_color_b],
1912            order_clauses: Vec::new(),
1913            mode: AverageMode::Aggregate,
1914            limit: None,
1915            prove: false,
1916            drive_config: &drive_config,
1917        };
1918
1919        let err = drive
1920            .execute_document_average_request(request, None, platform_version)
1921            .expect_err(
1922                "AVG no-proof must reject duplicate Equal on the same field via \
1923                 validate_and_canonicalize_where_clauses",
1924            );
1925        // The exact error variant comes from `WhereClause::group_clauses` —
1926        // pin only that the call returned `Err` and the error mentions
1927        // the problematic shape rather than a generic index-picker miss.
1928        let msg = format!("{err:?}");
1929        assert!(
1930            !msg.contains("WhereClauseOnNonIndexedProperty"),
1931            "validator should reject before the index picker would: {msg}"
1932        );
1933    }
1934
1935    /// `PerInValue` no-proof AVG must honor `request.limit` on the
1936    /// returned entry list. Regression for the reviewer's "joint
1937    /// dispatcher drops `request.limit`" finding on the PerInValue
1938    /// arm. Count's per-In executor truncates at this same point.
1939    #[test]
1940    fn per_in_value_avg_no_proof_honors_explicit_limit() {
1941        let drive = setup_drive_with_initial_state_structure(None);
1942        let platform_version = PlatformVersion::latest();
1943
1944        // `byColor` index: `summable: "amount"` + `countable:
1945        // "countable"`. No range flags — this is the no-range
1946        // PerInValue shape.
1947        let factory = DataContractFactory::new(PROTOCOL_VERSION_V12).expect("create factory");
1948        let document_schema = platform_value!({
1949            "type": "object",
1950            "properties": {
1951                "color":  {"type": "string",  "position": 0, "maxLength": 32},
1952                "amount": {"type": "integer", "position": 1, "minimum": 0, "maximum": 1000},
1953            },
1954            "required": ["color", "amount"],
1955            "indices": [{
1956                "name": "byColor",
1957                "properties": [{"color": "asc"}],
1958                "summable":  "amount",
1959                "countable": "countable",
1960            }],
1961            "additionalProperties": false,
1962        });
1963        let schemas = platform_value!({ "widget": document_schema });
1964        let data_contract = factory
1965            .create_with_value_config(
1966                dpp::tests::utils::generate_random_identifier_struct(),
1967                0,
1968                schemas,
1969                None,
1970                None,
1971            )
1972            .expect("create data contract")
1973            .data_contract_owned();
1974        drive
1975            .apply_contract(
1976                &data_contract,
1977                BlockInfo::default(),
1978                true,
1979                StorageFlags::optional_default_as_cow(),
1980                None,
1981                platform_version,
1982            )
1983            .expect("apply contract");
1984
1985        for (i, (color, amount)) in [("red", 5u64), ("green", 7), ("blue", 2), ("yellow", 4)]
1986            .iter()
1987            .enumerate()
1988        {
1989            insert_widget(&drive, &data_contract, i, color, *amount);
1990        }
1991
1992        let document_type = data_contract
1993            .document_type_for_name("widget")
1994            .expect("widget");
1995        let drive_config = DriveConfig::default();
1996
1997        // `In` over 4 color values, `limit = 2` — dispatcher must
1998        // truncate the per-In entry list to 2.
1999        let color_in = WhereClause {
2000            field: "color".to_string(),
2001            operator: WhereOperator::In,
2002            value: Value::Array(vec![
2003                Value::Text("red".to_string()),
2004                Value::Text("green".to_string()),
2005                Value::Text("blue".to_string()),
2006                Value::Text("yellow".to_string()),
2007            ]),
2008        };
2009        let request = DocumentAverageRequest {
2010            contract: &data_contract,
2011            document_type,
2012            sum_property: "amount".to_string(),
2013            where_clauses: vec![color_in],
2014            order_clauses: Vec::new(),
2015            mode: AverageMode::GroupByIn,
2016            limit: Some(2),
2017            prove: false,
2018            drive_config: &drive_config,
2019        };
2020
2021        let response = drive
2022            .execute_document_average_request(request, None, platform_version)
2023            .expect("dispatcher should succeed");
2024        let entries = match response {
2025            DocumentAverageResponse::Entries(e) => e,
2026            other => panic!("expected Entries, got {:?}", other),
2027        };
2028        assert_eq!(
2029            entries.len(),
2030            2,
2031            "PerInValue AVG no-proof must apply request.limit = 2 to the per-In \
2032             entry list (caller asked for 4 In values, dispatcher must truncate); \
2033             got {entries:?}"
2034        );
2035    }
2036
2037    /// Empty-where `Aggregate` AVG MUST exercise the
2038    /// [`Drive::execute_document_count_and_sum_total_no_proof`]
2039    /// primary-key fast path when the doctype declares
2040    /// `documentsAverageable` (= `documentsCountable: true +
2041    /// documentsSummable: "<prop>"`). The fast path reads
2042    /// `[contract_doc, contract_id, [1], doctype, 0]` — the PCPS
2043    /// primary-key element — and decodes `(count, sum)` from it in one
2044    /// grovedb call without any index. Consensus-critical: a regression
2045    /// here would silently produce wrong `(count, sum)` for the
2046    /// most-trafficked AVG shape (unfiltered total).
2047    #[test]
2048    fn empty_where_total_executor_uses_primary_key_count_sum_tree_fast_path() {
2049        let drive = setup_drive_with_initial_state_structure(None);
2050        let platform_version = PlatformVersion::latest();
2051
2052        // `documentsAverageable: "amount"` desugars to BOTH
2053        // `documentsCountable: true` AND `documentsSummable:
2054        // "amount"`, which is exactly what the empty-where fast path
2055        // requires. No `indices` block — the fast path doesn't use
2056        // an index, it reads the doctype's primary-key
2057        // count-sum-bearing tree directly at `[..., doctype, 0]`.
2058        let factory = DataContractFactory::new(PROTOCOL_VERSION_V12).expect("create factory");
2059        let document_schema = platform_value!({
2060            "type": "object",
2061            "properties": {
2062                "amount": {"type": "integer", "position": 0, "minimum": 0, "maximum": 1000},
2063            },
2064            "required": ["amount"],
2065            "documentsAverageable": "amount",
2066            "additionalProperties": false,
2067        });
2068        let schemas = platform_value!({ "score": document_schema });
2069        let data_contract = factory
2070            .create_with_value_config(
2071                dpp::tests::utils::generate_random_identifier_struct(),
2072                0,
2073                schemas,
2074                None,
2075                None,
2076            )
2077            .expect("create data contract")
2078            .data_contract_owned();
2079        drive
2080            .apply_contract(
2081                &data_contract,
2082                BlockInfo::default(),
2083                true,
2084                StorageFlags::optional_default_as_cow(),
2085                None,
2086                platform_version,
2087            )
2088            .expect("apply contract");
2089
2090        // Insert documents directly (no need for the widget helper —
2091        // this doctype has no color property).
2092        let document_type = data_contract
2093            .document_type_for_name("score")
2094            .expect("score type");
2095        for (i, amount) in [10u64, 20, 30, 40].iter().enumerate() {
2096            let mut properties = std::collections::BTreeMap::new();
2097            properties.insert("amount".to_string(), Value::U64(*amount));
2098            let document: Document = DocumentV0 {
2099                id: Identifier::from([(i + 1) as u8; 32]),
2100                owner_id: Identifier::from([0u8; 32]),
2101                properties,
2102                revision: None,
2103                created_at: None,
2104                updated_at: None,
2105                transferred_at: None,
2106                created_at_block_height: None,
2107                updated_at_block_height: None,
2108                transferred_at_block_height: None,
2109                created_at_core_block_height: None,
2110                updated_at_core_block_height: None,
2111                transferred_at_core_block_height: None,
2112                creator_id: None,
2113            }
2114            .into();
2115            let storage_flags = Some(std::borrow::Cow::Owned(StorageFlags::SingleEpoch(0)));
2116            drive
2117                .add_document_for_contract(
2118                    DocumentAndContractInfo {
2119                        owned_document_info: OwnedDocumentInfo {
2120                            document_info: DocumentRefInfo((&document, storage_flags)),
2121                            owner_id: None,
2122                        },
2123                        contract: &data_contract,
2124                        document_type,
2125                    },
2126                    false,
2127                    BlockInfo::default(),
2128                    true,
2129                    None,
2130                    platform_version,
2131                    None,
2132                )
2133                .expect("insert score");
2134        }
2135
2136        let drive_config = DriveConfig::default();
2137        let request = DocumentAverageRequest {
2138            contract: &data_contract,
2139            document_type,
2140            sum_property: "amount".to_string(),
2141            where_clauses: Vec::new(),
2142            order_clauses: Vec::new(),
2143            mode: AverageMode::Aggregate,
2144            limit: None,
2145            prove: false,
2146            drive_config: &drive_config,
2147        };
2148
2149        let response = drive
2150            .execute_document_average_request(request, None, platform_version)
2151            .expect("empty-where AVG no-proof must succeed via the primary-key fast path");
2152        match response {
2153            DocumentAverageResponse::Aggregate { count, sum } => {
2154                assert_eq!(
2155                    (count, sum),
2156                    (4, 100),
2157                    "primary-key count-sum tree fast path must return (4 docs, sum 10+20+30+40 = 100)"
2158                );
2159            }
2160            other => panic!("expected Aggregate, got {:?}", other),
2161        }
2162    }
2163
2164    /// `PerInValue` no-proof AVG with `limit = None` must default to
2165    /// `drive_config.default_query_limit` per
2166    /// `DocumentAverageRequest::limit`'s documented contract.
2167    /// Regression test paired with the explicit-limit case above; pins
2168    /// the no-proof contract parity reviewers flagged after the
2169    /// initial PerInValue fix landed.
2170    #[test]
2171    fn per_in_value_avg_no_proof_defaults_limit_to_operator_default_query_limit() {
2172        let drive = setup_drive_with_initial_state_structure(None);
2173        let platform_version = PlatformVersion::latest();
2174
2175        // Same `summable + countable` `byColor` index as
2176        // `per_in_value_avg_no_proof_honors_explicit_limit`, but with
2177        // `default_query_limit = 2` and `limit = None` on the request
2178        // — the dispatcher must fall back to the operator's runtime
2179        // default and truncate the per-In entry list to 2.
2180        let factory = DataContractFactory::new(PROTOCOL_VERSION_V12).expect("create factory");
2181        let document_schema = platform_value!({
2182            "type": "object",
2183            "properties": {
2184                "color":  {"type": "string",  "position": 0, "maxLength": 32},
2185                "amount": {"type": "integer", "position": 1, "minimum": 0, "maximum": 1000},
2186            },
2187            "required": ["color", "amount"],
2188            "indices": [{
2189                "name": "byColor",
2190                "properties": [{"color": "asc"}],
2191                "summable":  "amount",
2192                "countable": "countable",
2193            }],
2194            "additionalProperties": false,
2195        });
2196        let schemas = platform_value!({ "widget": document_schema });
2197        let data_contract = factory
2198            .create_with_value_config(
2199                dpp::tests::utils::generate_random_identifier_struct(),
2200                0,
2201                schemas,
2202                None,
2203                None,
2204            )
2205            .expect("create data contract")
2206            .data_contract_owned();
2207        drive
2208            .apply_contract(
2209                &data_contract,
2210                BlockInfo::default(),
2211                true,
2212                StorageFlags::optional_default_as_cow(),
2213                None,
2214                platform_version,
2215            )
2216            .expect("apply contract");
2217
2218        for (i, (color, amount)) in [("red", 5u64), ("green", 7), ("blue", 2), ("yellow", 4)]
2219            .iter()
2220            .enumerate()
2221        {
2222            insert_widget(&drive, &data_contract, i, color, *amount);
2223        }
2224
2225        let document_type = data_contract
2226            .document_type_for_name("widget")
2227            .expect("widget");
2228        let drive_config = DriveConfig {
2229            default_query_limit: 2,
2230            ..Default::default()
2231        };
2232
2233        let color_in = WhereClause {
2234            field: "color".to_string(),
2235            operator: WhereOperator::In,
2236            value: Value::Array(vec![
2237                Value::Text("red".to_string()),
2238                Value::Text("green".to_string()),
2239                Value::Text("blue".to_string()),
2240                Value::Text("yellow".to_string()),
2241            ]),
2242        };
2243        let request = DocumentAverageRequest {
2244            contract: &data_contract,
2245            document_type,
2246            sum_property: "amount".to_string(),
2247            where_clauses: vec![color_in],
2248            order_clauses: Vec::new(),
2249            mode: AverageMode::GroupByIn,
2250            limit: None,
2251            prove: false,
2252            drive_config: &drive_config,
2253        };
2254
2255        let response = drive
2256            .execute_document_average_request(request, None, platform_version)
2257            .expect("dispatcher should succeed");
2258        let entries = match response {
2259            DocumentAverageResponse::Entries(e) => e,
2260            other => panic!("expected Entries, got {:?}", other),
2261        };
2262        assert_eq!(
2263            entries.len(),
2264            2,
2265            "PerInValue AVG no-proof with `limit = None` must default to \
2266             `drive_config.default_query_limit` (= 2 here) and truncate the \
2267             per-In entry list; got {entries:?}"
2268        );
2269    }
2270}