Skip to main content

Module composite_document_query

Module composite_document_query 

Source
Expand description

Composite document queries — a DriveDocumentQuery page plus sub-queries derived from its proven results (joins, lookups, counts), proven as one merged proof against one state root. See the module docs. Composite document queries: one page query plus sub-queries derived from its proven results, answered as ONE merged grovedb proof.

There is no separate composite query type: a composite query is a DriveDocumentQuery — the page — whose sub_queries are non-empty. This module holds the sub-query shapes (DriveSubQuery and friends) and the composite behaviour of DriveDocumentQuery: shape validation, derivation, the component path-query builders, proof merging, and the server-side executors behind Drive::query_composite_documents / query_composite_documents_with_proof (the verifier half lives in verify::composite_document).

A feed is a page of posts and then, for that page, the things a card renders: the referenced (quoted) posts, the per-post engagement counts, the authors’ profiles, the viewer’s own likes. Each of those is a query whose INPUT is the page — its ids, its owners, a property’s values — and asking for them one round trip at a time turns a single feed into a burst of dependent calls. A composite query carries the page and its sub-queries in one request and proves them together: the server materializes the page, derives every sub-query’s IN clause from it (or from an earlier sub-query’s documents), and DriveDocumentQuery::merged_path_query merges all the component path queries into one proof over one state root.

Soundness never rests on the server’s derivation. The verifier bootstraps the page (a subset pass against the merged proof), derives every sub-query itself with the SAME builders the server ran, merges the same way, and verifies the whole composition in one authoritative pass; then it recomputes the derived values from the proven page and refuses any divergence from the bootstrap, any result outside a derived value set, and (for by-id joins on refersTo: permanentDocument properties, which cannot dangle) any missing referenced document. A node that ignores the sub-queries serves a page-only proof, which cannot satisfy the merged query whenever a sub-query derived anything — the composition fails closed.

Three sub-query shapes, one binding rule:

  • Documents by id (bind.field == "$id"): the classic join. The source property must declare refersTo: permanentDocument targeting the sub-query’s type, so every derived id MUST resolve — the result is the referenced documents in first-appearance order, set-equal to the derived ids.
  • Documents by an indexed property (bind.field is $ownerId or an indexed property): a lookup, WHERE <fixed clauses> AND <field> IN <derived values>, with an explicit limit unless the values already bound it (a unique index, or an indexOnly terminal with every prefix fixed, yields at most one row per value). Absence is inherent in the range proof (a value with no document simply yields none), so profiles keyed by owner or reposts keyed by post work without absence proofs, and the target may live in another contract.
  • Count by an indexed property: the grouped point-lookup count COUNT(*) WHERE <fixed clauses> AND <field> IN <derived values> GROUP BY <field> on a countable index — one entry per value that has a count tree (zero-count trees are not materialized).

A sub-query without a binding is a sibling: an independent documents query proven under the same root (counts must be bound — the aggregate and range count shapes have their own proof primitives and stay on the regular count surface).

Derived values are identifiers only (v1): the page’s $id, its $ownerId, or an identifier-typed property. The page limit is required and capped at MAX_BOUND_VALUES (an IN clause admits at most that many values); the page takes no cursor and no offset — paginate with a range clause, exactly as chained queries do. A by-ids page is proven without its limit, which must therefore cover its ids (a plain documents query would truncate instead). Every component carries its limit as its root query’s per-instance cap, the form the merged proof budgets it in.

Direction: grovedb merges only queries that agree on their walk direction, so every component walks in the page’s. Counts and by-id joins are aligned freely — their selected sets do not depend on it — while a documents lookup the caller left unordered on its bound field inherits it (which decides WHICH rows a limited lookup returns under a descending page), an explicit ordering that disagrees is refused, and so is an unordered sibling under a descending page: order it, in the page’s direction.

Structs§

CompositeDocumentsResult
The materialized result of a composite query.
DriveSubQuery
One sub-query of a composite request.
SubQueryBinding
The derived clause of a sub-query: <field> IN <values>, where the values are read off the source’s proven documents.

Enums§

BindingSource
Where a sub-query’s derived values come from.
SubQueryKind
What a sub-query returns.
SubQueryResult
One sub-query’s materialized result.

Constants§

MAX_BOUND_VALUES
The most values one binding can derive: a derived IN clause admits at most this many (WhereClause::in_values), so the page limit and every sub-query limit that feeds a later binding are capped here.
MAX_SUB_QUERIES
The most sub-queries one composite request carries. Every sub-query is another branch of one merged proof; ten covers a feed card’s whole enrichment (quotes, four counts, reposts, profiles, names, the viewer’s marks) with room to spare.