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