Skip to main content

Module document_having_entries

Module document_having_entries 

Source
Expand description

FromProof + Fetch for [DocumentHavingEntries] — the having-range (GROUP BY … HAVING <aggregate> <op> <value> LIMIT n) view of the unified getDocuments endpoint.

A having-range query answers “which groups’ aggregate falls inside a value bound?” — hashtags with more than 100 posts — in O(log n + k), with a proof whose Merk range boundaries also attest completeness: a node cannot silently omit a matching group. It reads the same pre-sorted per-axis secondary Merk the ranked surface walks (grovedb PR #657), addressed by value bound instead of by rank.

Per-request resolution (which axis, which bounds the operator translates to, which index covers them) lives in [super::having_proof_helpers]; this module is the thin Fetch-side wrapper.

§Request shape

Exactly one aggregate select, exactly one group_by property, exactly one having clause bounding the selected aggregate with a contiguous-range operator (=, >, >=, <, <=, BETWEEN*!= and IN are rejected), and a LIMIT. ORDER BY is optional: omitted means ascending by the aggregate; naming the selected aggregate sets the direction. where clauses are pins on a covering compound ranked index’s leading properties (one per leading property, selecting which prefix’s groups the bound reads) — absent for a single-property index. Each pin is an equality, except that at most one may be an IN of 2..=10 distinct elements: the bound fans out across one prefix branch per element and merges, entries carrying the encoded branch segment in in_key (unset on single-branch responses; a single-element IN normalizes to the equality pin; a null pin on another property cannot combine with the IN). No offset, no start_at.

§Contract prerequisites

Same as the ranked surface: the index must opt in with rankedCountable / rankedSummable / rankedAverageable (meta-schema v3, protocol version 14+). The index may be single-property (group_by its property, no where) or compound (group_by its trailing property, pin every leading one — equality pins, at most one of them an IN). Against a pre-v14 node the request is refused with “HAVING clause is not yet implemented” — the intended activation gate.

§Reading the result

Entries come back in axis order in the walk direction; do not re-sort. Fewer than n entries means fewer groups matched. Exactly n may mean the match set was cut at the limit. Tightening the bound past the last aggregate value seen continues past distinct values only: a cut inside a tie (several groups sharing the boundary aggregate) cannot be continued — the tied groups past the limit stay unreachable until a composite-key cursor exists — so size the limit above the widest expected tie. Averages are fixed-point integers, exact on this (proved) path; see the ranked module’s notes, which apply verbatim.

§Example: hashtags with more than 100 posts

SELECT COUNT(*) GROUP BY hashtag HAVING $count > 100 ORDER BY $count DESC LIMIT 100

use dash_sdk::{Sdk, platform::{DataContract, DocumentQuery, Fetch, Identifier}};
use dash_sdk::drive::query::{
    HavingAggregate, HavingAggregateFunction, HavingClause, HavingOperator,
    HavingRightOperand, SelectProjection,
};
use dash_sdk::platform::documents::document_query::RankingDirection;
use dpp::platform_value::Value;
use drive_proof_verifier::DocumentHavingEntries;
use futures::executor::block_on;

let sdk = Sdk::new_mock();
let contract = block_on(DataContract::fetch(&sdk, Identifier::new(POSTS_CONTRACT_ID)))
    .expect("fetch contract")
    .expect("contract exists");

let query = DocumentQuery::new(contract, "post")
    .expect("document type exists")
    .with_select(SelectProjection::count_star())
    .with_group_by("hashtag")
    .with_having(vec![HavingClause {
        aggregate: HavingAggregate {
            function: HavingAggregateFunction::Count,
            field: String::new(),
        },
        operator: HavingOperator::GreaterThan,
        right: HavingRightOperand::Value(Value::U64(100)),
    }])
    .order_by_selected_aggregate(RankingDirection::Descending)
    .with_limit(100);

let matching = block_on(DocumentHavingEntries::fetch(&sdk, query))
    .expect("fetch succeeds")
    .expect("a well-formed having query always answers");

for entry in &matching.entries {
    let hashtag = String::from_utf8_lossy(&entry.key);
    println!("#{hashtag}: {} posts", entry.value.as_f64());
}