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