Skip to main content

drive/query/
mod.rs

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