Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions book/src/drive/average-index-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -1702,3 +1702,13 @@ The split closely parallels the count and sum chapters — point lookups for Q1
The chapter is grounded in the [`document_average_worst_case`](https://github.com/dashpay/platform/blob/v3.1-dev/packages/rs-drive/benches/document_average_worst_case.rs) bench's measured numbers — Q1–Q7 verify cleanly end-to-end against the shared root hash `8b15f732…ffc7`.

A natural expansion follow-up (out of scope here): a worked example of "exact-precision" averages — for callers that need fractional averages (e.g. `avg = 50.7142857…` rather than `50.99`), the protocol-level approach is to return `(count, sum)` and let the client compute in its preferred numeric format (the chapter notes this in [Numerical Considerations](#numerical-considerations) above; a future expansion could walk through the fixed-point vs. floating-point trade-offs).

### No-proof path: joint count-and-sum dispatch

The no-proof AVG path lives in `crate::query::drive_document_count_and_sum_query`. It consumes the same `DocumentAverageRequest` the prove path uses and resolves routing through sum's versioned mode-detection table, so the `(where_clauses × mode)` → executor mapping has a single source of truth shared with the sum and count surfaces. The dispatcher splits on the resolved mode:

- **`Total` / `PerInValue`** (no-range `Equal`/`In` on a `summable + countable` index) walks the point-lookup path query and decodes `(count, sum)` from each visited `CountSumTree` terminator in one call via `Element::count_sum_value_or_default()`. One grovedb call per `In` branch, both metrics together.
- **`RangeNoProof` distinct shapes** (`GroupByRange` / `GroupByCompound` + range on an index that declares BOTH `rangeCountable: true` AND `rangeSummable: true` — DPP exposes `rangeAverageable: true` as shorthand for the pair) walk `ProvableCountProvableSumTree` terminators once via the same `distinct_sum_path_query` builder the sum surface uses, emitting one `(count, sum)` per distinct in-range key — strictly better than the count + sum surfaces' parallel walks because both metrics come from each visited element.
- **`RangeNoProof` aggregate shapes** (`Aggregate` / `GroupByIn` + range) call grovedb's merk-internal accumulators directly: `query_aggregate_count` against the count path query AND `query_aggregate_sum` against the sum path query, under a shared read transaction for snapshot consistency. Two O(log n) merk reads regardless of how many documents the range matches — keeping the public DAPI endpoint bounded against amplification.

The reason for the two-shape split on `RangeNoProof`: grovedb exposes `query_aggregate_count` and `query_aggregate_sum` as bounded no-prove accumulators (the proof-side analog `AggregateCountAndSumOnRange` is what Q5 / Q7 above use), but no combined no-prove primitive yet. A future grovedb addition could collapse the two aggregate calls into a single merk-internal `query_aggregate_count_and_sum` accumulator, closing the residual gap with the prove path's combined `AggregateCountAndSumOnRange` walk. Not a blocker — two bounded accumulator reads is the same cost class as the surfaces it composes — but it would simplify the dispatcher's aggregate branch into a one-call read.
1,693 changes: 1,365 additions & 328 deletions packages/rs-drive/src/query/drive_document_average_query/drive_dispatcher.rs

Large diffs are not rendered by default.

19 changes: 10 additions & 9 deletions packages/rs-drive/src/query/drive_document_average_query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
//! [`book/src/drive/average-index-examples.md`](../../../../../book/src/drive/average-index-examples.md)
//! for the design and the grades-contract worked example.
//!
//! Wired end-to-end: the dispatcher composes the count + sum
//! executors on the no-proof path under a shared read-transaction
//! (see [`drive_dispatcher`] module docstring for the atomicity
//! contract), and the prove path dispatches directly to the PCPS /
//! primary-key proof executors. A planned follow-up tracked at
//! [dashpay/platform#3687](https://github.com/dashpay/platform/issues/3687)
//! will collapse the no-proof path's two-request composition into a
//! single unified executor that reads both metrics from each visited
//! PCPS element in one walk.
//! Wired end-to-end: the dispatcher routes prove-true requests to the
//! PCPS / primary-key proof executors, and prove-false requests to the
//! joint single-walk count-and-sum dispatcher at
//! [`crate::query::drive_document_count_and_sum_query`]. Both paths
//! read `(count, sum)` from each visited count-sum-bearing element in
//! a single grovedb walk — see the
//! [`drive_dispatcher`](drive_dispatcher) module docstring for the
//! routing details and
//! [`crate::query::drive_document_count_and_sum_query`] for the
//! no-prove perf / atomicity contract.

#[cfg(feature = "server")]
pub mod drive_dispatcher;
Expand Down
Loading
Loading