Skip to main content

drive/query/composite_document_query/
mod.rs

1//! Composite document queries: one page query plus sub-queries derived
2//! from its proven results, answered as ONE merged grovedb proof.
3//!
4//! There is no separate composite query type: a composite query is a
5//! [`DriveDocumentQuery`] — the page — whose
6//! [`sub_queries`](DriveDocumentQuery::sub_queries) are non-empty. This
7//! module holds the sub-query shapes ([`DriveSubQuery`] and friends) and
8//! the composite behaviour of `DriveDocumentQuery`: shape validation,
9//! derivation, the component path-query builders, proof merging, and the
10//! server-side executors behind `Drive::query_composite_documents` /
11//! `query_composite_documents_with_proof` (the verifier half lives in
12//! `verify::composite_document`).
13//!
14//! A feed is a page of posts and then, for that page, the things a card
15//! renders: the referenced (quoted) posts, the per-post engagement
16//! counts, the authors' profiles, the viewer's own likes. Each of those
17//! is a query whose INPUT is the page — its ids, its owners, a
18//! property's values — and asking for them one round trip at a time
19//! turns a single feed into a burst of dependent calls. A composite
20//! query carries the page and its sub-queries in one request and proves
21//! them together: the server materializes the page, derives every
22//! sub-query's `IN` clause from it (or from an earlier sub-query's
23//! documents), and [`DriveDocumentQuery::merged_path_query`] merges all
24//! the component path queries into one proof over one state root.
25//!
26//! Soundness never rests on the server's derivation. The verifier
27//! bootstraps the page (a subset pass against the merged proof), derives
28//! every sub-query itself with the SAME builders the server ran, merges
29//! the same way, and verifies the whole composition in one authoritative
30//! pass; then it recomputes the derived values from the proven page and
31//! refuses any divergence from the bootstrap, any result outside a
32//! derived value set, and (for by-id joins on `refersTo:
33//! permanentDocument` properties, which cannot dangle) any missing
34//! referenced document. A by-id join on a `refersTo: moderatedDocument`
35//! property proves the removal records of its derived ids beside their
36//! documents (see [`moderated_join`](crate::query::moderated_join)), and
37//! reports a derived id with no document by its record; one with neither
38//! is refused. A by-id join on a `refersTo: deletableDocument`
39//! property leaves a derived id with no document out instead: the
40//! target may have been deleted since, and the absence is proven (every
41//! derived id is a queried key grovedb must show present or absent). A
42//! node that ignores the sub-queries serves a
43//! page-only proof, which cannot satisfy the merged query whenever a
44//! sub-query derived anything — the composition fails closed.
45//!
46//! Three sub-query shapes, one binding rule:
47//!
48//! - **Documents by id** (`bind.field == "$id"`): the classic join. The
49//!   source property must declare `refersTo: permanentDocument`,
50//!   `refersTo: moderatedDocument` or `refersTo: deletableDocument`
51//!   targeting the sub-query's type — the result is the referenced
52//!   documents in first-appearance order. For a `permanentDocument`
53//!   source every derived id MUST resolve, so the result is set-equal to
54//!   the derived ids; for a `moderatedDocument` source a derived id whose
55//!   document a moderator removed is reported with its removal record;
56//!   for a `deletableDocument` source a derived id whose document was
57//!   deleted is left out.
58//! - **Documents by an indexed property** (`bind.field` is `$ownerId` or
59//!   an indexed property): a lookup, `WHERE <fixed clauses> AND <field>
60//!   IN <derived values>`, with an explicit limit unless the values
61//!   already bound it (a unique index, or an indexOnly terminal with
62//!   every prefix fixed, yields at most one row per value). Absence is
63//!   inherent in the range proof (a value with no document simply
64//!   yields none), so profiles keyed by owner or reposts keyed by post
65//!   work without absence proofs, and the target may live in another
66//!   contract.
67//! - **Count** by an indexed property: the grouped point-lookup count
68//!   `COUNT(*) WHERE <fixed clauses> AND <field> IN <derived values>
69//!   GROUP BY <field>` on a `countable` index — one entry per value that
70//!   has a count tree (zero-count trees are not materialized).
71//!
72//! A sub-query without a binding is a **sibling**: an independent
73//! documents query proven under the same root (counts must be bound —
74//! the aggregate and range count shapes have their own proof
75//! primitives and stay on the regular count surface).
76//!
77//! Derived values are identifiers only (v1): the page's `$id`, its
78//! `$ownerId`, or an identifier-typed property. The page limit is
79//! required and capped at [`MAX_BOUND_VALUES`] (an `IN` clause admits at
80//! most that many values); the page takes no cursor and no offset —
81//! paginate with a range clause, exactly as chained queries do. A by-ids
82//! page is proven without its limit, which must therefore cover its ids
83//! (a plain documents query would truncate instead). Every component
84//! carries its limit as its root query's per-instance cap, the form the
85//! merged proof budgets it in.
86//!
87//! Direction: grovedb merges only queries that agree on their walk
88//! direction, so every component walks in the page's. Counts and by-id
89//! joins are aligned freely — their selected sets do not depend on it —
90//! while a documents lookup the caller left unordered on its bound field
91//! inherits it (which decides WHICH rows a limited lookup returns under a
92//! descending page), an explicit ordering that disagrees is refused, and
93//! so is an unordered sibling under a descending page: order it, in the
94//! page's direction.
95
96use crate::drive::contract::moderation::types::ContractDocumentRemovalEntry;
97use crate::drive::contract::paths::contract_document_type_removals_path_vec;
98use crate::error::drive::DriveError;
99use crate::error::proof::ProofError;
100use crate::error::query::QuerySyntaxError;
101use crate::error::Error;
102use crate::query::drive_document_count_query::point_lookup_count_entries;
103use crate::query::index_only_synthesis::synthesize_index_only_document;
104#[cfg(feature = "server")]
105use crate::query::is_absent_path;
106#[cfg(feature = "server")]
107use crate::query::moderated_join::fetch_removals;
108use crate::query::moderated_join::{
109    decode_removals, pair_missing_with_removals, removals_path_query,
110};
111use crate::query::{
112    key_array_element_clauses, DriveDocumentCountQuery, DriveDocumentQuery, InternalClauses,
113    OrderClause, SplitCountEntry, WhereClause, WhereOperator,
114};
115use dpp::data_contract::accessors::v0::DataContractV0Getters;
116use dpp::data_contract::document_type::accessors::{DocumentTypeV0Getters, DocumentTypeV2Getters};
117use dpp::data_contract::document_type::{
118    DocumentPropertyReferenceTarget, DocumentPropertyType, DocumentReferenceDeclaration,
119    DocumentReferenceKind, DocumentTypeRef, Index,
120};
121use dpp::data_contract::DataContract;
122use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0;
123use dpp::document::{Document, DocumentV0Getters};
124use dpp::identifier::Identifier;
125use dpp::platform_value::btreemap_extensions::BTreeValueMapPathHelper;
126use dpp::platform_value::Value;
127use dpp::version::PlatformVersion;
128use grovedb::{Element, PathQuery};
129use std::collections::{BTreeMap, BTreeSet};
130
131/// The most sub-queries one composite request carries. Every sub-query
132/// is another branch of one merged proof; ten covers a feed card's
133/// whole enrichment (quotes, four counts, reposts, profiles, names,
134/// the viewer's marks) with room to spare.
135pub const MAX_SUB_QUERIES: usize = 10;
136
137/// The most values one binding can derive: a derived `IN` clause admits
138/// at most this many (`WhereClause::in_values`), so the page limit and
139/// every sub-query limit that feeds a later binding are capped here.
140pub const MAX_BOUND_VALUES: usize = 100;
141
142/// Where a sub-query's derived values come from.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum BindingSource {
145    /// The page's proven documents.
146    Page,
147    /// An earlier documents sub-query's proven documents (its index in
148    /// [`DriveDocumentQuery::sub_queries`]).
149    SubQuery(usize),
150}
151
152/// The derived clause of a sub-query: `<field> IN <values>`, where the
153/// values are read off the source's proven documents.
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct SubQueryBinding {
156    /// Whose documents supply the values.
157    pub source: BindingSource,
158    /// The source property read off each document: `$id`, `$ownerId`,
159    /// or an identifier-typed property (dotted paths reach nested
160    /// properties). Documents without the property contribute nothing.
161    pub source_property: String,
162    /// The sub-query field that receives the `IN` clause: `$id` for a
163    /// by-id join, otherwise `$ownerId` or an indexed property.
164    pub field: String,
165}
166
167/// What a sub-query returns.
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum SubQueryKind {
170    /// The matching documents.
171    Documents,
172    /// One count per derived value, from the countable index covering
173    /// the fixed clauses plus the bound field.
174    Count,
175}
176
177/// One sub-query of a composite request.
178#[derive(Debug, Clone, PartialEq)]
179pub struct DriveSubQuery<'a> {
180    /// The contract the sub-query targets — the page's, or another one.
181    pub contract: &'a DataContract,
182    /// The document type queried.
183    pub document_type: DocumentTypeRef<'a>,
184    /// Documents or counts.
185    pub kind: SubQueryKind,
186    /// The fixed clauses (everything but the derived `IN`), typed.
187    /// Must be empty for a by-id join, which resolves every derived id.
188    pub where_clauses: Vec<WhereClause>,
189    /// Ordering; documents only. Every component of the merged proof
190    /// walks in the page's direction, so a documents sub-query must agree
191    /// with it: a bound field the caller did not order by is appended in
192    /// the page's direction (a minimal request never conflicts), and an
193    /// explicit ordering that disagrees is refused, because changing it
194    /// for the proof would change the rows its limit selects.
195    pub order_by: Vec<OrderClause>,
196    /// Required for a documents lookup on a non-unique index: it caps the
197    /// rows the lookup returns in total, in walk order, exactly as the
198    /// limit of an ordinary `IN` query does (at most `MAX_BOUND_VALUES`).
199    /// Forbidden for a value-bounded lookup, a by-id join (completeness is
200    /// set-based) and a count.
201    pub limit: Option<u16>,
202    /// The derived clause, or `None` for a sibling.
203    pub binding: Option<SubQueryBinding>,
204}
205
206/// One sub-query's materialized result.
207#[derive(Debug, Clone, PartialEq)]
208pub enum SubQueryResult {
209    /// Documents: for a by-id join, in first-appearance order of their
210    /// ids among the source documents; otherwise in query order.
211    Documents(Vec<Document>),
212    /// Counts keyed by the bound value's index-key bytes (a 32-byte
213    /// identifier), one entry per value with a materialized count.
214    Counts(Vec<SplitCountEntry>),
215}
216
217impl SubQueryResult {
218    /// The documents of a documents result, or an empty slice.
219    pub fn documents(&self) -> &[Document] {
220        match self {
221            Self::Documents(documents) => documents,
222            Self::Counts(_) => &[],
223        }
224    }
225
226    /// The entries of a count result, or an empty slice.
227    pub fn counts(&self) -> &[SplitCountEntry] {
228        match self {
229            Self::Counts(entries) => entries,
230            Self::Documents(_) => &[],
231        }
232    }
233}
234
235/// The materialized result of a composite query.
236#[derive(Debug, Default)]
237pub struct CompositeDocumentsResult {
238    /// The page, exactly as the page query alone would return it.
239    pub page_documents: Vec<Document>,
240    /// One result per sub-query, in request order.
241    pub sub_results: Vec<SubQueryResult>,
242    /// One list per sub-query, in request order: for a by-id join, the
243    /// derived ids that have NO document, in first-appearance order;
244    /// empty for every other sub-query. Only a join off a `refersTo:
245    /// deletableDocument` property can report any (a referenced document
246    /// deleted after the referring one was written); off a
247    /// `permanentDocument` property a missing document is refused
248    /// instead. On the proof path each reported id is a proven absence.
249    pub sub_result_missing_ids: Vec<Vec<Identifier>>,
250    /// One list per sub-query, in request order: for a by-id join off a
251    /// `refersTo: moderatedDocument` property, the removal records of the
252    /// derived ids whose document the contract's moderators removed, in
253    /// first-appearance order; empty for every other sub-query. A derived
254    /// id with neither a document nor a record is refused. On the proof
255    /// path each is a proven absence of the document and a proven record.
256    pub sub_result_removals: Vec<Vec<ContractDocumentRemovalEntry>>,
257}
258
259/// The values one binding derived, deduplicated to first appearance.
260type DerivedValues = Vec<Identifier>;
261
262/// A `(path, key, element)` triple as grovedb's verifier reports it —
263/// the element absent for a queried key that is not there.
264pub(crate) type ProvedTrio = (Vec<Vec<u8>>, Vec<u8>, Option<Element>);
265
266/// A proved triple whose element is present.
267pub(crate) type PresentTrio = (Vec<Vec<u8>>, Vec<u8>, Element);
268
269/// The component path queries of a composite proof, as
270/// [`DriveDocumentQuery::proof_path_queries`] builds them: the page, one entry per
271/// sub-query (`None` for a bound one that derived nothing), and the removal records
272/// components of the by-id joins off `moderatedDocument` properties.
273pub type CompositeProofPathQueries = (PathQuery, Vec<Option<PathQuery>>, Vec<PathQuery>);
274
275/// The proved removal records of a composite proof, keyed and valued as they were
276/// proved, by the path of the records component that covers them.
277type RemovalEntriesByPath = BTreeMap<Vec<Vec<u8>>, Vec<(Vec<u8>, Element)>>;
278
279/// What the by-id joins report of the derived ids that have no document, one list per
280/// sub-query: the missing ids, and the removal records.
281type SubResultAbsences = (Vec<Vec<Identifier>>, Vec<Vec<ContractDocumentRemovalEntry>>);
282
283/// A component of the merged proof: the page or one sub-query.
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285enum Component {
286    Page,
287    Sub(usize),
288}
289
290fn unsupported(message: String) -> Error {
291    Error::Query(QuerySyntaxError::Unsupported(message))
292}
293
294fn corrupted_proof(message: String) -> Error {
295    Error::Proof(ProofError::CorruptedProof(message))
296}
297
298/// A merge refusal is a property of the request's shape (the same
299/// components refuse identically on every node and every verifier), so it
300/// is reported as one rather than as an internal grovedb failure.
301fn merge_error_to_shape_error(error: grovedb::Error) -> Error {
302    match error {
303        grovedb::Error::NotSupported(message) => unsupported(format!(
304            "the composite query's components cannot be merged into one proof: {}",
305            message
306        )),
307        other => Error::from(other),
308    }
309}
310
311/// An unserved cap is a property of the read-only query, not a storage fault.
312/// This execution check does not affect block execution or proof construction.
313#[cfg(feature = "server")]
314fn check_composite_instance_limits(
315    path_query: &PathQuery,
316    platform_version: &PlatformVersion,
317) -> Result<(), Error> {
318    if platform_version
319        .drive
320        .grove_version
321        .grovedb_versions
322        .path_query_methods
323        .per_instance_query_limits
324        == 0
325        && path_query.has_instance_limits()
326    {
327        return Err(unsupported(
328            "composite query limits require a protocol version that supports per-instance limits"
329                .to_string(),
330        ));
331    }
332    Ok(())
333}
334
335/// The bound identifier a document carries for `field`, or `None` when
336/// the property is absent.
337fn document_bound_value(document: &Document, field: &str) -> Result<Option<Identifier>, Error> {
338    use dpp::document::property_names::{ID, OWNER_ID};
339    if field == ID {
340        return Ok(Some(document.id()));
341    }
342    if field == OWNER_ID {
343        return Ok(Some(document.owner_id()));
344    }
345    let Some(value) = document
346        .properties()
347        .get_optional_at_path(field)
348        .ok()
349        .flatten()
350    else {
351        return Ok(None);
352    };
353    value.to_identifier().map(Some).map_err(|_| {
354        Error::Drive(DriveError::CorruptedCodeExecution(
355            "a bound composite property must decode as an identifier: validate() only \
356             admits identifier-typed properties",
357        ))
358    })
359}
360
361/// Canonical value order for a derived `IN` clause: byte-ascending, so
362/// the built query — and therefore the proof — is byte-identical between
363/// the server and a verifier that extracted the ids in any order.
364fn sorted_values(values: &[Identifier]) -> Vec<Identifier> {
365    let mut sorted = values.to_vec();
366    sorted.sort();
367    sorted
368}
369
370/// The document reference a property type declares, of either kind. Only
371/// a scalar reference counts: a binding reads one identifier out of the
372/// property, and a typed array whose elements are references holds many,
373/// read only by their elements (`contains`), so it is never a join field.
374fn document_reference_of(
375    property_type: &DocumentPropertyType,
376) -> Option<DocumentReferenceDeclaration<'_>> {
377    match property_type {
378        DocumentPropertyType::IdentifierWithReference(reference_target) => {
379            reference_target.as_document_reference()
380        }
381        _ => None,
382    }
383}
384
385impl<'a> DriveSubQuery<'a> {
386    fn bound_field(&self) -> Option<&str> {
387        self.binding.as_ref().map(|binding| binding.field.as_str())
388    }
389
390    fn is_by_id_join(&self) -> bool {
391        self.bound_field() == Some(dpp::document::property_names::ID)
392    }
393}
394
395impl<'a> DriveDocumentQuery<'a> {
396    /// Validates the composite shape: this query as the page plus its
397    /// [`sub_queries`](Self::sub_queries). Called by the server before
398    /// executing and by the verifier before verifying, so an invalid
399    /// request fails identically on both sides.
400    ///
401    /// Construction contract: every sub-query's `document_type` MUST be a
402    /// document type of its own `contract`. This validates everything
403    /// derivable from the shapes themselves.
404    pub fn validate_composite(&self, platform_version: &PlatformVersion) -> Result<(), Error> {
405        if self.sub_queries.is_empty() {
406            return Err(unsupported(
407                "a composite query needs at least one sub-query; a page alone is a plain \
408                 documents query"
409                    .to_string(),
410            ));
411        }
412        if self.sub_queries.len() > MAX_SUB_QUERIES {
413            return Err(unsupported(format!(
414                "a composite query carries at most {} sub-queries, got {}",
415                MAX_SUB_QUERIES,
416                self.sub_queries.len(),
417            )));
418        }
419        let page_limit = match self.limit {
420            None => {
421                return Err(unsupported(
422                    "composite queries require an explicit limit on the page: the page size \
423                     bounds every derived sub-query"
424                        .to_string(),
425                ));
426            }
427            Some(0) => {
428                return Err(unsupported(
429                    "a composite page limit must be at least 1".to_string(),
430                ));
431            }
432            Some(limit) if limit as usize > MAX_BOUND_VALUES => {
433                return Err(unsupported(format!(
434                    "a composite page limit of {} exceeds {}: a derived `IN` clause admits at \
435                     most that many values",
436                    limit, MAX_BOUND_VALUES,
437                )));
438            }
439            Some(limit) => limit,
440        };
441        if self.offset.is_some() {
442            return Err(unsupported(
443                "composite queries do not support a page offset; paginate with a range clause"
444                    .to_string(),
445            ));
446        }
447        if self.start_at.is_some() {
448            return Err(unsupported(
449                "composite queries do not support a page cursor (startAt/startAfter); \
450                 paginate with a range clause on the page's ordering property"
451                    .to_string(),
452            ));
453        }
454        // A by-ids page is proven without its limit (see
455        // `page_path_query`), so the limit must not be what bounds it.
456        if self.page_is_by_ids() {
457            let ids = self.page_ids()?.len();
458            if (page_limit as usize) < ids {
459                return Err(unsupported(format!(
460                    "a by-ids composite page addresses {} ids but its limit is {}: the ids \
461                     bound the page, so the limit must cover them",
462                    ids, page_limit,
463                )));
464            }
465        }
466        // The page must lower to a path query at all — an unindexed
467        // shape fails here, before any sub-query is inspected.
468        let direction = self.page_direction(platform_version)?;
469
470        for (index, sub_query) in self.sub_queries.iter().enumerate() {
471            self.validate_sub_query(index, sub_query, direction, platform_version)?;
472        }
473        self.validate_component_paths(platform_version)
474    }
475
476    fn validate_sub_query(
477        &self,
478        index: usize,
479        sub_query: &DriveSubQuery<'a>,
480        direction: bool,
481        platform_version: &PlatformVersion,
482    ) -> Result<(), Error> {
483        let label = |message: &str| unsupported(format!("sub-query {}: {}", index, message));
484
485        // A sub-query's documents are read out of the merged proof as they
486        // were walked, without the repeats a `containsAny` walk reaches a
487        // document under (`DriveDocumentQuery::drop_repeated_element_matches`
488        // runs on a page only), and a count sub-query answers per bound
489        // value, not per element. So a sub-query reads one element at most.
490        if sub_query
491            .where_clauses
492            .iter()
493            .any(|clause| clause.operator == WhereOperator::ContainsAny)
494        {
495            return Err(label(
496                "containsAny is not supported in a sub-query: a document holding several of \
497                 the elements would be returned or counted once per element; use contains",
498            ));
499        }
500
501        let Some(binding) = &sub_query.binding else {
502            // A sibling: an independent documents query.
503            if sub_query.kind == SubQueryKind::Count {
504                return Err(label(
505                    "a count sub-query must be bound (`COUNT ... WHERE <field> IN <derived \
506                     values> GROUP BY <field>`); unbound counts stay on the regular count \
507                     surface",
508                ));
509            }
510            match sub_query.limit {
511                None => {
512                    return Err(label(
513                        "a sibling documents sub-query requires an explicit limit",
514                    ));
515                }
516                Some(0) => {
517                    return Err(label("a sibling's limit must be at least 1"));
518                }
519                Some(limit) if limit as usize > MAX_BOUND_VALUES => {
520                    return Err(label(&format!(
521                        "limit {} exceeds {}",
522                        limit, MAX_BOUND_VALUES
523                    )));
524                }
525                Some(_) => {}
526            }
527            // Must lower to a path query.
528            self.sub_query_document_query_with_direction(
529                sub_query,
530                &[],
531                direction,
532                platform_version,
533            )?
534            .construct_path_query(None, platform_version)?;
535            return Ok(());
536        };
537
538        // The source must precede this sub-query and produce documents.
539        let (source_contract, source_type, source_is_index_only_query) = match binding.source {
540            BindingSource::Page => (
541                self.contract,
542                self.document_type,
543                self.document_type.index_only(),
544            ),
545            BindingSource::SubQuery(source_index) => {
546                if source_index >= index {
547                    return Err(label("a binding may only reference an earlier sub-query"));
548                }
549                let source = &self.sub_queries[source_index];
550                if source.kind != SubQueryKind::Documents {
551                    return Err(label("a binding must reference a documents sub-query"));
552                }
553                (
554                    source.contract,
555                    source.document_type,
556                    source.document_type.index_only(),
557                )
558            }
559        };
560
561        // The source property: a system identifier or an identifier-typed
562        // property of the source type.
563        let source_property_type: Option<&DocumentPropertyType> = {
564            use dpp::document::property_names::{ID, OWNER_ID};
565            if binding.source_property == ID || binding.source_property == OWNER_ID {
566                None
567            } else {
568                let Some(property) = source_type
569                    .flattened_properties()
570                    .get(binding.source_property.as_str())
571                else {
572                    return Err(label(&format!(
573                        "source property \"{}\" does not name a property of \"{}\"",
574                        binding.source_property,
575                        source_type.name(),
576                    )));
577                };
578                if !matches!(
579                    property.property_type,
580                    DocumentPropertyType::Identifier
581                        | DocumentPropertyType::IdentifierWithReference(_)
582                ) {
583                    return Err(label(&format!(
584                        "source property \"{}\" is not identifier-typed; composite bindings \
585                         derive identifiers only",
586                        binding.source_property,
587                    )));
588                }
589                Some(&property.property_type)
590            }
591        };
592
593        // An indexOnly source proves only what its resolved index
594        // carries, so the property must sit on that index.
595        if source_is_index_only_query {
596            let carries = |index: &dpp::data_contract::document_type::Index| {
597                index.terminal_contains(&binding.source_property)
598                    || index
599                        .properties
600                        .iter()
601                        .any(|property| property.name == binding.source_property)
602            };
603            let (carried, index_name) = match binding.source {
604                BindingSource::Page => {
605                    let index = self.index_only_query_index(platform_version)?;
606                    (carries(index), index.name.clone())
607                }
608                BindingSource::SubQuery(source_index) => {
609                    let source = &self.sub_queries[source_index];
610                    let shape = self.sub_query_document_query_with_direction(
611                        source,
612                        &[Identifier::default()],
613                        direction,
614                        platform_version,
615                    )?;
616                    let index = shape.index_only_query_index(platform_version)?;
617                    (carries(index), index.name.clone())
618                }
619            };
620            if !carried {
621                return Err(label(&format!(
622                    "the indexOnly source resolves to index \"{}\", which does not carry the \
623                     source property \"{}\"",
624                    index_name, binding.source_property,
625                )));
626            }
627        }
628
629        if sub_query
630            .where_clauses
631            .iter()
632            .any(|clause| clause.field == binding.field)
633        {
634            return Err(label(&format!(
635                "the fixed clauses may not name the bound field \"{}\"; its `IN` clause is \
636                 derived",
637                binding.field,
638            )));
639        }
640
641        // The bound field must hold identifiers on the sub-query's own
642        // type: `$ownerId`, or an identifier-typed property (`$id` is the
643        // by-id join, checked below). Derived values are identifiers, so
644        // any other type could never match, and assembly reads the field
645        // back as an identifier.
646        if !sub_query.is_by_id_join() && binding.field != dpp::document::property_names::OWNER_ID {
647            let Some(property) = sub_query
648                .document_type
649                .flattened_properties()
650                .get(binding.field.as_str())
651            else {
652                return Err(label(&format!(
653                    "bound field \"{}\" does not name a property of \"{}\"",
654                    binding.field,
655                    sub_query.document_type.name(),
656                )));
657            };
658            if !matches!(
659                property.property_type,
660                DocumentPropertyType::Identifier | DocumentPropertyType::IdentifierWithReference(_)
661            ) {
662                return Err(label(&format!(
663                    "bound field \"{}\" is not identifier-typed; composite bindings derive \
664                     identifiers only",
665                    binding.field,
666                )));
667            }
668        }
669
670        match sub_query.kind {
671            SubQueryKind::Documents if sub_query.is_by_id_join() => {
672                if sub_query.document_type.index_only() {
673                    return Err(label(
674                        "a by-id join cannot target an indexOnly type: there is no \
675                         primary-key tree to fetch from",
676                    ));
677                }
678                if sub_query.limit.is_some() {
679                    return Err(label(
680                        "a by-id join takes no limit: every derived id must resolve, so \
681                         completeness is set equality, not a page",
682                    ));
683                }
684                if !sub_query.order_by.is_empty() {
685                    return Err(label(
686                        "a by-id join takes no ordering: results follow the derived ids' \
687                         first appearance",
688                    ));
689                }
690                // The source must be a document reference: it is what
691                // names the type the derived ids resolve in. A
692                // permanentDocument one guarantees every derived id
693                // resolves, which lets a missing document be an invalid
694                // proof; a moderatedDocument one guarantees it resolves or
695                // has a removal record, proven beside it; a
696                // deletableDocument one guarantees neither, and a missing
697                // document is then a proven absence (see
698                // `assemble_documents`).
699                // The values of a reference found by `findBy` are not document
700                // ids, so `document_reference_of` leaves it out; it is named
701                // here so the refusal says why
702                if let Some(DocumentPropertyType::IdentifierWithReference(
703                    DocumentPropertyReferenceTarget::PermanentDocumentLookup { lookup, .. }
704                    | DocumentPropertyReferenceTarget::DeletableDocumentLookup { lookup, .. },
705                )) = source_property_type
706                {
707                    return Err(label(&format!(
708                        "the source property's refersTo finds its document by findBy ({}), so \
709                         its values are not document ids: a by-id join needs a reference whose \
710                         value is the referenced document's $id",
711                        lookup.find_by_names(),
712                    )));
713                }
714                // A reference expression names no single type the derived ids
715                // resolve in
716                if let Some(DocumentPropertyType::IdentifierWithReference(
717                    DocumentPropertyReferenceTarget::AnyOf(_)
718                    | DocumentPropertyReferenceTarget::AllOf(_),
719                )) = source_property_type
720                {
721                    return Err(label(
722                        "the source property declares a refersTo anyOf or allOf expression: a \
723                         by-id join needs a reference to one document type",
724                    ));
725                }
726                match source_property_type.and_then(document_reference_of) {
727                    Some(DocumentReferenceDeclaration {
728                        contract_id,
729                        document_type_name,
730                        ..
731                    }) => {
732                        let referenced_contract =
733                            contract_id.unwrap_or_else(|| source_contract.id());
734                        if referenced_contract != sub_query.contract.id()
735                            || document_type_name != sub_query.document_type.name()
736                        {
737                            return Err(label(&format!(
738                                "the source property's refersTo targets \"{}\", not this \
739                                 sub-query's type \"{}\"",
740                                document_type_name,
741                                sub_query.document_type.name(),
742                            )));
743                        }
744                    }
745                    None => {
746                        return Err(label(&format!(
747                            "a by-id join needs a source property declaring `refersTo: \
748                             permanentDocument`, `refersTo: moderatedDocument` or `refersTo: \
749                             deletableDocument` (\"{}\" does not): the declaration names the \
750                             document type the derived ids resolve in",
751                            binding.source_property,
752                        )));
753                    }
754                }
755            }
756            SubQueryKind::Documents => {
757                // Must lower to a path query with a representative value.
758                let shape = self.sub_query_document_query_with_direction(
759                    sub_query,
760                    &[Identifier::default()],
761                    direction,
762                    platform_version,
763                )?;
764                shape.construct_path_query(None, platform_version)?;
765                if sub_query.document_type.index_only() {
766                    // The lookup's own field must be provable positionally:
767                    // the resolved index has to carry it.
768                    let index = shape.index_only_query_index(platform_version)?;
769                    let carried = index.terminal_contains(&binding.field)
770                        || index
771                            .properties
772                            .iter()
773                            .any(|property| property.name == binding.field);
774                    if !carried {
775                        return Err(label(&format!(
776                            "the indexOnly lookup resolves to index \"{}\", which does not \
777                             carry the bound field \"{}\"",
778                            index.name, binding.field,
779                        )));
780                    }
781                }
782                // A lookup whose rows are bounded by its values (at most
783                // one per derived value) carries no limit: the values
784                // are the bound, and a limit it does not need is exactly
785                // what would keep it from merging with another lookup on
786                // the same index. Anything else needs one, to bound the
787                // walk under each value.
788                let value_bounded =
789                    self.lookup_is_value_bounded(sub_query, binding, &shape, platform_version)?;
790                match (value_bounded, sub_query.limit) {
791                    (true, Some(_)) => {
792                        return Err(label(
793                            "a value-bounded lookup (a unique index, or an indexOnly terminal \
794                             with every prefix fixed, yields at most one row per derived \
795                             value) takes no limit",
796                        ));
797                    }
798                    (false, Some(0)) => {
799                        return Err(label("a lookup's limit must be at least 1"));
800                    }
801                    (false, None) => {
802                        return Err(label(
803                            "a documents lookup on a non-unique index requires an explicit \
804                             limit: it bounds the walk under each derived value",
805                        ));
806                    }
807                    (false, Some(limit)) if limit as usize > MAX_BOUND_VALUES => {
808                        return Err(label(&format!(
809                            "limit {} exceeds {}",
810                            limit, MAX_BOUND_VALUES
811                        )));
812                    }
813                    _ => {}
814                }
815            }
816            SubQueryKind::Count => {
817                if sub_query.limit.is_some() {
818                    return Err(label("a count sub-query takes no limit"));
819                }
820                if !sub_query.order_by.is_empty() {
821                    return Err(label("a count sub-query takes no ordering"));
822                }
823                if sub_query.is_by_id_join() {
824                    return Err(label(
825                        "a count sub-query counts by an indexed property, not by `$id`",
826                    ));
827                }
828                // Must resolve a countable index with a representative value.
829                self.sub_query_count_query(sub_query, &[Identifier::default()], platform_version)?
830                    .point_lookup_count_path_query(platform_version)?;
831            }
832        }
833        Ok(())
834    }
835
836    /// Whether a bound documents lookup yields at most one row per
837    /// derived value: on an indexOnly type, when the resolved index's
838    /// terminal is the bound field and every prefix property is fixed
839    /// by an equality (entries are unique per full index path); on a
840    /// stored type, when a `unique` index's properties are exactly the
841    /// fixed equality fields plus the bound field.
842    fn lookup_is_value_bounded(
843        &self,
844        sub_query: &DriveSubQuery<'a>,
845        binding: &SubQueryBinding,
846        shape: &DriveDocumentQuery<'a>,
847        platform_version: &PlatformVersion,
848    ) -> Result<bool, Error> {
849        let fixed_equalities: BTreeSet<&str> = sub_query
850            .where_clauses
851            .iter()
852            .filter(|clause| clause.operator == WhereOperator::Equal)
853            .map(|clause| clause.field.as_str())
854            .collect();
855        if sub_query.document_type.index_only() {
856            let index = shape.index_only_query_index(platform_version)?;
857            // A composite terminal is bound only through every component;
858            // a single-component terminal through its one field.
859            let terminal_is_bound = index.single_terminal() == Some(binding.field.as_str());
860            let prefix_fixed = index
861                .properties
862                .iter()
863                .all(|property| fixed_equalities.contains(property.name.as_str()));
864            return Ok(terminal_is_bound && prefix_fixed);
865        }
866        let mut wanted: BTreeSet<&str> = fixed_equalities.clone();
867        wanted.insert(binding.field.as_str());
868        Ok(sub_query.document_type.indexes().values().any(|index| {
869            index.unique
870                && index.properties.len() == wanted.len()
871                && index
872                    .properties
873                    .iter()
874                    .all(|property| wanted.contains(property.name.as_str()))
875        }))
876    }
877
878    /// Whether the page is a primary-key fetch (`$id IN` / `$id ==`).
879    fn page_is_by_ids(&self) -> bool {
880        self.internal_clauses.primary_key_in_clause.is_some()
881            || self.internal_clauses.primary_key_equal_clause.is_some()
882    }
883
884    /// A component's budget lives on its root query as a per-instance
885    /// cap (`Query::limit`), never on the path query's global
886    /// `SizedQuery::limit` that the plain documents lowering emits. The
887    /// two are not interchangeable once proven: at a layer with subquery
888    /// branches the prover truncates the children it emits under a
889    /// global limit but only the descendant rows under an instance cap.
890    /// The merge would lift a global limit into exactly this cap, so
891    /// authoring it at construction keeps ONE form for validation, the
892    /// merge, the prover and every subset pass of the verifier, whether
893    /// or not the component ends up merged with anything. A component's
894    /// root executes once (its path is a concrete key chain), so "N rows
895    /// per instance" is "N rows".
896    fn budget_as_instance_cap(mut path_query: PathQuery) -> PathQuery {
897        if let Some(limit) = path_query.query.limit.take() {
898            path_query.query.query.limit = Some(limit);
899        }
900        path_query
901    }
902
903    /// The page as a component of the proof, its limit carried as its
904    /// root query's per-instance cap (see [`Self::budget_as_instance_cap`]).
905    /// A by-ids page is built WITHOUT its limit: its ids already bound
906    /// it, and grovedb refuses a budget on a query that lands at the
907    /// merged root (which a by-ids page shares with a join on the same
908    /// type).
909    pub fn page_path_query(&self, platform_version: &PlatformVersion) -> Result<PathQuery, Error> {
910        if self.page_is_by_ids() {
911            let mut unlimited = self.clone();
912            unlimited.limit = None;
913            let mut path_query = unlimited.construct_path_query(None, platform_version)?;
914            // A `$id ==` page lowers with a limit of one whatever the
915            // query's own limit says; the single key already bounds it,
916            // so the proof query carries no limit either way.
917            path_query.query.limit = None;
918            return Ok(path_query);
919        }
920        Ok(Self::budget_as_instance_cap(
921            self.construct_path_query(None, platform_version)?,
922        ))
923    }
924
925    /// The shape rules routing and merging need up front. Document
926    /// entries are routed back to components by the longest matching
927    /// base path and then by bound-value membership (counts by their
928    /// exact terminal positions), so documents components sharing a base
929    /// path must be tellable apart by their derived values: a sibling,
930    /// which has none, stays alone, and a page only shares the primary
931    /// tree with joins when it is itself a by-ids fetch. And no limited
932    /// component may land at the merged root, where grovedb refuses a
933    /// budget (it would govern every component's rows). A bound
934    /// sub-query that derives
935    /// nothing contributes no branch, so the merged root is not fixed by
936    /// the shapes: it is the common prefix of whichever components are
937    /// present, and a limited component lands on it exactly when every
938    /// other present component's path extends its own. The page and the
939    /// siblings are always present and any bound sub-query may be
940    /// absent, so the rule is checked over that worst case rather than
941    /// over the full set, and a request that validates never fails the
942    /// merge for lack of data.
943    fn validate_component_paths(&self, platform_version: &PlatformVersion) -> Result<(), Error> {
944        let representative = [Identifier::default()];
945        let mut components: Vec<(Vec<Vec<u8>>, Component, bool)> = Vec::new();
946        let page = self.page_path_query(platform_version)?;
947        let direction = page.query.query.left_to_right;
948        components.push((page.path, Component::Page, page.query.query.limit.is_some()));
949        for (index, sub_query) in self.sub_queries.iter().enumerate() {
950            let path_query = self.sub_query_proof_path_query(
951                sub_query,
952                &representative,
953                direction,
954                platform_version,
955            )?;
956            components.push((
957                path_query.path,
958                Component::Sub(index),
959                path_query.query.query.limit.is_some(),
960            ));
961        }
962
963        let is_bound = |component: &Component| matches!(component, Component::Sub(index) if self.sub_queries[*index].binding.is_some());
964        for (path, component, limited) in &components {
965            if !*limited {
966                continue;
967            }
968            let lands_at_root = match component {
969                // Any bound sub-query below the page puts the page at the
970                // root once it is the only other component present; so
971                // do the siblings when every one of them is below it.
972                Component::Page => {
973                    let (siblings, bound): (Vec<_>, Vec<_>) = components
974                        .iter()
975                        .skip(1)
976                        .partition(|(_, other, _)| !is_bound(other));
977                    bound.iter().any(|(other, _, _)| other.starts_with(path))
978                        || (!siblings.is_empty()
979                            && siblings.iter().all(|(other, _, _)| other.starts_with(path)))
980                }
981                // The page and every other sibling are always present:
982                // when all of them are below this component, the bound
983                // sub-queries deriving nothing leaves it at the root.
984                Component::Sub(_) => components
985                    .iter()
986                    .filter(|(_, other, _)| other != component && !is_bound(other))
987                    .all(|(other, _, _)| other.starts_with(path)),
988            };
989            if lands_at_root {
990                return Err(unsupported(format!(
991                    "{} carries a limit and lands at the merged root of the composite proof \
992                     (once the bound sub-queries that derive nothing drop out), where grovedb \
993                     refuses a budget; give it a clause that narrows its path, or split it \
994                     into a separate request",
995                    match component {
996                        Component::Page => "the page".to_string(),
997                        Component::Sub(index) => format!("sub-query {}", index),
998                    }
999                )));
1000            }
1001        }
1002
1003        let mut groups: BTreeMap<&Vec<Vec<u8>>, Vec<(Component, bool)>> = BTreeMap::new();
1004        for (path, component, limited) in &components {
1005            groups.entry(path).or_default().push((*component, *limited));
1006        }
1007        for members in groups.values() {
1008            let documents_members: Vec<Component> = members
1009                .iter()
1010                .map(|(component, _)| *component)
1011                .filter(|component| match component {
1012                    Component::Page => true,
1013                    Component::Sub(index) => {
1014                        self.sub_queries[*index].kind == SubQueryKind::Documents
1015                    }
1016                })
1017                .collect();
1018            let has_count_member = members.iter().any(|(component, _)| {
1019                matches!(component, Component::Sub(index) if self.sub_queries[*index].kind == SubQueryKind::Count)
1020            });
1021            // A count reads an index's value trees themselves; a documents
1022            // component on the same index descends past them to the rows.
1023            // One tree node cannot serve both selections in one proof, and
1024            // grovedb's merge does not refuse the combination: the descent
1025            // wins and the count silently drops out of the merged query, so
1026            // this guard (and the concrete-value one in
1027            // `proof_path_queries`, for nested bases) is what keeps a count
1028            // from verifying as empty. Shapes sharing a base are refused
1029            // here regardless of data, so acceptance stays predictable.
1030            if has_count_member && !documents_members.is_empty() {
1031                return Err(unsupported(
1032                    "a count sub-query shares its index path with a documents component: \
1033                     the count reads the index's value trees themselves while the documents \
1034                     query descends past them, and one proof cannot serve both; count on \
1035                     another index, or split them into separate requests"
1036                        .to_string(),
1037                ));
1038            }
1039            if documents_members.len() < 2 {
1040                continue;
1041            }
1042            let has_sibling = documents_members.iter().any(|component| {
1043                matches!(component, Component::Sub(index) if self.sub_queries[*index].binding.is_none())
1044            });
1045            let has_page = documents_members.contains(&Component::Page);
1046            let all_subs_are_joins = documents_members.iter().all(|component| match component {
1047                Component::Page => true,
1048                Component::Sub(index) => self.sub_queries[*index].is_by_id_join(),
1049            });
1050            if has_sibling || (has_page && !(self.page_is_by_ids() && all_subs_are_joins)) {
1051                return Err(unsupported(
1052                    "two documents components of the composite query address the same index \
1053                     path and cannot be told apart by their derived values (a sibling, or a \
1054                     page that is not a by-ids fetch, shares a path with another component); \
1055                     split them into separate requests"
1056                        .to_string(),
1057                ));
1058            }
1059            // Components sharing a base path merge into one body, and
1060            // budgets never blend: a limited one among them can never be
1061            // merged (value-bounded lookups, which carry none, can).
1062            if members.iter().any(|(_, limited)| *limited) {
1063                return Err(unsupported(
1064                    "two documents components of the composite query address the same index \
1065                     path and one of them carries a limit, which cannot be merged with the \
1066                     other's selection; split them into separate requests"
1067                        .to_string(),
1068                ));
1069            }
1070        }
1071        Ok(())
1072    }
1073
1074    /// Extracts a binding's values from its source documents in their
1075    /// order, deduplicated to first appearance. ONE extraction both the
1076    /// server and the verifier run — the single-builder rule that keeps
1077    /// every derived sub-query identical on both sides.
1078    pub fn derive_values(
1079        &self,
1080        binding: &SubQueryBinding,
1081        source_documents: &[Document],
1082    ) -> Result<DerivedValues, Error> {
1083        let mut seen: BTreeSet<Identifier> = BTreeSet::new();
1084        let mut values = Vec::new();
1085        for document in source_documents {
1086            if let Some(value) = document_bound_value(document, &binding.source_property)? {
1087                if seen.insert(value) {
1088                    values.push(value);
1089                }
1090            }
1091        }
1092        if values.len() > MAX_BOUND_VALUES {
1093            // The page limit, every sub-query limit and every value-bounded
1094            // lookup cap a source at MAX_BOUND_VALUES documents, so this is
1095            // an invariant on both sides, not a shape or proof condition.
1096            return Err(Error::Drive(DriveError::CorruptedCodeExecution(
1097                "a composite binding source yielded more documents than the shapes allow",
1098            )));
1099        }
1100        Ok(values)
1101    }
1102
1103    /// The concrete documents query of a sub-query for `values`: the
1104    /// fixed clauses plus the derived `IN`, or a pure by-ids fetch for
1105    /// a join. A sibling ignores `values`.
1106    pub fn sub_query_document_query(
1107        &self,
1108        sub_query: &DriveSubQuery<'a>,
1109        values: &[Identifier],
1110        platform_version: &PlatformVersion,
1111    ) -> Result<DriveDocumentQuery<'a>, Error> {
1112        let direction = self.page_direction(platform_version)?;
1113        self.sub_query_document_query_with_direction(sub_query, values, direction, platform_version)
1114    }
1115
1116    /// [`Self::sub_query_document_query`] with the page's direction
1117    /// already in hand: what every internal caller uses, so the page path
1118    /// query is lowered once per request rather than once per sub-query.
1119    pub(crate) fn sub_query_document_query_with_direction(
1120        &self,
1121        sub_query: &DriveSubQuery<'a>,
1122        values: &[Identifier],
1123        direction: bool,
1124        platform_version: &PlatformVersion,
1125    ) -> Result<DriveDocumentQuery<'a>, Error> {
1126        let ids = sorted_values(values);
1127        let in_value = || {
1128            Value::Array(
1129                ids.iter()
1130                    .map(|id| Value::Identifier(id.to_buffer()))
1131                    .collect(),
1132            )
1133        };
1134
1135        if sub_query.is_by_id_join() {
1136            if !sub_query.where_clauses.is_empty() {
1137                return Err(unsupported(
1138                    "a by-id join takes no fixed clauses: every derived id must resolve"
1139                        .to_string(),
1140                ));
1141            }
1142            return Ok(DriveDocumentQuery {
1143                contract: sub_query.contract,
1144                document_type: sub_query.document_type,
1145                internal_clauses: InternalClauses {
1146                    primary_key_in_clause: Some(WhereClause {
1147                        field: dpp::document::property_names::ID.to_string(),
1148                        operator: WhereOperator::In,
1149                        value: in_value(),
1150                    }),
1151                    primary_key_equal_clause: None,
1152                    in_clauses: Vec::new(),
1153                    range_clause: None,
1154                    equal_clauses: Default::default(),
1155                },
1156                offset: None,
1157                limit: None,
1158                order_by: Default::default(),
1159                start_at: None,
1160                start_at_included: false,
1161                block_time_ms: None,
1162                resolved_time_ranges: Vec::new(),
1163                sub_queries: Vec::new(),
1164            });
1165        }
1166
1167        let mut clauses = sub_query.where_clauses.clone();
1168        let mut order_by: indexmap::IndexMap<String, OrderClause> = sub_query
1169            .order_by
1170            .iter()
1171            .map(|clause| (clause.field.clone(), clause.clone()))
1172            .collect();
1173        if let Some(binding) = &sub_query.binding {
1174            clauses.push(WhereClause {
1175                field: binding.field.clone(),
1176                operator: WhereOperator::In,
1177                value: in_value(),
1178            });
1179            // An `IN` on a secondary index orders by the bound field;
1180            // supply the ordering when the caller did not, so the
1181            // request stays minimal and both sides build the same query.
1182            // It inherits the page's direction: the merged proof walks
1183            // every component the page's way, and a documents sub-query
1184            // may not be turned around behind the caller's back (see
1185            // `sub_query_proof_path_query`), so this default is what
1186            // keeps an unordered lookup mergeable under a descending page.
1187            if !order_by.contains_key(&binding.field) {
1188                order_by.insert(
1189                    binding.field.clone(),
1190                    OrderClause {
1191                        field: binding.field.clone(),
1192                        ascending: direction,
1193                    },
1194                );
1195            }
1196        }
1197        Ok(DriveDocumentQuery {
1198            contract: sub_query.contract,
1199            document_type: sub_query.document_type,
1200            internal_clauses: InternalClauses::extract_from_clauses(clauses, platform_version)?,
1201            offset: None,
1202            limit: sub_query.limit,
1203            order_by,
1204            start_at: None,
1205            start_at_included: false,
1206            block_time_ms: None,
1207            resolved_time_ranges: Vec::new(),
1208            sub_queries: Vec::new(),
1209        })
1210    }
1211
1212    /// The concrete count query of a bound count sub-query for `values`.
1213    /// Borrows the covering index through `sub_query`, so the count query
1214    /// lives as long as that reference.
1215    pub fn sub_query_count_query<'b>(
1216        &'b self,
1217        sub_query: &'b DriveSubQuery<'a>,
1218        values: &[Identifier],
1219        platform_version: &PlatformVersion,
1220    ) -> Result<DriveDocumentCountQuery<'b>, Error> {
1221        let Some(binding) = &sub_query.binding else {
1222            return Err(unsupported("a count sub-query must be bound".to_string()));
1223        };
1224        // A typed array's `contains` pin is keyed as the equality its index
1225        // level holds, as the count surface keys it; a sub-query reads one
1226        // element at most (see `validate_sub_query`).
1227        let mut where_clauses = key_array_element_clauses(
1228            sub_query.where_clauses.clone(),
1229            sub_query.document_type,
1230            false,
1231            platform_version,
1232        )?;
1233        where_clauses.push(WhereClause {
1234            field: binding.field.clone(),
1235            operator: WhereOperator::In,
1236            value: Value::Array(
1237                sorted_values(values)
1238                    .into_iter()
1239                    .map(|id| Value::Identifier(id.to_buffer()))
1240                    .collect(),
1241            ),
1242        });
1243        let index = DriveDocumentCountQuery::find_countable_index_for_where_clauses(
1244            sub_query.document_type.indexes(),
1245            &where_clauses,
1246            &[],
1247        )
1248        .ok_or_else(|| {
1249            unsupported(format!(
1250                "count sub-query on \"{}\" needs a `countable: true` (or summableOffCountIndex) index covering its fixed \
1251                 clauses and the bound field \"{}\"",
1252                sub_query.document_type.name(),
1253                binding.field,
1254            ))
1255        })?;
1256        Ok(DriveDocumentCountQuery {
1257            document_type: sub_query.document_type,
1258            contract_id: sub_query.contract.id().to_buffer(),
1259            document_type_name: sub_query.document_type.name().to_string(),
1260            index,
1261            where_clauses,
1262        })
1263    }
1264
1265    /// The path query of one sub-query for `values`.
1266    pub fn sub_query_path_query(
1267        &self,
1268        sub_query: &DriveSubQuery<'a>,
1269        values: &[Identifier],
1270        platform_version: &PlatformVersion,
1271    ) -> Result<PathQuery, Error> {
1272        let direction = self.page_direction(platform_version)?;
1273        self.sub_query_path_query_with_direction(sub_query, values, direction, platform_version)
1274    }
1275
1276    fn sub_query_path_query_with_direction(
1277        &self,
1278        sub_query: &DriveSubQuery<'a>,
1279        values: &[Identifier],
1280        direction: bool,
1281        platform_version: &PlatformVersion,
1282    ) -> Result<PathQuery, Error> {
1283        let path_query = match sub_query.kind {
1284            SubQueryKind::Documents => self
1285                .sub_query_document_query_with_direction(
1286                    sub_query,
1287                    values,
1288                    direction,
1289                    platform_version,
1290                )?
1291                .construct_path_query(None, platform_version)?,
1292            SubQueryKind::Count => self
1293                .sub_query_count_query(sub_query, values, platform_version)?
1294                .point_lookup_count_path_query(platform_version)?,
1295        };
1296        Ok(Self::budget_as_instance_cap(path_query))
1297    }
1298
1299    /// The page's walk direction: what every component of the merged
1300    /// proof walks in, and what an unordered documents sub-query inherits.
1301    fn page_direction(&self, platform_version: &PlatformVersion) -> Result<bool, Error> {
1302        Ok(self
1303            .page_path_query(platform_version)?
1304            .query
1305            .query
1306            .left_to_right)
1307    }
1308
1309    /// Aligns set-based components for merging without changing a
1310    /// documents query's ordering or the rows selected by its limit.
1311    /// Validation, proof generation and bootstrap use the same check,
1312    /// including when a binding will derive no values at execution time.
1313    pub(crate) fn sub_query_proof_path_query(
1314        &self,
1315        sub_query: &DriveSubQuery<'a>,
1316        values: &[Identifier],
1317        direction: bool,
1318        platform_version: &PlatformVersion,
1319    ) -> Result<PathQuery, Error> {
1320        let mut path_query = self.sub_query_path_query_with_direction(
1321            sub_query,
1322            values,
1323            direction,
1324            platform_version,
1325        )?;
1326        if sub_query.kind == SubQueryKind::Documents
1327            && !sub_query.is_by_id_join()
1328            && path_query.query.query.left_to_right != direction
1329        {
1330            return Err(unsupported(if sub_query.binding.is_none() {
1331                "a sibling sub-query's ordering must match the page's direction; order it \
1332                 explicitly by its index property, in the page's direction"
1333                    .to_string()
1334            } else {
1335                "a documents sub-query's outer ordering must match the page's direction; \
1336                 changing it for the merged proof would change its result"
1337                    .to_string()
1338            }));
1339        }
1340        // Joins restore first-appearance order after decoding. Counts
1341        // restore key order. Their selected sets do not depend on direction.
1342        path_query.query.query.left_to_right = direction;
1343        Ok(path_query)
1344    }
1345
1346    /// The component path queries the merged proof covers, in component
1347    /// order: the page, then one entry per sub-query — `None` for a
1348    /// bound sub-query whose binding derived nothing (it has no branch) —
1349    /// and last the removal records components of the by-id joins off
1350    /// `moderatedDocument` properties ([`Self::removal_path_queries`]).
1351    /// Every sub-query walks in the page's direction: documents must
1352    /// already agree, while counts and by-id joins may be aligned without
1353    /// changing their selected sets. ONE builder both the prover and the
1354    /// verifier feed into [`Self::merged_path_query`], so the merged
1355    /// query is byte-identical on both sides.
1356    pub fn proof_path_queries(
1357        &self,
1358        derived: &[DerivedValues],
1359        platform_version: &PlatformVersion,
1360    ) -> Result<CompositeProofPathQueries, Error> {
1361        if derived.len() != self.sub_queries.len() {
1362            return Err(Error::Drive(DriveError::CorruptedCodeExecution(
1363                "one derived value list per sub-query",
1364            )));
1365        }
1366        let page = self.page_path_query(platform_version)?;
1367        let direction = page.query.query.left_to_right;
1368        let mut sub_path_queries = Vec::with_capacity(self.sub_queries.len());
1369        for (sub_query, values) in self.sub_queries.iter().zip(derived) {
1370            if sub_query.binding.is_some() && values.is_empty() {
1371                sub_path_queries.push(None);
1372                continue;
1373            }
1374            let path_query =
1375                self.sub_query_proof_path_query(sub_query, values, direction, platform_version)?;
1376            sub_path_queries.push(Some(path_query));
1377        }
1378        // GroveDB cannot return a count tree and descend through that
1379        // same tree for another component in one merged selection. Check
1380        // concrete values so disjoint selections on the same index remain
1381        // usable, including documents whose base path is below the count's.
1382        let mut count_terminal_paths = BTreeSet::new();
1383        for (sub_query, path_query) in self.sub_queries.iter().zip(&sub_path_queries) {
1384            if sub_query.kind != SubQueryKind::Count {
1385                continue;
1386            }
1387            if let Some(path_query) = path_query {
1388                for (mut path, key) in path_query
1389                    .terminal_keys(MAX_BOUND_VALUES, &platform_version.drive.grove_version)?
1390                {
1391                    path.push(key);
1392                    count_terminal_paths.insert(path);
1393                }
1394            }
1395        }
1396        for terminal_path in count_terminal_paths {
1397            for component in std::iter::once(&page).chain(sub_path_queries.iter().flatten()) {
1398                // A walk never leaves its own base path, so a component
1399                // reaches the terminal only when one path prefixes the
1400                // other (a base below the terminal passes through it).
1401                if !terminal_path.starts_with(&component.path)
1402                    && !component.path.starts_with(&terminal_path)
1403                {
1404                    continue;
1405                }
1406                if Self::path_query_descends_through(component, &terminal_path, platform_version)? {
1407                    return Err(unsupported(
1408                        "a count sub-query selects a tree another component descends through; \
1409                         split them into separate requests"
1410                            .to_string(),
1411                    ));
1412                }
1413            }
1414        }
1415        // Document entries are routed to the component with the longest
1416        // base path that prefixes them, which is only right when no
1417        // documents component walks through another's base path to
1418        // deeper rows (those rows would be routed to the deeper one).
1419        // Exact base-path sharing is refused by the shapes; nesting
1420        // depends on the concrete values, so it is checked here.
1421        let documents: Vec<&PathQuery> = std::iter::once(&page)
1422            .chain(
1423                sub_path_queries
1424                    .iter()
1425                    .zip(&self.sub_queries)
1426                    .filter(|(_, sub_query)| sub_query.kind == SubQueryKind::Documents)
1427                    .filter_map(|(path_query, _)| path_query.as_ref()),
1428            )
1429            .collect();
1430        for deeper in &documents {
1431            for shallower in &documents {
1432                if deeper.path.len() <= shallower.path.len()
1433                    || !deeper.path.starts_with(&shallower.path)
1434                {
1435                    continue;
1436                }
1437                if Self::path_query_descends_through(shallower, &deeper.path, platform_version)? {
1438                    return Err(unsupported(
1439                        "a documents sub-query walks through another documents component's \
1440                         subtree, so their rows could not be told apart; split them into \
1441                         separate requests"
1442                            .to_string(),
1443                    ));
1444                }
1445            }
1446        }
1447        // The removal records sit under the contract's other tree, beside
1448        // its documents tree, so no documents or count component walks
1449        // through them, nor they through one
1450        let removal_path_queries = self.removal_path_queries(derived, direction);
1451        Ok((page, sub_path_queries, removal_path_queries))
1452    }
1453
1454    /// Whether a component walks through this count terminal to a deeper
1455    /// result. Check membership at every level: a default subquery alone
1456    /// does not mean its parent key was selected by this component.
1457    fn path_query_descends_through(
1458        query: &PathQuery,
1459        terminal_path: &[Vec<u8>],
1460        platform_version: &PlatformVersion,
1461    ) -> Result<bool, Error> {
1462        let mut prefix = Vec::with_capacity(terminal_path.len());
1463        for key in terminal_path {
1464            let Some(selection) =
1465                query.query_items_at_path(&prefix, &platform_version.drive.grove_version)?
1466            else {
1467                return Ok(false);
1468            };
1469            if !selection.items.iter().any(|item| item.contains(key))
1470                || !selection.has_subquery_or_matching_in_path_on_key(key)
1471            {
1472                return Ok(false);
1473            }
1474            prefix.push(key.as_slice());
1475        }
1476        Ok(true)
1477    }
1478
1479    /// Merges the component path queries into the one query the proof
1480    /// covers. Components carry their budgets as per-instance caps (see
1481    /// [`Self::budget_as_instance_cap`]), which the merge carries along
1482    /// on their branches, so a page alone is proven in the very form it
1483    /// would have inside a merge.
1484    pub fn merged_path_query(
1485        page: &PathQuery,
1486        sub_path_queries: &[Option<PathQuery>],
1487        removal_path_queries: &[PathQuery],
1488        platform_version: &PlatformVersion,
1489    ) -> Result<PathQuery, Error> {
1490        let mut components: Vec<&PathQuery> = vec![page];
1491        components.extend(sub_path_queries.iter().flatten());
1492        components.extend(removal_path_queries);
1493        if components.len() == 1 {
1494            return Ok(page.clone());
1495        }
1496        PathQuery::merge(components, &platform_version.drive.grove_version)
1497            .map_err(merge_error_to_shape_error)
1498    }
1499
1500    /// Decodes the proved entries of a documents component: stored
1501    /// documents from item elements, indexOnly projections synthesized
1502    /// from their proved positions.
1503    pub(crate) fn decode_document_trios(
1504        query: &DriveDocumentQuery<'a>,
1505        trios: Vec<PresentTrio>,
1506        platform_version: &PlatformVersion,
1507    ) -> Result<Vec<Document>, Error> {
1508        if query.document_type.index_only() {
1509            let index = query.index_only_query_index(platform_version)?;
1510            // Edited in place to pass the platform version, which picks the
1511            // key generation synthesis decodes with: only an indexOnly type
1512            // reaches this line, and only protocol version 14 parses one.
1513            return trios
1514                .into_iter()
1515                .map(|(path, key, element)| {
1516                    synthesize_index_only_document(
1517                        query.contract.id(),
1518                        query.document_type,
1519                        index,
1520                        &path,
1521                        &key,
1522                        Some(&element),
1523                        platform_version,
1524                    )
1525                })
1526                .collect();
1527        }
1528        trios
1529            .into_iter()
1530            .map(|(_, _, element)| {
1531                let serialized = element.into_item_bytes().map_err(Error::from)?;
1532                Document::from_bytes(serialized.as_slice(), query.document_type, platform_version)
1533                    .map_err(|e| Error::Protocol(Box::new(e)))
1534            })
1535            .collect()
1536    }
1537
1538    /// Decodes a documents sub-query and applies the same result assembly
1539    /// as execution, particularly a join's first-appearance ordering,
1540    /// before its documents can supply values to a later binding.
1541    pub(crate) fn decode_sub_query_document_trios(
1542        &self,
1543        sub_query: &DriveSubQuery<'a>,
1544        values: &[Identifier],
1545        direction: bool,
1546        trios: Vec<PresentTrio>,
1547        platform_version: &PlatformVersion,
1548    ) -> Result<Vec<Document>, Error> {
1549        let query = self.sub_query_document_query_with_direction(
1550            sub_query,
1551            values,
1552            direction,
1553            platform_version,
1554        )?;
1555        let documents = Self::decode_document_trios(&query, trios, platform_version)?;
1556        self.assemble_documents(sub_query, values, &documents)
1557    }
1558
1559    /// Decodes the proved entries of a count component: one entry per
1560    /// count tree, keyed by the `IN` value — which sits one segment
1561    /// past the base path when the walk descended through trailing
1562    /// equalities, and IS the key otherwise (the same layout
1563    /// `verify_point_lookup_count_proof` reads).
1564    fn decode_count_trios(
1565        index: &Index,
1566        base_path_len: usize,
1567        trios: Vec<PresentTrio>,
1568    ) -> Vec<SplitCountEntry> {
1569        // A composite count is always bound, so it always carries an `IN`.
1570        let mut entries = point_lookup_count_entries(
1571            index,
1572            base_path_len,
1573            true,
1574            trios
1575                .into_iter()
1576                .map(|(path, key, element)| (path, key, Some(element))),
1577        );
1578        // Proof merging may align the count walk with a descending page;
1579        // count results retain the ordinary point-lookup's key order.
1580        entries.sort_by(|a, b| a.key.cmp(&b.key));
1581        entries
1582    }
1583
1584    /// What each by-id join reports of the derived ids its assembled result
1585    /// has no document for, in first-appearance order, one list of each per
1586    /// sub-query: off a `deletableDocument` property the ids themselves (the
1587    /// missing ids), off a `moderatedDocument` property their removal records
1588    /// (see [`pair_missing_with_removals`]), and nothing for every other
1589    /// sub-query. [`Self::assemble_documents`] has already refused a missing
1590    /// document of a `permanentDocument` join. `removals` holds the decoded
1591    /// records by the path of the component that covers them.
1592    fn sub_result_absences(
1593        &self,
1594        derived: &[DerivedValues],
1595        sub_results: &[SubQueryResult],
1596        removals: &BTreeMap<Vec<Vec<u8>>, BTreeMap<Identifier, ContractDocumentRemovalEntry>>,
1597    ) -> Result<SubResultAbsences, Error> {
1598        let none = BTreeMap::new();
1599        let mut missing_ids = Vec::with_capacity(self.sub_queries.len());
1600        let mut sub_result_removals = Vec::with_capacity(self.sub_queries.len());
1601        for ((sub_query, values), result) in self.sub_queries.iter().zip(derived).zip(sub_results) {
1602            let kind = self.sub_query_join_kind(sub_query);
1603            let missing: Vec<Identifier> = match kind {
1604                Some(DocumentReferenceKind::Deletable | DocumentReferenceKind::Moderated) => {
1605                    let present: BTreeSet<Identifier> = result
1606                        .documents()
1607                        .iter()
1608                        .map(|document| document.id())
1609                        .collect();
1610                    values
1611                        .iter()
1612                        .filter(|value| !present.contains(*value))
1613                        .copied()
1614                        .collect()
1615                }
1616                _ => Vec::new(),
1617            };
1618            if kind == Some(DocumentReferenceKind::Moderated) {
1619                let path = contract_document_type_removals_path_vec(
1620                    sub_query.contract.id().as_slice(),
1621                    sub_query.document_type.name(),
1622                );
1623                sub_result_removals.push(pair_missing_with_removals(
1624                    &missing,
1625                    removals.get(&path).unwrap_or(&none),
1626                )?);
1627                missing_ids.push(Vec::new());
1628            } else {
1629                sub_result_removals.push(Vec::new());
1630                missing_ids.push(missing);
1631            }
1632        }
1633        Ok((missing_ids, sub_result_removals))
1634    }
1635
1636    /// What a by-id join's source property guarantees of its targets:
1637    /// that they stay in state (`permanentDocument`), leave it only on a
1638    /// moderator's record (`moderatedDocument`), or nothing
1639    /// (`deletableDocument`). A source that is none of them, which
1640    /// `validate_sub_query` refuses, is held to the strict rule.
1641    fn by_id_join_kind(&self, binding: &SubQueryBinding) -> DocumentReferenceKind {
1642        let source_type = match binding.source {
1643            BindingSource::Page => Some(self.document_type),
1644            BindingSource::SubQuery(source_index) => self
1645                .sub_queries
1646                .get(source_index)
1647                .map(|source| source.document_type),
1648        };
1649        let Some(source_type) = source_type else {
1650            return DocumentReferenceKind::Permanent;
1651        };
1652        source_type
1653            .flattened_properties()
1654            .get(binding.source_property.as_str())
1655            .and_then(|property| document_reference_of(&property.property_type))
1656            .map_or(DocumentReferenceKind::Permanent, |declaration| {
1657                declaration.kind
1658            })
1659    }
1660
1661    /// The kind of a sub-query that is a by-id join, `None` for every other
1662    /// sub-query.
1663    fn sub_query_join_kind(&self, sub_query: &DriveSubQuery<'a>) -> Option<DocumentReferenceKind> {
1664        match &sub_query.binding {
1665            Some(binding) if sub_query.is_by_id_join() => Some(self.by_id_join_kind(binding)),
1666            _ => None,
1667        }
1668    }
1669
1670    /// The removal records components of the merged proof: one per
1671    /// document type the by-id joins off `moderatedDocument` properties
1672    /// target, over every id those joins derived (see
1673    /// [`removals_path_query`]), walking in the page's `direction`. One
1674    /// component per type, never one per join, so two joins into the same
1675    /// type do not select the same records tree twice. In (contract, type)
1676    /// order, so the prover and the verifier merge the same list.
1677    fn removal_path_queries(&self, derived: &[DerivedValues], direction: bool) -> Vec<PathQuery> {
1678        let mut ids_by_type: BTreeMap<(Identifier, &str), BTreeSet<Identifier>> = BTreeMap::new();
1679        for (sub_query, values) in self.sub_queries.iter().zip(derived) {
1680            if values.is_empty()
1681                || self.sub_query_join_kind(sub_query) != Some(DocumentReferenceKind::Moderated)
1682            {
1683                continue;
1684            }
1685            ids_by_type
1686                .entry((
1687                    sub_query.contract.id(),
1688                    sub_query.document_type.name().as_str(),
1689                ))
1690                .or_default()
1691                .extend(values.iter().copied());
1692        }
1693        ids_by_type
1694            .into_iter()
1695            .map(|((contract_id, document_type_name), ids)| {
1696                let ids: Vec<Identifier> = ids.into_iter().collect();
1697                removals_path_query(contract_id, document_type_name, &ids, direction)
1698            })
1699            .collect()
1700    }
1701
1702    /// Assembles one documents sub-query's result from its decoded
1703    /// documents, keeping only the ones its derived values admit and, for
1704    /// a by-id join, putting them in first-appearance order. A derived id
1705    /// with no document is refused when the source property is a
1706    /// `permanentDocument` reference (exact set equality: it cannot
1707    /// dangle) and left out when it is a `moderatedDocument` reference
1708    /// (its removal record is reported instead) or a `deletableDocument` reference
1709    /// (the target was deleted since, and the fetch that found nothing
1710    /// under it is the query the proof covers). Shared by the server
1711    /// (where a violation is corrupted state) and the verifier (where it
1712    /// is an invalid proof).
1713    fn assemble_documents(
1714        &self,
1715        sub_query: &DriveSubQuery<'a>,
1716        values: &[Identifier],
1717        documents: &[Document],
1718    ) -> Result<Vec<Document>, Error> {
1719        let Some(binding) = &sub_query.binding else {
1720            return Ok(documents.to_vec());
1721        };
1722        let admitted: BTreeSet<Identifier> = values.iter().copied().collect();
1723        if sub_query.is_by_id_join() {
1724            let mut by_id: BTreeMap<Identifier, &Document> = BTreeMap::new();
1725            for document in documents {
1726                let id = document.id();
1727                if !admitted.contains(&id) {
1728                    // Another join on the same type owns it.
1729                    continue;
1730                }
1731                if by_id.insert(id, document).is_some() {
1732                    return Err(corrupted_proof(format!(
1733                        "composite join results carry document {} twice",
1734                        id
1735                    )));
1736                }
1737            }
1738            let target_is_permanent = self.by_id_join_kind(binding).is_permanent();
1739            let mut ordered = Vec::with_capacity(values.len());
1740            for value in values {
1741                match by_id.remove(value) {
1742                    Some(document) => ordered.push(document.clone()),
1743                    None if target_is_permanent => {
1744                        return Err(corrupted_proof(format!(
1745                            "composite join results are missing referenced document {}: a \
1746                             permanentDocument reference cannot dangle, so the proof does \
1747                             not cover the derived query",
1748                            value
1749                        )));
1750                    }
1751                    // A moderatedDocument target a moderator removed, reported
1752                    // by its record (`sub_result_absences`), or a
1753                    // deletableDocument target that is no longer in state.
1754                    None => {}
1755                }
1756            }
1757            return Ok(ordered);
1758        }
1759        let mut mine = Vec::new();
1760        for document in documents {
1761            match document_bound_value(document, &binding.field)? {
1762                Some(value) if admitted.contains(&value) => mine.push(document.clone()),
1763                _ => {}
1764            }
1765        }
1766        Ok(mine)
1767    }
1768
1769    /// Assembles one count sub-query's result: the entries its derived
1770    /// values admit.
1771    fn assemble_counts(
1772        values: &[Identifier],
1773        entries: Vec<SplitCountEntry>,
1774    ) -> Result<Vec<SplitCountEntry>, Error> {
1775        let admitted: BTreeSet<Identifier> = values.iter().copied().collect();
1776        let mut mine = Vec::with_capacity(entries.len());
1777        for entry in entries {
1778            let Ok(value) = Identifier::from_bytes(&entry.key) else {
1779                return Err(corrupted_proof(
1780                    "a composite count entry is keyed by something other than an identifier"
1781                        .to_string(),
1782                ));
1783            };
1784            if admitted.contains(&value) {
1785                mine.push(entry);
1786            }
1787        }
1788        Ok(mine)
1789    }
1790
1791    /// Routes the proved trios of the merged query back to the page and
1792    /// the sub-queries, decodes each group, and assembles every
1793    /// component's result. Every trio must land in a component, and
1794    /// every decoded item must be claimed by one — an entry the
1795    /// derivation never asked for means the responding node steered the
1796    /// composition. `sum_bearing_items_are_documents` reads every item
1797    /// variant as a document (the composite verifier from version 1), where
1798    /// version 0 reads only a plain `Item` as one.
1799    #[allow(clippy::too_many_arguments)]
1800    pub(crate) fn assemble_from_trios(
1801        &self,
1802        derived: &[DerivedValues],
1803        page_path_query: &PathQuery,
1804        sub_path_queries: &[Option<PathQuery>],
1805        removal_path_queries: &[PathQuery],
1806        trios: Vec<ProvedTrio>,
1807        sum_bearing_items_are_documents: bool,
1808        platform_version: &PlatformVersion,
1809    ) -> Result<CompositeDocumentsResult, Error> {
1810        // Group documents by base path. Counts instead route by their
1811        // complete terminal positions: a shared base and bound value can
1812        // still select different trailing equality values. A terminal may
1813        // belong to several counts, including counts with nested base paths.
1814        let direction = page_path_query.query.query.left_to_right;
1815        let mut groups: Vec<(Vec<Vec<u8>>, Vec<Component>)> = Vec::new();
1816        let mut count_members_by_position: BTreeMap<_, Vec<usize>> = BTreeMap::new();
1817        let mut register = |path: &Vec<Vec<u8>>, component: Component| {
1818            if let Some((_, members)) = groups.iter_mut().find(|(p, _)| p == path) {
1819                members.push(component);
1820            } else {
1821                groups.push((path.clone(), vec![component]));
1822            }
1823        };
1824        register(&page_path_query.path, Component::Page);
1825        for (index, path_query) in sub_path_queries.iter().enumerate() {
1826            if let Some(path_query) = path_query {
1827                if self.sub_queries[index].kind == SubQueryKind::Count {
1828                    for position in path_query
1829                        .terminal_keys(MAX_BOUND_VALUES, &platform_version.drive.grove_version)?
1830                    {
1831                        count_members_by_position
1832                            .entry(position)
1833                            .or_default()
1834                            .push(index);
1835                    }
1836                } else {
1837                    register(&path_query.path, Component::Sub(index));
1838                }
1839            }
1840        }
1841
1842        // Distribute counts before their positional information is lost
1843        // during decoding, and documents by the longest matching base path.
1844        let mut trios_by_group: Vec<Vec<PresentTrio>> = vec![Vec::new(); groups.len()];
1845        let mut count_trios_by_sub: Vec<Vec<PresentTrio>> =
1846            vec![Vec::new(); self.sub_queries.len()];
1847        // A removal record sits at exactly its component's path, which no
1848        // documents or count component's path equals
1849        let mut removal_entries_by_path: RemovalEntriesByPath = removal_path_queries
1850            .iter()
1851            .map(|path_query| (path_query.path.clone(), Vec::new()))
1852            .collect();
1853        for (path, key, element) in trios {
1854            let Some(element) = element else {
1855                continue;
1856            };
1857            if let Some(entries) = removal_entries_by_path.get_mut(&path) {
1858                entries.push((key, element));
1859                continue;
1860            }
1861            // A document is an item; a count is a tree or a counter. From the
1862            // composite verifier's version 1 (`sum_bearing_items_are_documents`),
1863            // the sum-bearing items of a `documentsSummable` type or a
1864            // `summable` indexOnly index are documents too (the items
1865            // `decode_document_trios` reads), which version 0 reads as counts.
1866            let is_document = if sum_bearing_items_are_documents {
1867                element.has_basic_item()
1868            } else {
1869                matches!(element, Element::Item(..))
1870            };
1871            if !is_document {
1872                let position = (path, key);
1873                let members = count_members_by_position.get(&position).ok_or_else(|| {
1874                    corrupted_proof(
1875                        "the composite proof carries a count at a position no component \
1876                         selected"
1877                            .to_string(),
1878                    )
1879                })?;
1880                // Every member takes a copy; the last takes the original.
1881                let (last, others) = members.split_last().ok_or_else(|| {
1882                    Error::Drive(DriveError::CorruptedCodeExecution(
1883                        "a registered count position has at least one member",
1884                    ))
1885                })?;
1886                for index in others {
1887                    count_trios_by_sub[*index].push((
1888                        position.0.clone(),
1889                        position.1.clone(),
1890                        element.clone(),
1891                    ));
1892                }
1893                count_trios_by_sub[*last].push((position.0, position.1, element));
1894                continue;
1895            }
1896            let best = groups
1897                .iter()
1898                .enumerate()
1899                .filter(|(_, (base, _))| path.starts_with(base))
1900                .max_by_key(|(_, (base, _))| base.len())
1901                .map(|(index, _)| index)
1902                .ok_or_else(|| {
1903                    corrupted_proof(
1904                        "the composite proof proved an entry outside every component's \
1905                         subtree"
1906                            .to_string(),
1907                    )
1908                })?;
1909            trios_by_group[best].push((path, key, element));
1910        }
1911
1912        // Decode each documents group once, then let every member claim
1913        // its share.
1914        let mut page_documents: Option<Vec<Document>> = None;
1915        let mut sub_results: Vec<Option<SubQueryResult>> = vec![None; self.sub_queries.len()];
1916        for ((_, documents_members), document_trios) in groups.iter().zip(trios_by_group) {
1917            // Every documents member of a group addresses the same
1918            // type, so any member's query decodes the group.
1919            let documents = match documents_members[0] {
1920                Component::Page => {
1921                    Self::decode_document_trios(self, document_trios, platform_version)?
1922                }
1923                Component::Sub(index) => {
1924                    let query = self.sub_query_document_query_with_direction(
1925                        &self.sub_queries[index],
1926                        &derived[index],
1927                        direction,
1928                        platform_version,
1929                    )?;
1930                    Self::decode_document_trios(&query, document_trios, platform_version)?
1931                }
1932            };
1933            let mut claimed: BTreeSet<usize> = BTreeSet::new();
1934            for member in documents_members {
1935                match member {
1936                    Component::Page => {
1937                        let page_ids: Option<BTreeSet<Identifier>> = if documents_members.len() > 1
1938                        {
1939                            Some(self.page_ids()?)
1940                        } else {
1941                            None
1942                        };
1943                        let mut mine = Vec::new();
1944                        for (position, document) in documents.iter().enumerate() {
1945                            let is_mine = page_ids
1946                                .as_ref()
1947                                .is_none_or(|ids| ids.contains(&document.id()));
1948                            if is_mine {
1949                                claimed.insert(position);
1950                                mine.push(document.clone());
1951                            }
1952                        }
1953                        page_documents = Some(mine);
1954                    }
1955                    Component::Sub(index) => {
1956                        let sub_query = &self.sub_queries[*index];
1957                        let mine =
1958                            self.assemble_documents(sub_query, &derived[*index], &documents)?;
1959                        let mine_ids: BTreeSet<Identifier> =
1960                            mine.iter().map(|document| document.id()).collect();
1961                        for (position, document) in documents.iter().enumerate() {
1962                            if mine_ids.contains(&document.id()) {
1963                                claimed.insert(position);
1964                            }
1965                        }
1966                        sub_results[*index] = Some(SubQueryResult::Documents(mine));
1967                    }
1968                }
1969            }
1970            if claimed.len() != documents.len() {
1971                return Err(corrupted_proof(
1972                    "the composite proof carries a document that no component's \
1973                         derivation asked for"
1974                        .to_string(),
1975                ));
1976            }
1977        }
1978
1979        for (index, count_trios) in count_trios_by_sub.into_iter().enumerate() {
1980            if self.sub_queries[index].kind != SubQueryKind::Count {
1981                continue;
1982            }
1983            let Some(path_query) = &sub_path_queries[index] else {
1984                continue;
1985            };
1986            // Only the covering index is read here, which the bound field's
1987            // clause resolves whatever identifiers it holds: one value spares
1988            // sorting every derived value into a clause again
1989            let values = &derived[index];
1990            let count_query = self.sub_query_count_query(
1991                &self.sub_queries[index],
1992                &values[..values.len().min(1)],
1993                platform_version,
1994            )?;
1995            let entries =
1996                Self::decode_count_trios(count_query.index, path_query.path.len(), count_trios);
1997            sub_results[index] = Some(SubQueryResult::Counts(Self::assemble_counts(
1998                &derived[index],
1999                entries,
2000            )?));
2001        }
2002
2003        let sub_results: Vec<SubQueryResult> = sub_results
2004            .into_iter()
2005            .zip(&self.sub_queries)
2006            .map(|(result, sub_query)| {
2007                result.unwrap_or_else(|| match sub_query.kind {
2008                    SubQueryKind::Documents => SubQueryResult::Documents(Vec::new()),
2009                    SubQueryKind::Count => SubQueryResult::Counts(Vec::new()),
2010                })
2011            })
2012            .collect();
2013        let removals = removal_entries_by_path
2014            .into_iter()
2015            .map(|(path, entries)| Ok((path, decode_removals(entries)?)))
2016            .collect::<Result<BTreeMap<_, _>, Error>>()?;
2017        let (sub_result_missing_ids, sub_result_removals) =
2018            self.sub_result_absences(derived, &sub_results, &removals)?;
2019        Ok(CompositeDocumentsResult {
2020            page_documents: page_documents.unwrap_or_default(),
2021            sub_results,
2022            sub_result_missing_ids,
2023            sub_result_removals,
2024        })
2025    }
2026
2027    /// The ids a by-ids page addresses (its `$id IN` / `$id ==` clause),
2028    /// used to tell the page's documents from a join's when they share
2029    /// the primary tree.
2030    fn page_ids(&self) -> Result<BTreeSet<Identifier>, Error> {
2031        let mut ids = BTreeSet::new();
2032        // The clauses hold the client's values, so a malformed one is the request's fault, with
2033        // the plain query path's errors.
2034        if let Some(clause) = &self.internal_clauses.primary_key_equal_clause {
2035            ids.insert(clause.value.to_identifier().map_err(|_| {
2036                Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(
2037                    "primary key equality must compare an identifier",
2038                ))
2039            })?);
2040        }
2041        if let Some(clause) = &self.internal_clauses.primary_key_in_clause {
2042            for value in clause.in_values().into_data_with_error()??.iter() {
2043                ids.insert(value.to_identifier().map_err(|_| {
2044                    Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(
2045                        "primary key IN must contain identifiers",
2046                    ))
2047                })?);
2048            }
2049        }
2050        Ok(ids)
2051    }
2052
2053    /// Derives one sub-query's values from the (materialized or proven)
2054    /// page and earlier sub-query documents: `sub_documents(i)` is the
2055    /// documents of sub-query `i`, which every source has by the time a
2056    /// later sub-query binds it (validation orders bindings; the
2057    /// executors and the verifier's bootstrap materialize sources
2058    /// first). ONE derivation every path runs — the no-proof executor,
2059    /// the prover, the verifier's bootstrap and its authoritative
2060    /// re-check — which is what keeps them identical.
2061    pub(crate) fn derive_for<'d>(
2062        &self,
2063        sub_query: &DriveSubQuery<'a>,
2064        page_documents: &[Document],
2065        sub_documents: impl Fn(usize) -> Option<&'d [Document]>,
2066    ) -> Result<DerivedValues, Error> {
2067        let Some(binding) = &sub_query.binding else {
2068            return Ok(Vec::new());
2069        };
2070        match binding.source {
2071            BindingSource::Page => self.derive_values(binding, page_documents),
2072            BindingSource::SubQuery(source_index) => {
2073                let documents = sub_documents(source_index).ok_or_else(|| {
2074                    Error::Drive(DriveError::CorruptedCodeExecution(
2075                        "a binding's source sub-query was not materialized before it",
2076                    ))
2077                })?;
2078                self.derive_values(binding, documents)
2079            }
2080        }
2081    }
2082
2083    /// Derives every sub-query's values, in request order — see
2084    /// [`Self::derive_for`].
2085    pub fn derive_all<'d>(
2086        &self,
2087        page_documents: &[Document],
2088        sub_documents: impl Fn(usize) -> Option<&'d [Document]>,
2089    ) -> Result<Vec<DerivedValues>, Error> {
2090        self.sub_queries
2091            .iter()
2092            .map(|sub_query| self.derive_for(sub_query, page_documents, &sub_documents))
2093            .collect()
2094    }
2095
2096    /// Whether a sub-query's documents feed a later binding.
2097    pub(crate) fn is_binding_source(&self, index: usize) -> bool {
2098        self.sub_queries.iter().any(|sub_query| {
2099            matches!(
2100                sub_query.binding,
2101                Some(SubQueryBinding {
2102                    source: BindingSource::SubQuery(source),
2103                    ..
2104                }) if source == index
2105            )
2106        })
2107    }
2108}
2109
2110#[cfg(feature = "server")]
2111impl<'a> DriveDocumentQuery<'a> {
2112    /// Materializes a documents component without a proof, from the
2113    /// very path query the proof covers. The plain documents lowering
2114    /// would walk the same selection under a global limit, and grovedb
2115    /// charges an empty index branch (a preallocated bucket nobody wrote
2116    /// to yet) against a global limit but not against the per-instance
2117    /// cap the component carries (see [`Self::budget_as_instance_cap`]),
2118    /// so the two can fill a page differently. Everything derived from
2119    /// the page rides on this selection, so it has to be the proof's.
2120    /// indexOnly projections are synthesized from their positions,
2121    /// stored documents deserialized. A chained query reads its inner
2122    /// page through here too.
2123    pub(crate) fn materialize_component(
2124        query: &DriveDocumentQuery<'a>,
2125        path_query: &PathQuery,
2126        drive: &crate::drive::Drive,
2127        transaction: grovedb::TransactionArg,
2128        drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2129        platform_version: &PlatformVersion,
2130    ) -> Result<Vec<Document>, Error> {
2131        use grovedb::query_result_type::QueryResultType;
2132
2133        if query.document_type.index_only() {
2134            let results = match drive.grove_get_path_query(
2135                path_query,
2136                transaction,
2137                QueryResultType::QueryPathKeyElementTrioResultType,
2138                drive_operations,
2139                &platform_version.drive,
2140            ) {
2141                Err(error) if is_absent_path(&error) => return Ok(Vec::new()),
2142                other => other?.0,
2143            };
2144            return Self::decode_document_trios(
2145                query,
2146                results.to_path_key_elements(),
2147                platform_version,
2148            );
2149        }
2150        // Stored documents sit behind index references: the serialized
2151        // read follows them, a trio read would hand back the references.
2152        let serialized = match drive.grove_get_path_query_serialized_results(
2153            path_query,
2154            transaction,
2155            drive_operations,
2156            &platform_version.drive,
2157        ) {
2158            Err(error) if is_absent_path(&error) => return Ok(Vec::new()),
2159            other => other?.0,
2160        };
2161        serialized
2162            .into_iter()
2163            .map(|bytes| {
2164                Document::from_bytes(bytes.as_slice(), query.document_type, platform_version)
2165                    .map_err(|e| Error::Protocol(Box::new(e)))
2166            })
2167            .collect()
2168    }
2169
2170    /// Materializes one sub-query's result without a proof.
2171    // The drive handle, transaction and operation sink travel together
2172    // through every materializer here; bundling them buys nothing.
2173    #[allow(clippy::too_many_arguments)]
2174    fn materialize_sub_result(
2175        &self,
2176        sub_query: &DriveSubQuery<'a>,
2177        values: &[Identifier],
2178        direction: bool,
2179        drive: &crate::drive::Drive,
2180        transaction: grovedb::TransactionArg,
2181        drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2182        platform_version: &PlatformVersion,
2183    ) -> Result<SubQueryResult, Error> {
2184        use grovedb::query_result_type::{QueryResultElement, QueryResultType};
2185
2186        if sub_query.binding.is_some() && values.is_empty() {
2187            return Ok(match sub_query.kind {
2188                SubQueryKind::Documents => SubQueryResult::Documents(Vec::new()),
2189                SubQueryKind::Count => SubQueryResult::Counts(Vec::new()),
2190            });
2191        }
2192        match sub_query.kind {
2193            SubQueryKind::Documents => {
2194                let query = self.sub_query_document_query_with_direction(
2195                    sub_query,
2196                    values,
2197                    direction,
2198                    platform_version,
2199                )?;
2200                let path_query = self.sub_query_proof_path_query(
2201                    sub_query,
2202                    values,
2203                    direction,
2204                    platform_version,
2205                )?;
2206                check_composite_instance_limits(&path_query, platform_version)?;
2207                let documents = Self::materialize_component(
2208                    &query,
2209                    &path_query,
2210                    drive,
2211                    transaction,
2212                    drive_operations,
2213                    platform_version,
2214                )?;
2215                Ok(SubQueryResult::Documents(
2216                    self.assemble_documents(sub_query, values, &documents)?,
2217                ))
2218            }
2219            SubQueryKind::Count => {
2220                let count_query =
2221                    self.sub_query_count_query(sub_query, values, platform_version)?;
2222                let path_query = count_query.point_lookup_count_path_query(platform_version)?;
2223                let base_path_len = path_query.path.len();
2224                check_composite_instance_limits(&path_query, platform_version)?;
2225                let (results, _skipped) = match drive.grove_get_path_query(
2226                    &path_query,
2227                    transaction,
2228                    QueryResultType::QueryPathKeyElementTrioResultType,
2229                    drive_operations,
2230                    &platform_version.drive,
2231                ) {
2232                    // No count tree yet under this index: every count is zero.
2233                    Err(error) if is_absent_path(&error) => {
2234                        return Ok(SubQueryResult::Counts(Vec::new()));
2235                    }
2236                    other => other?,
2237                };
2238                let trios = results
2239                    .elements
2240                    .into_iter()
2241                    .filter_map(|element| match element {
2242                        QueryResultElement::PathKeyElementTrioResultItem(trio) => Some(trio),
2243                        _ => None,
2244                    })
2245                    .collect();
2246                let entries = Self::decode_count_trios(count_query.index, base_path_len, trios);
2247                Ok(SubQueryResult::Counts(Self::assemble_counts(
2248                    values, entries,
2249                )?))
2250            }
2251        }
2252    }
2253
2254    /// Executes the composite query without proofs.
2255    pub(crate) fn execute_composite_no_proof_internal(
2256        &self,
2257        drive: &crate::drive::Drive,
2258        transaction: grovedb::TransactionArg,
2259        drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2260        platform_version: &PlatformVersion,
2261    ) -> Result<CompositeDocumentsResult, Error> {
2262        self.validate_composite(platform_version)?;
2263
2264        let page_path_query = self.page_path_query(platform_version)?;
2265        let direction = page_path_query.query.query.left_to_right;
2266        // Documents are serialized whole, as a documents query's are: refused
2267        // before any read when an indexOnly page or documents sub-query reads
2268        // through an index lacking a property. A sub-query is judged with one
2269        // representative value: the index it resolves may be a pivot index the
2270        // read itself, binding more values than its limit, passes over for one
2271        // holding more properties, so this can refuse a read its real values
2272        // would have served, never admit one they would not.
2273        self.refuse_an_uncovered_index_only_projection(platform_version)?;
2274        for sub_query in &self.sub_queries {
2275            if sub_query.kind == SubQueryKind::Documents && sub_query.document_type.index_only() {
2276                self.sub_query_document_query_with_direction(
2277                    sub_query,
2278                    &[Identifier::default()],
2279                    direction,
2280                    platform_version,
2281                )?
2282                .refuse_an_uncovered_index_only_projection(platform_version)?;
2283            }
2284        }
2285        check_composite_instance_limits(&page_path_query, platform_version)?;
2286        let page_documents = Self::materialize_component(
2287            self,
2288            &page_path_query,
2289            drive,
2290            transaction,
2291            drive_operations,
2292            platform_version,
2293        )?;
2294        let mut sub_results: Vec<SubQueryResult> = Vec::with_capacity(self.sub_queries.len());
2295        let mut derived = Vec::with_capacity(self.sub_queries.len());
2296        for sub_query in &self.sub_queries {
2297            let values = self.derive_for(sub_query, &page_documents, |source| {
2298                sub_results.get(source).map(|result| result.documents())
2299            })?;
2300            sub_results.push(self.materialize_sub_result(
2301                sub_query,
2302                &values,
2303                direction,
2304                drive,
2305                transaction,
2306                drive_operations,
2307                platform_version,
2308            )?);
2309            derived.push(values);
2310        }
2311        // Count-tree conflicts depend on the actual derived values, not
2312        // just the representative shapes checked by validate(). Reject
2313        // them on the materialized entry point as on the proof entry point.
2314        let (_, _, removal_path_queries) = self.proof_path_queries(&derived, platform_version)?;
2315        // The same removal records components the proof covers
2316        let mut removals = BTreeMap::new();
2317        for path_query in &removal_path_queries {
2318            removals.insert(
2319                path_query.path.clone(),
2320                fetch_removals(
2321                    drive,
2322                    path_query,
2323                    transaction,
2324                    drive_operations,
2325                    platform_version,
2326                )?,
2327            );
2328        }
2329        let (sub_result_missing_ids, sub_result_removals) =
2330            self.sub_result_absences(&derived, &sub_results, &removals)?;
2331        Ok(CompositeDocumentsResult {
2332            page_documents,
2333            sub_results,
2334            sub_result_missing_ids,
2335            sub_result_removals,
2336        })
2337    }
2338
2339    /// Executes the composite query AND generates its single merged
2340    /// proof.
2341    ///
2342    /// The page (and every sub-query that feeds a later binding) is
2343    /// materialized so the sub-queries can be derived; then
2344    /// [`Self::proof_path_queries`] builds the component path queries
2345    /// and [`Self::merged_path_query`] merges them — one proof, one root
2346    /// by construction. Grovedb proves committed state only, so the
2347    /// materialize/prove sequence is bracketed by root-hash reads and
2348    /// retried if a block commit interleaved (otherwise the proof's page
2349    /// branch could disagree with the sub-queries derived from a stale
2350    /// materialization and every verifier would reject it).
2351    ///
2352    /// Returns the proof and the materialized page (the caller's
2353    /// pagination cursor derives from it); the sub-query results are
2354    /// covered by the proof and not materialized twice.
2355    pub(crate) fn execute_composite_with_proof_internal(
2356        &self,
2357        drive: &crate::drive::Drive,
2358        drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2359        platform_version: &PlatformVersion,
2360    ) -> Result<(Vec<u8>, Vec<Document>), Error> {
2361        self.validate_composite(platform_version)?;
2362        let page_path_query = self.page_path_query(platform_version)?;
2363        let direction = page_path_query.query.query.left_to_right;
2364
2365        // Block commits are seconds apart while an attempt is
2366        // milliseconds, so a bracket collision is rare and two in a row
2367        // vanishingly so; three attempts is generosity, not need.
2368        const MAX_ATTEMPTS: usize = 3;
2369        for _ in 0..MAX_ATTEMPTS {
2370            // An attempt that loses the race is discarded whole, its
2371            // operations included: the caller is billed for one run.
2372            let operations_before = drive_operations.len();
2373            let root_before = drive
2374                .grove
2375                .root_hash(None, &platform_version.drive.grove_version)
2376                .unwrap()?;
2377
2378            check_composite_instance_limits(&page_path_query, platform_version)?;
2379            let page_documents = Self::materialize_component(
2380                self,
2381                &page_path_query,
2382                drive,
2383                None,
2384                drive_operations,
2385                platform_version,
2386            )?;
2387            // Sub-queries that feed later bindings are materialized in
2388            // order; everything else is only derived.
2389            let mut derived: Vec<DerivedValues> = Vec::with_capacity(self.sub_queries.len());
2390            let mut materialized: Vec<Option<Vec<Document>>> = vec![None; self.sub_queries.len()];
2391            for (index, sub_query) in self.sub_queries.iter().enumerate() {
2392                let values = self.derive_for(sub_query, &page_documents, |source| {
2393                    materialized
2394                        .get(source)
2395                        .and_then(|documents| documents.as_deref())
2396                })?;
2397                if self.is_binding_source(index) {
2398                    let result = self.materialize_sub_result(
2399                        sub_query,
2400                        &values,
2401                        direction,
2402                        drive,
2403                        None,
2404                        drive_operations,
2405                        platform_version,
2406                    )?;
2407                    materialized[index] = Some(result.documents().to_vec());
2408                }
2409                derived.push(values);
2410            }
2411
2412            let (page_path_query, sub_path_queries, removal_path_queries) =
2413                self.proof_path_queries(&derived, platform_version)?;
2414            // The same builder the verifier re-merges with, so the proof
2415            // covers exactly the query the verifier reconstructs.
2416            let merged_query = Self::merged_path_query(
2417                &page_path_query,
2418                &sub_path_queries,
2419                &removal_path_queries,
2420                platform_version,
2421            )?;
2422            check_composite_instance_limits(&merged_query, platform_version)?;
2423            let proof = drive
2424                .grove
2425                .prove_query(&merged_query, None, &platform_version.drive.grove_version)
2426                .unwrap()?;
2427
2428            let root_after = drive
2429                .grove
2430                .root_hash(None, &platform_version.drive.grove_version)
2431                .unwrap()?;
2432            if root_before != root_after {
2433                drive_operations.truncate(operations_before);
2434                continue;
2435            }
2436            return Ok((proof, page_documents));
2437        }
2438        Err(Error::Drive(DriveError::NotSupported(
2439            "composite proof generation raced a block commit on every attempt; transient — \
2440             retry the request",
2441        )))
2442    }
2443}
2444
2445#[cfg(test)]
2446mod tests {
2447    use super::*;
2448    use grovedb::{Query, SizedQuery, SubqueryBranch};
2449
2450    /// The nested-documents guard asks whether one component's walk
2451    /// passes through another's base path to deeper rows: it must follow
2452    /// the query's own base path, its selected keys and its subqueries,
2453    /// and stop at a key the query does not select.
2454    #[test]
2455    fn should_follow_a_walk_through_selected_keys_and_subqueries_only() {
2456        let pv = PlatformVersion::latest();
2457        let key = |name: &str| name.as_bytes().to_vec();
2458        let mut body = Query::new();
2459        body.insert_key(key("x"));
2460        body.default_subquery_branch = SubqueryBranch {
2461            subquery_path: Some(vec![key("c")]),
2462            subquery: Some(Box::new(Query::new_range_full())),
2463        };
2464        let shallower = PathQuery::new(vec![key("a"), key("b")], SizedQuery::new(body, None, None));
2465        let descends = |path: &[&str]| {
2466            DriveDocumentQuery::path_query_descends_through(
2467                &shallower,
2468                &path.iter().map(|segment| key(segment)).collect::<Vec<_>>(),
2469                pv,
2470            )
2471            .expect("the walk resolves")
2472        };
2473        assert!(
2474            descends(&["a", "b", "x", "c"]),
2475            "selected key, then its subquery path"
2476        );
2477        assert!(!descends(&["a", "b", "x", "d"]), "not the subquery path");
2478        assert!(!descends(&["a", "b", "y", "c"]), "an unselected key");
2479        assert!(!descends(&["a", "z"]), "off the base path");
2480        assert!(
2481            !descends(&["a", "b", "x", "c", "k"]),
2482            "past the walk's leaves"
2483        );
2484    }
2485}