Skip to main content

drive/query/
mod.rs

1#[cfg(feature = "server")]
2use dpp::data_contract::document_type::DocumentPropertyType;
3use dpp::data_contract::document_type::{
4    IndexBucketing, IntegerRangeTransform, TimeRangeTransform,
5};
6use std::sync::Arc;
7
8#[cfg(any(feature = "server", feature = "verify"))]
9pub use {
10    // Chained-query building blocks: the result shape and the join-value
11    // cap. The join itself is a by-id join sub-query in
12    // [`DriveDocumentQuery::sub_queries`].
13    chained_document_query::{
14        ChainedDocumentsResult, ChainedOuterDocuments, MAX_CHAINED_JOIN_VALUES,
15    },
16    // Composite-query building blocks: the sub-query shapes carried by
17    // [`DriveDocumentQuery::sub_queries`] and the assembled result. The
18    // verifier needs them all to rebuild and route the merged proof.
19    composite_document_query::{
20        BindingSource, CompositeDocumentsResult, DriveSubQuery, SubQueryBinding, SubQueryKind,
21        SubQueryResult, MAX_BOUND_VALUES, MAX_SUB_QUERIES,
22    },
23    conditions::{ValueClause, WhereClause, WhereOperator},
24    // Average-query verifier-shareable types — same split as sum:
25    // `AverageEntry` is the per-key `(count, sum)` pair the verifier
26    // returns; `AverageMode` is the SQL-shape input the verifier needs
27    // to rebuild the path query.
28    drive_document_average_query::{AverageEntry, AverageMode},
29    // `CountMode` is the SQL-shape contract (Aggregate /
30    // GroupByIn / GroupByRange / GroupByCompound) the prover
31    // dispatches on; the verifier needs the same enum to route
32    // proof verification to the matching primitive
33    // (`DocumentCountMode`). Available under either `server`
34    // (executor input) or `verify` (proof-decode input).
35    drive_document_count_query::{
36        CountMode, DocumentCountMode, DriveDocumentCountQuery, SplitCountEntry,
37    },
38    // Having-range verifier-shareable types — same split as ranked:
39    // `DocumentHavingMode` + `AxisRangeBounds` to re-run the same
40    // versioned request validation (and bounds translation) the prover
41    // ran, `DriveDocumentHavingQuery` to rebuild the proved grove path
42    // and secondary query. Entries reuse the ranked `RankedEntry` shape.
43    drive_document_having_query::{
44        AxisRangeBounds, DocumentHavingMode, DriveDocumentHavingQuery, MAX_HAVING_LIMIT,
45    },
46    // Ranked-query verifier-shareable types. The verifier needs the
47    // whole set: `DocumentRankedMode` + `RankedPaginationInputs` to
48    // re-run the same versioned request validation the prover ran,
49    // `DriveDocumentRankedQuery` to rebuild the proved grove path, and
50    // `RankedEntry` / `RankedEntryValue` as the verified result shape.
51    drive_document_ranked_query::{
52        DocumentRankedMode, DriveDocumentRankedQuery, RankedAxis, RankedEntry, RankedEntryValue,
53        RankedPage, RankedPaginationInputs, MAX_RANKED_LIMIT, RANKED_AVG_SCALE,
54        RANKED_COUNT_ORDER_KEY,
55    },
56    // Sum-query verifier-shareable types: `SumEntry` is the per-key
57    // entry type the verifier returns, `SumMode` / `DriveDocumentSumQuery`
58    // are shape inputs the verifier needs to rebuild the path query.
59    // Parallels the count-side exports above.
60    drive_document_sum_query::{DriveDocumentSumQuery, SumEntry, SumMode},
61    grovedb::{PathQuery, Query, QueryItem, SizedQuery},
62    having::{
63        HavingAggregate, HavingAggregateFunction, HavingClause, HavingOperator, HavingRightOperand,
64    },
65    ordering::OrderClause,
66    projection::{SelectFunction, SelectProjection},
67    single_document_drive_query::SingleDocumentDriveQuery,
68    single_document_drive_query::SingleDocumentDriveQueryContestedStatus,
69    vote_polls_by_end_date_query::VotePollsByEndDateDriveQuery,
70    vote_query::IdentityBasedVoteDriveQuery,
71};
72
73// `DocumentCountRequest` / `RangeCountOptions` are the
74// server-side executor inputs and stay `server`-only.
75#[cfg(feature = "server")]
76pub use drive_document_count_query::{
77    DocumentCountRequest, DocumentCountResponse, RangeCountOptions, MAX_LIMIT_AS_FAILSAFE,
78};
79
80// `DocumentSumRequest` / `DocumentSumResponse` / range-sum options are
81// the server-side executor inputs and stay `server`-only (parallels
82// the count-side `DocumentCountRequest` etc. above).
83#[cfg(feature = "server")]
84pub use drive_document_sum_query::{
85    DocumentSumRequest, DocumentSumResponse, RangeSumOptions, RangeSumWalkMode,
86};
87
88// `DocumentAverageRequest` / `DocumentAverageResponse` are the
89// server-side executor inputs for the average surface and stay
90// `server`-only (parallels the sum-side server-only exports above).
91#[cfg(feature = "server")]
92pub use drive_document_average_query::{DocumentAverageRequest, DocumentAverageResponse};
93
94// `DocumentRankedRequest` / `DocumentRankedResponse` are the
95// server-side dispatcher ABI for the ranked surface — the types
96// drive-abci's routing layer names. Server-only for the same reason
97// as the count / sum / average request types above.
98#[cfg(feature = "server")]
99pub use drive_document_ranked_query::{DocumentRankedRequest, DocumentRankedResponse};
100
101// `DocumentHavingRequest` / `DocumentHavingResponse` are the
102// server-side dispatcher ABI for the having-range surface — the types
103// drive-abci's routing layer names. Server-only for the same reason as
104// the ranked request types above.
105#[cfg(feature = "server")]
106pub use drive_document_having_query::{DocumentHavingRequest, DocumentHavingResponse};
107// Imports available when either "server" or "verify" features are enabled
108#[cfg(any(feature = "server", feature = "verify"))]
109use {
110    crate::{
111        drive::contract::paths::DataContractPaths,
112        error::{drive::DriveError, query::QuerySyntaxError, Error},
113    },
114    dpp::{
115        data_contract::{
116            accessors::v0::DataContractV0Getters,
117            document_type::{accessors::DocumentTypeV0Getters, methods::DocumentTypeV0Methods},
118            document_type::{DocumentTypeRef, Index},
119            DataContract,
120        },
121        document::{document_methods::DocumentMethodsV0, Document},
122        platform_value::{btreemap_extensions::BTreeValueRemoveFromMapHelper, Value},
123        version::PlatformVersion,
124        ProtocolError,
125    },
126    indexmap::IndexMap,
127    sqlparser::{
128        ast::{self, OrderByExpr, Select, Statement, TableFactor::Table, Value::Number},
129        dialect::MySqlDialect,
130        parser::Parser,
131    },
132    std::{collections::BTreeMap, ops::BitXor},
133};
134
135#[cfg(all(feature = "server", feature = "verify"))]
136use crate::verify::RootHash;
137
138#[cfg(any(feature = "server", feature = "verify"))]
139use crate::drive::document::ranked_index_tree_type::property_name_tree_type_and_ranked_axes_for_level;
140#[cfg(any(feature = "server", feature = "verify"))]
141use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
142#[cfg(feature = "server")]
143use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0;
144#[cfg(any(feature = "server", feature = "verify"))]
145use dpp::document::DocumentV0Getters;
146#[cfg(feature = "server")]
147pub use grovedb::{
148    query_result_type::{QueryResultElements, QueryResultType},
149    Element, Error as GroveError, TransactionArg,
150};
151
152use dpp::document;
153use dpp::prelude::Identifier;
154use dpp::validation::{SimpleValidationResult, ValidationResult};
155#[cfg(feature = "server")]
156use {
157    crate::{drive::Drive, fees::op::LowLevelDriveOperation},
158    dpp::block::block_info::BlockInfo,
159};
160// Crate-local unconditional imports
161use crate::config::DriveConfig;
162// Crate-local unconditional imports
163use crate::util::common::encode::encode_u64;
164#[cfg(feature = "server")]
165use crate::util::grove_operations::QueryType::StatefulQuery;
166#[cfg(feature = "server")]
167use dpp::version::FeatureVersion;
168
169// Module declarations that are conditional on either "server" or "verify" features
170#[cfg(any(feature = "server", feature = "verify"))]
171pub mod canonicalize;
172#[cfg(any(feature = "server", feature = "verify"))]
173pub use canonicalize::validate_and_canonicalize_where_clauses;
174#[cfg(any(feature = "server", feature = "verify"))]
175pub mod conditions;
176#[cfg(any(feature = "server", feature = "verify"))]
177mod defaults;
178#[cfg(any(feature = "server", feature = "verify"))]
179pub mod having;
180mod non_primary_key_path_query;
181#[cfg(any(feature = "server", feature = "verify"))]
182pub mod ordering;
183#[cfg(any(feature = "server", feature = "verify"))]
184pub mod projection;
185#[cfg(any(feature = "server", feature = "verify"))]
186mod single_document_drive_query;
187/// Versioned grouping of raw where clauses into equality / range / in buckets
188pub(crate) mod where_clause_grouping;
189
190// Module declarations exclusively for "server" feature
191#[cfg(feature = "server")]
192mod test_index;
193
194#[cfg(any(feature = "server", feature = "verify"))]
195/// Vote poll vote state query module
196pub mod vote_poll_vote_state_query;
197#[cfg(any(feature = "server", feature = "verify"))]
198/// Vote Query module
199pub mod vote_query;
200
201#[cfg(any(feature = "server", feature = "verify"))]
202/// Vote poll contestant votes query module
203pub mod vote_poll_contestant_votes_query;
204
205#[cfg(any(feature = "server", feature = "verify"))]
206/// Vote polls by end date query
207pub mod vote_polls_by_end_date_query;
208
209#[cfg(any(feature = "server", feature = "verify"))]
210/// Vote polls by document type query
211pub mod vote_polls_by_document_type_query;
212
213/// Function type for looking up a contract by identifier
214///
215/// This function is used to look up a contract by its identifier.
216/// It should be implemented by the caller in order to provide data
217/// contract required for operations like proof verification.
218#[cfg(any(feature = "server", feature = "verify"))]
219pub type ContractLookupFn<'a> =
220    dyn Fn(&Identifier) -> Result<Option<Arc<DataContract>>, Error> + 'a;
221
222/// Creates a [ContractLookupFn] function that returns provided data contract when requested.
223///
224/// # Arguments
225///
226/// * `data_contract` - [Arc<DataContract>](DataContract) to return
227///
228/// # Returns
229///
230/// [ContractLookupFn] that will return the `data_contract`, or `None` if
231/// the requested contract is not the same as the provided one.
232#[cfg(any(feature = "server", feature = "verify"))]
233pub fn contract_lookup_fn_for_contract<'a>(
234    data_contract: Arc<DataContract>,
235) -> Box<ContractLookupFn<'a>> {
236    let func = move |id: &Identifier| -> Result<Option<Arc<DataContract>>, Error> {
237        if data_contract.id().ne(id) {
238            return Ok(None);
239        }
240        Ok(Some(Arc::clone(&data_contract)))
241    };
242    Box::new(func)
243}
244
245/// A query to get the votes given out by an identity
246#[cfg(any(feature = "server", feature = "verify"))]
247pub mod contested_resource_votes_given_by_identity_query;
248/// A query to get contested documents before they have been awarded
249#[cfg(any(feature = "server", feature = "verify"))]
250pub mod drive_contested_document_query;
251
252/// A query to get the block counts of proposers in an epoch
253#[cfg(any(feature = "server", feature = "verify"))]
254pub mod proposer_block_count_query;
255
256/// A query to get the identity's token balance
257#[cfg(any(feature = "server", feature = "verify"))]
258pub mod identity_token_balance_drive_query;
259/// A query to get the identity's token info
260#[cfg(any(feature = "server", feature = "verify"))]
261pub mod identity_token_info_drive_query;
262
263/// Document subscription filtering
264#[cfg(any(feature = "server", feature = "verify"))]
265pub mod filter;
266/// A query to get the token's status
267#[cfg(any(feature = "server", feature = "verify"))]
268pub mod token_status_drive_query;
269
270/// A query to count documents using CountTree elements
271#[cfg(any(feature = "server", feature = "verify"))]
272pub mod drive_document_count_query;
273
274/// A query to sum an integer property across documents using SumTree
275/// elements. Parallels [`drive_document_count_query`] for the sum
276/// surface — see `book/src/drive/document-sum-trees.md` for the
277/// design and `book/src/drive/sum-index-examples.md` for the worked
278/// example contract.
279#[cfg(any(feature = "server", feature = "verify"))]
280pub mod drive_document_sum_query;
281
282/// A query to compute the average of an integer property across
283/// documents using `CountSumTree` / `ProvableCountProvableSumTree`
284/// (PCPS) elements. Averages are NOT computed server-side; the
285/// response carries a `(count, sum)` pair (atomic per group) and the
286/// client divides. See `book/src/drive/average-index-examples.md` for
287/// the worked example contract.
288#[cfg(any(feature = "server", feature = "verify"))]
289pub mod drive_document_average_query;
290
291/// A query to filter an index's groups by a per-group aggregate bound —
292/// "hashtags with more than 100 posts" — served as a value-bounded
293/// range read of the same per-axis secondary Merk the ranked surface
294/// walks (PR #657, PV14). Like ranked, it never opens the value trees,
295/// so a having-range read is `O(log n + k)` with a proof.
296#[cfg(any(feature = "server", feature = "verify"))]
297pub mod drive_document_having_query;
298
299/// A query to rank an index's groups by a per-group aggregate — "top
300/// 5 restaurants by average grade" — reading grovedb's per-axis
301/// secondary Merk of an indexed tree (PR #657, PV14). Unlike the
302/// count / sum / average surfaces this one never opens the value
303/// trees: the ordering is maintained on write, so a ranked read is
304/// `O(log n + k)` with a proof.
305#[cfg(any(feature = "server", feature = "verify"))]
306pub mod drive_document_ranked_query;
307
308/// Document synthesis for indexOnly queries: an indexOnly entry's proved
309/// `(path, key)` position IS the document, and this module is the single
310/// builder both the server's no-proof execution and the proof verifier
311/// call to turn one back into a `Document`.
312#[cfg(any(feature = "server", feature = "verify"))]
313pub(crate) mod index_only_synthesis;
314
315/// Chained document queries — a provable semi-join: an inner indexOnly
316/// [`DriveDocumentQuery`] whose proven `refersTo` values become the outer
317/// query's primary keys (carried as a single by-id join in
318/// [`DriveDocumentQuery::sub_queries`]), proven against one state root.
319/// See the module docs.
320#[cfg(any(feature = "server", feature = "verify"))]
321pub mod chained_document_query;
322
323/// Joins through a `refersTo: moderatedDocument` property: the removal
324/// records a chained or composite join proves beside the documents it joins.
325/// See the module docs.
326#[cfg(any(feature = "server", feature = "verify"))]
327pub mod moderated_join;
328
329/// Composite document queries — a [`DriveDocumentQuery`] page plus
330/// sub-queries derived from its proven results (joins, lookups, counts),
331/// proven as one merged proof against one state root. See the module docs.
332#[cfg(any(feature = "server", feature = "verify"))]
333pub mod composite_document_query;
334
335/// Joint count-and-sum no-prove executor surface — backs the AVG
336/// no-prove path's unified single-walk dispatch. See its module
337/// docstring for the perf / atomicity contract. Server-only because
338/// the surface only fires on the no-prove (server-materialized) path.
339#[cfg(feature = "server")]
340pub mod drive_document_count_and_sum_query;
341
342/// A Query Syntax Validation Result that contains data
343pub type QuerySyntaxValidationResult<TData> = ValidationResult<TData, QuerySyntaxError>;
344
345/// A Query Syntax Validation Result
346pub type QuerySyntaxSimpleValidationResult = SimpleValidationResult<QuerySyntaxError>;
347
348#[cfg(any(feature = "server", feature = "verify"))]
349/// Represents a starting point for a query based on a specific document.
350///
351/// This struct encapsulates all the necessary details to define the starting
352/// conditions for a query, including the document to start from, its type,
353/// associated index property, and whether the document itself should be included
354/// in the query results.
355#[derive(Debug, Clone)]
356pub struct StartAtDocument<'a> {
357    /// The document that serves as the starting point for the query.
358    pub document: Document,
359
360    /// The type of the document, providing metadata about its schema and structure.
361    pub document_type: DocumentTypeRef<'a>,
362
363    /// Indicates whether the starting document itself should be included in the query results.
364    /// - `true`: The document is included in the results.
365    /// - `false`: The document is excluded, and the query starts from the next matching document.
366    pub included: bool,
367}
368
369/// Internal clauses struct
370#[cfg(any(feature = "server", feature = "verify"))]
371#[derive(Clone, Debug, PartialEq, Default)]
372pub struct InternalClauses {
373    /// Primary key in clause
374    pub primary_key_in_clause: Option<WhereClause>,
375    /// Primary key equal clause
376    pub primary_key_equal_clause: Option<WhereClause>,
377    /// In clauses, on distinct non-primary-key fields.
378    ///
379    /// The grammar groups any number of them structurally; whether more
380    /// than one is accepted is a protocol-versioned decision made at
381    /// path-query lowering (protocol version 14 is the first to accept
382    /// multiple in clauses, on consecutive index properties).
383    pub in_clauses: Vec<WhereClause>,
384    /// Range clause.
385    ///
386    /// On an indexOnly document type this may sit on an index's TERMINAL
387    /// (member-key) property, not only on an index prefix property — see
388    /// [`InternalClauses::classify_fields`] for the modeled roles instead
389    /// of assuming property placement.
390    pub range_clause: Option<WhereClause>,
391    /// Equal clause
392    pub equal_clauses: BTreeMap<String, WhereClause>,
393}
394
395/// How one where-clause (or order-by) field relates to a document type's
396/// indexes — classified ONCE against the doctype instead of re-derived by
397/// every consumer. Roles are not exclusive: on the yappr fixture `postId`
398/// is a prefix property of `byHashtagPost`/`byPost` AND the terminal of
399/// `byLiker`.
400#[cfg(any(feature = "server", feature = "verify"))]
401#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
402pub struct ClauseFieldRoles {
403    /// The field is `$id`.
404    pub primary_key: bool,
405    /// The field is a prefix property of at least one index.
406    pub index_property: bool,
407    /// The field is the terminal (member-key property) of at least one
408    /// index — only ever true on indexOnly document types.
409    pub terminal: bool,
410}
411
412#[cfg(any(feature = "server", feature = "verify"))]
413impl ClauseFieldRoles {
414    /// The field appears in no index at all (and is not the primary key)
415    /// — a clause on it can never be served.
416    pub fn unindexed(&self) -> bool {
417        !self.primary_key && !self.index_property && !self.terminal
418    }
419}
420
421/// The outcome of generic index selection
422/// ([`DriveDocumentQuery::select_best_index`]): a match, or the fact that
423/// no index serves the query — carried as a value, not an error, so a
424/// route that may legitimately stand in for a miss (the indexOnly
425/// terminal route) never has to reconstruct that fact from error
426/// variants. Structural failures never appear here; they stay `Err`.
427#[cfg(any(feature = "server", feature = "verify"))]
428pub(crate) enum BestIndexOutcome<'a> {
429    /// An index serves the query.
430    Matched(&'a Index),
431    /// No index matches; carries the error [`DriveDocumentQuery::find_best_index`]
432    /// reports for this query.
433    NoIndexMatches(Error),
434}
435
436impl InternalClauses {
437    /// Every constraint these clauses and `order_by_keys` make, for the
438    /// `skipIfAbsent` admissibility gate
439    /// ([`index_admissible_for_skip_if_absent`]).
440    #[cfg(any(feature = "server", feature = "verify"))]
441    pub fn skip_if_absent_bindings<'a>(
442        &'a self,
443        order_by_keys: &[&'a str],
444    ) -> Vec<SkipIfAbsentBinding<'a>> {
445        self.equal_clauses
446            .values()
447            .chain(self.in_clauses.iter())
448            .chain(self.range_clause.iter())
449            .map(SkipIfAbsentBinding::for_where_clause)
450            .chain(
451                order_by_keys
452                    .iter()
453                    .map(|key| SkipIfAbsentBinding::ordering(key)),
454            )
455            .collect()
456    }
457
458    /// Classify one field's index roles against `document_type`. The
459    /// single derivation site for "is this a prefix property, a terminal,
460    /// or `$id`" — consumers must branch on this instead of assuming a
461    /// clause sits on an index prefix property (on indexOnly types it may
462    /// sit on a terminal).
463    #[cfg(any(feature = "server", feature = "verify"))]
464    pub fn classify_field(document_type: DocumentTypeRef, field: &str) -> ClauseFieldRoles {
465        let mut roles = ClauseFieldRoles {
466            primary_key: field == "$id",
467            ..Default::default()
468        };
469        for index in document_type.indexes().values() {
470            if index
471                .properties
472                .iter()
473                .any(|property| property.name == field)
474            {
475                roles.index_property = true;
476            }
477            if index.terminal_contains(field) {
478                roles.terminal = true;
479            }
480            if roles.index_property && roles.terminal {
481                break;
482            }
483        }
484        roles
485    }
486
487    /// [`Self::classify_field`] over every field these clauses name —
488    /// classification happens once, at the seam between clause extraction
489    /// and routing, instead of being re-derived downstream.
490    #[cfg(any(feature = "server", feature = "verify"))]
491    pub fn classify_fields(
492        &self,
493        document_type: DocumentTypeRef,
494    ) -> BTreeMap<String, ClauseFieldRoles> {
495        let mut classified = BTreeMap::new();
496        let mut add = |field: &str| {
497            classified
498                .entry(field.to_string())
499                .or_insert_with(|| Self::classify_field(document_type, field));
500        };
501        if self.primary_key_equal_clause.is_some() || self.primary_key_in_clause.is_some() {
502            add("$id");
503        }
504        for field in self.equal_clauses.keys() {
505            add(field);
506        }
507        if let Some(range_clause) = &self.range_clause {
508            add(&range_clause.field);
509        }
510        for in_clause in &self.in_clauses {
511            add(&in_clause.field);
512        }
513        classified
514    }
515
516    #[cfg(any(feature = "server", feature = "verify"))]
517    /// Returns true if the clause is a valid format.
518    pub fn verify(&self) -> bool {
519        // There can only be 1 primary key clause, or many other clauses
520        if self
521            .primary_key_in_clause
522            .is_some()
523            .bitxor(self.primary_key_equal_clause.is_some())
524        {
525            // One is set, all rest must be empty
526            !(!self.in_clauses.is_empty()
527                || self.range_clause.is_some()
528                || !self.equal_clauses.is_empty())
529        } else {
530            !(self.primary_key_in_clause.is_some() && self.primary_key_equal_clause.is_some())
531        }
532    }
533
534    #[cfg(any(feature = "server", feature = "verify"))]
535    /// Returns true if the query clause is for primary keys.
536    pub fn is_for_primary_key(&self) -> bool {
537        self.primary_key_in_clause.is_some() || self.primary_key_equal_clause.is_some()
538    }
539
540    #[cfg(any(feature = "server", feature = "verify"))]
541    /// Returns true if self is empty.
542    pub fn is_empty(&self) -> bool {
543        self.in_clauses.is_empty()
544            && self.range_clause.is_none()
545            && self.equal_clauses.is_empty()
546            && self.primary_key_in_clause.is_none()
547            && self.primary_key_equal_clause.is_none()
548    }
549
550    #[cfg(any(feature = "server", feature = "verify"))]
551    /// Extracts the `WhereClause`s and returns them as type `InternalClauses`.
552    pub fn extract_from_clauses(
553        all_where_clauses: Vec<WhereClause>,
554        platform_version: &PlatformVersion,
555    ) -> Result<Self, Error> {
556        let primary_key_equal_clauses_array = all_where_clauses
557            .iter()
558            .filter_map(|where_clause| match where_clause.operator {
559                WhereOperator::Equal => match where_clause.is_identifier() {
560                    true => Some(where_clause.clone()),
561                    false => None,
562                },
563                _ => None,
564            })
565            .collect::<Vec<WhereClause>>();
566
567        let primary_key_in_clauses_array = all_where_clauses
568            .iter()
569            .filter_map(|where_clause| match where_clause.operator {
570                WhereOperator::In => match where_clause.is_identifier() {
571                    true => Some(where_clause.clone()),
572                    false => None,
573                },
574                _ => None,
575            })
576            .collect::<Vec<WhereClause>>();
577
578        let (equal_clauses, range_clause, in_clauses) =
579            WhereClause::group_clauses(&all_where_clauses, platform_version)?;
580
581        let primary_key_equal_clause = match primary_key_equal_clauses_array.len() {
582            0 => Ok(None),
583            1 => Ok(Some(
584                primary_key_equal_clauses_array
585                    .first()
586                    .expect("there must be a value")
587                    .clone(),
588            )),
589            _ => Err(Error::Query(
590                QuerySyntaxError::DuplicateNonGroupableClauseSameField(
591                    "There should only be one equal clause for the primary key",
592                ),
593            )),
594        }?;
595
596        let primary_key_in_clause = match primary_key_in_clauses_array.len() {
597            0 => Ok(None),
598            1 => Ok(Some(
599                primary_key_in_clauses_array
600                    .first()
601                    .expect("there must be a value")
602                    .clone(),
603            )),
604            _ => Err(Error::Query(
605                QuerySyntaxError::DuplicateNonGroupableClauseSameField(
606                    "There should only be one in clause for the primary key",
607                ),
608            )),
609        }?;
610
611        let internal_clauses = InternalClauses {
612            primary_key_equal_clause,
613            primary_key_in_clause,
614            in_clauses,
615            range_clause,
616            equal_clauses,
617        };
618
619        match internal_clauses.verify() {
620            true => Ok(internal_clauses),
621            false => Err(Error::Query(
622                QuerySyntaxError::InvalidWhereClauseComponents("Query has invalid where clauses"),
623            )),
624        }
625    }
626
627    /// Validate this collection of InternalClauses against the document schema
628    #[cfg(any(feature = "server", feature = "verify"))]
629    pub fn validate_against_schema(
630        &self,
631        document_type: DocumentTypeRef,
632    ) -> QuerySyntaxSimpleValidationResult {
633        // Basic composition
634        if !self.verify() {
635            return QuerySyntaxSimpleValidationResult::new_with_error(
636                QuerySyntaxError::InvalidWhereClauseComponents(
637                    "invalid composition of where clauses",
638                ),
639            );
640        }
641
642        // Validate in_clauses against schema
643        for in_clause in &self.in_clauses {
644            // Forbid $id in non-primary-key clauses
645            if in_clause.field == "$id" {
646                return QuerySyntaxSimpleValidationResult::new_with_error(
647                    QuerySyntaxError::InvalidWhereClauseComponents(
648                        "use primary_key_* clauses for $id",
649                    ),
650                );
651            }
652            let result = in_clause.validate_against_schema(document_type);
653            if !result.is_valid() {
654                return result;
655            }
656        }
657
658        // Validate range_clause against schema
659        if let Some(range_clause) = &self.range_clause {
660            // Forbid $id in non-primary-key clauses
661            if range_clause.field == "$id" {
662                return QuerySyntaxSimpleValidationResult::new_with_error(
663                    QuerySyntaxError::InvalidWhereClauseComponents(
664                        "use primary_key_* clauses for $id",
665                    ),
666                );
667            }
668            let result = range_clause.validate_against_schema(document_type);
669            if !result.is_valid() {
670                return result;
671            }
672        }
673
674        // Validate equal_clauses against schema
675        for (field, eq_clause) in &self.equal_clauses {
676            // Forbid $id in non-primary-key clauses
677            if field.as_str() == "$id" {
678                return QuerySyntaxSimpleValidationResult::new_with_error(
679                    QuerySyntaxError::InvalidWhereClauseComponents(
680                        "use primary_key_* clauses for $id",
681                    ),
682                );
683            }
684            let result = eq_clause.validate_against_schema(document_type);
685            if !result.is_valid() {
686                return result;
687            }
688        }
689
690        // Validate primary key clauses typing
691        if let Some(pk_eq) = &self.primary_key_equal_clause {
692            if pk_eq.operator != WhereOperator::Equal
693                || !matches!(pk_eq.value, Value::Identifier(_))
694            {
695                return QuerySyntaxSimpleValidationResult::new_with_error(
696                    QuerySyntaxError::InvalidWhereClauseComponents(
697                        "primary key equality must compare an identifier",
698                    ),
699                );
700            }
701        }
702        if let Some(pk_in) = &self.primary_key_in_clause {
703            if pk_in.operator != WhereOperator::In {
704                return QuerySyntaxSimpleValidationResult::new_with_error(
705                    QuerySyntaxError::InvalidWhereClauseComponents(
706                        "primary key IN must use IN operator",
707                    ),
708                );
709            }
710            // enforce array shape and no duplicates/size
711            let result = pk_in.in_values();
712            if !result.is_valid() {
713                return QuerySyntaxSimpleValidationResult::new_with_errors(result.errors);
714            }
715            if let Value::Array(arr) = &pk_in.value {
716                if !arr.iter().all(|v| matches!(v, Value::Identifier(_))) {
717                    return QuerySyntaxSimpleValidationResult::new_with_error(
718                        QuerySyntaxError::InvalidWhereClauseComponents(
719                            "primary key IN must contain identifiers",
720                        ),
721                    );
722                }
723            } else {
724                return QuerySyntaxSimpleValidationResult::new_with_error(
725                    QuerySyntaxError::InvalidWhereClauseComponents(
726                        "primary key IN must contain an array of identifiers",
727                    ),
728                );
729            }
730        }
731
732        QuerySyntaxSimpleValidationResult::default()
733    }
734}
735
736impl From<InternalClauses> for Vec<WhereClause> {
737    fn from(clauses: InternalClauses) -> Self {
738        let mut result: Self = clauses.equal_clauses.into_values().collect();
739
740        result.extend(clauses.in_clauses);
741        if let Some(clause) = clauses.primary_key_equal_clause {
742            result.push(clause);
743        };
744        if let Some(clause) = clauses.primary_key_in_clause {
745            result.push(clause);
746        };
747        if let Some(clause) = clauses.range_clause {
748            result.push(clause);
749        };
750
751        result
752    }
753}
754
755/// Which window of a `timeRange` grid an `IN_TIME_RANGE` selection resolves
756/// to. Time-range queries are a v1-only feature; the v0 query surface is
757/// unaffected.
758///
759/// The two relative selectors are resolved against an authoritative "now"
760/// (block time on the server, the quorum-signed metadata time on the
761/// verifier); [`Self::ByStart`] names a window absolutely, so its resolution
762/// reads the query alone and needs no clock at all — which is what makes
763/// historic windows addressable.
764#[cfg(any(feature = "server", feature = "verify"))]
765#[derive(Copy, Clone, Debug, PartialEq, Eq)]
766#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
767#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
768pub enum TimeRangeSelector {
769    /// The freshest started range (largest start ≤ now). Covers the latest
770    /// partial slice (0..step of history).
771    Newest,
772    /// The oldest range still active at now. Covers a near-full trailing
773    /// window of ~range of history. Best for "trending over the last window".
774    Oldest,
775    /// The range starting exactly at `start_ms` (a millisecond timestamp).
776    /// Must lie on the grid — `phase + k * step`, the same values
777    /// [`TimeRangeTransform::containing_buckets`] produces and the storage
778    /// keys spell — or resolution rejects it (see
779    /// [`TimeRangeTransform::is_bucket_start`]). A window with no documents,
780    /// including one that has not started yet, is a provable empty answer
781    /// rather than an error.
782    ByStart {
783        /// The selected window's start on the millisecond timeline.
784        start_ms: u64,
785    },
786}
787
788#[cfg(any(feature = "server", feature = "verify"))]
789impl TimeRangeSelector {
790    /// The selector's JSON spelling — the `selector` string of the wasm-sdk
791    /// query surface. The single source of truth for the string form: the
792    /// wasm-sdk JSON parser and every error message quote these spellings.
793    /// (The gRPC wire does not use them: since the typed
794    /// `TimeRangeSelection` operand, the selector rides as a proto enum.)
795    ///
796    /// [`Self::ByStart`] names its *kind* only — the `start_ms` payload
797    /// rides in a separate JSON field, so [`Self::from_string`] cannot
798    /// construct it and parsers of the full shape handle it themselves.
799    pub fn as_str(&self) -> &'static str {
800        match self {
801            TimeRangeSelector::Newest => "newest",
802            TimeRangeSelector::Oldest => "oldest",
803            TimeRangeSelector::ByStart { .. } => "byStart",
804        }
805    }
806
807    /// Parses the spelling of the payload-free selectors. Returns `None`
808    /// for anything else — including `"byStart"`, whose `start_ms` payload
809    /// a bare string cannot carry (see [`Self::as_str`]).
810    pub fn from_string(value: &str) -> Option<Self> {
811        match value {
812            "newest" => Some(TimeRangeSelector::Newest),
813            "oldest" => Some(TimeRangeSelector::Oldest),
814            _ => None,
815        }
816    }
817}
818
819/// A concrete grid specification, matching a contract's `timeRange`
820/// declaration verbatim (`range` / `step` / `phase`, in seconds).
821///
822/// The structured `IN_TIME_RANGE` operand carries one of these when the
823/// queried field is bucketed by more than one grid: the bare selector
824/// (`"newest"` / `"oldest"`) is unambiguous only while exactly one time-range
825/// index exists on the field, so a multi-grid field requires the query to
826/// name the grid it wants.
827#[cfg(any(feature = "server", feature = "verify"))]
828#[derive(Debug, Clone, Copy, PartialEq, Eq)]
829#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
830pub struct TimeRangeGridSpec {
831    /// Window length in seconds, as the contract declares it.
832    pub range_seconds: u64,
833    /// Interval between window starts in seconds, as the contract declares it.
834    pub step_seconds: u64,
835    /// Grid alignment phase in seconds (0 when the contract omits `phase`).
836    pub phase_seconds: u64,
837}
838
839#[cfg(any(feature = "server", feature = "verify"))]
840impl TimeRangeGridSpec {
841    /// Whether this spec names exactly the given transform's grid.
842    pub fn matches(&self, transform: &TimeRangeTransform) -> bool {
843        self.range_seconds == transform.range_seconds
844            && self.step_seconds == transform.step_seconds
845            && self.phase_seconds == transform.phase_seconds
846    }
847}
848
849/// Resolution provenance for one window selection — an `IN_TIME_RANGE` or
850/// an `IN_INTEGER_RANGE` clause: the field the selection named and the
851/// exact grid the resolution used. Recorded by the resolver's caller on the
852/// query (see [`DriveDocumentQuery::resolved_time_ranges`]) and consumed by
853/// the index pickers through [`index_admissible_for_resolved_time_range`],
854/// which pins selection to the index carrying exactly this grid — a field
855/// may be bucketed by several grids, so the field name alone no longer
856/// identifies the index the resolution was computed against.
857///
858/// Named for the first kind of grid; an `integerRange` resolution rides the
859/// same provenance, since both kinds store window starts under a
860/// grid-qualified level and every admissibility rule is shared.
861#[cfg(any(feature = "server", feature = "verify"))]
862#[derive(Debug, Clone, PartialEq)]
863pub struct ResolvedTimeRange {
864    /// The grid the window start was computed from. The grid carries its own
865    /// source field, so the provenance cannot name a field the grid does not
866    /// bucket — [`Self::field`] reads it from here.
867    pub transform: IndexBucketing,
868}
869
870#[cfg(any(feature = "server", feature = "verify"))]
871impl ResolvedTimeRange {
872    /// The bucketed source field the resolved equality is on — always the
873    /// grid's own source.
874    pub fn field(&self) -> &str {
875        self.transform.source()
876    }
877
878    /// The kind of selection the equality was resolved from, as refusals
879    /// name it: `"time-range"` or `"integer-range"`.
880    pub fn kind(&self) -> &'static str {
881        match self.transform {
882            IndexBucketing::Time(_) => "time-range",
883            IndexBucketing::Integer(_) => "integer-range",
884        }
885    }
886
887    /// The where operator that selects a window of this kind.
888    pub fn operator(&self) -> &'static str {
889        match self.transform {
890            IndexBucketing::Time(_) => "IN_TIME_RANGE",
891            IndexBucketing::Integer(_) => "IN_INTEGER_RANGE",
892        }
893    }
894}
895
896/// How a window selection names its kind of grid in refusals.
897#[cfg(any(feature = "server", feature = "verify"))]
898struct SelectionGridKind {
899    /// `"time-range"` or `"integer-range"`.
900    kind: &'static str,
901    /// The where operator the selection rides.
902    operator: &'static str,
903    /// The unit note of the grid's parameters, e.g. `", in seconds,"`.
904    units: &'static str,
905}
906
907/// The grid a window selection on `field` resolves against, shared by the
908/// time and integer resolvers so they pick grids by the same rules: the
909/// distinct grids of `field` (indexes sharing a grid share its storage
910/// level too, so they dedupe), the named one when `spec` names one, else
911/// the only one — a bare selection on a field with several grids is
912/// ambiguous and refused.
913#[cfg(any(feature = "server", feature = "verify"))]
914fn select_selection_grid<'a, T>(
915    document_type: &'a DocumentTypeRef<'_>,
916    field: &str,
917    grid_of: impl Fn(&'a Index) -> Option<&'a T>,
918    spec: Option<(impl Fn(&T) -> bool, String)>,
919    names: SelectionGridKind,
920) -> Result<&'a T, Error>
921where
922    T: BucketSource + PartialEq,
923{
924    let mut grids: Vec<&'a T> = Vec::new();
925    for index in document_type.indexes().values() {
926        if let Some(transform) = grid_of(index).filter(|transform| transform.source() == field) {
927            if !grids.contains(&transform) {
928                grids.push(transform);
929            }
930        }
931    }
932    let SelectionGridKind {
933        kind,
934        operator,
935        units,
936    } = names;
937    let Some(first) = grids.first().copied() else {
938        return Err(Error::Query(
939            QuerySyntaxError::WhereClauseOnNonIndexedProperty(format!(
940                "no {kind} index is defined on field \"{field}\""
941            )),
942        ));
943    };
944    match spec {
945        Some((matches, described)) => grids
946            .into_iter()
947            .find(|transform| matches(transform))
948            .ok_or(Error::Query(QuerySyntaxError::Unsupported(format!(
949                "no {kind} index on \"{field}\" declares the grid {described}"
950            )))),
951        None if grids.len() > 1 => Err(Error::Query(QuerySyntaxError::Unsupported(format!(
952            "field \"{field}\" is bucketed by {} different {kind} grids; the {operator} \
953             selection must name one in its `grid` (range/step/phase{units} as the contract \
954             declares them)",
955            grids.len()
956        )))),
957        None => Ok(first),
958    }
959}
960
961/// The bucketed source field of a grid, for [`select_selection_grid`].
962#[cfg(any(feature = "server", feature = "verify"))]
963trait BucketSource {
964    fn source(&self) -> &str;
965}
966
967#[cfg(any(feature = "server", feature = "verify"))]
968impl BucketSource for TimeRangeTransform {
969    fn source(&self) -> &str {
970        &self.source
971    }
972}
973
974#[cfg(any(feature = "server", feature = "verify"))]
975impl BucketSource for IntegerRangeTransform {
976    fn source(&self) -> &str {
977        &self.source
978    }
979}
980
981/// Resolves a time-range selection on `field` into a concrete equality
982/// [`WhereClause`] on the bucketed source field, using the named grid's
983/// `timeRange` transform and an authoritative `block_time_ms`.
984///
985/// For the relative selectors the server supplies `block_time_ms` from
986/// current block time and the verifier re-derives it from the quorum-signed
987/// response metadata `time_ms`, so both produce the identical concrete
988/// equality query — the existing index/count proofs apply unchanged and the
989/// engine never needs a dedicated time-range operator. A
990/// [`TimeRangeSelector::ByStart`] selection reads its start from the query
991/// itself (validated to lie on the grid) and consults `block_time_ms` only
992/// to reject windows past a declared `ttl`'s horizon — a window that may be
993/// mid-drainage must not serve a truncated answer, and since drainage only
994/// touches expired buckets, every window this resolver admits is complete.
995///
996/// `grid` selects among several time-range indexes on the same field: `None`
997/// is accepted only while exactly one grid buckets the field (the common
998/// case); with two or more grids the caller must name one, and naming a grid
999/// no index declares is an error either way.
1000///
1001/// What comes back is an ordinary equality clause, byte-identical to one a
1002/// client could have written by hand against a raw timestamp, plus the
1003/// [`ResolvedTimeRange`] provenance callers must record on the query (see
1004/// [`DriveDocumentQuery::resolved_time_ranges`]), which
1005/// [`DriveDocumentQuery::find_best_index`] and the aggregate index pickers
1006/// consume through [`index_admissible_for_resolved_time_range`] to pin
1007/// selection to the grid's index — and to keep raw queries off it.
1008#[cfg(any(feature = "server", feature = "verify"))]
1009pub fn resolve_time_range_bucket_clause(
1010    field: &str,
1011    selector: TimeRangeSelector,
1012    grid: Option<TimeRangeGridSpec>,
1013    document_type: DocumentTypeRef,
1014    block_time_ms: u64,
1015) -> Result<(WhereClause, ResolvedTimeRange), Error> {
1016    let transform = select_selection_grid(
1017        &document_type,
1018        field,
1019        |index| index.time_range.as_ref(),
1020        grid.map(|spec| {
1021            (
1022                move |transform: &TimeRangeTransform| spec.matches(transform),
1023                format!(
1024                    "range={}s step={}s phase={}s",
1025                    spec.range_seconds, spec.step_seconds, spec.phase_seconds
1026                ),
1027            )
1028        }),
1029        SelectionGridKind {
1030            kind: "time-range",
1031            operator: "IN_TIME_RANGE",
1032            units: ", in seconds,",
1033        },
1034    )?;
1035
1036    let bucket_start = match selector {
1037        TimeRangeSelector::Newest => transform.newest_active_start(block_time_ms),
1038        TimeRangeSelector::Oldest => transform.oldest_active_start(block_time_ms),
1039        // Absolute selection: the start comes from the query itself, so the
1040        // clock is consulted only for the TTL gate — prover and verifier
1041        // agree by construction (the verifier passes the signed time_ms).
1042        // Only grid membership is checked; an empty (or not-yet-started)
1043        // window is a provable empty answer, not an invalid question.
1044        TimeRangeSelector::ByStart { start_ms } => {
1045            if !transform.is_bucket_start(start_ms) {
1046                return Err(Error::Query(QuerySyntaxError::Unsupported(format!(
1047                    "byStart {} on \"{}\" is not a window start of the grid range={}s \
1048                     step={}s phase={}s: starts are phase + k*step on the millisecond \
1049                     timeline, and an off-grid start is rejected rather than snapped",
1050                    start_ms,
1051                    field,
1052                    transform.range_seconds,
1053                    transform.step_seconds,
1054                    transform.phase_seconds
1055                ))));
1056            }
1057            // TTL gate: an expired window may be mid-drainage, and a
1058            // partially drained window would serve a truncated answer that
1059            // looks authoritative. Drainage only ever touches expired
1060            // buckets (same `bucket_expired` predicate), so everything on
1061            // the queryable side of this gate is complete — and rejecting
1062            // the question is deterministic where "whatever the drain has
1063            // left" is not. The verifier resolves this clause with the
1064            // quorum-signed response `time_ms`, so a node cannot serve an
1065            // expired window's remnants past a verifying client.
1066            if transform.bucket_expired(start_ms, block_time_ms) {
1067                return Err(Error::Query(QuerySyntaxError::Unsupported(format!(
1068                    "byStart {} on \"{}\" is past the ttl horizon ({}s): expired windows \
1069                     drain lazily and may be mid-removal, so they are not queryable — \
1070                     entries under this index live at most `ttl` past their window's start",
1071                    start_ms,
1072                    field,
1073                    transform.ttl_seconds.unwrap_or_default()
1074                ))));
1075            }
1076            Some(start_ms)
1077        }
1078    }
1079    .ok_or(Error::Query(QuerySyntaxError::Unsupported(format!(
1080        "no time range on \"{}\" is active yet: the block time predates the grid's phase \
1081         anchor (only possible within the first step after the epoch)",
1082        field
1083    ))))?;
1084
1085    Ok((
1086        WhereClause {
1087            field: field.to_string(),
1088            operator: WhereOperator::Equal,
1089            value: Value::U64(bucket_start),
1090        },
1091        ResolvedTimeRange {
1092            transform: transform.clone().into(),
1093        },
1094    ))
1095}
1096
1097/// A concrete integer grid, matching a contract's `integerRange`
1098/// declaration verbatim (`range` / `step` / `phase`).
1099///
1100/// An `IN_INTEGER_RANGE` selection carries one when the queried field is
1101/// bucketed by more than one grid; with a single grid it may be omitted.
1102#[cfg(any(feature = "server", feature = "verify"))]
1103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1105pub struct IntegerRangeGridSpec {
1106    /// Window length, as the contract declares it.
1107    pub range: u64,
1108    /// Interval between window starts, as the contract declares it.
1109    pub step: u64,
1110    /// Grid alignment (0 when the contract omits `phase`).
1111    pub phase: u64,
1112}
1113
1114#[cfg(any(feature = "server", feature = "verify"))]
1115impl IntegerRangeGridSpec {
1116    /// Whether this spec names exactly the given transform's grid.
1117    pub fn matches(&self, transform: &IntegerRangeTransform) -> bool {
1118        self.range == transform.range
1119            && self.step == transform.step
1120            && self.phase == transform.phase
1121    }
1122}
1123
1124/// Resolves an `IN_INTEGER_RANGE` selection on `field` — the window of an
1125/// `integerRange` grid starting at `start` — into a concrete equality
1126/// [`WhereClause`] on the bucketed source field, plus the
1127/// [`ResolvedTimeRange`] provenance callers must record on the query (see
1128/// [`DriveDocumentQuery::resolved_time_ranges`]).
1129///
1130/// The integer counterpart of [`resolve_time_range_bucket_clause`]'s
1131/// `byStart`: the window is named absolutely, so the server and the proof
1132/// verifier resolve it from the query alone, with no clock. `start` must be
1133/// an integer that names a window of the grid (a grid start the property's
1134/// type can hold, or the type's minimum for the clamped bottom window — see
1135/// [`IntegerRangeTransform::is_window_start`]); anything else is rejected
1136/// rather than snapped. An empty window is a provable empty answer.
1137///
1138/// `grid` selects among several integer-range indexes on the same field:
1139/// `None` is accepted only while exactly one grid buckets the field.
1140///
1141/// The equality's value is the start as an integer; the query path
1142/// serializes it through the schema exactly like a raw value of the field,
1143/// which is how the walkers stored it.
1144#[cfg(any(feature = "server", feature = "verify"))]
1145pub fn resolve_integer_range_bucket_clause(
1146    field: &str,
1147    start: &Value,
1148    grid: Option<IntegerRangeGridSpec>,
1149    document_type: DocumentTypeRef,
1150) -> Result<(WhereClause, ResolvedTimeRange), Error> {
1151    let transform = select_selection_grid(
1152        &document_type,
1153        field,
1154        |index| index.integer_range.as_ref(),
1155        grid.map(|spec| {
1156            (
1157                move |transform: &IntegerRangeTransform| spec.matches(transform),
1158                format!(
1159                    "range={} step={} phase={}",
1160                    spec.range, spec.step, spec.phase
1161                ),
1162            )
1163        }),
1164        SelectionGridKind {
1165            kind: "integer-range",
1166            operator: "IN_INTEGER_RANGE",
1167            units: "",
1168        },
1169    )?;
1170
1171    let start = start.as_integer::<i128>().ok_or(Error::Query(
1172        QuerySyntaxError::InvalidWhereClauseComponents(
1173            "an IN_INTEGER_RANGE selection's start must be an integer",
1174        ),
1175    ))?;
1176    if !transform.is_window_start(start) {
1177        return Err(Error::Query(QuerySyntaxError::Unsupported(format!(
1178            "{} on \"{}\" is not a window start of the grid range={} step={} phase={}: starts \
1179             are phase + k*step within the property's integer type (or the type's minimum, for \
1180             the clamped bottom window), and an off-grid start is rejected rather than snapped",
1181            start, field, transform.range, transform.step, transform.phase
1182        ))));
1183    }
1184    // `is_window_start` checked the key type holds the start, so it always
1185    // converts; the one conversion the uniqueness probe shares.
1186    let value = transform.key_type.value_of(start).ok_or(Error::Drive(
1187        DriveError::CorruptedCodeExecution("a window start is held by its key type"),
1188    ))?;
1189
1190    Ok((
1191        WhereClause {
1192            field: field.to_string(),
1193            operator: WhereOperator::Equal,
1194            value,
1195        },
1196        ResolvedTimeRange {
1197            transform: transform.clone().into(),
1198        },
1199    ))
1200}
1201
1202/// Whether `index` may serve a query whose equality clauses on
1203/// `resolved_time_ranges` were produced by
1204/// [`resolve_time_range_bucket_clause`] or
1205/// [`resolve_integer_range_bucket_clause`].
1206///
1207/// A bucketed (time- or integer-range) index does not store the source
1208/// field's raw values: under its grid-qualified first level it stores window
1209/// *starts*, and one document is stored once per window that contains its
1210/// value. So a bucketed index, a
1211/// raw index and another grid's bucketed index are never interchangeable, and
1212/// every mismatch is silent — a validly-proven wrong answer rather than an
1213/// error:
1214///
1215/// - A raw query (`resolved_time_ranges` empty) that landed on a bucketed
1216///   index would compare a real timestamp against bucket starts and see
1217///   nothing (or, for range/IN shapes, walk overlapping buckets and count the
1218///   same document up to `overlap_factor` times).
1219/// - A resolved query that landed on a raw index would compare a bucket start
1220///   against real timestamps and see nothing.
1221/// - A resolved query that landed on a *different grid's* index would compare
1222///   one grid's bucket start against another grid's — every 6-hour start is
1223///   also a 3-hour start, so this can silently return the wrong window.
1224///
1225/// Hence the rule: with no resolution only non-bucketed indexes are
1226/// admissible, and with one resolution only an index bucketing exactly that
1227/// field *with exactly that grid* is. Two resolutions can never be served by
1228/// a single index — a transform's source must be its index's first property,
1229/// so one index buckets exactly one field — and are rejected by the caller.
1230#[cfg(any(feature = "server", feature = "verify"))]
1231pub fn index_admissible_for_resolved_time_range(
1232    index: &Index,
1233    resolved_time_ranges: &[ResolvedTimeRange],
1234) -> bool {
1235    match resolved_time_ranges {
1236        [] => !index.is_bucketed(),
1237        // The provenance's grid must equal the candidate's — kind, grid AND
1238        // source field, since the grid carries its own source. The
1239        // provenance cannot name a field its grid does not bucket
1240        // ([`ResolvedTimeRange::field`] is derived from the grid), so a
1241        // fabricated field/grid pair is unrepresentable rather than guarded
1242        // against.
1243        [resolved] => index.is_bucketed_by(&resolved.transform),
1244        _ => false,
1245    }
1246}
1247
1248/// How a query constrains one field, as the `skipIfAbsent` gate reads it
1249/// ([`index_admissible_for_skip_if_absent`]).
1250#[cfg(any(feature = "server", feature = "verify"))]
1251#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1252pub struct SkipIfAbsentBinding<'a> {
1253    /// The field.
1254    pub field: &'a str,
1255    /// Whether the constraint can never match a document missing the field.
1256    /// On a stored type a missing value is indexed under the empty key,
1257    /// which sorts first, so an ordering, a range with no lower bound and a
1258    /// null equality all reach it on an index that does not skip.
1259    pub excludes_missing: bool,
1260}
1261
1262#[cfg(any(feature = "server", feature = "verify"))]
1263impl<'a> SkipIfAbsentBinding<'a> {
1264    /// An order-by on `field`: it reaches documents missing the field on an
1265    /// index that does not skip them.
1266    pub fn ordering(field: &'a str) -> Self {
1267        Self {
1268            field,
1269            excludes_missing: false,
1270        }
1271    }
1272
1273    /// The binding a where clause makes: equality and `in` exclude missing
1274    /// documents unless they name a value that may encode as missing, a
1275    /// range excludes them when its lower bound may not (strict bounds
1276    /// exclude the empty key outright), and `startsWith` when its prefix is
1277    /// not empty.
1278    pub fn for_where_clause(clause: &'a WhereClause) -> Self {
1279        let lower_bound_excludes_missing = |value: &Value| {
1280            value
1281                .as_array()
1282                .and_then(|bounds| bounds.first())
1283                .is_some_and(|lower| !may_encode_as_missing(lower))
1284        };
1285        let excludes_missing = match clause.operator {
1286            WhereOperator::Equal | WhereOperator::GreaterThanOrEquals => {
1287                !may_encode_as_missing(&clause.value)
1288            }
1289            // `in_values` reads every spelling the query layer accepts,
1290            // bytes on a U8 property included.
1291            WhereOperator::In => clause
1292                .in_values()
1293                .data
1294                .is_some_and(|values| values.iter().all(|value| !may_encode_as_missing(value))),
1295            WhereOperator::GreaterThan
1296            | WhereOperator::BetweenExcludeBounds
1297            | WhereOperator::BetweenExcludeLeft => true,
1298            WhereOperator::Between | WhereOperator::BetweenExcludeRight => {
1299                lower_bound_excludes_missing(&clause.value)
1300            }
1301            WhereOperator::StartsWith => clause
1302                .value
1303                .as_text()
1304                .is_some_and(|prefix| !prefix.is_empty()),
1305            WhereOperator::LessThan | WhereOperator::LessThanOrEquals => false,
1306        };
1307        Self {
1308            field: clause.field.as_str(),
1309            excludes_missing,
1310        }
1311    }
1312
1313    /// The bindings of `where_clauses`.
1314    pub fn for_where_clauses(where_clauses: &'a [WhereClause]) -> Vec<Self> {
1315        where_clauses.iter().map(Self::for_where_clause).collect()
1316    }
1317}
1318
1319/// Whether `value` may encode to the empty key a missing value takes on a
1320/// stored index: null, or an empty byte array in any spelling a byteArray
1321/// property accepts (bytes, an array, base64 text). A string's `""` encodes
1322/// apart, but a binding does not know the property's type, so it is read
1323/// the cautious way.
1324#[cfg(any(feature = "server", feature = "verify"))]
1325fn may_encode_as_missing(value: &Value) -> bool {
1326    match value {
1327        Value::Null => true,
1328        Value::Bytes(bytes) => bytes.is_empty(),
1329        Value::Array(values) => values.is_empty(),
1330        Value::Text(text) => text.is_empty(),
1331        _ => false,
1332    }
1333}
1334
1335/// Whether a query constraining fields as `bindings` may be served by
1336/// `index` given its `skipIfAbsent` participation.
1337///
1338/// A `skipIfAbsent` index holds only the documents that carry every property
1339/// of its skip set — it is a SPARSE projection of the document type. An index
1340/// picker must not route a query to it that never binds those properties:
1341/// the generic matcher does not require contiguously bound prefixes (an
1342/// unused property merely counts toward the difference score), and the
1343/// aggregate pickers match a prefix that may stop above a deep skip
1344/// property. Without this gate such a query would silently omit every
1345/// document the index skipped — a result a complete index would have
1346/// included. So every skip property must be bound:
1347///
1348/// - On an indexOnly type (whose indexes carry a `terminal`) any binding
1349///   counts, the order-by included: a missing value has no index
1350///   representation anywhere on such a type, so binding the property is
1351///   asking "among documents carrying it", which is exactly what the index
1352///   holds.
1353/// - On a stored type a missing value is indexed under the empty key by
1354///   every index that does not skip it, so a skip property counts as bound
1355///   only by a constraint that no missing value can meet
1356///   ([`SkipIfAbsentBinding::excludes_missing`]).
1357///
1358/// For every index a contract could declare before skip properties could sit
1359/// below the first position, the skip set is the first property, which the
1360/// contiguous and exact matchers already bind; the gate then changes no
1361/// choice.
1362#[cfg(any(feature = "server", feature = "verify"))]
1363pub fn index_admissible_for_skip_if_absent(
1364    index: &Index,
1365    bindings: &[SkipIfAbsentBinding<'_>],
1366) -> bool {
1367    if !index.skip_if_absent {
1368        return true;
1369    }
1370    // Every protocol version reaches this; `is_index_only`'s counter term is
1371    // inert before protocol version 14, the only one whose grammar admits the
1372    // keyword.
1373    let index_only = index.is_index_only();
1374    index.skip_if_absent_properties.iter().all(|skip_property| {
1375        bindings.iter().any(|binding| {
1376            binding.field == skip_property.as_str() && (index_only || binding.excludes_missing)
1377        })
1378    })
1379}
1380
1381/// Whether `index` may serve a query at all: the one candidate filter every
1382/// index picker applies, so no picker can take one rule and miss the other.
1383/// It joins the time-range provenance rule
1384/// ([`index_admissible_for_resolved_time_range`]) and the `skipIfAbsent`
1385/// rule ([`index_admissible_for_skip_if_absent`]). A picker reading documents
1386/// through the index's entries applies
1387/// [`document_index_admissible_for_query`] instead.
1388#[cfg(any(feature = "server", feature = "verify"))]
1389pub fn index_admissible_for_query(
1390    index: &Index,
1391    resolved_time_ranges: &[ResolvedTimeRange],
1392    skip_bindings: &[SkipIfAbsentBinding<'_>],
1393) -> bool {
1394    index_admissible_for_resolved_time_range(index, resolved_time_ranges)
1395        && index_admissible_for_skip_if_absent(index, skip_bindings)
1396}
1397
1398/// [`index_admissible_for_query`] for a picker reading documents through the
1399/// index's entries: a `summableOffCountIndex` index keeps none (its source
1400/// serves the documents it counts), so it never serves such a read.
1401///
1402/// Unversioned, so every protocol version reaches it (the document pickers of
1403/// every lowering): only protocol version 14's grammar admits a
1404/// `summableOffCountIndex` index, so before it this is exactly
1405/// [`index_admissible_for_query`].
1406#[cfg(any(feature = "server", feature = "verify"))]
1407pub fn document_index_admissible_for_query(
1408    index: &Index,
1409    resolved_time_ranges: &[ResolvedTimeRange],
1410    skip_bindings: &[SkipIfAbsentBinding<'_>],
1411) -> bool {
1412    !index.is_summable_off_count_index()
1413        && index_admissible_for_query(index, resolved_time_ranges, skip_bindings)
1414}
1415
1416/// Whether a point read pinning the first `pin_depth` properties of `index`
1417/// may stop at the deepest pin's value tree: the pins reach the chain
1418/// starting at `chain_position` (the shallowest ranked `at` level, whose
1419/// value trees and every one below aggregate their whole subtree) and leave a
1420/// deeper property free. The count and sum pickers and path builders all ask
1421/// this, each with its own chain position, so a picker never admits what its
1422/// builder refuses.
1423#[cfg(any(feature = "server", feature = "verify"))]
1424pub fn pins_reach_chain(index: &Index, pin_depth: usize, chain_position: Option<usize>) -> bool {
1425    pin_depth >= 1
1426        && pin_depth < index.properties.len()
1427        && chain_position.is_some_and(|min_at| min_at < pin_depth)
1428}
1429
1430/// Whether a range walk over `index` keeps a group whose aggregates are
1431/// zero: whether `index` can hold an empty group. A preallocated index
1432/// creates every group on its path with its referenced document, before any
1433/// entry, so its own groups can be empty, and so can those of any index of
1434/// the type ending at a level a preallocated index passes through, whose
1435/// value trees that preallocation creates too. An `outlivesDelete` index
1436/// passing through that level empties a group as well: its entries stay when
1437/// a delete takes the index's own, so the group's value tree stands at zero.
1438/// The walk's limit counted such a group, so dropping it would shorten a page
1439/// while more groups follow; the count, sum and average walks and the count
1440/// verifier all ask this, so the proof keeps what the unproven read keeps.
1441/// Only meta-schema v3 (protocol version 14) admits `preallocated` and
1442/// `outlivesDelete`, so every earlier index drops its empty groups as before.
1443///
1444/// One empty group it does not cover: a `nullSearchable: false` index keeps
1445/// the value tree of the null key (the empty key) with nothing in it, since
1446/// the walker creates the value tree before the reference step declines a
1447/// document without the value, and no delete prunes it. A range that includes
1448/// the empty key (`<`, `<=`, a `between` from it) still counts that group in
1449/// its limit and leaves it out, so such a page may come back short with more
1450/// groups after it. Keeping it here would change what the shipped count
1451/// verifier returns at every protocol version, so it stays a documented
1452/// exception.
1453#[cfg(any(feature = "server", feature = "verify"))]
1454pub(crate) fn index_keeps_empty_groups(document_type: DocumentTypeRef, index: &Index) -> bool {
1455    document_type.indexes().values().any(|other| {
1456        (other.preallocated || other.outlives_delete)
1457            && other.shares_leading_levels(index, index.properties.len())
1458    })
1459}
1460
1461/// Whether a grovedb error from a raw path query says the queried path does
1462/// not exist yet (no document of the type, no entry under the index), which a
1463/// walk answers with no rows.
1464#[cfg(feature = "server")]
1465pub(crate) fn is_absent_path(error: &Error) -> bool {
1466    matches!(
1467        error,
1468        Error::GroveDB(e) if matches!(
1469            e.as_ref(),
1470            grovedb::Error::PathKeyNotFound(_)
1471                | grovedb::Error::PathNotFound(_)
1472                | grovedb::Error::PathParentLayerNotFound(_)
1473        )
1474    )
1475}
1476
1477/// The refusal of a non-proof read whose indexOnly documents lack a required
1478/// property their index does not hold, so they cannot be serialized: the
1479/// query's doing, refused with a query error (`Unsupported`), not an internal
1480/// one.
1481#[cfg(feature = "server")]
1482pub fn uncovered_required_property_refusal() -> QuerySyntaxError {
1483    QuerySyntaxError::Unsupported(
1484        "this indexOnly query's index does not cover every required property, so the \
1485         documents it synthesizes cannot be serialized into a non-proof response; query \
1486         through an index covering all properties, or use a proved query"
1487            .to_string(),
1488    )
1489}
1490
1491/// The query error a failure serializing a document an indexOnly index
1492/// synthesized answers a non-proof read with, `None` when the failure is no
1493/// query's doing: a missing required property, which
1494/// `DriveDocumentQuery::refuse_an_uncovered_index_only_projection` refuses
1495/// before the read, so this only guards a read that check let through. The
1496/// documents query and drive-abci's chained and composite handlers share it.
1497#[cfg(feature = "server")]
1498pub fn index_only_serialization_refusal(error: &ProtocolError) -> Option<QuerySyntaxError> {
1499    matches!(
1500        error,
1501        ProtocolError::DataContractError(
1502            dpp::data_contract::errors::DataContractError::MissingRequiredKey(_)
1503        )
1504    )
1505    .then(uncovered_required_property_refusal)
1506}
1507
1508/// A range total's aggregate read without a proof (the tree at `path`), or
1509/// zero when that tree does not exist. An equality or `IN` value on the prefix
1510/// that no document holds has no subtree, and grovedb's
1511/// `query_aggregate_count`, `query_aggregate_sum` and
1512/// `query_aggregate_count_and_sum` open the leaf tree directly and fail with
1513/// `InvalidParentLayerPath`. grovedb raises that for any failure reading a
1514/// parent key, a corrupt or unreadable element included, so the zero is
1515/// answered only once a plain read of the path, from its root down, finds a key
1516/// missing; any other error stays an error. From protocol version 14 the
1517/// proof agrees: the range-total verifiers at version 1 accept the prover's
1518/// proof that the key is missing as a zero total
1519/// ([`crate::verify::or_empty_range_total`]), where version 0 refuses it. Not
1520/// where the missing key sits below an `IN` value of a carrier proof, between
1521/// the `IN` and the range: the carrier verifier refuses that proof, though
1522/// zero is the answer.
1523///
1524/// Keyed on `range_total_verifier`, the version of the verifier proving the
1525/// same total, so the unproven answer moves with the proved one: from version
1526/// 1, which only protocol version 14 selects, an absent value reads zero; at
1527/// version 0 the read fails with grovedb's `InvalidParentLayerPath` error, as
1528/// released.
1529#[cfg(feature = "server")]
1530pub(crate) fn aggregate_or_zero_when_absent<T: Default>(
1531    drive: &Drive,
1532    path: &[Vec<u8>],
1533    value: Result<T, grovedb::Error>,
1534    range_total_verifier: FeatureVersion,
1535    transaction: TransactionArg,
1536    platform_version: &PlatformVersion,
1537) -> Result<T, Error> {
1538    match (value, range_total_verifier) {
1539        (value, 0) => value.map_err(|e| Error::GroveDB(Box::new(e))),
1540        (Err(error @ grovedb::Error::InvalidParentLayerPath(_)), 1) => {
1541            for depth in 0..path.len() {
1542                let found = drive
1543                    .grove
1544                    .get_raw_optional(
1545                        path[..depth].into(),
1546                        &path[depth],
1547                        transaction,
1548                        &platform_version.drive.grove_version,
1549                    )
1550                    .unwrap()
1551                    .map_err(|e| Error::GroveDB(Box::new(e)))?;
1552                if found.is_none() {
1553                    return Ok(T::default());
1554                }
1555            }
1556            Err(Error::GroveDB(Box::new(error)))
1557        }
1558        (value, 1) => value.map_err(|e| Error::GroveDB(Box::new(e))),
1559        (_, version) => Err(Error::Drive(DriveError::UnknownVersionMismatch {
1560            method: "aggregate_or_zero_when_absent".to_string(),
1561            known_versions: vec![0, 1],
1562            received: version,
1563        })),
1564    }
1565}
1566
1567/// The prefix-to-last point read's path query, shared by the count and sum
1568/// surfaces so a `count(*)` and a `sum`/`avg` over one tree build one shape:
1569/// the tree of the index's last property read as one element by its level key
1570/// `terminal_level_key` from the last pin's value tree at `base_path`.
1571/// Structurally the `Key([0])` shape with the terminal level key in place of
1572/// the bucket key: nothing is hoisted, so the `In` branches (`in_outer_keys`,
1573/// the pinned `In` values under `base_path`) keep their full trailing pairs
1574/// (`subquery_path_extension`) in `set_subquery_path`.
1575///
1576/// Unversioned, so every protocol version reaches it: the count point read
1577/// over a regular index (protocol version 12 on) builds exactly this, moved
1578/// here unchanged, so an edit for the sum surface changes those proofs too.
1579#[cfg(any(feature = "server", feature = "verify"))]
1580pub(crate) fn prefix_to_last_path_query(
1581    base_path: Vec<Vec<u8>>,
1582    in_outer_keys: Option<Vec<Vec<u8>>>,
1583    subquery_path_extension: Vec<Vec<u8>>,
1584    terminal_level_key: Vec<u8>,
1585) -> PathQuery {
1586    match in_outer_keys {
1587        None => {
1588            let mut query = Query::new();
1589            query.insert_key(terminal_level_key);
1590            PathQuery::new(base_path, SizedQuery::new(query, None, None))
1591        }
1592        Some(keys) => {
1593            let mut outer_query = Query::new();
1594            for key in keys {
1595                outer_query.insert_key(key);
1596            }
1597            let mut subquery = Query::new();
1598            subquery.insert_key(terminal_level_key);
1599            if !subquery_path_extension.is_empty() {
1600                outer_query.set_subquery_path(subquery_path_extension);
1601            }
1602            outer_query.set_subquery(subquery);
1603            PathQuery::new(base_path, SizedQuery::new(outer_query, None, None))
1604        }
1605    }
1606}
1607
1608/// Refuses a range total (one aggregate over a range, or one per carrier
1609/// branch) read through `index` when its path passes through a ranked level:
1610/// a level whose property-name tree Drive lays out as an indexed tree,
1611/// whichever of the type's indexes ranks it (an index may continue below
1612/// another's ranked last property). It walks the type's index structure along
1613/// the index's levels and asks each the resolver the write path uses
1614/// ([`property_name_tree_type_and_ranked_axes_for_level`]), so it reads the
1615/// layout Drive builds. grovedb's range aggregates neither read an indexed
1616/// tree (`AggregateCountOnRange`, `AggregateSumOnRange` and
1617/// `AggregateCountAndSumOnRange` take provable trees only) nor descend
1618/// through one when proving. An unproven read through a ranked ancestor
1619/// would succeed (it opens only the leaf tree), but it is refused as well:
1620/// every count, sum and count-and-sum range-total path builder calls this, so
1621/// the unproven read, the proof and its verification refuse alike rather than
1622/// answering only without a proof. Grouped by the last property, the same
1623/// range reads each value.
1624///
1625/// Unversioned, so every protocol version reaches it: rankings exist only
1626/// from protocol version 14 (meta-schema v3), so no level is an indexed tree
1627/// before and it refuses nothing.
1628#[cfg(any(feature = "server", feature = "verify"))]
1629pub fn refuse_a_range_total_through_a_ranked_index(
1630    document_type: DocumentTypeRef,
1631    index: &Index,
1632) -> Result<(), Error> {
1633    let mut level = document_type.index_structure();
1634    for (position, property) in index.properties.iter().enumerate() {
1635        let Some(sub_level) = level
1636            .sub_levels()
1637            .get(&index.level_key(position, &property.name))
1638        else {
1639            return Ok(());
1640        };
1641        if !property_name_tree_type_and_ranked_axes_for_level(sub_level)?
1642            .1
1643            .is_empty()
1644        {
1645            let last = index
1646                .properties
1647                .last()
1648                .map(|property| property.name.as_str())
1649                .unwrap_or_default();
1650            return Err(Error::Query(QuerySyntaxError::Unsupported(format!(
1651                "a range total over the index `{}` is not available: its path passes through a \
1652                 ranked level, and a range total is read only through unranked trees; group by \
1653                 `{last}` to read each value in the range",
1654                index.name
1655            ))));
1656        }
1657        level = sub_level;
1658    }
1659    Ok(())
1660}
1661
1662/// Rejects a query whose resolution provenance and clause shapes disagree:
1663/// every field in `resolved_time_ranges` must appear in the where
1664/// clauses as exactly one `Equal` clause — the only shape
1665/// [`resolve_time_range_bucket_clause`] produces.
1666///
1667/// A range or `In` clause on a resolved field means the caller attached
1668/// provenance to a clause the resolver never built. Executors that fan a
1669/// clause out per value (the per-`In`-value count/sum paths rewrite each `In`
1670/// value into an equality) would then present raw client values to the index
1671/// pickers as if they were resolved bucket starts, and the pickers would
1672/// admit the bucketed index for them. The wire path can never produce the
1673/// mismatch — provenance is not parseable from the wire, and the abci handler
1674/// pushes the resolved equality itself — so this guards direct API callers,
1675/// and it runs identically under `server` and `verify`.
1676#[cfg(any(feature = "server", feature = "verify"))]
1677pub fn validate_resolved_time_range_clause_shapes(
1678    where_clauses: &[WhereClause],
1679    resolved_time_ranges: &[ResolvedTimeRange],
1680) -> Result<(), Error> {
1681    for field in resolved_time_ranges.iter().map(|resolved| resolved.field()) {
1682        let mut equalities = 0usize;
1683        for clause in where_clauses.iter().filter(|c| c.field == field) {
1684            if clause.operator == WhereOperator::Equal {
1685                equalities += 1;
1686            } else {
1687                return Err(Error::Query(
1688                    QuerySyntaxError::InvalidWhereClauseComponents(
1689                        "a field resolved from a window selection (IN_TIME_RANGE or \
1690                         IN_INTEGER_RANGE) may only carry the single equality its resolution \
1691                         produced, not a range or In clause",
1692                    ),
1693                ));
1694            }
1695        }
1696        if equalities != 1 {
1697            return Err(Error::Query(
1698                QuerySyntaxError::InvalidWhereClauseComponents(
1699                    "a field resolved from a window selection (IN_TIME_RANGE or \
1700                     IN_INTEGER_RANGE) must carry exactly one equality clause — the one its \
1701                     resolution produced",
1702                ),
1703            ));
1704        }
1705    }
1706    Ok(())
1707}
1708
1709#[cfg(any(feature = "server", feature = "verify"))]
1710/// Drive query struct
1711#[derive(Debug, PartialEq, Clone)]
1712pub struct DriveDocumentQuery<'a> {
1713    ///DataContract
1714    pub contract: &'a DataContract,
1715    /// Document type
1716    pub document_type: DocumentTypeRef<'a>,
1717    /// Internal clauses
1718    pub internal_clauses: InternalClauses,
1719    /// Offset
1720    pub offset: Option<u16>,
1721    /// Limit
1722    pub limit: Option<u16>,
1723    /// Order by
1724    pub order_by: IndexMap<String, OrderClause>,
1725    /// Start at document id
1726    pub start_at: Option<[u8; 32]>,
1727    /// Start at included
1728    pub start_at_included: bool,
1729    /// Block time
1730    pub block_time_ms: Option<u64>,
1731    /// The fields whose equality clause in `internal_clauses` was produced by
1732    /// `IN_TIME_RANGE` resolution — i.e. by
1733    /// [`resolve_time_range_bucket_clause`], on the server from committed
1734    /// block time and in the verifier from the quorum-signed response metadata
1735    /// time.
1736    ///
1737    /// Never parsed from the wire: every `from_cbor` / `from_value` /
1738    /// `from_typed_clauses` entry point leaves this empty, so a client cannot
1739    /// claim resolution it did not go through. It is what
1740    /// [`Self::find_best_index`] uses to pin index selection to the index that
1741    /// buckets the field (see [`index_admissible_for_resolved_time_range`]),
1742    /// which is required because the resolved clause is an ordinary equality
1743    /// and cannot be told apart from a raw-timestamp lookup once built.
1744    ///
1745    /// Empty for every raw query.
1746    pub resolved_time_ranges: Vec<ResolvedTimeRange>,
1747    /// The composite sub-queries: queries whose `IN` clauses are derived
1748    /// from this query's proven results (by-id joins, indexed lookups,
1749    /// counts — see the [`composite_document_query`] module docs), listed
1750    /// in binding order (a sub-query may only bind an earlier one) and
1751    /// answered together with this query as ONE merged grovedb proof.
1752    ///
1753    /// Empty for an ordinary documents query, which is what every plain
1754    /// entry point requires: a query carrying sub-queries is served by
1755    /// `Drive::query_composite_documents` /
1756    /// `query_composite_documents_with_proof` and verified by
1757    /// `verify_composite_documents_proof`, and the plain
1758    /// query/proof/verify surfaces refuse it rather than silently prove
1759    /// the page alone.
1760    ///
1761    /// Never parsed from the wire: every `from_cbor` / `from_value` /
1762    /// `from_typed_clauses` entry point leaves this empty; composite
1763    /// requests are built programmatically (see
1764    /// [`Self::with_sub_queries`]).
1765    pub sub_queries: Vec<DriveSubQuery<'a>>,
1766}
1767
1768impl<'a> DriveDocumentQuery<'a> {
1769    /// Gets a document by their primary key
1770    #[cfg(any(feature = "server", feature = "verify"))]
1771    pub fn new_primary_key_single_item_query(
1772        contract: &'a DataContract,
1773        document_type: DocumentTypeRef<'a>,
1774        id: Identifier,
1775    ) -> Self {
1776        DriveDocumentQuery {
1777            contract,
1778            document_type,
1779            internal_clauses: InternalClauses {
1780                primary_key_in_clause: None,
1781                primary_key_equal_clause: Some(WhereClause {
1782                    field: document::property_names::ID.to_string(),
1783                    operator: WhereOperator::Equal,
1784                    value: Value::Identifier(id.to_buffer()),
1785                }),
1786                in_clauses: Vec::new(),
1787                range_clause: None,
1788                equal_clauses: Default::default(),
1789            },
1790            offset: None,
1791            limit: None,
1792            order_by: Default::default(),
1793            start_at: None,
1794            start_at_included: false,
1795            block_time_ms: None,
1796            resolved_time_ranges: vec![],
1797            sub_queries: vec![],
1798        }
1799    }
1800
1801    #[cfg(feature = "server")]
1802    /// Returns any item
1803    pub fn any_item_query(contract: &'a DataContract, document_type: DocumentTypeRef<'a>) -> Self {
1804        DriveDocumentQuery {
1805            contract,
1806            document_type,
1807            internal_clauses: Default::default(),
1808            offset: None,
1809            limit: Some(1),
1810            order_by: Default::default(),
1811            start_at: None,
1812            start_at_included: true,
1813            block_time_ms: None,
1814            resolved_time_ranges: vec![],
1815            sub_queries: vec![],
1816        }
1817    }
1818
1819    #[cfg(feature = "server")]
1820    /// Returns all items
1821    pub fn all_items_query(
1822        contract: &'a DataContract,
1823        document_type: DocumentTypeRef<'a>,
1824        limit: Option<u16>,
1825    ) -> Self {
1826        DriveDocumentQuery {
1827            contract,
1828            document_type,
1829            internal_clauses: Default::default(),
1830            offset: None,
1831            limit,
1832            order_by: Default::default(),
1833            start_at: None,
1834            start_at_included: true,
1835            block_time_ms: None,
1836            resolved_time_ranges: vec![],
1837            sub_queries: vec![],
1838        }
1839    }
1840
1841    #[cfg(any(feature = "server", feature = "verify"))]
1842    /// Extends this query into a composite one: `self` becomes the page
1843    /// and `sub_queries` are derived from its proven results — see
1844    /// [`Self::sub_queries`] and the [`composite_document_query`] module
1845    /// docs.
1846    pub fn with_sub_queries(mut self, sub_queries: Vec<DriveSubQuery<'a>>) -> Self {
1847        self.sub_queries = sub_queries;
1848        self
1849    }
1850
1851    #[cfg(any(feature = "server", feature = "verify"))]
1852    /// Appends a by-id join sub-query: `source_property`'s values, read
1853    /// off this query's proven documents, become the `$id`s of
1854    /// `document_type` documents fetched from the same contract. The
1855    /// property must carry a `refersTo: permanentDocument` declaration
1856    /// targeting `document_type`, so every derived id resolves.
1857    ///
1858    /// This is the one shape the chained surface
1859    /// (`Drive::query_chained_documents`,
1860    /// `verify_chained_documents_proof`) requires exactly one of, and one
1861    /// of the composite sub-query shapes. A cross-contract by-id join
1862    /// (composite only) is built by pushing a [`DriveSubQuery`] with the
1863    /// target contract instead.
1864    pub fn with_by_id_join(
1865        mut self,
1866        source_property: impl Into<String>,
1867        document_type: DocumentTypeRef<'a>,
1868    ) -> Self {
1869        self.sub_queries.push(DriveSubQuery {
1870            contract: self.contract,
1871            document_type,
1872            kind: SubQueryKind::Documents,
1873            where_clauses: vec![],
1874            order_by: vec![],
1875            limit: None,
1876            binding: Some(SubQueryBinding {
1877                source: BindingSource::Page,
1878                source_property: source_property.into(),
1879                field: document::property_names::ID.to_string(),
1880            }),
1881        });
1882        self
1883    }
1884
1885    #[cfg(any(feature = "server", feature = "verify"))]
1886    /// Refuses a query carrying composite sub-queries on a plain
1887    /// (page-only) surface, which would otherwise silently ignore them —
1888    /// on the verify side that would mean reporting the composition
1889    /// verified when only the page was.
1890    pub(crate) fn ensure_no_sub_queries(&self, surface: &str) -> Result<(), Error> {
1891        if self.sub_queries.is_empty() {
1892            return Ok(());
1893        }
1894        Err(Error::Query(QuerySyntaxError::Unsupported(format!(
1895            "this query carries {} sub-queries, which {} would silently ignore; execute and \
1896             verify it on the composite surface (query_composite_documents / \
1897             verify_composite_documents_proof) or, for a single by-id join, the chained one \
1898             (query_chained_documents / verify_chained_documents_proof)",
1899            self.sub_queries.len(),
1900            surface,
1901        ))))
1902    }
1903
1904    #[cfg(any(feature = "server", feature = "verify"))]
1905    /// Returns true if the query clause if for primary keys.
1906    pub fn is_for_primary_key(&self) -> bool {
1907        self.internal_clauses.is_for_primary_key()
1908            || (self.internal_clauses.is_empty()
1909                && (self.order_by.is_empty()
1910                    || (self.order_by.len() == 1
1911                        && self
1912                            .order_by
1913                            .keys()
1914                            .collect::<Vec<&String>>()
1915                            .first()
1916                            .unwrap()
1917                            .as_str()
1918                            == "$id")))
1919    }
1920
1921    #[cfg(feature = "cbor_query")]
1922    /// Converts a query CBOR to a `DriveQuery`.
1923    pub fn from_cbor(
1924        query_cbor: &[u8],
1925        contract: &'a DataContract,
1926        document_type: DocumentTypeRef<'a>,
1927        config: &DriveConfig,
1928        platform_version: &PlatformVersion,
1929    ) -> Result<Self, Error> {
1930        let query_document_value: Value = ciborium::de::from_reader(query_cbor).map_err(|_| {
1931            Error::Query(QuerySyntaxError::DeserializationError(
1932                "unable to decode query from cbor".to_string(),
1933            ))
1934        })?;
1935        Self::from_value(
1936            query_document_value,
1937            contract,
1938            document_type,
1939            config,
1940            platform_version,
1941        )
1942    }
1943
1944    #[cfg(any(feature = "server", feature = "verify"))]
1945    /// Converts a query Value to a `DriveQuery`.
1946    pub fn from_value(
1947        query_value: Value,
1948        contract: &'a DataContract,
1949        document_type: DocumentTypeRef<'a>,
1950        config: &DriveConfig,
1951        platform_version: &PlatformVersion,
1952    ) -> Result<Self, Error> {
1953        let query_document: BTreeMap<String, Value> = query_value.into_btree_string_map()?;
1954        Self::from_btree_map_value(
1955            query_document,
1956            contract,
1957            document_type,
1958            config,
1959            platform_version,
1960        )
1961    }
1962
1963    #[cfg(any(feature = "server", feature = "verify"))]
1964    /// Converts a query Value to a `DriveQuery`.
1965    pub fn from_btree_map_value(
1966        mut query_document: BTreeMap<String, Value>,
1967        contract: &'a DataContract,
1968        document_type: DocumentTypeRef<'a>,
1969        config: &DriveConfig,
1970        platform_version: &PlatformVersion,
1971    ) -> Result<Self, Error> {
1972        if let Some(contract_id) = query_document
1973            .remove_optional_identifier("contract_id")
1974            .map_err(|e| Error::Protocol(Box::new(ProtocolError::ValueError(e))))?
1975        {
1976            if contract.id() != contract_id {
1977                return Err(ProtocolError::IdentifierError(format!(
1978                    "data contract id mismatch, expected: {}, got: {}",
1979                    contract.id(),
1980                    contract_id
1981                ))
1982                .into());
1983            };
1984        }
1985
1986        if let Some(document_type_name) = query_document
1987            .remove_optional_string("document_type_name")
1988            .map_err(|e| Error::Protocol(Box::new(ProtocolError::ValueError(e))))?
1989        {
1990            if document_type.name() != &document_type_name {
1991                return Err(ProtocolError::IdentifierError(format!(
1992                    "document type name mismatch, expected: {}, got: {}",
1993                    document_type.name(),
1994                    document_type_name
1995                ))
1996                .into());
1997            }
1998        }
1999
2000        let maybe_limit: Option<u16> = query_document
2001            .remove_optional_integer("limit")
2002            .map_err(|e| Error::Protocol(Box::new(ProtocolError::ValueError(e))))?;
2003
2004        let limit = maybe_limit
2005            .map_or(Some(config.default_query_limit), |limit_value| {
2006                if limit_value == 0 || limit_value > config.default_query_limit {
2007                    None
2008                } else {
2009                    Some(limit_value)
2010                }
2011            })
2012            .ok_or(Error::Query(QuerySyntaxError::InvalidLimit(format!(
2013                "limit greater than max limit {}",
2014                config.max_query_limit
2015            ))))?;
2016
2017        let offset: Option<u16> = query_document
2018            .remove_optional_integer("offset")
2019            .map_err(|e| Error::Protocol(Box::new(ProtocolError::ValueError(e))))?;
2020
2021        let block_time_ms: Option<u64> = query_document
2022            .remove_optional_integer("blockTime")
2023            .map_err(|e| Error::Protocol(Box::new(ProtocolError::ValueError(e))))?;
2024
2025        let all_where_clauses: Vec<WhereClause> =
2026            query_document
2027                .remove("where")
2028                .map_or(Ok(vec![]), |id_cbor| {
2029                    if let Value::Array(clauses) = id_cbor {
2030                        clauses
2031                            .iter()
2032                            .map(|where_clause| {
2033                                if let Value::Array(clauses_components) = where_clause {
2034                                    WhereClause::from_components(clauses_components)
2035                                } else {
2036                                    Err(Error::Query(QuerySyntaxError::InvalidFormatWhereClause(
2037                                        "where clause must be an array".to_string(),
2038                                    )))
2039                                }
2040                            })
2041                            .collect::<Result<Vec<WhereClause>, Error>>()
2042                    } else {
2043                        Err(Error::Query(QuerySyntaxError::InvalidFormatWhereClause(
2044                            "where clause must be an array".to_string(),
2045                        )))
2046                    }
2047                })?;
2048
2049        let internal_clauses =
2050            InternalClauses::extract_from_clauses(all_where_clauses, platform_version)?;
2051
2052        let start_at_option = query_document.remove("startAt");
2053        let start_after_option = query_document.remove("startAfter");
2054        if start_after_option.is_some() && start_at_option.is_some() {
2055            return Err(Error::Query(QuerySyntaxError::DuplicateStartConditions(
2056                "only one of startAt or startAfter should be provided",
2057            )));
2058        }
2059
2060        let mut start_at_included = true;
2061
2062        let mut start_option: Option<Value> = None;
2063
2064        if start_after_option.is_some() {
2065            start_option = start_after_option;
2066            start_at_included = false;
2067        } else if start_at_option.is_some() {
2068            start_option = start_at_option;
2069            start_at_included = true;
2070        }
2071
2072        let start_at: Option<[u8; 32]> = start_option
2073            .map(|v| {
2074                v.into_identifier()
2075                    .map_err(|e| Error::Protocol(Box::new(ProtocolError::ValueError(e))))
2076                    .map(|identifier| identifier.into_buffer())
2077            })
2078            .transpose()?;
2079
2080        let order_by: IndexMap<String, OrderClause> =
2081            query_document
2082                .remove("orderBy")
2083                .map_or(Ok(IndexMap::new()), |id_cbor| {
2084                    if let Value::Array(clauses) = id_cbor {
2085                        clauses
2086                            .into_iter()
2087                            .filter_map(|order_clause| {
2088                                if let Value::Array(clauses_components) = order_clause {
2089                                    let order_clause =
2090                                        OrderClause::from_components(&clauses_components)
2091                                            .map_err(Error::from);
2092                                    match order_clause {
2093                                        Ok(order_clause) => {
2094                                            Some(Ok((order_clause.field.clone(), order_clause)))
2095                                        }
2096                                        Err(err) => Some(Err(err)),
2097                                    }
2098                                } else {
2099                                    None
2100                                }
2101                            })
2102                            .collect::<Result<IndexMap<String, OrderClause>, Error>>()
2103                    } else {
2104                        Err(Error::Query(QuerySyntaxError::InvalidOrderByProperties(
2105                            "order clauses must be an array",
2106                        )))
2107                    }
2108                })?;
2109
2110        if !query_document.is_empty() {
2111            return Err(Error::Query(QuerySyntaxError::Unsupported(format!(
2112                "unsupported syntax in where clause: {:?}",
2113                query_document
2114            ))));
2115        }
2116
2117        Ok(DriveDocumentQuery {
2118            contract,
2119            document_type,
2120            internal_clauses,
2121            limit: Some(limit),
2122            offset,
2123            order_by,
2124            start_at,
2125            start_at_included,
2126            block_time_ms,
2127            resolved_time_ranges: vec![],
2128            sub_queries: vec![],
2129        })
2130    }
2131
2132    #[cfg(any(feature = "server", feature = "verify"))]
2133    /// Converts a query Value to a `DriveQuery`.
2134    #[allow(clippy::too_many_arguments)]
2135    pub fn from_decomposed_values(
2136        where_clause: Value,
2137        order_by: Option<Value>,
2138        maybe_limit: Option<u16>,
2139        start_at: Option<[u8; 32]>,
2140        start_at_included: bool,
2141        block_time_ms: Option<u64>,
2142        contract: &'a DataContract,
2143        document_type: DocumentTypeRef<'a>,
2144        config: &DriveConfig,
2145        platform_version: &PlatformVersion,
2146    ) -> Result<Self, Error> {
2147        let all_where_clauses: Vec<WhereClause> = match where_clause {
2148            Value::Null => Ok(vec![]),
2149            Value::Array(clauses) => clauses
2150                .iter()
2151                .map(|where_clause| {
2152                    if let Value::Array(clauses_components) = where_clause {
2153                        WhereClause::from_components(clauses_components)
2154                    } else {
2155                        Err(Error::Query(QuerySyntaxError::InvalidFormatWhereClause(
2156                            "where clause must be an array".to_string(),
2157                        )))
2158                    }
2159                })
2160                .collect::<Result<Vec<WhereClause>, Error>>(),
2161            _ => Err(Error::Query(QuerySyntaxError::InvalidFormatWhereClause(
2162                "where clause must be an array".to_string(),
2163            ))),
2164        }?;
2165
2166        // Malformed `order_by` payloads reject the request — the
2167        // pre-existing `filter_map(... .ok())` here silently dropped
2168        // bad clauses (or the whole field for non-array shapes),
2169        // which could mutate result ordering and (on the prove
2170        // path) proof bytes without telling the caller. Tighten the
2171        // contract: every clause must parse, and the top-level
2172        // shape must be `Value::Null` or `Value::Array`.
2173        let order_by_clauses: Vec<OrderClause> = match order_by {
2174            None | Some(Value::Null) => Vec::new(),
2175            Some(Value::Array(clauses)) => clauses
2176                .iter()
2177                .map(|order_clause| match order_clause {
2178                    Value::Array(components) => {
2179                        OrderClause::from_components(components).map_err(|_| {
2180                            Error::Query(QuerySyntaxError::InvalidOrderByProperties(
2181                                "invalid order_by clause components",
2182                            ))
2183                        })
2184                    }
2185                    _ => Err(Error::Query(QuerySyntaxError::InvalidOrderByProperties(
2186                        "order_by clause must be an array",
2187                    ))),
2188                })
2189                .collect::<Result<Vec<_>, _>>()?,
2190            Some(_) => {
2191                return Err(Error::Query(QuerySyntaxError::InvalidOrderByProperties(
2192                    "order_by must be an array",
2193                )));
2194            }
2195        };
2196
2197        Self::from_typed_clauses(
2198            all_where_clauses,
2199            order_by_clauses,
2200            maybe_limit,
2201            start_at,
2202            start_at_included,
2203            block_time_ms,
2204            contract,
2205            document_type,
2206            config,
2207            platform_version,
2208        )
2209    }
2210
2211    /// Build a `DriveDocumentQuery` from already-structured where /
2212    /// order_by clauses. This is the typed-input twin of
2213    /// [`Self::from_decomposed_values`] — same downstream shape, just
2214    /// without the `Value::Array(...)` parse step.
2215    ///
2216    /// Used by the v1 `getDocuments` ABCI handler whose wire format
2217    /// carries `repeated WhereClause` / `repeated OrderClause`
2218    /// natively (no CBOR envelope). The v0 path keeps using
2219    /// `from_decomposed_values` so its CBOR-decoded inputs flow
2220    /// through the existing `WhereClause::from_components` parser
2221    /// for shape validation; the typed path expects that validation
2222    /// (or the equivalent proto→drive conversion) to have run
2223    /// upstream.
2224    ///
2225    /// Limit semantics mirror `from_decomposed_values`:
2226    /// `maybe_limit = None` or `Some(0)` falls back to
2227    /// `config.default_query_limit`; `Some(N)` with `N >
2228    /// config.default_query_limit` is rejected as
2229    /// `QuerySyntaxError::InvalidLimit`.
2230    #[cfg(any(feature = "server", feature = "verify"))]
2231    #[allow(clippy::too_many_arguments)]
2232    pub fn from_typed_clauses(
2233        where_clauses: Vec<WhereClause>,
2234        order_by_clauses: Vec<OrderClause>,
2235        maybe_limit: Option<u16>,
2236        start_at: Option<[u8; 32]>,
2237        start_at_included: bool,
2238        block_time_ms: Option<u64>,
2239        contract: &'a DataContract,
2240        document_type: DocumentTypeRef<'a>,
2241        config: &DriveConfig,
2242        platform_version: &PlatformVersion,
2243    ) -> Result<Self, Error> {
2244        let limit = maybe_limit
2245            .map_or(Some(config.default_query_limit), |limit_value| {
2246                if limit_value == 0 || limit_value > config.default_query_limit {
2247                    None
2248                } else {
2249                    Some(limit_value)
2250                }
2251            })
2252            .ok_or(Error::Query(QuerySyntaxError::InvalidLimit(format!(
2253                "limit greater than max limit {}",
2254                config.max_query_limit
2255            ))))?;
2256
2257        let internal_clauses =
2258            InternalClauses::extract_from_clauses(where_clauses, platform_version)?;
2259
2260        let order_by: IndexMap<String, OrderClause> = order_by_clauses
2261            .into_iter()
2262            .map(|c| (c.field.clone(), c))
2263            .collect();
2264
2265        Ok(DriveDocumentQuery {
2266            contract,
2267            document_type,
2268            internal_clauses,
2269            offset: None,
2270            limit: Some(limit),
2271            order_by,
2272            start_at,
2273            start_at_included,
2274            block_time_ms,
2275            resolved_time_ranges: vec![],
2276            sub_queries: vec![],
2277        })
2278    }
2279
2280    #[cfg(any(feature = "server", feature = "verify"))]
2281    /// Converts a SQL expression to a `DriveQuery`.
2282    pub fn from_sql_expr(
2283        sql_string: &str,
2284        contract: &'a DataContract,
2285        config: Option<&DriveConfig>,
2286        platform_version: &PlatformVersion,
2287    ) -> Result<Self, Error> {
2288        let dialect: MySqlDialect = MySqlDialect {};
2289        let statements: Vec<Statement> = Parser::parse_sql(&dialect, sql_string)
2290            .map_err(|e| Error::Query(QuerySyntaxError::SQLParsingError(e)))?;
2291
2292        // Should ideally iterate over each statement
2293        let first_statement =
2294            statements
2295                .first()
2296                .ok_or(Error::Query(QuerySyntaxError::InvalidSQL(
2297                    "Issue parsing sql getting first statement".to_string(),
2298                )))?;
2299
2300        let query: &ast::Query = match first_statement {
2301            ast::Statement::Query(query_struct) => Some(query_struct),
2302            _ => None,
2303        }
2304        .ok_or(Error::Query(QuerySyntaxError::InvalidSQL(
2305            "Issue parsing sql: not a query".to_string(),
2306        )))?;
2307
2308        let max_limit = config
2309            .map(|config| config.max_query_limit)
2310            .unwrap_or(DriveConfig::default().max_query_limit);
2311
2312        let limit: u16 = if let Some(limit_expr) = &query.limit {
2313            match limit_expr {
2314                ast::Expr::Value(Number(num_string, _)) => {
2315                    let cast_num_string: &String = num_string;
2316                    let user_limit = cast_num_string.parse::<u16>().map_err(|e| {
2317                        Error::Query(QuerySyntaxError::InvalidLimit(format!(
2318                            "limit could not be parsed {}",
2319                            e
2320                        )))
2321                    })?;
2322                    if user_limit > max_limit {
2323                        return Err(Error::Query(QuerySyntaxError::InvalidLimit(format!(
2324                            "limit {} greater than max limit {}",
2325                            user_limit, max_limit
2326                        ))));
2327                    }
2328                    user_limit
2329                }
2330                result => {
2331                    return Err(Error::Query(QuerySyntaxError::InvalidLimit(format!(
2332                        "expression not a limit {}",
2333                        result
2334                    ))));
2335                }
2336            }
2337        } else {
2338            config
2339                .map(|config| config.default_query_limit)
2340                .unwrap_or(DriveConfig::default().default_query_limit)
2341        };
2342
2343        let order_by: IndexMap<String, OrderClause> = query
2344            .order_by
2345            .iter()
2346            .map(|order_exp: &OrderByExpr| {
2347                let ascending = order_exp.asc.is_none() || order_exp.asc.unwrap();
2348                let field = order_exp.expr.to_string();
2349                (field.clone(), OrderClause { field, ascending })
2350            })
2351            .collect::<IndexMap<String, OrderClause>>();
2352
2353        // Grab the select section of the query
2354        let select: &Select = match &*query.body {
2355            ast::SetExpr::Select(select) => Some(select),
2356            _ => None,
2357        }
2358        .ok_or(Error::Query(QuerySyntaxError::InvalidSQL(
2359            "Issue parsing sql: Not a select".to_string(),
2360        )))?;
2361
2362        // Get the document type from the 'from' section
2363        let document_type_name = match &select
2364            .from
2365            .first()
2366            .ok_or(Error::Query(QuerySyntaxError::InvalidSQL(
2367                "Invalid query: missing from section".to_string(),
2368            )))?
2369            .relation
2370        {
2371            Table { name, .. } => name.0.first().as_ref().map(|identifier| &identifier.value),
2372            _ => None,
2373        }
2374        .ok_or(Error::Query(QuerySyntaxError::InvalidSQL(
2375            "Issue parsing sql: invalid from value".to_string(),
2376        )))?;
2377
2378        let document_type =
2379            contract
2380                .document_types()
2381                .get(document_type_name)
2382                .ok_or(Error::Query(QuerySyntaxError::DocumentTypeNotFound(
2383                    "document type not found in contract",
2384                )))?;
2385
2386        // Restrictions
2387        // only binary where clauses are supported
2388        // i.e. [<fieldname>, <operator>, <value>]
2389        // [and] is used to separate where clauses
2390        // currently where clauses are either binary operations or list descriptions (in clauses)
2391        // hence once [and] is encountered [left] and [right] must be only one of the above
2392        // i.e other where clauses
2393        // e.g. firstname = wisdom and lastname = ogwu
2394        // if op is not [and] then [left] or [right] must not be a binary operation or list description
2395        let mut all_where_clauses: Vec<WhereClause> = Vec::new();
2396        let selection_tree = select.selection.as_ref();
2397
2398        // Where clauses are optional
2399        if let Some(selection_tree) = selection_tree {
2400            WhereClause::build_where_clauses_from_operations(
2401                selection_tree,
2402                document_type,
2403                &mut all_where_clauses,
2404            )?;
2405        }
2406
2407        let internal_clauses =
2408            InternalClauses::extract_from_clauses(all_where_clauses, platform_version)?;
2409
2410        let start_at_option = None; //todo
2411        let start_after_option = None; //todo
2412        let mut start_at_included = true;
2413        let mut start_option: Option<Value> = None;
2414
2415        if start_after_option.is_some() {
2416            start_option = start_after_option;
2417            start_at_included = false;
2418        } else if start_at_option.is_some() {
2419            start_option = start_at_option;
2420            start_at_included = true;
2421        }
2422
2423        let start_at: Option<[u8; 32]> = start_option
2424            .map(|v| {
2425                v.into_identifier()
2426                    .map_err(|e| Error::Protocol(Box::new(ProtocolError::ValueError(e))))
2427                    .map(|identifier| identifier.into_buffer())
2428            })
2429            .transpose()?;
2430
2431        Ok(DriveDocumentQuery {
2432            contract,
2433            document_type: document_type.as_ref(),
2434            internal_clauses,
2435            offset: None,
2436            limit: Some(limit),
2437            order_by,
2438            start_at,
2439            start_at_included,
2440            block_time_ms: None,
2441            resolved_time_ranges: vec![],
2442            sub_queries: vec![],
2443        })
2444    }
2445
2446    /// Serialize drive query to CBOR format.
2447    ///
2448    /// FIXME: The data contract is only referred as ID, and document type as its name.
2449    /// This can change in the future to include full data contract and document type.
2450    #[cfg(feature = "cbor_query")]
2451    pub fn to_cbor(&self) -> Result<Vec<u8>, Error> {
2452        let data: BTreeMap<String, Value> = self.into();
2453        let cbor: BTreeMap<String, ciborium::Value> = Value::convert_to_cbor_map(data)?;
2454        let mut output = Vec::new();
2455
2456        ciborium::ser::into_writer(&cbor, &mut output)
2457            .map_err(|e| ProtocolError::PlatformSerializationError(e.to_string()))?;
2458        Ok(output)
2459    }
2460
2461    #[cfg(any(feature = "server", feature = "verify"))]
2462    /// Operations to construct a path query.
2463    pub fn start_at_document_path_and_key(&self, starts_at: &[u8; 32]) -> (Vec<Vec<u8>>, Vec<u8>) {
2464        if self.document_type.documents_keep_history() {
2465            let document_holding_path = self.contract.documents_with_history_primary_key_path(
2466                self.document_type.name().as_str(),
2467                starts_at,
2468            );
2469            (
2470                document_holding_path
2471                    .into_iter()
2472                    .map(|key| key.to_vec())
2473                    .collect::<Vec<_>>(),
2474                vec![0],
2475            )
2476        } else {
2477            let document_holding_path = self
2478                .contract
2479                .documents_primary_key_path(self.document_type.name().as_str());
2480            (
2481                document_holding_path
2482                    .into_iter()
2483                    .map(|key| key.to_vec())
2484                    .collect::<Vec<_>>(),
2485                starts_at.to_vec(),
2486            )
2487        }
2488    }
2489
2490    #[cfg(any(feature = "server", feature = "verify"))]
2491    /// Versioned preflight over the non-primary-key `In` clause shape.
2492    ///
2493    /// Runs before any cursor storage lookup or proof processing so the
2494    /// rejection precedence matches each protocol version's contract: v0
2495    /// rejects more than one `In` clause with `MultipleInClauses` before a
2496    /// `startAt`/`startAfter` document is ever fetched (matching the
2497    /// pre-protocol-version-14 parse-time rejection), and v1 rejects the
2498    /// unsupported multi-`In` + cursor combination with `Unsupported`
2499    /// before spending state or proof work on the cursor. The lowering
2500    /// keeps equivalent guards for callers that reach it directly.
2501    pub fn validate_in_clause_shape(
2502        &self,
2503        platform_version: &PlatformVersion,
2504    ) -> Result<(), Error> {
2505        match platform_version
2506            .drive
2507            .methods
2508            .document
2509            .query
2510            .non_primary_key_path_query
2511        {
2512            0 => {
2513                if self.internal_clauses.in_clauses.len() > 1 {
2514                    return Err(Error::Query(QuerySyntaxError::MultipleInClauses(
2515                        "There should only be one in clause",
2516                    )));
2517                }
2518                Ok(())
2519            }
2520            1 => {
2521                if self.internal_clauses.in_clauses.len() > 1 && self.start_at.is_some() {
2522                    return Err(Error::Query(QuerySyntaxError::Unsupported(
2523                        "startAt/startAfter is not supported with multiple in clauses".to_string(),
2524                    )));
2525                }
2526                Ok(())
2527            }
2528            version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
2529                method: "DriveDocumentQuery::validate_in_clause_shape".to_string(),
2530                known_versions: vec![0, 1],
2531                received: version,
2532            })),
2533        }
2534    }
2535
2536    #[cfg(feature = "server")]
2537    /// Operations to construct a path query.
2538    pub fn construct_path_query_operations(
2539        &self,
2540        drive: &Drive,
2541        include_start_at_for_proof: bool,
2542        transaction: TransactionArg,
2543        drive_operations: &mut Vec<LowLevelDriveOperation>,
2544        platform_version: &PlatformVersion,
2545    ) -> Result<PathQuery, Error> {
2546        self.validate_in_clause_shape(platform_version)?;
2547        // indexOnly documents have no primary-key tree: nothing is ever
2548        // addressed by document id, so a by-id query has no tree to land on.
2549        {
2550            use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
2551            if self.document_type.index_only()
2552                && self.is_for_primary_key()
2553                && !self.index_only_flat_scan_applies()
2554            {
2555                return Err(Error::Query(QuerySyntaxError::Unsupported(
2556                    "indexOnly documents cannot be fetched by id: there is no primary-key \
2557                     tree; query through one of the type's indexes"
2558                        .to_string(),
2559                )));
2560            }
2561            if self.document_type.index_only() && self.start_at.is_some() {
2562                return Err(Error::Query(QuerySyntaxError::Unsupported(
2563                    "startAt/startAfter cursors cannot address an indexOnly position (the \
2564                     synthesized document id is a one-way hash of it); paginate with a \
2565                     range clause on the terminal property instead — equality clauses on \
2566                     the index's properties, `terminal > <last seen value>` ordered by the \
2567                     terminal, and a limit"
2568                        .to_string(),
2569                )));
2570            }
2571        }
2572        let drive_version = &platform_version.drive;
2573        // First we should get the overall document_type_path
2574        let document_type_path = self
2575            .contract
2576            .document_type_path(self.document_type.name().as_str())
2577            .into_iter()
2578            .map(|a| a.to_vec())
2579            .collect::<Vec<Vec<u8>>>();
2580
2581        // indexOnly terminal-clause route: a clause on an index's terminal
2582        // lowers onto the entry level's member keys when the generic
2583        // matcher cannot serve the query. Shared with the verifier-side
2584        // constructor below so prover and verifier build the same query.
2585        {
2586            use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
2587            if self.document_type.index_only() {
2588                if let Some(path_query) =
2589                    self.index_only_route(&document_type_path, platform_version)?
2590                {
2591                    return Ok(path_query);
2592                }
2593            }
2594        }
2595
2596        let cursor_included = self.start_at_included || self.pads_cursor_page(platform_version);
2597        let (starts_at_document, start_at_path_query) = match &self.start_at {
2598            None => Ok((None, None)),
2599            Some(starts_at) => {
2600                // First if we have a startAt or startsAfter we must get the element
2601                // from the backing store
2602
2603                let (start_at_document_path, start_at_document_key) =
2604                    self.start_at_document_path_and_key(starts_at);
2605                let start_at_document = drive
2606                    .grove_get(
2607                        start_at_document_path.as_slice().into(),
2608                        &start_at_document_key,
2609                        StatefulQuery,
2610                        transaction,
2611                        drive_operations,
2612                        drive_version,
2613                    )
2614                    .map_err(|e| match e {
2615                        Error::GroveDB(e)
2616                            if matches!(
2617                                e.as_ref(),
2618                                GroveError::PathKeyNotFound(_)
2619                                    | GroveError::PathNotFound(_)
2620                                    | GroveError::PathParentLayerNotFound(_)
2621                            ) =>
2622                        {
2623                            let error_message = if self.start_at_included {
2624                                "startAt document not found"
2625                            } else {
2626                                "startAfter document not found"
2627                            };
2628
2629                            Error::Query(QuerySyntaxError::StartDocumentNotFound(error_message))
2630                        }
2631                        _ => e,
2632                    })?
2633                    .ok_or(Error::Drive(DriveError::CorruptedCodeExecution(
2634                        "expected a value",
2635                    )))?;
2636
2637                let path_query =
2638                    PathQuery::new_single_key(start_at_document_path, start_at_document_key);
2639
2640                if let Element::Item(item, _) = start_at_document {
2641                    let document = Document::from_bytes(
2642                        item.as_slice(),
2643                        self.document_type,
2644                        platform_version,
2645                    )?;
2646                    Ok((Some((document, cursor_included)), Some(path_query)))
2647                } else {
2648                    Err(Error::Drive(DriveError::CorruptedDocumentPath(
2649                        "Holding paths should only have items",
2650                    )))
2651                }
2652            }
2653        }?;
2654        let mut main_path_query = if self.is_for_primary_key() {
2655            self.get_primary_key_path_query(
2656                document_type_path,
2657                starts_at_document,
2658                platform_version,
2659            )
2660        } else {
2661            self.get_non_primary_key_path_query(
2662                document_type_path,
2663                starts_at_document,
2664                platform_version,
2665            )
2666        }?;
2667        self.pad_cursor_page_limit(&mut main_path_query, platform_version)?;
2668        if !include_start_at_for_proof {
2669            return Ok(main_path_query);
2670        }
2671
2672        if let Some(mut start_at_path_query) = start_at_path_query {
2673            // The cursor query selects exactly one key, so its walk
2674            // direction carries no meaning — but grovedb's merge (V4+)
2675            // requires every input to agree on direction, so align it to
2676            // the main query's `orderBy` direction so a descending page
2677            // merges at all.
2678            start_at_path_query.query.query.left_to_right =
2679                main_path_query.query.query.left_to_right;
2680            let limit = main_path_query.query.limit.take();
2681            let mut merged = PathQuery::merge(
2682                vec![&start_at_path_query, &main_path_query],
2683                &platform_version.drive.grove_version,
2684            )
2685            .map_err(Error::from)?;
2686            // Where the merge lands decides how the two queries combine. An
2687            // index-ordered page lives under its index tree while the cursor
2688            // lookup lives under the primary-key tree, so the merge synthesizes
2689            // a root one level above both with each query in its own branch.
2690            // A `$id`-ordered page addresses the primary-key tree directly: the
2691            // cursor lookup shares that path (or, for a history-keeping type,
2692            // sits one level below it), so the merge point is the page query's
2693            // own root layer and the merged query IS that layer.
2694            let cursor_on_page_layer = merged.path == main_path_query.path;
2695            // The cursor's key on that shared layer: the cursor query's own key
2696            // when both paths coincide, otherwise the path component the cursor
2697            // query descends through.
2698            let cursor_key_on_page_layer: Option<&[u8]> = if !cursor_on_page_layer {
2699                None
2700            } else if let Some(component) = start_at_path_query.path.get(merged.path.len()) {
2701                Some(component.as_slice())
2702            } else {
2703                match start_at_path_query.query.query.items.as_slice() {
2704                    [QueryItem::Key(cursor_key)] => Some(cursor_key.as_slice()),
2705                    _ => None,
2706                }
2707            };
2708            // On a shared layer the cursor row is already one of the page's
2709            // rows whenever the page's own items cover it (an inclusive
2710            // `startAt` on a range, or an `in` list that names the cursor). It
2711            // then needs no reserved slot: reserving one would make the layer
2712            // return `limit + 1` rows matching the page query, which the
2713            // verifier rejects as more data than its limit.
2714            let cursor_row_in_page = cursor_key_on_page_layer.is_some_and(|cursor_key| {
2715                main_path_query
2716                    .query
2717                    .query
2718                    .items
2719                    .iter()
2720                    .any(|item| item.contains(cursor_key))
2721            });
2722            merged.query.limit = limit.map(|a| {
2723                if cursor_row_in_page {
2724                    a
2725                } else {
2726                    a.saturating_add(1)
2727                }
2728            });
2729            if !cursor_on_page_layer {
2730                // A synthesized root must be walked ascending regardless of
2731                // the page's `orderBy` direction: the `limit + 1` above
2732                // reserves one result slot for the cursor document, and the
2733                // prover spends the budget in root traversal order. Ascending,
2734                // the cursor branch (key `[0]`) is visited first and takes its
2735                // reserved slot; descending, the index branch sorts first,
2736                // consumes the whole budget mid-timeline, and the prover then
2737                // omits the cursor subtree's lower layer — an unverifiable
2738                // proof (the verifier extracts the cursor document from the
2739                // proof before rebuilding the main query). Only this
2740                // synthesized root flips: each input's own query lands intact
2741                // inside a subquery branch, keeping in-branch result order.
2742                // The verifier never rebuilds the merged query — it runs the
2743                // cursor and main queries as separate subset queries — so the
2744                // root's direction is not client-visible.
2745                //
2746                // A shared layer has no synthesized root to flip: the merged
2747                // query IS the page query's own layer, which the verifier
2748                // walks in the requested direction, and the cursor row sits at
2749                // the page's boundary, so the requested direction reaches it
2750                // first anyway.
2751                merged.query.query.left_to_right = true;
2752            }
2753            Ok(merged)
2754        } else {
2755            Ok(main_path_query)
2756        }
2757    }
2758
2759    #[cfg(any(feature = "server", feature = "verify"))]
2760    /// Operations to construct a path query.
2761    pub fn construct_path_query(
2762        &self,
2763        starts_at_document: Option<Document>,
2764        platform_version: &PlatformVersion,
2765    ) -> Result<PathQuery, Error> {
2766        self.validate_in_clause_shape(platform_version)?;
2767        // indexOnly documents have no primary-key tree: nothing is ever
2768        // addressed by document id, so a by-id query has no tree to land on.
2769        {
2770            use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
2771            if self.document_type.index_only()
2772                && self.is_for_primary_key()
2773                && !self.index_only_flat_scan_applies()
2774            {
2775                return Err(Error::Query(QuerySyntaxError::Unsupported(
2776                    "indexOnly documents cannot be fetched by id: there is no primary-key \
2777                     tree; query through one of the type's indexes"
2778                        .to_string(),
2779                )));
2780            }
2781            if self.document_type.index_only() && self.start_at.is_some() {
2782                return Err(Error::Query(QuerySyntaxError::Unsupported(
2783                    "startAt/startAfter cursors cannot address an indexOnly position (the \
2784                     synthesized document id is a one-way hash of it); paginate with a \
2785                     range clause on the terminal property instead — equality clauses on \
2786                     the index's properties, `terminal > <last seen value>` ordered by the \
2787                     terminal, and a limit"
2788                        .to_string(),
2789                )));
2790            }
2791        }
2792        // First we should get the overall document_type_path
2793        let document_type_path = self
2794            .contract
2795            .document_type_path(self.document_type.name().as_str())
2796            .into_iter()
2797            .map(|a| a.to_vec())
2798            .collect::<Vec<Vec<u8>>>();
2799
2800        // indexOnly terminal-clause route — the verifier-side mirror of
2801        // the dispatch in `construct_path_query_operations`, so both
2802        // sides build the same query.
2803        {
2804            use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
2805            if self.document_type.index_only() {
2806                if let Some(path_query) =
2807                    self.index_only_route(&document_type_path, platform_version)?
2808                {
2809                    return Ok(path_query);
2810                }
2811            }
2812        }
2813
2814        let cursor_included = self.start_at_included || self.pads_cursor_page(platform_version);
2815        let starts_at_document =
2816            starts_at_document.map(|starts_at_document| (starts_at_document, cursor_included));
2817        let mut path_query = if self.is_for_primary_key() {
2818            self.get_primary_key_path_query(
2819                document_type_path,
2820                starts_at_document,
2821                platform_version,
2822            )
2823        } else {
2824            self.get_non_primary_key_path_query(
2825                document_type_path,
2826                starts_at_document,
2827                platform_version,
2828            )
2829        }?;
2830        self.pad_cursor_page_limit(&mut path_query, platform_version)?;
2831        Ok(path_query)
2832    }
2833
2834    #[cfg(any(feature = "server", feature = "verify"))]
2835    /// Whether a `startAfter` cursor on this query is lowered as `startAt`
2836    /// with one extra result slot, the cursor document then being dropped
2837    /// from the page by [`Self::strip_cursor_from_page`].
2838    ///
2839    /// GroveDB charges a result slot for every visited subtree whose
2840    /// subquery yields nothing, and the prover accounts the same way. A
2841    /// lowering that visits the cursor's branch or index key looking for
2842    /// rows *after* the cursor therefore pays that slot whenever nothing
2843    /// follows, which is nearly every page on unique-valued data: pages
2844    /// came back one row short, and a limit of one came back empty while
2845    /// later rows remained. Keeping the cursor document in the walk keeps
2846    /// every subtree on its path non-empty, and documents sharing its
2847    /// index key continue by document id. The primary-key path has no
2848    /// subtrees to charge and is left alone, as is the released (protocol
2849    /// version 13) lowering.
2850    pub fn pads_cursor_page(&self, platform_version: &PlatformVersion) -> bool {
2851        use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
2852        self.start_at.is_some()
2853            && !self.start_at_included
2854            && !self.is_for_primary_key()
2855            && !self.document_type.index_only()
2856            && platform_version
2857                .drive
2858                .methods
2859                .document
2860                .query
2861                .non_primary_key_path_query
2862                >= 1
2863    }
2864
2865    #[cfg(any(feature = "server", feature = "verify"))]
2866    /// Reserves the cursor document's slot on a padded `startAfter` page
2867    /// (see [`Self::pads_cursor_page`]). A page offset is folded into the
2868    /// fetch and applied by [`Self::strip_cursor_from_page`] once the
2869    /// cursor document is gone: left to GroveDB, the offset would consume
2870    /// the cursor row itself and the page would start one row early.
2871    fn pad_cursor_page_limit(
2872        &self,
2873        path_query: &mut PathQuery,
2874        platform_version: &PlatformVersion,
2875    ) -> Result<(), Error> {
2876        if !self.pads_cursor_page(platform_version) {
2877            return Ok(());
2878        }
2879        let offset = path_query.query.offset.take().unwrap_or(0);
2880        if let Some(limit) = path_query.query.limit {
2881            let padded = limit
2882                .checked_add(1)
2883                .and_then(|limit| limit.checked_add(offset))
2884                .ok_or_else(|| {
2885                    Error::Query(QuerySyntaxError::InvalidLimit(format!(
2886                        "limit {limit} and offset {offset} are too large together with a \
2887                         startAfter cursor"
2888                    )))
2889                })?;
2890            path_query.query.limit = Some(padded);
2891        }
2892        Ok(())
2893    }
2894
2895    #[cfg(any(feature = "server", feature = "verify"))]
2896    /// Drops the cursor document from a page produced by a padded
2897    /// `startAfter` query (see [`Self::pads_cursor_page`]), applies the
2898    /// query's offset, and trims the page back to the query's limit,
2899    /// returning the page with the number of rows the offset skipped.
2900    /// When the cursor document matches the query it is the page's first
2901    /// row, since the lowering excludes everything ordered before it; a
2902    /// cursor outside the query's clauses is simply absent and the trim
2903    /// restores the limit.
2904    pub(crate) fn strip_cursor_from_page(
2905        &self,
2906        mut serialized_documents: Vec<Vec<u8>>,
2907        platform_version: &PlatformVersion,
2908    ) -> Result<(Vec<Vec<u8>>, u16), Error> {
2909        use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0;
2910        use dpp::document::DocumentV0Getters;
2911        if !self.pads_cursor_page(platform_version) {
2912            return Ok((serialized_documents, 0));
2913        }
2914        let Some(start_at) = self.start_at else {
2915            return Ok((serialized_documents, 0));
2916        };
2917        if let Some(first) = serialized_documents.first() {
2918            let document = Document::from_bytes(first, self.document_type, platform_version)?;
2919            if document.id().to_buffer() == start_at {
2920                serialized_documents.remove(0);
2921            }
2922        }
2923        let skipped = (self.offset.unwrap_or(0) as usize).min(serialized_documents.len());
2924        serialized_documents.drain(..skipped);
2925        if let Some(limit) = self.limit {
2926            serialized_documents.truncate(limit as usize);
2927        }
2928        Ok((serialized_documents, skipped as u16))
2929    }
2930
2931    #[cfg(feature = "server")]
2932    /// [`Self::strip_cursor_from_page`] over query result elements.
2933    fn strip_cursor_from_elements(
2934        &self,
2935        mut elements: QueryResultElements,
2936        platform_version: &PlatformVersion,
2937    ) -> Result<(QueryResultElements, u16), Error> {
2938        use dpp::document::DocumentV0Getters;
2939        use grovedb::query_result_type::QueryResultElement;
2940        if !self.pads_cursor_page(platform_version) {
2941            return Ok((elements, 0));
2942        }
2943        let Some(start_at) = self.start_at else {
2944            return Ok((elements, 0));
2945        };
2946        let first_element = match elements.elements.first() {
2947            Some(QueryResultElement::ElementResultItem(element))
2948            | Some(QueryResultElement::KeyElementPairResultItem((_, element)))
2949            | Some(QueryResultElement::PathKeyElementTrioResultItem((_, _, element))) => {
2950                Some(element)
2951            }
2952            None => None,
2953        };
2954        let first_is_cursor = match first_element {
2955            Some(Element::Item(bytes, _)) => {
2956                Document::from_bytes(bytes, self.document_type, platform_version)?
2957                    .id()
2958                    .to_buffer()
2959                    == start_at
2960            }
2961            _ => false,
2962        };
2963        if first_is_cursor {
2964            elements.elements.remove(0);
2965        }
2966        let skipped = (self.offset.unwrap_or(0) as usize).min(elements.elements.len());
2967        elements.elements.drain(..skipped);
2968        if let Some(limit) = self.limit {
2969            elements.elements.truncate(limit as usize);
2970        }
2971        Ok((elements, skipped as u16))
2972    }
2973
2974    #[cfg(any(feature = "server", feature = "verify"))]
2975    /// Returns a path query given a document type path and starting document.
2976    pub fn get_primary_key_path_query(
2977        &self,
2978        document_type_path: Vec<Vec<u8>>,
2979        starts_at_document: Option<(Document, bool)>,
2980        platform_version: &PlatformVersion,
2981    ) -> Result<PathQuery, Error> {
2982        let mut path = document_type_path;
2983
2984        // Add primary key ($id) subtree
2985        path.push(vec![0]);
2986
2987        if let Some(primary_key_equal_clause) = &self.internal_clauses.primary_key_equal_clause {
2988            let mut query = Query::new();
2989            let key = self.document_type.serialize_value_for_key(
2990                "$id",
2991                &primary_key_equal_clause.value,
2992                platform_version,
2993            )?;
2994            query.insert_key(key);
2995
2996            if self.document_type.documents_keep_history() {
2997                // if the documents keep history then we should insert a subquery
2998                if let Some(block_time) = self.block_time_ms {
2999                    let encoded_block_time = encode_u64(block_time);
3000                    let mut sub_query = Query::new_with_direction(false);
3001                    sub_query.insert_range_to_inclusive(..=encoded_block_time);
3002                    query.set_subquery(sub_query);
3003                } else {
3004                    query.set_subquery_key(vec![0]);
3005                }
3006            }
3007
3008            Ok(PathQuery::new(path, SizedQuery::new(query, Some(1), None)))
3009        } else {
3010            // This is for a range
3011            let left_to_right = if self.order_by.keys().len() == 1 {
3012                if self.order_by.keys().next().unwrap() != "$id" {
3013                    return Err(Error::Query(QuerySyntaxError::InvalidOrderByProperties(
3014                        "order by should include $id only",
3015                    )));
3016                }
3017
3018                let order_clause = self.order_by.get("$id").unwrap();
3019
3020                order_clause.ascending
3021            } else {
3022                true
3023            };
3024
3025            let mut query = Query::new_with_direction(left_to_right);
3026            // If there is a start_at_document, we need to get the value that it has for the
3027            // current field.
3028            let starts_at_key_option = match starts_at_document {
3029                None => None,
3030                Some((document, included)) => {
3031                    // if the key doesn't exist then we should ignore the starts at key
3032                    document
3033                        .get_raw_for_document_type(
3034                            "$id",
3035                            self.document_type,
3036                            None,
3037                            platform_version,
3038                        )?
3039                        .map(|raw_value_option| (raw_value_option, included))
3040                }
3041            };
3042
3043            if let Some(primary_key_in_clause) = &self.internal_clauses.primary_key_in_clause {
3044                let in_values = primary_key_in_clause.in_values().into_data_with_error()??;
3045
3046                match starts_at_key_option {
3047                    None => {
3048                        for value in in_values.iter() {
3049                            let key = self.document_type.serialize_value_for_key(
3050                                "$id",
3051                                value,
3052                                platform_version,
3053                            )?;
3054                            query.insert_key(key)
3055                        }
3056                    }
3057                    Some((starts_at_key, included)) => {
3058                        for value in in_values.iter() {
3059                            let key = self.document_type.serialize_value_for_key(
3060                                "$id",
3061                                value,
3062                                platform_version,
3063                            )?;
3064
3065                            if (left_to_right && starts_at_key < key)
3066                                || (!left_to_right && starts_at_key > key)
3067                                || (included && starts_at_key == key)
3068                            {
3069                                query.insert_key(key);
3070                            }
3071                        }
3072                    }
3073                }
3074
3075                if self.document_type.documents_keep_history() {
3076                    // if the documents keep history then we should insert a subquery
3077                    if let Some(_block_time) = self.block_time_ms {
3078                        //todo
3079                        return Err(Error::Query(QuerySyntaxError::Unsupported(
3080                            "Not yet implemented".to_string(),
3081                        )));
3082                        // in order to be able to do this we would need limited subqueries
3083                        // as we only want the first element before the block_time
3084
3085                        // let encoded_block_time = encode_float(block_time)?;
3086                        // let mut sub_query = Query::new_with_direction(false);
3087                        // sub_query.insert_range_to_inclusive(..=encoded_block_time);
3088                        // query.set_subquery(sub_query);
3089                    } else {
3090                        query.set_subquery_key(vec![0]);
3091                    }
3092                }
3093
3094                Ok(PathQuery::new(
3095                    path,
3096                    SizedQuery::new(query, self.limit, self.offset),
3097                ))
3098            } else {
3099                // this is a range on all elements
3100                match starts_at_key_option {
3101                    None => {
3102                        query.insert_all();
3103                    }
3104                    Some((starts_at_key, included)) => match left_to_right {
3105                        true => match included {
3106                            true => query.insert_range_from(starts_at_key..),
3107                            false => query.insert_range_after(starts_at_key..),
3108                        },
3109                        false => match included {
3110                            true => query.insert_range_to_inclusive(..=starts_at_key),
3111                            false => query.insert_range_to(..starts_at_key),
3112                        },
3113                    },
3114                }
3115
3116                if self.document_type.documents_keep_history() {
3117                    // if the documents keep history then we should insert a subquery
3118                    if let Some(_block_time) = self.block_time_ms {
3119                        return Err(Error::Query(QuerySyntaxError::Unsupported(
3120                            "this query is not supported".to_string(),
3121                        )));
3122                        // in order to be able to do this we would need limited subqueries
3123                        // as we only want the first element before the block_time
3124
3125                        // let encoded_block_time = encode_float(block_time)?;
3126                        // let mut sub_query = Query::new_with_direction(false);
3127                        // sub_query.insert_range_to_inclusive(..=encoded_block_time);
3128                        // query.set_subquery(sub_query);
3129                    } else {
3130                        query.set_subquery_key(vec![0]);
3131                    }
3132                }
3133
3134                Ok(PathQuery::new(
3135                    path,
3136                    SizedQuery::new(query, self.limit, self.offset),
3137                ))
3138            }
3139        }
3140    }
3141
3142    #[cfg(any(feature = "server", feature = "verify"))]
3143    /// Finds the best index for the query.
3144    ///
3145    /// Queries with more than one `In` clause use their own selection
3146    /// ([`Self::find_best_index_for_multiple_in_clauses`]); they only
3147    /// reach it through the v1 (protocol version 14+) path-query
3148    /// lowering, since the v0 lowering rejects them first.
3149    ///
3150    /// Selection is restricted to the indexes admissible for this query's
3151    /// [`Self::resolved_time_ranges`]: a query carrying an
3152    /// `IN_TIME_RANGE`-resolved equality may only be served by the index that
3153    /// buckets that field, and a raw query may never be served by a bucketed
3154    /// index. See [`index_admissible_for_resolved_time_range`] for why either
3155    /// mismatch would produce a validly-proven wrong answer. The rule applies
3156    /// on both routes, including the multiple-`In` selection.
3157    pub fn find_best_index(&self, platform_version: &PlatformVersion) -> Result<&Index, Error> {
3158        match self.select_best_index(platform_version)? {
3159            BestIndexOutcome::Matched(index) => Ok(index),
3160            BestIndexOutcome::NoIndexMatches(no_index_error) => Err(no_index_error),
3161        }
3162    }
3163
3164    /// Generic index selection with "no index matches" separated from the
3165    /// structural failures, in the type instead of in error variants:
3166    /// `Err` is a structural problem with the query itself (preflight,
3167    /// resolved-source shape, version dispatch) and always propagates,
3168    /// while `NoIndexMatches` carries the would-be [`Self::find_best_index`]
3169    /// error as a value — a routing fact the indexOnly terminal route is
3170    /// allowed to stand in for. [`Self::find_best_index`] collapses both
3171    /// non-matches back into `Err` for every ordinary caller.
3172    pub(crate) fn select_best_index(
3173        &self,
3174        platform_version: &PlatformVersion,
3175    ) -> Result<BestIndexOutcome<'_>, Error> {
3176        // A transform's source must be its index's first property, so one
3177        // index buckets exactly one field and no index can carry two resolved
3178        // equalities. Serving such a query would need a join across two
3179        // bucketed indexes, which the engine has no shape for. This runs
3180        // before any routing so the multiple-`In` path cannot bypass it.
3181        if self.resolved_time_ranges.len() > 1 {
3182            return Err(Error::Query(QuerySyntaxError::Unsupported(format!(
3183                "at most one window selection (IN_TIME_RANGE or IN_INTEGER_RANGE) is \
3184                 supported per query; this one resolves {:?}, and no single index can bucket \
3185                 more than one field",
3186                self.resolved_time_ranges
3187            ))));
3188        }
3189
3190        // One shared source-shape guard for every selection route — see its
3191        // doc for the contract. Running it before routing keeps the single-
3192        // and multiple-`In` routes rejecting the same shapes.
3193        self.validate_resolved_source_shape()?;
3194
3195        if self.internal_clauses.in_clauses.len() > 1 {
3196            // The multi-`In` machinery keeps its own error surface; its
3197            // shapes are never terminal-routable, so there is nothing to
3198            // classify as a plain miss.
3199            return Ok(BestIndexOutcome::Matched(
3200                self.find_best_index_for_multiple_in_clauses()?.0,
3201            ));
3202        }
3203
3204        let equal_fields = self
3205            .internal_clauses
3206            .equal_clauses
3207            .keys()
3208            .map(|s| s.as_str())
3209            .collect::<Vec<&str>>();
3210        let in_field = self
3211            .internal_clauses
3212            .in_clauses
3213            .first()
3214            .map(|in_clause| in_clause.field.as_str());
3215        let range_field = self
3216            .internal_clauses
3217            .range_clause
3218            .as_ref()
3219            .map(|range_clause| range_clause.field.as_str());
3220        let order_by_keys: Vec<&str> = self.order_by.keys().map(String::as_str).collect();
3221
3222        // Every constraint the query makes, for the skip-index
3223        // admissibility gate — the by-role slices above are what the
3224        // matcher consumes. An all-unused match inside the difference
3225        // budget could otherwise select a sparse index for a query that
3226        // never names its skip properties.
3227        let skip_bindings = self
3228            .internal_clauses
3229            .skip_if_absent_bindings(&order_by_keys);
3230
3231        let Some((index, difference)) = self.document_type.index_for_types_matching(
3232            equal_fields.as_slice(),
3233            range_field,
3234            in_field,
3235            order_by_keys.as_slice(),
3236            // The document form of the gate, reached by every protocol version:
3237            // its counter exclusion is inert before protocol version 14 (see
3238            // `document_index_admissible_for_query`).
3239            |index| {
3240                document_index_admissible_for_query(
3241                    index,
3242                    &self.resolved_time_ranges,
3243                    &skip_bindings,
3244                )
3245            },
3246            platform_version,
3247        )?
3248        else {
3249            return Ok(BestIndexOutcome::NoIndexMatches(
3250                match self.resolved_time_ranges.first() {
3251                    // A window query is only servable by the index that
3252                    // buckets the field with the resolved grid, so "no index"
3253                    // here is a narrower fact than the generic case: some index
3254                    // buckets the field (the clause could not have been resolved
3255                    // otherwise), but none with that grid also covers the rest
3256                    // of the query.
3257                    Some(resolved) => {
3258                        Error::Query(QuerySyntaxError::WhereClauseOnNonIndexedProperty(format!(
3259                            "a {} query on \"{}\" requires an index that buckets it with \
3260                         the resolved grid AND covers the query's other where and order-by \
3261                         fields; valid indexes are: {:?}",
3262                            resolved.kind(),
3263                            resolved.field(),
3264                            self.document_type.indexes()
3265                        )))
3266                    }
3267                    None => {
3268                        // A raw query never binds to a bucketed index; when one
3269                        // exists, say so — the caller may be holding a
3270                        // time-range proof on a surface that cannot supply
3271                        // resolution provenance (e.g. the standalone wasm
3272                        // verifiers), where this refusal is otherwise opaque.
3273                        let has_bucketed_index = self
3274                            .document_type
3275                            .indexes()
3276                            .values()
3277                            .any(|index| index.is_bucketed());
3278                        if has_bucketed_index {
3279                            Error::Query(QuerySyntaxError::WhereClauseOnNonIndexedProperty(
3280                                format!(
3281                            "query must be for valid indexes, valid indexes are: {:?}; note: \
3282                             this document type's bucketed (timeRange / integerRange) indexes \
3283                             only serve IN_TIME_RANGE / IN_INTEGER_RANGE selections carrying \
3284                             their resolution — a raw clause on the bucketed field never binds \
3285                             to them",
3286                            self.document_type.indexes()
3287                        ),
3288                            ))
3289                        } else {
3290                            Error::Query(QuerySyntaxError::WhereClauseOnNonIndexedProperty(
3291                                format!(
3292                                    "query must be for valid indexes, valid indexes are: {:?}",
3293                                    self.document_type.indexes()
3294                                ),
3295                            ))
3296                        }
3297                    }
3298                },
3299            ));
3300        };
3301        if difference > defaults::MAX_INDEX_DIFFERENCE {
3302            return Ok(BestIndexOutcome::NoIndexMatches(Error::Query(
3303                QuerySyntaxError::QueryTooFarFromIndex("query must better match an existing index"),
3304            )));
3305        }
3306
3307        // The residual source-shape contract already ran at the top of this
3308        // function ([`Self::validate_resolved_source_shape`]) — with a
3309        // resolution present, admissibility restricts candidates to the one
3310        // index bucketing exactly the resolved field, so guarding by
3311        // provenance there is equivalent to guarding by the selected index's
3312        // transform here.
3313        Ok(BestIndexOutcome::Matched(index))
3314    }
3315
3316    /// The residual source-shape contract for a query carrying a
3317    /// time-range resolution: the resolved equality must be present on the
3318    /// bucketed source, and the source must not ALSO carry an `In`, a
3319    /// range, or an ordering — those walk overlapping bucket keys and
3320    /// return each document up to `overlap_factor` times with a perfectly
3321    /// valid proof. The `!has_equality_on_source` arm is defensive:
3322    /// resolution always pushes the equality, so reaching it means the
3323    /// provenance and the clauses disagree.
3324    ///
3325    /// Runs identically on the server and in proof verification, and on
3326    /// every selection route: [`Self::find_best_index`] calls it before
3327    /// routing, and [`Self::find_best_index_for_multiple_in_clauses`]
3328    /// calls it itself because the multiple-`In` execution lowering picks
3329    /// its index directly, without going through `find_best_index`.
3330    #[cfg(any(feature = "server", feature = "verify"))]
3331    pub(crate) fn validate_resolved_source_shape(&self) -> Result<(), Error> {
3332        let Some(resolved) = self.resolved_time_ranges.first() else {
3333            return Ok(());
3334        };
3335        let source = resolved.field();
3336        let has_equality_on_source = self.internal_clauses.equal_clauses.contains_key(source);
3337        let range_or_in_on_source = self
3338            .internal_clauses
3339            .range_clause
3340            .as_ref()
3341            .is_some_and(|clause| clause.field == source)
3342            || self
3343                .internal_clauses
3344                .in_clauses
3345                .iter()
3346                .any(|clause| clause.field == source);
3347        if !has_equality_on_source || range_or_in_on_source || self.order_by.contains_key(source) {
3348            return Err(Error::Query(QuerySyntaxError::Unsupported(format!(
3349                "the index on \"{source}\" buckets it into {kind} windows: it can only be \
3350                 queried through a {kind} selection ({operator}, which resolves to an exact \
3351                 window equality), not with ranges, IN, or ordering on that property",
3352                kind = resolved.kind(),
3353                operator = resolved.operator(),
3354            ))));
3355        }
3356        Ok(())
3357    }
3358
3359    #[cfg(any(feature = "server", feature = "verify"))]
3360    /// Returns a `QueryItem` given a start key and query direction.
3361    pub fn query_item_for_starts_at_key(starts_at_key: Vec<u8>, left_to_right: bool) -> QueryItem {
3362        if left_to_right {
3363            QueryItem::RangeAfter(starts_at_key..)
3364        } else {
3365            QueryItem::RangeTo(..starts_at_key)
3366        }
3367    }
3368
3369    #[cfg(any(feature = "server", feature = "verify"))]
3370    /// Returns a path query for non-primary keys given a document type path and starting document.
3371    ///
3372    /// Versioned because the set of accepted query shapes is part of the
3373    /// consensus query contract: v0 rejects more than one `In` clause per
3374    /// query, v1 (protocol version 14) lowers multiple `In` clauses on
3375    /// consecutive index properties to a multi-level key-set path query.
3376    pub fn get_non_primary_key_path_query(
3377        &self,
3378        document_type_path: Vec<Vec<u8>>,
3379        starts_at_document: Option<(Document, bool)>,
3380        platform_version: &PlatformVersion,
3381    ) -> Result<PathQuery, Error> {
3382        let starts_at_document = match starts_at_document {
3383            Some((document, included)) => Some((
3384                self.cursor_with_derived_values(document, platform_version)?,
3385                included,
3386            )),
3387            None => None,
3388        };
3389        match platform_version
3390            .drive
3391            .methods
3392            .document
3393            .query
3394            .non_primary_key_path_query
3395        {
3396            0 => self.get_non_primary_key_path_query_v0(
3397                document_type_path,
3398                starts_at_document,
3399                platform_version,
3400            ),
3401            1 => self.get_non_primary_key_path_query_v1(
3402                document_type_path,
3403                starts_at_document,
3404                platform_version,
3405            ),
3406            version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
3407                method: "DriveDocumentQuery::get_non_primary_key_path_query".to_string(),
3408                known_versions: vec![0, 1],
3409                received: version,
3410            })),
3411        }
3412    }
3413
3414    /// A startAt or startAfter cursor places the page by the values the document it names
3415    /// holds for the index's properties, read off the stored document (by the server, and by
3416    /// a verifier from the proof), which holds no derived index property's value (protocol
3417    /// version 14: read from the document a reference points at). So a query paging with a
3418    /// cursor may only go through an index whose derived properties it fixes with `==`: the
3419    /// cursor document is given those values from the query itself, as the page it places
3420    /// holds them. Any other such query is refused. A cursor of a type without derived index
3421    /// properties passes through unchanged, which is every cursor before protocol version 14.
3422    #[cfg(any(feature = "server", feature = "verify"))]
3423    fn cursor_with_derived_values(
3424        &self,
3425        mut document: Document,
3426        platform_version: &PlatformVersion,
3427    ) -> Result<Document, Error> {
3428        let derived_index_properties = self.document_type.derived_index_properties();
3429        if derived_index_properties.is_empty() {
3430            return Ok(document);
3431        }
3432        let index = self.find_best_index(platform_version)?;
3433        for property in &index.properties {
3434            if !derived_index_properties.contains_key(&property.name) {
3435                continue;
3436            }
3437            let Some(clause) = self.internal_clauses.equal_clauses.get(&property.name) else {
3438                return Err(Error::Query(QuerySyntaxError::Unsupported(format!(
3439                    "a startAt or startAfter cursor is placed by the values the document it \
3440                     names stores, and the index {} reads \"{}\" from the document a reference \
3441                     points at instead: fix \"{}\" with == to page with a cursor, or page by a \
3442                     range on another property of the index",
3443                    index.name, property.name, property.name
3444                ))));
3445            };
3446            document
3447                .properties_mut()
3448                .insert(property.name.clone(), clause.value.clone());
3449        }
3450        Ok(document)
3451    }
3452
3453    #[cfg(feature = "server")]
3454    /// Executes a query with proof and returns the items and fee.
3455    pub fn execute_with_proof(
3456        self,
3457        drive: &Drive,
3458        block_info: Option<BlockInfo>,
3459        transaction: TransactionArg,
3460        platform_version: &PlatformVersion,
3461    ) -> Result<(Vec<u8>, u64), Error> {
3462        self.ensure_no_sub_queries("execute_with_proof")?;
3463        let mut drive_operations = vec![];
3464        let items = self.execute_with_proof_internal(
3465            drive,
3466            transaction,
3467            &mut drive_operations,
3468            platform_version,
3469        )?;
3470        let cost = if let Some(block_info) = block_info {
3471            let fee_result = Drive::calculate_fee(
3472                None,
3473                Some(drive_operations),
3474                &block_info.epoch,
3475                drive.config.epochs_per_era,
3476                platform_version,
3477                None,
3478            )?;
3479            fee_result.processing_fee
3480        } else {
3481            0
3482        };
3483        Ok((items, cost))
3484    }
3485
3486    #[cfg(feature = "server")]
3487    /// Executes an internal query with proof and returns the items.
3488    pub(crate) fn execute_with_proof_internal(
3489        self,
3490        drive: &Drive,
3491        transaction: TransactionArg,
3492        drive_operations: &mut Vec<LowLevelDriveOperation>,
3493        platform_version: &PlatformVersion,
3494    ) -> Result<Vec<u8>, Error> {
3495        let path_query = self.construct_path_query_operations(
3496            drive,
3497            true,
3498            transaction,
3499            drive_operations,
3500            platform_version,
3501        )?;
3502        drive.grove_get_proved_path_query(
3503            &path_query,
3504            transaction,
3505            drive_operations,
3506            &platform_version.drive,
3507        )
3508    }
3509
3510    #[cfg(all(feature = "server", feature = "verify"))]
3511    /// Executes a query with proof and returns the root hash, items, and fee.
3512    pub fn execute_with_proof_only_get_elements(
3513        self,
3514        drive: &Drive,
3515        block_info: Option<BlockInfo>,
3516        transaction: TransactionArg,
3517        platform_version: &PlatformVersion,
3518    ) -> Result<(RootHash, Vec<Vec<u8>>, u64), Error> {
3519        self.ensure_no_sub_queries("execute_with_proof_only_get_elements")?;
3520        let mut drive_operations = vec![];
3521        let (root_hash, items) = self.execute_with_proof_only_get_elements_internal(
3522            drive,
3523            transaction,
3524            &mut drive_operations,
3525            platform_version,
3526        )?;
3527        let cost = if let Some(block_info) = block_info {
3528            let fee_result = Drive::calculate_fee(
3529                None,
3530                Some(drive_operations),
3531                &block_info.epoch,
3532                drive.config.epochs_per_era,
3533                platform_version,
3534                None,
3535            )?;
3536            fee_result.processing_fee
3537        } else {
3538            0
3539        };
3540        Ok((root_hash, items, cost))
3541    }
3542
3543    #[cfg(all(feature = "server", feature = "verify"))]
3544    /// Executes an internal query with proof and returns the root hash and values.
3545    pub(crate) fn execute_with_proof_only_get_elements_internal(
3546        self,
3547        drive: &Drive,
3548        transaction: TransactionArg,
3549        drive_operations: &mut Vec<LowLevelDriveOperation>,
3550        platform_version: &PlatformVersion,
3551    ) -> Result<(RootHash, Vec<Vec<u8>>), Error> {
3552        let path_query = self.construct_path_query_operations(
3553            drive,
3554            true,
3555            transaction,
3556            drive_operations,
3557            platform_version,
3558        )?;
3559
3560        let proof = drive.grove_get_proved_path_query(
3561            &path_query,
3562            transaction,
3563            drive_operations,
3564            &platform_version.drive,
3565        )?;
3566        self.verify_proof_keep_serialized(proof.as_slice(), platform_version)
3567    }
3568
3569    #[cfg(feature = "server")]
3570    /// Executes a query with no proof and returns the items, skipped items, and fee.
3571    pub fn execute_raw_results_no_proof(
3572        &self,
3573        drive: &Drive,
3574        block_info: Option<BlockInfo>,
3575        transaction: TransactionArg,
3576        platform_version: &PlatformVersion,
3577    ) -> Result<(Vec<Vec<u8>>, u16, u64), Error> {
3578        self.ensure_no_sub_queries("execute_raw_results_no_proof")?;
3579        let mut drive_operations = vec![];
3580        let (items, skipped) = self.execute_raw_results_no_proof_internal(
3581            drive,
3582            transaction,
3583            &mut drive_operations,
3584            platform_version,
3585        )?;
3586        let cost = if let Some(block_info) = block_info {
3587            let fee_result = Drive::calculate_fee(
3588                None,
3589                Some(drive_operations),
3590                &block_info.epoch,
3591                drive.config.epochs_per_era,
3592                platform_version,
3593                None,
3594            )?;
3595            fee_result.processing_fee
3596        } else {
3597            0
3598        };
3599        Ok((items, skipped, cost))
3600    }
3601
3602    #[cfg(feature = "server")]
3603    /// Refuses a read without a proof of an indexOnly type through an index
3604    /// that does not cover EVERY property: such a read serializes the
3605    /// documents it synthesizes, and a projection lacking a property cannot
3606    /// produce a faithful one. Partial projections only travel the proved read
3607    /// surface, where the client synthesizes them itself from the proof. The
3608    /// check includes optional properties (skipIfAbsent skip properties): the
3609    /// wire encodes absent-vs-present, and a projection that does not carry
3610    /// an optional property cannot distinguish "absent on the row" from "not
3611    /// in this index", so serializing it would assert an absence the index
3612    /// cannot know (and a delete built from it would not match the row).
3613    /// Every required system property must be one synthesis fills too. Checked
3614    /// from the schema before any read, by the documents query and by the
3615    /// chained and composite reads of indexOnly documents, so the answer does
3616    /// not depend on whether a document matches. Does nothing on any other
3617    /// type.
3618    pub(crate) fn refuse_an_uncovered_index_only_projection(
3619        &self,
3620        platform_version: &PlatformVersion,
3621    ) -> Result<(), Error> {
3622        if !self.document_type.index_only() {
3623            return Ok(());
3624        }
3625        // By-id and cursor shapes carry dedicated guidance deeper in the route
3626        // (no primary-key tree; keyset pagination) — let them reach it instead
3627        // of preempting with the coverage refusal below, which would
3628        // misdescribe the problem. A clause-free flat scan is classified as a
3629        // primary-key query, but still synthesizes an index projection and
3630        // must pass the same coverage check as a filtered query.
3631        if (self.is_for_primary_key() && !self.index_only_flat_scan_applies())
3632            || self.start_at.is_some()
3633        {
3634            return Ok(());
3635        }
3636        let index = self.index_only_query_index(platform_version)?;
3637        let covers_every_property = self
3638            .document_type
3639            .flattened_properties()
3640            .iter()
3641            .filter(|(_, property)| {
3642                !matches!(property.property_type, DocumentPropertyType::Object(_))
3643            })
3644            .all(|(name, _)| {
3645                index.terminal_contains(name)
3646                    || self.document_type.entry_payload().contains(name.as_str())
3647                    || index
3648                        .properties
3649                        .iter()
3650                        .any(|index_property| index_property.name == *name)
3651            });
3652        // Synthesis fills `$id`, `$ownerId` (every entry index involves it) and
3653        // `$createdAt` where the index holds it, so any other required system
3654        // property, or a required `$createdAt` the index lacks, would fail the
3655        // serialization of every document read: refused here, before the read,
3656        // so the answer does not depend on whether a document matches
3657        let covers_required_system_properties = self
3658            .document_type
3659            .required_fields()
3660            .iter()
3661            .filter(|name| {
3662                name.starts_with('$')
3663                    && name.as_str() != document::property_names::ID
3664                    && name.as_str() != document::property_names::OWNER_ID
3665            })
3666            .all(|name| {
3667                name.as_str() == document::property_names::CREATED_AT
3668                    && index.involves(document::property_names::CREATED_AT)
3669            });
3670        if !covers_required_system_properties {
3671            return Err(Error::Query(uncovered_required_property_refusal()));
3672        }
3673        if covers_every_property {
3674            return Ok(());
3675        }
3676        Err(Error::Query(QuerySyntaxError::Unsupported(
3677            "this indexOnly query's index does not cover every property, so the documents it \
3678             synthesizes cannot be serialized into a non-proof response; query through an \
3679             index covering all properties, or use a proved query"
3680                .to_string(),
3681        )))
3682    }
3683
3684    #[cfg(feature = "server")]
3685    /// Executes an internal query with no proof and returns the values and skipped items.
3686    pub(crate) fn execute_raw_results_no_proof_internal(
3687        &self,
3688        drive: &Drive,
3689        transaction: TransactionArg,
3690        drive_operations: &mut Vec<LowLevelDriveOperation>,
3691        platform_version: &PlatformVersion,
3692    ) -> Result<(Vec<Vec<u8>>, u16), Error> {
3693        // indexOnly documents have no stored bodies — the raw elements under
3694        // the entries are row commitments, not documents. Synthesize the
3695        // documents from their (path, key) positions and serialize them into
3696        // the wire shape this path's callers return, once the index is known
3697        // to cover every property
3698        // (`refuse_an_uncovered_index_only_projection`).
3699        {
3700            use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
3701            if self.document_type.index_only() {
3702                self.refuse_an_uncovered_index_only_projection(platform_version)?;
3703                let (documents, skipped) = self.execute_index_only_documents_no_proof_internal(
3704                    drive,
3705                    transaction,
3706                    drive_operations,
3707                    platform_version,
3708                )?;
3709                let serialized = documents
3710                    .into_iter()
3711                    .map(|document| {
3712                        document
3713                            .serialize(self.document_type, self.contract, platform_version)
3714                            .map_err(|error| match index_only_serialization_refusal(&error) {
3715                                Some(refusal) => Error::Query(refusal),
3716                                None => error.into(),
3717                            })
3718                    })
3719                    .collect::<Result<Vec<_>, Error>>()?;
3720                return Ok((serialized, skipped));
3721            }
3722        }
3723
3724        let path_query = self.construct_path_query_operations(
3725            drive,
3726            false,
3727            transaction,
3728            drive_operations,
3729            platform_version,
3730        )?;
3731
3732        let query_result = drive.grove_get_path_query_serialized_results(
3733            &path_query,
3734            transaction,
3735            drive_operations,
3736            &platform_version.drive,
3737        );
3738        match query_result {
3739            Err(Error::GroveDB(e))
3740                if matches!(
3741                    e.as_ref(),
3742                    GroveError::PathKeyNotFound(_)
3743                        | GroveError::PathNotFound(_)
3744                        | GroveError::PathParentLayerNotFound(_)
3745                ) =>
3746            {
3747                Ok((Vec::new(), 0))
3748            }
3749            _ => {
3750                let (data, skipped) = query_result?;
3751                let (data, cursor_skipped) = self.strip_cursor_from_page(data, platform_version)?;
3752                Ok((data, skipped.saturating_add(cursor_skipped)))
3753            }
3754        }
3755    }
3756
3757    #[cfg(feature = "server")]
3758    /// Executes an internal query with no proof and returns the values and skipped items.
3759    pub(crate) fn execute_no_proof_internal(
3760        &self,
3761        drive: &Drive,
3762        result_type: QueryResultType,
3763        transaction: TransactionArg,
3764        drive_operations: &mut Vec<LowLevelDriveOperation>,
3765        platform_version: &PlatformVersion,
3766    ) -> Result<(QueryResultElements, u16), Error> {
3767        let path_query = self.construct_path_query_operations(
3768            drive,
3769            false,
3770            transaction,
3771            drive_operations,
3772            platform_version,
3773        )?;
3774        let query_result = drive.grove_get_path_query(
3775            &path_query,
3776            transaction,
3777            result_type,
3778            drive_operations,
3779            &platform_version.drive,
3780        );
3781        match query_result {
3782            Err(Error::GroveDB(e))
3783                if matches!(
3784                    e.as_ref(),
3785                    GroveError::PathKeyNotFound(_)
3786                        | GroveError::PathNotFound(_)
3787                        | GroveError::PathParentLayerNotFound(_)
3788                ) =>
3789            {
3790                Ok((QueryResultElements::new(), 0))
3791            }
3792            _ => {
3793                let (data, skipped) = query_result?;
3794                let (data, cursor_skipped) =
3795                    self.strip_cursor_from_elements(data, platform_version)?;
3796                Ok((data, skipped.saturating_add(cursor_skipped)))
3797            }
3798        }
3799    }
3800}
3801
3802/// Convert DriveQuery to a BTreeMap of values
3803impl<'a> From<&DriveDocumentQuery<'a>> for BTreeMap<String, Value> {
3804    fn from(query: &DriveDocumentQuery<'a>) -> Self {
3805        let mut response = BTreeMap::<String, Value>::new();
3806
3807        //  contract
3808        // TODO: once contract can be serialized, maybe put full contract here instead of id
3809        response.insert(
3810            "contract_id".to_string(),
3811            Value::Identifier(query.contract.id().to_buffer()),
3812        );
3813
3814        // document_type
3815        // TODO: once DocumentType can be serialized, maybe put full DocumentType instead of name
3816        response.insert(
3817            "document_type_name".to_string(),
3818            Value::Text(query.document_type.name().to_string()),
3819        );
3820
3821        // Internal clauses
3822        let all_where_clauses: Vec<WhereClause> = query.internal_clauses.clone().into();
3823        response.insert(
3824            "where".to_string(),
3825            Value::Array(all_where_clauses.into_iter().map(|v| v.into()).collect()),
3826        );
3827
3828        // Offset
3829        if let Some(offset) = query.offset {
3830            response.insert("offset".to_string(), Value::U16(offset));
3831        };
3832        // Limit
3833        if let Some(limit) = query.limit {
3834            response.insert("limit".to_string(), Value::U16(limit));
3835        };
3836        // Order by
3837        let order_by = &query.order_by;
3838        let value: Vec<Value> = order_by
3839            .into_iter()
3840            .map(|(_k, v)| v.clone().into())
3841            .collect();
3842        response.insert("orderBy".to_string(), Value::Array(value));
3843
3844        // start_at, start_at_included
3845        if let Some(start_at) = query.start_at {
3846            let v = Value::Identifier(start_at);
3847            if query.start_at_included {
3848                response.insert("startAt".to_string(), v);
3849            } else {
3850                response.insert("startAfter".to_string(), v);
3851            }
3852        };
3853
3854        // block_time_ms
3855        if let Some(block_time_ms) = query.block_time_ms {
3856            response.insert("blockTime".to_string(), Value::U64(block_time_ms));
3857        };
3858
3859        response
3860    }
3861}
3862
3863#[cfg(feature = "server")]
3864#[cfg(test)]
3865mod tests {
3866
3867    use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
3868
3869    use dpp::prelude::Identifier;
3870    use grovedb::Query;
3871    use indexmap::IndexMap;
3872    use rand::prelude::StdRng;
3873    use rand::SeedableRng;
3874    use serde_json::json;
3875    use std::borrow::Cow;
3876    use std::collections::BTreeMap;
3877    use std::option::Option::None;
3878
3879    use crate::drive::Drive;
3880    use crate::query::{
3881        DriveDocumentQuery, InternalClauses, OrderClause, WhereClause, WhereOperator,
3882    };
3883    use crate::util::storage_flags::StorageFlags;
3884
3885    use dpp::data_contract::DataContract;
3886
3887    use serde_json::Value::Null;
3888
3889    use crate::config::DriveConfig;
3890    use crate::util::test_helpers::setup::{setup_drive, setup_drive_with_initial_state_structure};
3891    use dpp::block::block_info::BlockInfo;
3892    use dpp::data_contract::accessors::v0::DataContractV0Getters;
3893    use dpp::data_contracts::SystemDataContract;
3894    use dpp::document::DocumentV0;
3895    use dpp::platform_value::string_encoding::Encoding;
3896    use dpp::platform_value::Value;
3897    use dpp::system_data_contracts::load_system_data_contract;
3898    use dpp::tests::fixtures::{get_data_contract_fixture, get_dpns_data_contract_fixture};
3899    use dpp::tests::json_document::json_document_to_contract;
3900    use dpp::util::cbor_serializer;
3901    use dpp::version::PlatformVersion;
3902
3903    fn setup_family_contract() -> (Drive, DataContract) {
3904        let platform_version = PlatformVersion::latest();
3905
3906        let drive = setup_drive(None);
3907
3908        drive
3909            .create_initial_state_structure(None, platform_version)
3910            .expect("expected to create root tree successfully");
3911
3912        let contract_path = "tests/supporting_files/contract/family/family-contract.json";
3913
3914        // let's construct the grovedb structure for the dashpay data contract
3915        let contract = json_document_to_contract(contract_path, false, platform_version)
3916            .expect("expected to get document");
3917
3918        let storage_flags = Some(Cow::Owned(StorageFlags::SingleEpoch(0)));
3919        drive
3920            .apply_contract(
3921                &contract,
3922                BlockInfo::default(),
3923                true,
3924                storage_flags,
3925                None,
3926                platform_version,
3927            )
3928            .expect("expected to apply contract successfully");
3929
3930        (drive, contract)
3931    }
3932
3933    fn setup_withdrawal_contract() -> (Drive, DataContract) {
3934        let platform_version = PlatformVersion::latest();
3935
3936        let drive = setup_drive(None);
3937
3938        drive
3939            .create_initial_state_structure(None, platform_version)
3940            .expect("expected to create root tree successfully");
3941
3942        // let's construct the grovedb structure for the dashpay data contract
3943        let contract = load_system_data_contract(SystemDataContract::Withdrawals, platform_version)
3944            .expect("load system contact");
3945
3946        let storage_flags = Some(Cow::Owned(StorageFlags::SingleEpoch(0)));
3947        drive
3948            .apply_contract(
3949                &contract,
3950                BlockInfo::default(),
3951                true,
3952                storage_flags,
3953                None,
3954                platform_version,
3955            )
3956            .expect("expected to apply contract successfully");
3957
3958        (drive, contract)
3959    }
3960
3961    fn setup_family_birthday_contract() -> (Drive, DataContract) {
3962        let drive = setup_drive_with_initial_state_structure(None);
3963
3964        let platform_version = PlatformVersion::latest();
3965
3966        let contract_path =
3967            "tests/supporting_files/contract/family/family-contract-with-birthday.json";
3968
3969        // let's construct the grovedb structure for the dashpay data contract
3970        let contract = json_document_to_contract(contract_path, false, platform_version)
3971            .expect("expected to get document");
3972        let storage_flags = Some(Cow::Owned(StorageFlags::SingleEpoch(0)));
3973        drive
3974            .apply_contract(
3975                &contract,
3976                BlockInfo::default(),
3977                true,
3978                storage_flags,
3979                None,
3980                platform_version,
3981            )
3982            .expect("expected to apply contract successfully");
3983
3984        (drive, contract)
3985    }
3986
3987    #[test]
3988    fn test_drive_query_from_to_cbor() {
3989        let config = DriveConfig::default();
3990        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
3991        let document_type = contract
3992            .document_type_for_name("niceDocument")
3993            .expect("expected to get nice document");
3994        let start_after = Identifier::random();
3995
3996        let query_value = json!({
3997            "contract_id": contract.id(),
3998            "document_type_name": document_type.name(),
3999            "where": [
4000                ["firstName", "<", "Gilligan"],
4001                ["lastName", "=", "Doe"]
4002            ],
4003            "limit": 100u16,
4004            "offset": 10u16,
4005            "orderBy": [
4006                ["firstName", "asc"],
4007                ["lastName", "desc"],
4008            ],
4009            "startAfter": start_after,
4010            "blockTime": 13453432u64,
4011        });
4012
4013        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4014            .expect("expected to serialize to cbor");
4015        let query = DriveDocumentQuery::from_cbor(
4016            where_cbor.as_slice(),
4017            &contract,
4018            document_type,
4019            &config,
4020            PlatformVersion::latest(),
4021        )
4022        .expect("deserialize cbor shouldn't fail");
4023
4024        let cbor = query.to_cbor().expect("should serialize cbor");
4025
4026        let deserialized = DriveDocumentQuery::from_cbor(
4027            &cbor,
4028            &contract,
4029            document_type,
4030            &config,
4031            PlatformVersion::latest(),
4032        )
4033        .expect("should deserialize cbor");
4034
4035        assert_eq!(query, deserialized);
4036
4037        assert_eq!(deserialized.start_at, Some(start_after.to_buffer()));
4038        assert!(!deserialized.start_at_included);
4039        assert_eq!(deserialized.block_time_ms, Some(13453432u64));
4040    }
4041
4042    #[test]
4043    fn test_invalid_query_ranges_different_fields() {
4044        let query_value = json!({
4045            "where": [
4046                ["firstName", "<", "Gilligan"],
4047                ["lastName", "<", "Michelle"],
4048            ],
4049            "limit": 100,
4050            "orderBy": [
4051                ["firstName", "asc"],
4052                ["lastName", "asc"],
4053            ]
4054        });
4055        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
4056        let document_type = contract
4057            .document_type_for_name("niceDocument")
4058            .expect("expected to get nice document");
4059
4060        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4061            .expect("expected to serialize to cbor");
4062        DriveDocumentQuery::from_cbor(
4063            where_cbor.as_slice(),
4064            &contract,
4065            document_type,
4066            &DriveConfig::default(),
4067            PlatformVersion::latest(),
4068        )
4069        .expect_err("all ranges must be on same field");
4070    }
4071
4072    #[test]
4073    fn test_invalid_query_extra_invalid_field() {
4074        let query_value = json!({
4075            "where": [
4076                ["firstName", "<", "Gilligan"],
4077            ],
4078            "limit": 100,
4079            "orderBy": [
4080                ["firstName", "asc"],
4081                ["lastName", "asc"],
4082            ],
4083            "invalid": 0,
4084        });
4085        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
4086        let document_type = contract
4087            .document_type_for_name("niceDocument")
4088            .expect("expected to get nice document");
4089
4090        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4091            .expect("expected to serialize to cbor");
4092        DriveDocumentQuery::from_cbor(
4093            where_cbor.as_slice(),
4094            &contract,
4095            document_type,
4096            &DriveConfig::default(),
4097            PlatformVersion::latest(),
4098        )
4099        .expect_err("fields of queries must of defined supported types (where, limit, orderBy...)");
4100    }
4101
4102    #[test]
4103    fn test_invalid_query_conflicting_clauses() {
4104        let query_value = json!({
4105            "where": [
4106                ["firstName", "<", "Gilligan"],
4107                ["firstName", ">", "Gilligan"],
4108            ],
4109            "limit": 100,
4110            "orderBy": [
4111                ["firstName", "asc"],
4112                ["lastName", "asc"],
4113            ],
4114        });
4115
4116        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
4117        let document_type = contract
4118            .document_type_for_name("niceDocument")
4119            .expect("expected to get nice document");
4120
4121        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4122            .expect("expected to serialize to cbor");
4123        DriveDocumentQuery::from_cbor(
4124            where_cbor.as_slice(),
4125            &contract,
4126            document_type,
4127            &DriveConfig::default(),
4128            PlatformVersion::latest(),
4129        )
4130        .expect_err("the query should not be created");
4131    }
4132
4133    #[test]
4134    fn test_valid_query_groupable_meeting_clauses() {
4135        let query_value = json!({
4136            "where": [
4137                ["firstName", "<=", "Gilligan"],
4138                ["firstName", ">", "Gilligan"],
4139            ],
4140            "limit": 100,
4141            "orderBy": [
4142                ["firstName", "asc"],
4143                ["lastName", "asc"],
4144            ],
4145        });
4146
4147        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
4148        let document_type = contract
4149            .document_type_for_name("niceDocument")
4150            .expect("expected to get nice document");
4151
4152        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4153            .expect("expected to serialize to cbor");
4154        DriveDocumentQuery::from_cbor(
4155            where_cbor.as_slice(),
4156            &contract,
4157            document_type,
4158            &DriveConfig::default(),
4159            PlatformVersion::latest(),
4160        )
4161        .expect("the query should be created");
4162    }
4163
4164    #[test]
4165    fn test_valid_query_query_field_at_max_length() {
4166        let long_string = "t".repeat(255);
4167        let query_value = json!({
4168            "where": [
4169                ["firstName", "<", long_string],
4170            ],
4171            "limit": 100,
4172            "orderBy": [
4173                ["firstName", "asc"],
4174                ["lastName", "asc"],
4175            ],
4176        });
4177        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
4178        let document_type = contract
4179            .document_type_for_name("niceDocument")
4180            .expect("expected to get nice document");
4181
4182        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4183            .expect("expected to serialize to cbor");
4184        DriveDocumentQuery::from_cbor(
4185            where_cbor.as_slice(),
4186            &contract,
4187            document_type,
4188            &DriveConfig::default(),
4189            PlatformVersion::latest(),
4190        )
4191        .expect("query should be fine for a 255 byte long string");
4192    }
4193
4194    #[test]
4195    fn test_valid_query_drive_document_query() {
4196        let platform_version = PlatformVersion::latest();
4197        let mut rng = StdRng::seed_from_u64(5);
4198        let contract =
4199            get_dpns_data_contract_fixture(Some(Identifier::random_with_rng(&mut rng)), 0, 1)
4200                .data_contract_owned();
4201        let domain = contract
4202            .document_type_for_name("domain")
4203            .expect("expected to get domain");
4204
4205        let query_asc = DriveDocumentQuery {
4206            contract: &contract,
4207            document_type: domain,
4208            internal_clauses: InternalClauses {
4209                primary_key_in_clause: None,
4210                primary_key_equal_clause: None,
4211                in_clauses: Vec::new(),
4212                range_clause: Some(WhereClause {
4213                    field: "records.identity".to_string(),
4214                    operator: WhereOperator::LessThan,
4215                    value: Value::Identifier(
4216                        Identifier::from_string(
4217                            "AYN4srupPWDrp833iG5qtmaAsbapNvaV7svAdncLN5Rh",
4218                            Encoding::Base58,
4219                        )
4220                        .unwrap()
4221                        .to_buffer(),
4222                    ),
4223                }),
4224                equal_clauses: BTreeMap::new(),
4225            },
4226            offset: None,
4227            limit: Some(6),
4228            order_by: vec![(
4229                "records.identity".to_string(),
4230                OrderClause {
4231                    field: "records.identity".to_string(),
4232                    ascending: false,
4233                },
4234            )]
4235            .into_iter()
4236            .collect(),
4237            start_at: None,
4238            start_at_included: false,
4239            block_time_ms: None,
4240            resolved_time_ranges: vec![],
4241            sub_queries: vec![],
4242        };
4243
4244        let path_query = query_asc
4245            .construct_path_query(None, platform_version)
4246            .expect("expected to create path query");
4247
4248        assert_eq!(path_query.to_string(), "PathQuery { path: [@, 0x1da29f488023e306ff9a680bc9837153fb0778c8ee9c934a87dc0de1d69abd3c, 0x01, domain, 0x7265636f7264732e6964656e74697479], query: SizedQuery { query: Query {\n  items: [\n    RangeTo(.. 0x8dc201fd7ad7905f8a84d66218e2b387daea7fe4739ae0e21e8c3ee755e6a2c0),\n  ],\n  default_subquery_branch: SubqueryBranch { subquery_path: [0x00], subquery: Query {\n  items: [\n    RangeFull,\n  ],\n  default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n  left_to_right: false,\n  add_parent_tree_on_subquery: false,\n} },\n  conditional_subquery_branches: {\n    Key(): SubqueryBranch { subquery_path: [0x00], subquery: Query {\n  items: [\n    RangeFull,\n  ],\n  default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n  left_to_right: false,\n  add_parent_tree_on_subquery: false,\n} },\n  },\n  left_to_right: false,\n  add_parent_tree_on_subquery: false,\n}, limit: 6 } }");
4249
4250        // Serialize the PathQuery to a Vec<u8>
4251        let encoded = bincode::encode_to_vec(&path_query, bincode::config::standard())
4252            .expect("Failed to serialize PathQuery");
4253
4254        // Convert the encoded bytes to a hex string
4255        let hex_string = hex::encode(encoded);
4256
4257        // Note: The expected encoding changed due to an upstream GroveDB
4258        // serialization update. Keep this value in sync with the current
4259        // GroveDB revision pinned in Cargo.toml.
4260        assert_eq!(hex_string, "050140201da29f488023e306ff9a680bc9837153fb0778c8ee9c934a87dc0de1d69abd3c010106646f6d61696e107265636f7264732e6964656e74697479010105208dc201fd7ad7905f8a84d66218e2b387daea7fe4739ae0e21e8c3ee755e6a2c00101010001010103000000000001010000010101000101010300000000000000010600");
4261    }
4262
4263    #[test]
4264    fn test_invalid_query_field_too_long() {
4265        let (drive, contract) = setup_family_contract();
4266
4267        let platform_version = PlatformVersion::latest();
4268
4269        let document_type = contract
4270            .document_type_for_name("person")
4271            .expect("expected to get a document type");
4272
4273        let too_long_string = "t".repeat(256);
4274        let query_value = json!({
4275            "where": [
4276                ["firstName", "<", too_long_string],
4277            ],
4278            "limit": 100,
4279            "orderBy": [
4280                ["firstName", "asc"],
4281                ["lastName", "asc"],
4282            ],
4283        });
4284
4285        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4286            .expect("expected to serialize to cbor");
4287        let query = DriveDocumentQuery::from_cbor(
4288            where_cbor.as_slice(),
4289            &contract,
4290            document_type,
4291            &DriveConfig::default(),
4292            PlatformVersion::latest(),
4293        )
4294        .expect("fields of queries length must be under 256 bytes long");
4295        query
4296            .execute_raw_results_no_proof(&drive, None, None, platform_version)
4297            .expect_err("fields of queries length must be under 256 bytes long");
4298    }
4299
4300    // TODO: Eventually we want to error with weird Null values
4301    // #[test]
4302    // fn test_invalid_query_scalar_field_with_null_value() {
4303    //     let (drive, contract) = setup_family_contract();
4304    //
4305    //     let document_type = contract
4306    //         .document_type("person")
4307    //         .expect("expected to get a document type");
4308    //
4309    //     let query_value = json!({
4310    //         "where": [
4311    //             ["age", "<", Null],
4312    //         ],
4313    //         "limit": 100,
4314    //         "orderBy": [
4315    //             ["age", "asc"],
4316    //         ],
4317    //     });
4318    //
4319    //     let where_cbor = serializer::value_to_cbor(query_value, None).expect("expected to serialize to cbor");
4320    //     let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, document_type, &DriveConfig::default())
4321    //         .expect("The query itself should be valid for a null type");
4322    //     query
4323    //         .execute_no_proof(&drive, None, None)
4324    //         .expect_err("a Null value doesn't make sense for an integer");
4325    // }
4326
4327    // TODO: Eventually we want to error with weird Null values
4328    //
4329    // #[test]
4330    // fn test_invalid_query_timestamp_field_with_null_value() {
4331    //     let (drive, contract) = setup_family_birthday_contract();
4332    //
4333    //     let document_type = contract
4334    //         .document_type("person")
4335    //         .expect("expected to get a document type");
4336    //
4337    //     let query_value = json!({
4338    //         "where": [
4339    //             ["birthday", "<", Null],
4340    //         ],
4341    //         "limit": 100,
4342    //         "orderBy": [
4343    //             ["birthday", "asc"],
4344    //         ],
4345    //     });
4346    //
4347    //     let where_cbor = serializer::value_to_cbor(query_value, None).expect("expected to serialize to cbor");
4348    //     let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, document_type, &DriveConfig::default())
4349    //         .expect("The query itself should be valid for a null type");
4350    //     query
4351    //         .execute_no_proof(&drive, None, None)
4352    //         .expect_err("the value can not be less than Null");
4353    // }
4354
4355    #[test]
4356    fn test_valid_query_timestamp_field_with_null_value() {
4357        let (drive, contract) = setup_family_birthday_contract();
4358
4359        let platform_version = PlatformVersion::latest();
4360
4361        let document_type = contract
4362            .document_type_for_name("person")
4363            .expect("expected to get a document type");
4364
4365        let query_value = json!({
4366            "where": [
4367                ["birthday", ">=", Null],
4368            ],
4369            "limit": 100,
4370            "orderBy": [
4371                ["birthday", "asc"],
4372            ],
4373        });
4374
4375        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4376            .expect("expected to serialize to cbor");
4377        let query = DriveDocumentQuery::from_cbor(
4378            where_cbor.as_slice(),
4379            &contract,
4380            document_type,
4381            &DriveConfig::default(),
4382            PlatformVersion::latest(),
4383        )
4384        .expect("The query itself should be valid for a null type");
4385        query
4386            .execute_raw_results_no_proof(&drive, None, None, platform_version)
4387            .expect("a Null value doesn't make sense for a float");
4388    }
4389
4390    #[test]
4391    fn test_invalid_query_in_with_empty_array() {
4392        let (drive, contract) = setup_family_contract();
4393
4394        let platform_version = PlatformVersion::latest();
4395
4396        let document_type = contract
4397            .document_type_for_name("person")
4398            .expect("expected to get a document type");
4399
4400        let query_value = json!({
4401            "where": [
4402                ["firstName", "in", []],
4403            ],
4404            "limit": 100,
4405            "orderBy": [
4406                ["firstName", "asc"],
4407            ],
4408        });
4409
4410        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4411            .expect("expected to serialize to cbor");
4412        let query = DriveDocumentQuery::from_cbor(
4413            where_cbor.as_slice(),
4414            &contract,
4415            document_type,
4416            &DriveConfig::default(),
4417            PlatformVersion::latest(),
4418        )
4419        .expect("query should be valid for empty array");
4420
4421        query
4422            .execute_raw_results_no_proof(&drive, None, None, platform_version)
4423            .expect_err("query should not be able to execute for empty array");
4424    }
4425
4426    #[test]
4427    fn test_invalid_query_in_too_many_elements() {
4428        let (drive, contract) = setup_family_contract();
4429
4430        let platform_version = PlatformVersion::latest();
4431
4432        let document_type = contract
4433            .document_type_for_name("person")
4434            .expect("expected to get a document type");
4435
4436        let mut array: Vec<String> = Vec::with_capacity(101);
4437        for _ in 0..array.capacity() {
4438            array.push(String::from("a"));
4439        }
4440        let query_value = json!({
4441            "where": [
4442                ["firstName", "in", array],
4443            ],
4444            "limit": 100,
4445            "orderBy": [
4446                ["firstName", "asc"],
4447            ],
4448        });
4449
4450        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4451            .expect("expected to serialize to cbor");
4452        let query = DriveDocumentQuery::from_cbor(
4453            where_cbor.as_slice(),
4454            &contract,
4455            document_type,
4456            &DriveConfig::default(),
4457            PlatformVersion::latest(),
4458        )
4459        .expect("query is valid for too many elements");
4460
4461        query
4462            .execute_raw_results_no_proof(&drive, None, None, platform_version)
4463            .expect_err("query should not be able to execute with too many elements");
4464    }
4465
4466    #[test]
4467    fn test_invalid_query_in_unique_elements() {
4468        let (drive, contract) = setup_family_contract();
4469
4470        let platform_version = PlatformVersion::latest();
4471
4472        let document_type = contract
4473            .document_type_for_name("person")
4474            .expect("expected to get a document type");
4475
4476        let query_value = json!({
4477            "where": [
4478                ["firstName", "in", ["a", "a"]],
4479            ],
4480            "limit": 100,
4481            "orderBy": [
4482                ["firstName", "asc"],
4483            ],
4484        });
4485
4486        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4487            .expect("expected to serialize to cbor");
4488
4489        // The is actually valid, however executing it is not
4490        // This is in order to optimize query execution
4491
4492        let query = DriveDocumentQuery::from_cbor(
4493            where_cbor.as_slice(),
4494            &contract,
4495            document_type,
4496            &DriveConfig::default(),
4497            PlatformVersion::latest(),
4498        )
4499        .expect("the query should be created");
4500
4501        query
4502            .execute_raw_results_no_proof(&drive, None, None, platform_version)
4503            .expect_err("there should be no duplicates values for In query");
4504    }
4505
4506    #[test]
4507    fn test_invalid_query_starts_with_empty_string() {
4508        let query_value = json!({
4509            "where": [
4510                ["firstName", "startsWith", ""],
4511            ],
4512            "limit": 100,
4513            "orderBy": [
4514                ["firstName", "asc"],
4515            ],
4516        });
4517
4518        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
4519        let document_type = contract
4520            .document_type_for_name("niceDocument")
4521            .expect("expected to get nice document");
4522
4523        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4524            .expect("expected to serialize to cbor");
4525        DriveDocumentQuery::from_cbor(
4526            where_cbor.as_slice(),
4527            &contract,
4528            document_type,
4529            &DriveConfig::default(),
4530            PlatformVersion::latest(),
4531        )
4532        .expect_err("starts with can not start with an empty string");
4533    }
4534
4535    #[test]
4536    fn test_invalid_query_limit_too_high() {
4537        let query_value = json!({
4538            "where": [
4539                ["firstName", "startsWith", "a"],
4540            ],
4541            "limit": 101,
4542            "orderBy": [
4543                ["firstName", "asc"],
4544            ],
4545        });
4546
4547        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
4548        let document_type = contract
4549            .document_type_for_name("niceDocument")
4550            .expect("expected to get nice document");
4551
4552        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4553            .expect("expected to serialize to cbor");
4554        DriveDocumentQuery::from_cbor(
4555            where_cbor.as_slice(),
4556            &contract,
4557            document_type,
4558            &DriveConfig::default(),
4559            PlatformVersion::latest(),
4560        )
4561        .expect_err("starts with can not start with an empty string");
4562    }
4563
4564    #[test]
4565    fn test_invalid_query_limit_too_low() {
4566        let query_value = json!({
4567            "where": [
4568                ["firstName", "startsWith", "a"],
4569            ],
4570            "limit": -1,
4571            "orderBy": [
4572                ["firstName", "asc"],
4573            ],
4574        });
4575
4576        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
4577        let document_type = contract
4578            .document_type_for_name("niceDocument")
4579            .expect("expected to get nice document");
4580
4581        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4582            .expect("expected to serialize to cbor");
4583        DriveDocumentQuery::from_cbor(
4584            where_cbor.as_slice(),
4585            &contract,
4586            document_type,
4587            &DriveConfig::default(),
4588            PlatformVersion::latest(),
4589        )
4590        .expect_err("starts with can not start with an empty string");
4591    }
4592
4593    #[test]
4594    fn test_invalid_query_limit_zero() {
4595        let query_value = json!({
4596            "where": [
4597                ["firstName", "startsWith", "a"],
4598            ],
4599            "limit": 0,
4600            "orderBy": [
4601                ["firstName", "asc"],
4602            ],
4603        });
4604
4605        let contract = get_data_contract_fixture(None, 0, 1).data_contract_owned();
4606        let document_type = contract
4607            .document_type_for_name("niceDocument")
4608            .expect("expected to get nice document");
4609
4610        let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
4611            .expect("expected to serialize to cbor");
4612        DriveDocumentQuery::from_cbor(
4613            where_cbor.as_slice(),
4614            &contract,
4615            document_type,
4616            &DriveConfig::default(),
4617            PlatformVersion::latest(),
4618        )
4619        .expect_err("starts with can not start with an empty string");
4620    }
4621
4622    #[test]
4623    fn resolved_time_range_shape_guard_accepts_only_the_single_resolution_equality() {
4624        use crate::query::{validate_resolved_time_range_clause_shapes, ResolvedTimeRange};
4625        use dpp::data_contract::document_type::TimeRangeTransform;
4626
4627        let resolved = vec![ResolvedTimeRange {
4628            transform: TimeRangeTransform {
4629                source: "$createdAt".to_string(),
4630                range_seconds: 21_600,
4631                step_seconds: 7_200,
4632                phase_seconds: 0,
4633                ttl_seconds: None,
4634            }
4635            .into(),
4636        }];
4637        let equality = WhereClause {
4638            field: "$createdAt".to_string(),
4639            operator: WhereOperator::Equal,
4640            value: Value::U64(21_600_000),
4641        };
4642        let other = WhereClause {
4643            field: "hashtag".to_string(),
4644            operator: WhereOperator::Equal,
4645            value: Value::Text("ibiza".to_string()),
4646        };
4647
4648        validate_resolved_time_range_clause_shapes(&[equality.clone(), other.clone()], &resolved)
4649            .expect("one equality on the resolved field is the resolution shape");
4650
4651        // An `In` on the resolved field would be fanned out per raw value by
4652        // the aggregate executors and admitted against bucket keys.
4653        let in_clause = WhereClause {
4654            field: "$createdAt".to_string(),
4655            operator: WhereOperator::In,
4656            value: Value::Array(vec![Value::U64(0), Value::U64(7_200_000)]),
4657        };
4658        validate_resolved_time_range_clause_shapes(&[in_clause, other.clone()], &resolved)
4659            .expect_err("an In clause on a resolved field must be rejected");
4660
4661        let range_clause = WhereClause {
4662            field: "$createdAt".to_string(),
4663            operator: WhereOperator::GreaterThan,
4664            value: Value::U64(0),
4665        };
4666        validate_resolved_time_range_clause_shapes(&[equality.clone(), range_clause], &resolved)
4667            .expect_err("a range clause riding along on a resolved field must be rejected");
4668
4669        validate_resolved_time_range_clause_shapes(&[other], &resolved)
4670            .expect_err("a resolved field with no equality at all must be rejected");
4671    }
4672
4673    /// The TTL horizon gate: an expired window may be mid-drainage, so
4674    /// `byStart` must reject it rather than serve whatever the drain has
4675    /// left. The boundary is the drain's own predicate — a window starting
4676    /// exactly at the horizon is not yet expired and stays queryable — and
4677    /// an index without a `ttl` keeps serving arbitrarily old windows.
4678    #[test]
4679    fn by_start_rejects_windows_past_the_ttl_horizon() {
4680        use crate::query::{resolve_time_range_bucket_clause, TimeRangeSelector};
4681        use dpp::data_contract::DataContractFactory;
4682        use dpp::platform_value::platform_value;
4683        use dpp::prelude::Identifier;
4684
4685        let factory =
4686            DataContractFactory::new(PlatformVersion::latest().protocol_version).expect("factory");
4687        let hour_ms: u64 = 3_600_000;
4688        let build = |seed: u8, with_ttl: bool| {
4689            let mut time_range = vec![
4690                (
4691                    Value::Text("on".to_string()),
4692                    Value::Text("$createdAt".to_string()),
4693                ),
4694                (Value::Text("range".to_string()), Value::U64(7_200)),
4695                (Value::Text("step".to_string()), Value::U64(7_200)),
4696            ];
4697            if with_ttl {
4698                time_range.push((Value::Text("ttl".to_string()), Value::U64(14_400)));
4699            }
4700            let index_map = vec![
4701                (
4702                    Value::Text("name".to_string()),
4703                    Value::Text("trending".to_string()),
4704                ),
4705                (
4706                    Value::Text("properties".to_string()),
4707                    Value::Array(vec![
4708                        platform_value!({"$createdAt": "asc"}),
4709                        platform_value!({"hashtag": "asc"}),
4710                    ]),
4711                ),
4712                (Value::Text("timeRange".to_string()), Value::Map(time_range)),
4713                (
4714                    Value::Text("countable".to_string()),
4715                    Value::Text("countable".to_string()),
4716                ),
4717            ];
4718            let document_schema = platform_value!({
4719                "type": "object",
4720                "properties": {
4721                    "hashtag": {"type": "string", "maxLength": 61, "position": 0},
4722                },
4723                "required": ["hashtag", "$createdAt"],
4724                "indices": Value::Array(vec![Value::Map(index_map)]),
4725                "additionalProperties": false,
4726            });
4727            factory
4728                .create_with_value_config(
4729                    Identifier::from([seed; 32]),
4730                    0,
4731                    platform_value!({ "post": document_schema }),
4732                    None,
4733                    None,
4734                )
4735                .expect("contract registers")
4736                .data_contract_owned()
4737        };
4738
4739        let ttl_contract = build(101, true);
4740        let standing_contract = build(102, false);
4741        let expired_start = 5_000 * hour_ms;
4742        let block_time = expired_start + 6 * hour_ms;
4743
4744        let resolve = |contract: &DataContract, start_ms: u64| {
4745            resolve_time_range_bucket_clause(
4746                "$createdAt",
4747                TimeRangeSelector::ByStart { start_ms },
4748                None,
4749                contract
4750                    .document_type_for_name("post")
4751                    .expect("document type"),
4752                block_time,
4753            )
4754        };
4755
4756        let error = resolve(&ttl_contract, expired_start)
4757            .expect_err("a window past the ttl horizon must be rejected, not served");
4758        assert!(
4759            error.to_string().contains("ttl horizon"),
4760            "the rejection names the horizon: {error}"
4761        );
4762        resolve(&ttl_contract, expired_start + 2 * hour_ms).expect(
4763            "a window starting exactly at the horizon is not expired — same \
4764             strictly-below boundary the drain uses",
4765        );
4766        resolve(&ttl_contract, expired_start + 4 * hour_ms)
4767            .expect("a live window resolves normally");
4768        resolve_time_range_bucket_clause(
4769            "$createdAt",
4770            TimeRangeSelector::Newest,
4771            None,
4772            ttl_contract
4773                .document_type_for_name("post")
4774                .expect("document type"),
4775            block_time,
4776        )
4777        .expect("relative selectors never address expired windows and stay unaffected");
4778        resolve(&standing_contract, expired_start)
4779            .expect("without a ttl, arbitrarily old windows stay queryable");
4780    }
4781
4782    #[test]
4783    fn test_withdrawal_query_with_missing_transaction_index() {
4784        // Setup the withdrawal contract
4785        let (_, contract) = setup_withdrawal_contract();
4786        let platform_version = PlatformVersion::latest();
4787
4788        let document_type_name = "withdrawal";
4789        let document_type = contract
4790            .document_type_for_name(document_type_name)
4791            .expect("expected to get document type");
4792
4793        // Create a DriveDocumentQuery that simulates missing 'transactionIndex' in documents
4794        let drive_document_query = DriveDocumentQuery {
4795            contract: &contract,
4796            document_type,
4797            internal_clauses: InternalClauses {
4798                primary_key_in_clause: None,
4799                primary_key_equal_clause: None,
4800                in_clauses: vec![WhereClause {
4801                    field: "status".to_string(),
4802                    operator: WhereOperator::In,
4803                    value: Value::Array(vec![
4804                        Value::U64(0),
4805                        Value::U64(1),
4806                        Value::U64(2),
4807                        Value::U64(3),
4808                        Value::U64(4),
4809                    ]),
4810                }],
4811                range_clause: None,
4812                equal_clauses: BTreeMap::default(),
4813            },
4814            offset: None,
4815            limit: Some(3),
4816            order_by: IndexMap::from([
4817                (
4818                    "status".to_string(),
4819                    OrderClause {
4820                        field: "status".to_string(),
4821                        ascending: true,
4822                    },
4823                ),
4824                (
4825                    "transactionIndex".to_string(),
4826                    OrderClause {
4827                        field: "transactionIndex".to_string(),
4828                        ascending: true,
4829                    },
4830                ),
4831            ]),
4832            start_at: Some([3u8; 32]),
4833            start_at_included: false,
4834            block_time_ms: None,
4835            resolved_time_ranges: vec![],
4836            sub_queries: vec![],
4837        };
4838
4839        // Create a document that we are starting at, which may be missing 'transactionIndex'
4840        let mut properties = BTreeMap::new();
4841        properties.insert("status".to_string(), Value::U64(0));
4842        // We intentionally omit 'transactionIndex' to simulate missing field
4843
4844        let starts_at_document = DocumentV0 {
4845            contract_version: None,
4846            id: Identifier::from([3u8; 32]), // The same as start_at
4847            owner_id: Identifier::random(),
4848            properties,
4849            revision: None,
4850            created_at: None,
4851            updated_at: None,
4852            transferred_at: None,
4853            created_at_block_height: None,
4854            updated_at_block_height: None,
4855            transferred_at_block_height: None,
4856            created_at_core_block_height: None,
4857            updated_at_core_block_height: None,
4858            transferred_at_core_block_height: None,
4859            creator_id: None,
4860            moderated_at: None,
4861            moderated_by: None,
4862        }
4863        .into();
4864
4865        // Attempt to construct the path query
4866        let result = drive_document_query
4867            .construct_path_query(Some(starts_at_document), platform_version)
4868            .expect("expected to construct a path query");
4869
4870        assert_eq!(
4871            result
4872                .clone()
4873                .query
4874                .query
4875                .default_subquery_branch
4876                .subquery
4877                .expect("expected subquery")
4878                .items,
4879            Query::new_range_full().items
4880        );
4881    }
4882
4883    /// Unit coverage for the v1 multi-`In` path-query lowering. These
4884    /// mirror the storage-backed integration tests in
4885    /// `tests/query_tests.rs::multi_in_tests`, but exercise the lowering
4886    /// as the pure function it is (contract in, path query out), so the
4887    /// selection, validation, and rejection branches are covered by the
4888    /// lib test target.
4889    mod multiple_in_clause_lowering {
4890        use super::*;
4891        use crate::error::query::QuerySyntaxError;
4892        use crate::error::Error;
4893
4894        fn family_contract() -> DataContract {
4895            json_document_to_contract(
4896                "tests/supporting_files/contract/family/family-contract.json",
4897                false,
4898                PlatformVersion::latest(),
4899            )
4900            .expect("expected to load family contract")
4901        }
4902
4903        fn text_array(values: &[&str]) -> Value {
4904            Value::Array(
4905                values
4906                    .iter()
4907                    .map(|value| Value::Text(value.to_string()))
4908                    .collect(),
4909            )
4910        }
4911
4912        fn in_clause(field: &str, values: &[&str]) -> WhereClause {
4913            WhereClause {
4914                field: field.to_string(),
4915                operator: WhereOperator::In,
4916                value: text_array(values),
4917            }
4918        }
4919
4920        fn ascending_order_by(fields: &[&str]) -> IndexMap<String, OrderClause> {
4921            fields
4922                .iter()
4923                .map(|field| {
4924                    (
4925                        field.to_string(),
4926                        OrderClause {
4927                            field: field.to_string(),
4928                            ascending: true,
4929                        },
4930                    )
4931                })
4932                .collect()
4933        }
4934
4935        fn person_query<'a>(
4936            contract: &'a DataContract,
4937            where_clauses: Vec<WhereClause>,
4938            order_by_fields: &[&str],
4939        ) -> DriveDocumentQuery<'a> {
4940            let internal_clauses =
4941                InternalClauses::extract_from_clauses(where_clauses, PlatformVersion::latest())
4942                    .expect("clauses should group structurally");
4943            DriveDocumentQuery {
4944                contract,
4945                document_type: contract
4946                    .document_type_for_name("person")
4947                    .expect("person document type should exist"),
4948                internal_clauses,
4949                offset: None,
4950                limit: Some(100),
4951                order_by: ascending_order_by(order_by_fields),
4952                start_at: None,
4953                start_at_included: false,
4954                block_time_ms: None,
4955                resolved_time_ranges: vec![],
4956                sub_queries: vec![],
4957            }
4958        }
4959
4960        #[test]
4961        fn two_in_clauses_lower_to_nested_key_sets() {
4962            let contract = family_contract();
4963            let platform_version = PlatformVersion::latest();
4964            let query = person_query(
4965                &contract,
4966                vec![
4967                    in_clause("firstName", &["Adey", "Briney"]),
4968                    in_clause("lastName", &["Kriskov", "Randolf"]),
4969                ],
4970                &["firstName", "lastName"],
4971            );
4972
4973            let path_query = query
4974                .construct_path_query(None, platform_version)
4975                .expect("two in clauses should lower at protocol version 14");
4976
4977            // The path descends to the first in field of the
4978            // [firstName, lastName] index
4979            assert_eq!(
4980                path_query.path.last().expect("path should not be empty"),
4981                &b"firstName".to_vec()
4982            );
4983
4984            // Outer level: one key per firstName in value
4985            let outer = &path_query.query.query;
4986            assert_eq!(outer.items.len(), 2);
4987            assert!(outer.left_to_right);
4988
4989            // Second level: a key set over lastName under the subquery
4990            // path [lastName]
4991            assert_eq!(
4992                outer.default_subquery_branch.subquery_path,
4993                Some(vec![b"lastName".to_vec()])
4994            );
4995            let inner = outer
4996                .default_subquery_branch
4997                .subquery
4998                .as_deref()
4999                .expect("expected a lastName subquery");
5000            assert_eq!(inner.items.len(), 2);
5001
5002            // Terminal level: the document id tree under [0]
5003            assert_eq!(
5004                inner.default_subquery_branch.subquery_path,
5005                Some(vec![vec![0]])
5006            );
5007        }
5008
5009        #[test]
5010        #[cfg(feature = "cbor_query")]
5011        fn two_in_clauses_survive_cbor_round_trip() {
5012            let contract = family_contract();
5013            let mut query = person_query(
5014                &contract,
5015                vec![
5016                    in_clause("firstName", &["Adey", "Briney"]),
5017                    in_clause("lastName", &["Kriskov", "Randolf"]),
5018                ],
5019                &["firstName", "lastName"],
5020            );
5021            // `from_cbor` defaults start_at_included to true when no cursor
5022            // is present; align so the round trip compares equal
5023            query.start_at_included = true;
5024
5025            let cbor = query.to_cbor().expect("should serialize cbor");
5026            let deserialized = DriveDocumentQuery::from_cbor(
5027                &cbor,
5028                &contract,
5029                contract
5030                    .document_type_for_name("person")
5031                    .expect("person document type should exist"),
5032                &DriveConfig::default(),
5033                PlatformVersion::latest(),
5034            )
5035            .expect("should deserialize cbor");
5036
5037            assert_eq!(query, deserialized);
5038            assert_eq!(
5039                deserialized
5040                    .internal_clauses
5041                    .in_clauses
5042                    .iter()
5043                    .map(|in_clause| in_clause.field.as_str())
5044                    .collect::<Vec<_>>(),
5045                vec!["firstName", "lastName"],
5046                "both in clauses must survive the round trip in order"
5047            );
5048        }
5049
5050        #[test]
5051        fn descending_order_by_on_left_over_property_is_honored() {
5052            let contract = family_contract();
5053            let platform_version = PlatformVersion::latest();
5054            // [firstName, middleName, lastName]: two in levels, lastName
5055            // left over with an explicit descending order
5056            let mut query = person_query(
5057                &contract,
5058                vec![
5059                    in_clause("firstName", &["Adey", "Briney"]),
5060                    in_clause("middleName", &["Ivanna", "Evangeline"]),
5061                ],
5062                &["firstName", "middleName"],
5063            );
5064            query.order_by.insert(
5065                "lastName".to_string(),
5066                OrderClause {
5067                    field: "lastName".to_string(),
5068                    ascending: false,
5069                },
5070            );
5071
5072            let path_query = query
5073                .construct_path_query(None, platform_version)
5074                .expect("two in clauses with a left-over order should lower");
5075
5076            let outer = &path_query.query.query;
5077            let middle = outer
5078                .default_subquery_branch
5079                .subquery
5080                .as_deref()
5081                .expect("expected a middleName subquery");
5082            assert_eq!(
5083                middle.default_subquery_branch.subquery_path,
5084                Some(vec![b"lastName".to_vec()])
5085            );
5086            let left_over_level = middle
5087                .default_subquery_branch
5088                .subquery
5089                .as_deref()
5090                .expect("expected a lastName subquery");
5091            assert!(
5092                !left_over_level.left_to_right,
5093                "left-over lastName level must honor the descending order by"
5094            );
5095
5096            // Without an order by entry the level falls back to the index
5097            // property's direction (ascending)
5098            query.order_by.shift_remove("lastName");
5099            let path_query = query
5100                .construct_path_query(None, platform_version)
5101                .expect("two in clauses should lower");
5102            let left_over_level = path_query
5103                .query
5104                .query
5105                .default_subquery_branch
5106                .subquery
5107                .as_deref()
5108                .expect("expected a middleName subquery")
5109                .default_subquery_branch
5110                .subquery
5111                .as_deref()
5112                .expect("expected a lastName subquery");
5113            assert!(left_over_level.left_to_right);
5114        }
5115
5116        #[test]
5117        fn two_in_clauses_rejected_at_protocol_version_13() {
5118            let contract = family_contract();
5119            let platform_version_13 =
5120                PlatformVersion::get(13).expect("protocol version 13 should exist");
5121            let query = person_query(
5122                &contract,
5123                vec![
5124                    in_clause("firstName", &["Adey", "Briney"]),
5125                    in_clause("lastName", &["Kriskov", "Randolf"]),
5126                ],
5127                &["firstName", "lastName"],
5128            );
5129
5130            let error = query
5131                .construct_path_query(None, platform_version_13)
5132                .expect_err("multiple in clauses must be rejected before protocol version 14");
5133            assert!(
5134                matches!(error, Error::Query(QuerySyntaxError::MultipleInClauses(_))),
5135                "expected MultipleInClauses, got {error:?}"
5136            );
5137
5138            query
5139                .construct_path_query(None, PlatformVersion::latest())
5140                .expect("the same query should lower at protocol version 14");
5141        }
5142
5143        #[test]
5144        fn equality_prefix_two_in_clauses_and_trailing_range_lowering() {
5145            let contract = family_contract();
5146            let platform_version = PlatformVersion::latest();
5147            let mut query = person_query(
5148                &contract,
5149                vec![
5150                    WhereClause {
5151                        field: "age".to_string(),
5152                        operator: WhereOperator::Equal,
5153                        value: Value::U8(30),
5154                    },
5155                    in_clause("firstName", &["Adey", "Briney"]),
5156                    in_clause("middleName", &["Ivanna", "Evangeline"]),
5157                    WhereClause {
5158                        field: "lastName".to_string(),
5159                        operator: WhereOperator::GreaterThan,
5160                        value: Value::Text("M".to_string()),
5161                    },
5162                ],
5163                &["firstName", "middleName", "lastName"],
5164            );
5165            query.limit = Some(50);
5166
5167            // Matches the [age, firstName, middleName, lastName] index:
5168            // equality prefix on age, then two consecutive in levels, then
5169            // the range level
5170            let path_query = query
5171                .construct_path_query(None, platform_version)
5172                .expect("equality + in + in + range should lower");
5173
5174            let path_len = path_query.path.len();
5175            assert_eq!(path_query.path[path_len - 3], b"age".to_vec());
5176            assert_eq!(
5177                path_query.path.last().expect("path should not be empty"),
5178                &b"firstName".to_vec()
5179            );
5180
5181            let outer = &path_query.query.query;
5182            assert_eq!(outer.items.len(), 2);
5183            assert_eq!(
5184                outer.default_subquery_branch.subquery_path,
5185                Some(vec![b"middleName".to_vec()])
5186            );
5187            let middle = outer
5188                .default_subquery_branch
5189                .subquery
5190                .as_deref()
5191                .expect("expected a middleName subquery");
5192            assert_eq!(middle.items.len(), 2);
5193            assert_eq!(
5194                middle.default_subquery_branch.subquery_path,
5195                Some(vec![b"lastName".to_vec()])
5196            );
5197            let range_level = middle
5198                .default_subquery_branch
5199                .subquery
5200                .as_deref()
5201                .expect("expected a lastName subquery");
5202            // The trailing range is a single range item, not a key set
5203            assert_eq!(range_level.items.len(), 1);
5204            assert_eq!(
5205                range_level.default_subquery_branch.subquery_path,
5206                Some(vec![vec![0]])
5207            );
5208        }
5209
5210        #[test]
5211        fn cross_product_above_cap_is_rejected() {
5212            let contract = family_contract();
5213            let first_names: Vec<String> = (0..20).map(|i| format!("First{i:02}")).collect();
5214            let last_names: Vec<String> = (0..6).map(|i| format!("Last{i}")).collect();
5215            let query = person_query(
5216                &contract,
5217                vec![
5218                    WhereClause {
5219                        field: "firstName".to_string(),
5220                        operator: WhereOperator::In,
5221                        value: Value::Array(first_names.iter().cloned().map(Value::Text).collect()),
5222                    },
5223                    WhereClause {
5224                        field: "lastName".to_string(),
5225                        operator: WhereOperator::In,
5226                        value: Value::Array(last_names.iter().cloned().map(Value::Text).collect()),
5227                    },
5228                ],
5229                &["firstName", "lastName"],
5230            );
5231
5232            let error = query
5233                .construct_path_query(None, PlatformVersion::latest())
5234                .expect_err("a 120-branch cross product must be rejected");
5235            assert!(
5236                matches!(error, Error::Query(QuerySyntaxError::InvalidInClause(_))),
5237                "expected InvalidInClause, got {error:?}"
5238            );
5239        }
5240
5241        #[test]
5242        fn non_consecutive_in_fields_are_rejected() {
5243            let contract = family_contract();
5244            // [firstName, middleName, lastName] holds middleName and
5245            // lastName at positions 1 and 2 with no equality on firstName,
5246            // so no index conforms
5247            let query = person_query(
5248                &contract,
5249                vec![
5250                    in_clause("middleName", &["Ivanna", "Evangeline"]),
5251                    in_clause("lastName", &["Kriskov", "Randolf"]),
5252                ],
5253                &["middleName", "lastName"],
5254            );
5255
5256            let error = query
5257                .construct_path_query(None, PlatformVersion::latest())
5258                .expect_err("non-consecutive in clauses must be rejected");
5259            assert!(
5260                matches!(
5261                    error,
5262                    Error::Query(QuerySyntaxError::WhereClauseOnNonIndexedProperty(_))
5263                ),
5264                "expected WhereClauseOnNonIndexedProperty, got {error:?}"
5265            );
5266        }
5267
5268        #[test]
5269        fn cursor_pagination_is_rejected() {
5270            let contract = family_contract();
5271            let mut query = person_query(
5272                &contract,
5273                vec![
5274                    in_clause("firstName", &["Adey", "Briney"]),
5275                    in_clause("lastName", &["Kriskov", "Randolf"]),
5276                ],
5277                &["firstName", "lastName"],
5278            );
5279            query.start_at = Some([5u8; 32]);
5280            query.start_at_included = false;
5281
5282            let error = query
5283                .construct_path_query(None, PlatformVersion::latest())
5284                .expect_err("cursor pagination with multiple in clauses must be rejected");
5285            assert!(
5286                matches!(error, Error::Query(QuerySyntaxError::Unsupported(_))),
5287                "expected Unsupported, got {error:?}"
5288            );
5289        }
5290
5291        #[test]
5292        fn missing_order_by_on_an_in_field_is_rejected() {
5293            let contract = family_contract();
5294            let query = person_query(
5295                &contract,
5296                vec![
5297                    in_clause("firstName", &["Adey", "Briney"]),
5298                    in_clause("lastName", &["Kriskov", "Randolf"]),
5299                ],
5300                &["firstName"],
5301            );
5302
5303            let error = query
5304                .construct_path_query(None, PlatformVersion::latest())
5305                .expect_err("missing order by on an in field must be rejected");
5306            // Index selection rejects the shape first: the order-by
5307            // continuity rule in `Index::matches` disqualifies every
5308            // candidate index before the per-field `MissingOrderByForRange`
5309            // guard could fire
5310            assert!(
5311                matches!(
5312                    error,
5313                    Error::Query(QuerySyntaxError::WhereClauseOnNonIndexedProperty(_))
5314                ),
5315                "expected WhereClauseOnNonIndexedProperty, got {error:?}"
5316            );
5317        }
5318    }
5319}