Skip to main content

drive/query/
mod.rs

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