Skip to main content

drive/query/
mod.rs

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