Skip to main content

drive/query/
conditions.rs

1//! Query Conditions
2//!
3
4use crate::error::drive::DriveError;
5use crate::error::query::QuerySyntaxError;
6use crate::error::Error;
7use crate::query::where_clause_grouping::group_where_clauses;
8use crate::query::{QuerySyntaxSimpleValidationResult, QuerySyntaxValidationResult};
9#[cfg(any(feature = "server", feature = "verify"))]
10use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
11use dpp::data_contract::document_type::methods::DocumentTypeV0Methods;
12#[cfg(any(feature = "server", feature = "verify"))]
13use dpp::data_contract::document_type::MAX_INDEX_SIZE;
14use dpp::data_contract::document_type::{
15    typed_array_property, DocumentPropertyType, DocumentType, DocumentTypeRef,
16};
17use dpp::document::document_methods::DocumentMethodsV0;
18use dpp::document::Document;
19use dpp::platform_value::Value;
20use dpp::version::PlatformVersion;
21use grovedb::Query;
22use sqlparser::ast;
23use std::borrow::Cow;
24use std::cmp::Ordering;
25use std::collections::BTreeMap;
26use std::fmt::Display;
27use WhereOperator::{
28    Between, BetweenExcludeBounds, BetweenExcludeLeft, BetweenExcludeRight, Contains, ContainsAny,
29    Equal, GreaterThan, GreaterThanOrEquals, In, LessThan, LessThanOrEquals, StartsWith,
30};
31
32/// Converts SQL values to CBOR.
33fn sql_value_to_platform_value(sql_value: ast::Value) -> Option<Value> {
34    match sql_value {
35        ast::Value::Boolean(bool) => Some(Value::Bool(bool)),
36        ast::Value::Number(num, _) => {
37            let number_as_string = num as String;
38            if number_as_string.contains('.') {
39                // Float
40                let num_as_float = number_as_string.parse::<f64>().ok();
41                num_as_float.map(Value::Float)
42            } else {
43                // Integer
44                let num_as_int = number_as_string.parse::<i64>().ok();
45                num_as_int.map(Value::I64)
46            }
47        }
48        ast::Value::DoubleQuotedString(s) => Some(Value::Text(s)),
49        ast::Value::SingleQuotedString(s) => Some(Value::Text(s)),
50        ast::Value::HexStringLiteral(s) => Some(Value::Text(s)),
51        ast::Value::NationalStringLiteral(s) => Some(Value::Text(s)),
52        _ => None,
53    }
54}
55
56/// Where operator arguments
57#[derive(Copy, Clone, Debug, PartialEq, Eq)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub enum WhereOperator {
60    /// Equal
61    Equal,
62    /// Greater than
63    GreaterThan,
64    /// Greater than or equal
65    GreaterThanOrEquals,
66    /// Less than
67    LessThan,
68    /// Less than or equal
69    LessThanOrEquals,
70    /// Between
71    Between,
72    /// Between excluding bounds
73    BetweenExcludeBounds,
74    /// Between excluding left bound
75    BetweenExcludeLeft,
76    /// Between excluding right bound
77    BetweenExcludeRight,
78    /// In
79    In,
80    /// Starts with
81    StartsWith,
82    /// An element of a typed array equals the value (protocol version 14): the
83    /// array is read through an index naming it, which keys a document once by
84    /// each of its distinct elements, so the clause is an equality on that key.
85    Contains,
86    /// An element of a typed array is one of the values (protocol version 14):
87    /// an `In` on the elements an index keys the array by. A document holding
88    /// several of the values is under each of them; a documents query returns
89    /// it once, at its first. On an indexOnly type each entry is a document of
90    /// its own, rebuilt as one listing the element it sits under, so a
91    /// documents query returns one per value the document holds.
92    ContainsAny,
93}
94
95impl WhereOperator {
96    /// Matches the where operator argument and returns true if it allows `flip` function
97    pub fn allows_flip(&self) -> bool {
98        match self {
99            Equal => true,
100            GreaterThan => true,
101            GreaterThanOrEquals => true,
102            LessThan => true,
103            LessThanOrEquals => true,
104            Between => false,
105            BetweenExcludeBounds => false,
106            BetweenExcludeLeft => false,
107            BetweenExcludeRight => false,
108            In => false,
109            StartsWith => false,
110            Contains => false,
111            ContainsAny => false,
112        }
113    }
114
115    /// Flips the where operator
116    pub fn flip(&self) -> Result<WhereOperator, Error> {
117        match self {
118            Equal => Ok(Equal),
119            GreaterThan => Ok(LessThan),
120            GreaterThanOrEquals => Ok(LessThanOrEquals),
121            LessThan => Ok(GreaterThan),
122            LessThanOrEquals => Ok(GreaterThanOrEquals),
123            Between => Err(Error::Query(QuerySyntaxError::InvalidWhereClauseOrder(
124                "Between clause order invalid",
125            ))),
126            BetweenExcludeBounds => Err(Error::Query(QuerySyntaxError::InvalidWhereClauseOrder(
127                "Between clause order invalid",
128            ))),
129            BetweenExcludeLeft => Err(Error::Query(QuerySyntaxError::InvalidWhereClauseOrder(
130                "Between clause order invalid",
131            ))),
132            BetweenExcludeRight => Err(Error::Query(QuerySyntaxError::InvalidWhereClauseOrder(
133                "Between clause order invalid",
134            ))),
135            In => Err(Error::Query(QuerySyntaxError::InvalidWhereClauseOrder(
136                "In clause order invalid",
137            ))),
138            StartsWith => Err(Error::Query(QuerySyntaxError::InvalidWhereClauseOrder(
139                "Startswith clause order invalid",
140            ))),
141            Contains => Err(Error::Query(QuerySyntaxError::InvalidWhereClauseOrder(
142                "Contains clause order invalid",
143            ))),
144            ContainsAny => Err(Error::Query(QuerySyntaxError::InvalidWhereClauseOrder(
145                "ContainsAny clause order invalid",
146            ))),
147        }
148    }
149}
150
151impl WhereOperator {
152    /// Returns true if the where operator result is a range
153    pub const fn is_range(self) -> bool {
154        match self {
155            Equal | Contains => false,
156            GreaterThan | GreaterThanOrEquals | LessThan | LessThanOrEquals | Between
157            | BetweenExcludeBounds | BetweenExcludeLeft | BetweenExcludeRight | In | StartsWith
158            | ContainsAny => true,
159        }
160    }
161
162    /// The operator this one keys an index level like: an element of a typed
163    /// array is an index key of its own, so `Contains` keys like `Equal` and
164    /// `ContainsAny` like `In`. Every other operator keys as itself.
165    pub const fn as_key_operator(self) -> Self {
166        match self {
167            Contains => Equal,
168            ContainsAny => In,
169            other => other,
170        }
171    }
172
173    /// Whether the operator reads the elements of a typed array.
174    pub const fn reads_array_elements(self) -> bool {
175        matches!(self, Contains | ContainsAny)
176    }
177
178    /// Matches the where operator as a string and returns it as a proper `WhereOperator`
179    pub(crate) fn from_string(string: &str) -> Option<Self> {
180        match string {
181            "=" | "==" => Some(Equal),
182            ">" => Some(GreaterThan),
183            ">=" => Some(GreaterThanOrEquals),
184            "<" => Some(LessThan),
185            "<=" => Some(LessThanOrEquals),
186            "Between" | "between" => Some(Between),
187            "BetweenExcludeBounds"
188            | "betweenExcludeBounds"
189            | "betweenexcludebounds"
190            | "between_exclude_bounds" => Some(BetweenExcludeBounds),
191            "BetweenExcludeLeft"
192            | "betweenExcludeLeft"
193            | "betweenexcludeleft"
194            | "between_exclude_left" => Some(BetweenExcludeLeft),
195            "BetweenExcludeRight"
196            | "betweenExcludeRight"
197            | "betweenexcluderight"
198            | "between_exclude_right" => Some(BetweenExcludeRight),
199            "In" | "in" => Some(In),
200            "StartsWith" | "startsWith" | "startswith" | "starts_with" => Some(StartsWith),
201            "Contains" | "contains" => Some(Contains),
202            "ContainsAny" | "containsAny" | "containsany" | "contains_any" => Some(ContainsAny),
203            &_ => None,
204        }
205    }
206
207    /// Matches the where operator as a SQL operator and returns it as a proper `WhereOperator`
208    pub(crate) fn from_sql_operator(sql_operator: ast::BinaryOperator) -> Option<Self> {
209        match sql_operator {
210            ast::BinaryOperator::Eq => Some(Equal),
211            ast::BinaryOperator::Gt => Some(GreaterThan),
212            ast::BinaryOperator::GtEq => Some(GreaterThanOrEquals),
213            ast::BinaryOperator::Lt => Some(LessThan),
214            ast::BinaryOperator::LtEq => Some(LessThanOrEquals),
215            _ => None,
216        }
217    }
218
219    /// Shared operator evaluator for both WhereClause and ValueClause
220    pub fn eval(&self, left_value: &Value, right_value: &Value) -> bool {
221        match self {
222            Equal => left_value == right_value,
223            GreaterThan => left_value > right_value,
224            GreaterThanOrEquals => left_value >= right_value,
225            LessThan => left_value < right_value,
226            LessThanOrEquals => left_value <= right_value,
227            In => match right_value {
228                Value::Array(array) => array.contains(left_value),
229                Value::Bytes(bytes) => match left_value {
230                    Value::U8(b) => bytes.contains(b),
231                    _ => false,
232                },
233                _ => false,
234            },
235            Between => match right_value {
236                Value::Array(bounds) if bounds.len() == 2 => {
237                    match bounds[0].partial_cmp(&bounds[1]) {
238                        Some(Ordering::Less) => {
239                            left_value >= &bounds[0] && left_value <= &bounds[1]
240                        }
241                        _ => false,
242                    }
243                }
244                _ => false,
245            },
246            BetweenExcludeBounds => match right_value {
247                Value::Array(bounds) if bounds.len() == 2 => {
248                    match bounds[0].partial_cmp(&bounds[1]) {
249                        Some(Ordering::Less) => left_value > &bounds[0] && left_value < &bounds[1],
250                        _ => false,
251                    }
252                }
253                _ => false,
254            },
255            BetweenExcludeLeft => match right_value {
256                Value::Array(bounds) if bounds.len() == 2 => {
257                    match bounds[0].partial_cmp(&bounds[1]) {
258                        Some(Ordering::Less) => left_value > &bounds[0] && left_value <= &bounds[1],
259                        _ => false,
260                    }
261                }
262                _ => false,
263            },
264            BetweenExcludeRight => match right_value {
265                Value::Array(bounds) if bounds.len() == 2 => {
266                    match bounds[0].partial_cmp(&bounds[1]) {
267                        Some(Ordering::Less) => left_value >= &bounds[0] && left_value < &bounds[1],
268                        _ => false,
269                    }
270                }
271                _ => false,
272            },
273            StartsWith => match (left_value, right_value) {
274                (Value::Text(text), Value::Text(prefix)) => text.starts_with(prefix.as_str()),
275                _ => false,
276            },
277            Contains => match left_value {
278                Value::Array(elements) => elements.contains(right_value),
279                _ => false,
280            },
281            // An element matches as the `in` it keys like, so u8 elements
282            // also match a list given as bytes.
283            ContainsAny => match left_value {
284                Value::Array(elements) => {
285                    elements.iter().any(|element| In.eval(element, right_value))
286                }
287                _ => false,
288            },
289        }
290    }
291
292    /// Validates that a value matches the expected shape for this operator and property type
293    #[cfg(any(feature = "server", feature = "verify"))]
294    pub fn value_shape_ok(&self, value: &Value, property_type: &DocumentPropertyType) -> bool {
295        match self {
296            Equal => true,
297            In => matches!(value, Value::Array(_) | Value::Bytes(_)),
298            StartsWith => matches!(value, Value::Text(_)),
299            // One element, of the array's element type (checked with the type).
300            Contains => !matches!(value, Value::Array(_) | Value::Null),
301            ContainsAny => matches!(value, Value::Array(_)),
302            GreaterThan | GreaterThanOrEquals | LessThan | LessThanOrEquals => {
303                match property_type {
304                    DocumentPropertyType::F64 => is_numeric_value(value),
305                    DocumentPropertyType::String(_) => {
306                        matches!(value, Value::Text(_))
307                    }
308                    _ => matches!(
309                        value,
310                        Value::U128(_)
311                            | Value::I128(_)
312                            | Value::U64(_)
313                            | Value::I64(_)
314                            | Value::U32(_)
315                            | Value::I32(_)
316                            | Value::U16(_)
317                            | Value::I16(_)
318                            | Value::U8(_)
319                            | Value::I8(_)
320                    ),
321                }
322            }
323            Between | BetweenExcludeBounds | BetweenExcludeLeft | BetweenExcludeRight => {
324                if let Value::Array(arr) = value {
325                    arr.len() == 2
326                        && arr.iter().all(|x| match property_type {
327                            DocumentPropertyType::F64 => is_numeric_value(x),
328                            DocumentPropertyType::String(_) => {
329                                matches!(x, Value::Text(_))
330                            }
331                            _ => matches!(
332                                x,
333                                Value::U128(_)
334                                    | Value::I128(_)
335                                    | Value::U64(_)
336                                    | Value::I64(_)
337                                    | Value::U32(_)
338                                    | Value::I32(_)
339                                    | Value::U16(_)
340                                    | Value::I16(_)
341                                    | Value::U8(_)
342                                    | Value::I8(_)
343                            ),
344                        })
345                } else {
346                    false
347                }
348            }
349        }
350    }
351}
352
353impl Display for WhereOperator {
354    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355        let s = match self {
356            Equal => "=",
357            GreaterThan => ">",
358            GreaterThanOrEquals => ">=",
359            LessThan => "<",
360            LessThanOrEquals => "<=",
361            Between => "Between",
362            BetweenExcludeBounds => "BetweenExcludeBounds",
363            BetweenExcludeLeft => "BetweenExcludeLeft",
364            BetweenExcludeRight => "BetweenExcludeRight",
365            In => "In",
366            StartsWith => "StartsWith",
367            Contains => "Contains",
368            ContainsAny => "ContainsAny",
369        };
370
371        write!(f, "{}", s)
372    }
373}
374
375impl From<WhereOperator> for Value {
376    fn from(value: WhereOperator) -> Self {
377        Self::Text(value.to_string())
378    }
379}
380
381/// Where clause struct
382#[derive(Clone, Debug, PartialEq)]
383#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
384pub struct WhereClause {
385    /// Field
386    pub field: String,
387    /// Operator
388    pub operator: WhereOperator,
389    /// Value
390    pub value: Value,
391}
392
393impl<'a> WhereClause {
394    /// Returns true if the `WhereClause` is an identifier
395    pub fn is_identifier(&self) -> bool {
396        self.field == "$id"
397    }
398
399    /// Returns the where clause `in` values if they are an array of values, else an error
400    pub fn in_values(&self) -> QuerySyntaxValidationResult<Cow<'_, Vec<Value>>> {
401        let in_values = match &self.value {
402            Value::Array(array) => Cow::Borrowed(array),
403            Value::Bytes(bytes) => Cow::Owned(bytes.iter().map(|int| Value::U8(*int)).collect()),
404            _ => {
405                return QuerySyntaxValidationResult::new_with_error(
406                    QuerySyntaxError::InvalidInClause(
407                        "when using in operator you must provide an array of values".to_string(),
408                    ),
409                )
410            }
411        };
412
413        let len = in_values.len();
414        if len == 0 {
415            return QuerySyntaxValidationResult::new_with_error(QuerySyntaxError::InvalidInClause(
416                "in clause must have at least 1 value".to_string(),
417            ));
418        }
419
420        if len > 100 {
421            return QuerySyntaxValidationResult::new_with_error(QuerySyntaxError::InvalidInClause(
422                "in clause must have at most 100 values".to_string(),
423            ));
424        }
425
426        // Throw an error if there are duplicates
427        if (1..in_values.len()).any(|i| in_values[i..].contains(&in_values[i - 1])) {
428            return QuerySyntaxValidationResult::new_with_error(QuerySyntaxError::InvalidInClause(
429                "there should be no duplicates values for In query".to_string(),
430            ));
431        }
432        QuerySyntaxValidationResult::new_with_data(in_values)
433    }
434
435    /// Returns true if the less than where clause is true
436    pub fn less_than(&self, other: &Self, allow_eq: bool) -> Result<bool, Error> {
437        match (&self.value, &other.value) {
438            (Value::I128(x), Value::I128(y)) => {
439                if allow_eq {
440                    Ok(x.le(y))
441                } else {
442                    Ok(x.lt(y))
443                }
444            }
445            (Value::U128(x), Value::U128(y)) => {
446                if allow_eq {
447                    Ok(x.le(y))
448                } else {
449                    Ok(x.lt(y))
450                }
451            }
452            (Value::I64(x), Value::I64(y)) => {
453                if allow_eq {
454                    Ok(x.le(y))
455                } else {
456                    Ok(x.lt(y))
457                }
458            }
459            (Value::U64(x), Value::U64(y)) => {
460                if allow_eq {
461                    Ok(x.le(y))
462                } else {
463                    Ok(x.lt(y))
464                }
465            }
466            (Value::I32(x), Value::I32(y)) => {
467                if allow_eq {
468                    Ok(x.le(y))
469                } else {
470                    Ok(x.lt(y))
471                }
472            }
473            (Value::U32(x), Value::U32(y)) => {
474                if allow_eq {
475                    Ok(x.le(y))
476                } else {
477                    Ok(x.lt(y))
478                }
479            }
480            (Value::I16(x), Value::I16(y)) => {
481                if allow_eq {
482                    Ok(x.le(y))
483                } else {
484                    Ok(x.lt(y))
485                }
486            }
487            (Value::U16(x), Value::U16(y)) => {
488                if allow_eq {
489                    Ok(x.le(y))
490                } else {
491                    Ok(x.lt(y))
492                }
493            }
494            (Value::I8(x), Value::I8(y)) => {
495                if allow_eq {
496                    Ok(x.le(y))
497                } else {
498                    Ok(x.lt(y))
499                }
500            }
501            (Value::U8(x), Value::U8(y)) => {
502                if allow_eq {
503                    Ok(x.le(y))
504                } else {
505                    Ok(x.lt(y))
506                }
507            }
508            (Value::Bytes(x), Value::Bytes(y)) => {
509                if allow_eq {
510                    Ok(x.le(y))
511                } else {
512                    Ok(x.lt(y))
513                }
514            }
515            (Value::Float(x), Value::Float(y)) => {
516                if allow_eq {
517                    Ok(x.le(y))
518                } else {
519                    Ok(x.lt(y))
520                }
521            }
522            (Value::Text(x), Value::Text(y)) => {
523                if allow_eq {
524                    Ok(x.le(y))
525                } else {
526                    Ok(x.lt(y))
527                }
528            }
529            _ => Err(Error::Query(QuerySyntaxError::RangeClausesNotGroupable(
530                "range clauses can not be coherently grouped",
531            ))),
532        }
533    }
534
535    /// Returns a `WhereClause` given a list of clause components
536    pub fn from_components(clause_components: &'a [Value]) -> Result<Self, Error> {
537        if clause_components.len() != 3 {
538            return Err(Error::Query(
539                QuerySyntaxError::InvalidWhereClauseComponents(
540                    "where clauses should have at most 3 components",
541                ),
542            ));
543        }
544
545        let field_value = clause_components
546            .first()
547            .expect("check above enforces it exists");
548        let field_ref = field_value.as_text().ok_or(Error::Query(
549            QuerySyntaxError::InvalidWhereClauseComponents(
550                "first field of where component should be a string",
551            ),
552        ))?;
553        let field = String::from(field_ref);
554
555        let operator_value = clause_components
556            .get(1)
557            .expect("check above enforces it exists");
558        let operator_string = operator_value.as_text().ok_or(Error::Query(
559            QuerySyntaxError::InvalidWhereClauseComponents(
560                "second field of where component should be a string",
561            ),
562        ))?;
563
564        let operator = WhereOperator::from_string(operator_string).ok_or({
565            Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(
566                "second field of where component should be a known operator",
567            ))
568        })?;
569
570        let value = clause_components
571            .get(2)
572            .ok_or(Error::Query(
573                QuerySyntaxError::InvalidWhereClauseComponents(
574                    "third field of where component should exist",
575                ),
576            ))?
577            .clone();
578
579        Ok(WhereClause {
580            field,
581            operator,
582            value,
583        })
584    }
585
586    pub(crate) fn lower_bound_clause(
587        where_clauses: &'a [&WhereClause],
588    ) -> Result<Option<&'a Self>, Error> {
589        let lower_range_clauses: Vec<&&WhereClause> = where_clauses
590            .iter()
591            .filter(|&where_clause| {
592                matches!(where_clause.operator, GreaterThan | GreaterThanOrEquals)
593            })
594            .collect::<Vec<&&WhereClause>>();
595        match lower_range_clauses.len() {
596            0 => Ok(None),
597            1 => Ok(Some(lower_range_clauses.first().unwrap())),
598            _ => Err(Error::Query(QuerySyntaxError::MultipleRangeClauses(
599                "there can only at most one range clause with a lower bound",
600            ))),
601        }
602    }
603
604    pub(crate) fn upper_bound_clause(
605        where_clauses: &'a [&WhereClause],
606    ) -> Result<Option<&'a Self>, Error> {
607        let upper_range_clauses: Vec<&&WhereClause> = where_clauses
608            .iter()
609            .filter(|&where_clause| matches!(where_clause.operator, LessThan | LessThanOrEquals))
610            .collect::<Vec<&&WhereClause>>();
611        match upper_range_clauses.len() {
612            0 => Ok(None),
613            1 => Ok(Some(upper_range_clauses.first().unwrap())),
614            _ => Err(Error::Query(QuerySyntaxError::MultipleRangeClauses(
615                "there can only at most one range clause with a lower bound",
616            ))),
617        }
618    }
619
620    /// Given a list of where clauses, returns them in groups of equal, range,
621    /// and in clauses, under the platform version's grammar.
622    ///
623    /// Versioned via
624    /// `platform_version.drive.methods.document.query.where_clause_grouping`
625    /// (see [`crate::query::where_clause_grouping`]): v0 — every protocol
626    /// version up to 13 — rejects more than one non-primary-key `In` clause
627    /// with `MultipleInClauses` before any other same-field checks; v1
628    /// (protocol version 14) groups multiple `In` clauses structurally, and
629    /// their acceptance is decided by the versioned path-query lowering.
630    #[allow(clippy::type_complexity)]
631    pub(crate) fn group_clauses(
632        where_clauses: &'a [WhereClause],
633        platform_version: &PlatformVersion,
634    ) -> Result<(BTreeMap<String, Self>, Option<Self>, Vec<Self>), Error> {
635        group_where_clauses(where_clauses, platform_version)
636    }
637
638    fn split_value_for_between(
639        &self,
640        document_type: DocumentTypeRef,
641        platform_version: &PlatformVersion,
642    ) -> Result<(Vec<u8>, Vec<u8>), Error> {
643        let in_values = match &self.value {
644            Value::Array(array) => Some(array),
645            _ => None,
646        }
647        .ok_or({
648            Error::Query(QuerySyntaxError::InvalidBetweenClause(
649                "when using between operator you must provide a tuple array of values",
650            ))
651        })?;
652        if in_values.len() != 2 {
653            return Err(Error::Query(QuerySyntaxError::InvalidBetweenClause(
654                "when using between operator you must provide an array of exactly two values",
655            )));
656        }
657        let left_key = document_type.serialize_value_for_key(
658            self.field.as_str(),
659            in_values.first().unwrap(),
660            platform_version,
661        )?;
662        let right_key = document_type.serialize_value_for_key(
663            self.field.as_str(),
664            in_values.get(1).unwrap(),
665            platform_version,
666        )?;
667        Ok((left_key, right_key))
668    }
669
670    /// Returns a path query given the parameters
671    // The start at document fields are:
672    // document: The Document that we should start at
673    // included: whether we should start at or after this document
674    // left_to_right: should we be going left to right or right to left?
675    pub(crate) fn to_path_query(
676        &self,
677        document_type: DocumentTypeRef,
678        start_at_document: &Option<(Document, bool)>,
679        left_to_right: bool,
680        platform_version: &PlatformVersion,
681    ) -> Result<Query, Error> {
682        // If there is a start_at_document, we need to get the value that it has for the
683        // current field.
684        let starts_at_key_option = match start_at_document {
685            None => None,
686            Some((document, included)) => {
687                // if the key doesn't exist then we should ignore the starts at key
688                document
689                    .get_raw_for_document_type(
690                        self.field.as_str(),
691                        document_type,
692                        None,
693                        platform_version,
694                    )?
695                    .map(|raw_value_option| (raw_value_option, *included))
696            }
697        };
698
699        let mut query = Query::new_with_direction(left_to_right);
700        // An element of a typed array keys its index level like a value does:
701        // `Contains` is the equality on that key and `ContainsAny` the `In`.
702        match self.operator.as_key_operator() {
703            Equal => {
704                let key = document_type.serialize_value_for_key(
705                    self.field.as_str(),
706                    &self.value,
707                    platform_version,
708                )?;
709                match starts_at_key_option {
710                    None => {
711                        query.insert_key(key);
712                    }
713                    Some((starts_at_key, included)) => {
714                        if (left_to_right && starts_at_key < key)
715                            || (!left_to_right && starts_at_key > key)
716                            || (included && starts_at_key == key)
717                        {
718                            query.insert_key(key);
719                        }
720                    }
721                }
722            }
723            In => {
724                let in_values = self.in_values().into_data_with_error()??;
725
726                match starts_at_key_option {
727                    None => {
728                        for value in in_values.iter() {
729                            let key = document_type.serialize_value_for_key(
730                                self.field.as_str(),
731                                value,
732                                platform_version,
733                            )?;
734                            query.insert_key(key)
735                        }
736                    }
737                    Some((starts_at_key, included)) => {
738                        for value in in_values.iter() {
739                            let key = document_type.serialize_value_for_key(
740                                self.field.as_str(),
741                                value,
742                                platform_version,
743                            )?;
744
745                            if (left_to_right && starts_at_key < key)
746                                || (!left_to_right && starts_at_key > key)
747                                || (included && starts_at_key == key)
748                            {
749                                query.insert_key(key);
750                            }
751                        }
752                    }
753                }
754            }
755            GreaterThan => {
756                let key = document_type.serialize_value_for_key(
757                    self.field.as_str(),
758                    &self.value,
759                    platform_version,
760                )?;
761                match starts_at_key_option {
762                    None => query.insert_range_after(key..),
763                    Some((starts_at_key, included)) => {
764                        if left_to_right {
765                            if starts_at_key <= key {
766                                query.insert_range_after(key..);
767                            } else if included {
768                                query.insert_range_from(starts_at_key..);
769                            } else {
770                                query.insert_range_after(starts_at_key..);
771                            }
772                        } else if starts_at_key > key {
773                            if included {
774                                query.insert_range_after_to_inclusive(key..=starts_at_key);
775                            } else {
776                                query.insert_range_after_to(key..starts_at_key);
777                            }
778                        }
779                    }
780                }
781            }
782            GreaterThanOrEquals => {
783                let key = document_type.serialize_value_for_key(
784                    self.field.as_str(),
785                    &self.value,
786                    platform_version,
787                )?;
788                match starts_at_key_option {
789                    None => query.insert_range_from(key..),
790                    Some((starts_at_key, included)) => {
791                        if left_to_right {
792                            if starts_at_key < key || (included && starts_at_key == key) {
793                                query.insert_range_from(key..);
794                            } else if included {
795                                query.insert_range_from(starts_at_key..);
796                            } else {
797                                query.insert_range_after(starts_at_key..);
798                            }
799                        } else if starts_at_key > key {
800                            if included {
801                                query.insert_range_inclusive(key..=starts_at_key);
802                            } else {
803                                query.insert_range(key..starts_at_key);
804                            }
805                        } else if included && starts_at_key == key {
806                            query.insert_key(key);
807                        }
808                    }
809                }
810            }
811            LessThan => {
812                let key = document_type.serialize_value_for_key(
813                    self.field.as_str(),
814                    &self.value,
815                    platform_version,
816                )?;
817                match starts_at_key_option {
818                    None => query.insert_range_to(..key),
819                    Some((starts_at_key, included)) => {
820                        if left_to_right {
821                            if starts_at_key < key {
822                                if included {
823                                    query.insert_range(starts_at_key..key);
824                                } else {
825                                    query.insert_range_after_to(starts_at_key..key);
826                                }
827                            }
828                        } else if starts_at_key > key {
829                            query.insert_range_to(..key);
830                        } else if included {
831                            query.insert_range_to_inclusive(..=starts_at_key);
832                        } else {
833                            query.insert_range_to(..starts_at_key);
834                        }
835                    }
836                }
837            }
838            LessThanOrEquals => {
839                let key = document_type.serialize_value_for_key(
840                    self.field.as_str(),
841                    &self.value,
842                    platform_version,
843                )?;
844                match starts_at_key_option {
845                    None => query.insert_range_to_inclusive(..=key),
846                    Some((starts_at_key, included)) => {
847                        if left_to_right {
848                            if included && starts_at_key == key {
849                                query.insert_key(key);
850                            } else if starts_at_key < key {
851                                if included {
852                                    query.insert_range_inclusive(starts_at_key..=key);
853                                } else {
854                                    query.insert_range_after_to_inclusive(starts_at_key..=key);
855                                }
856                            }
857                        } else if starts_at_key > key || (included && starts_at_key == key) {
858                            query.insert_range_to_inclusive(..=key);
859                        } else if included {
860                            query.insert_range_to_inclusive(..=starts_at_key);
861                        } else {
862                            query.insert_range_to(..starts_at_key);
863                        }
864                    }
865                }
866            }
867            Between => {
868                let (left_key, right_key) =
869                    self.split_value_for_between(document_type, platform_version)?;
870                match starts_at_key_option {
871                    None => query.insert_range_inclusive(left_key..=right_key),
872                    Some((starts_at_key, included)) => {
873                        if left_to_right {
874                            if starts_at_key < left_key || (included && starts_at_key == left_key) {
875                                query.insert_range_inclusive(left_key..=right_key)
876                            } else if starts_at_key == left_key {
877                                query.insert_range_after_to_inclusive(left_key..=right_key)
878                            } else if starts_at_key > left_key && starts_at_key < right_key {
879                                if included {
880                                    query.insert_range_inclusive(starts_at_key..=right_key);
881                                } else {
882                                    query
883                                        .insert_range_after_to_inclusive(starts_at_key..=right_key);
884                                }
885                            } else if starts_at_key == right_key && included {
886                                query.insert_key(right_key);
887                            }
888                        } else if starts_at_key > right_key
889                            || (included && starts_at_key == right_key)
890                        {
891                            query.insert_range_inclusive(left_key..=right_key)
892                        } else if starts_at_key == right_key {
893                            query.insert_range(left_key..right_key)
894                        } else if starts_at_key > left_key && starts_at_key < right_key {
895                            if included {
896                                query.insert_range_inclusive(left_key..=starts_at_key);
897                            } else {
898                                query.insert_range(left_key..starts_at_key);
899                            }
900                        } else if starts_at_key == left_key && included {
901                            query.insert_key(left_key);
902                        }
903                    }
904                }
905            }
906            BetweenExcludeBounds => {
907                let (left_key, right_key) =
908                    self.split_value_for_between(document_type, platform_version)?;
909                match starts_at_key_option {
910                    None => query.insert_range_after_to(left_key..right_key),
911                    Some((starts_at_key, included)) => {
912                        if left_to_right {
913                            if starts_at_key <= left_key {
914                                query.insert_range_after_to(left_key..right_key)
915                            } else if starts_at_key > left_key && starts_at_key < right_key {
916                                if included {
917                                    query.insert_range(starts_at_key..right_key);
918                                } else {
919                                    query.insert_range_after_to(starts_at_key..right_key);
920                                }
921                            }
922                        } else if starts_at_key > right_key {
923                            query.insert_range_inclusive(left_key..=right_key)
924                        } else if starts_at_key == right_key {
925                            query.insert_range(left_key..right_key)
926                        } else if starts_at_key > left_key && starts_at_key < right_key {
927                            if included {
928                                query.insert_range_after_to_inclusive(left_key..=starts_at_key);
929                            } else {
930                                query.insert_range_after_to(left_key..starts_at_key);
931                            }
932                        }
933                    }
934                }
935            }
936            BetweenExcludeLeft => {
937                let (left_key, right_key) =
938                    self.split_value_for_between(document_type, platform_version)?;
939                match starts_at_key_option {
940                    None => query.insert_range_after_to_inclusive(left_key..=right_key),
941                    Some((starts_at_key, included)) => {
942                        if left_to_right {
943                            if starts_at_key <= left_key {
944                                query.insert_range_after_to_inclusive(left_key..=right_key)
945                            } else if starts_at_key > left_key && starts_at_key < right_key {
946                                if included {
947                                    query.insert_range_inclusive(starts_at_key..=right_key);
948                                } else {
949                                    query
950                                        .insert_range_after_to_inclusive(starts_at_key..=right_key);
951                                }
952                            } else if starts_at_key == right_key && included {
953                                query.insert_key(right_key);
954                            }
955                        } else if starts_at_key > right_key
956                            || (included && starts_at_key == right_key)
957                        {
958                            query.insert_range_after_to_inclusive(left_key..=right_key)
959                        } else if starts_at_key > left_key && starts_at_key < right_key {
960                            if included {
961                                query.insert_range_inclusive(left_key..=starts_at_key);
962                            } else {
963                                query.insert_range(left_key..starts_at_key);
964                            }
965                        }
966                    }
967                }
968            }
969            BetweenExcludeRight => {
970                let (left_key, right_key) =
971                    self.split_value_for_between(document_type, platform_version)?;
972                match starts_at_key_option {
973                    None => query.insert_range(left_key..right_key),
974                    Some((starts_at_key, included)) => {
975                        if left_to_right {
976                            if starts_at_key < left_key || (included && starts_at_key == left_key) {
977                                query.insert_range(left_key..right_key)
978                            } else if starts_at_key == left_key {
979                                query.insert_range_after_to(left_key..right_key)
980                            } else if starts_at_key > left_key && starts_at_key < right_key {
981                                if included {
982                                    query.insert_range(starts_at_key..right_key);
983                                } else {
984                                    query.insert_range_after_to(starts_at_key..right_key);
985                                }
986                            }
987                        } else if starts_at_key >= right_key {
988                            query.insert_range(left_key..right_key)
989                        } else if starts_at_key > left_key && starts_at_key < right_key {
990                            if included {
991                                query.insert_range_inclusive(left_key..=starts_at_key);
992                            } else {
993                                query.insert_range(left_key..starts_at_key);
994                            }
995                        } else if starts_at_key == left_key && included {
996                            query.insert_key(left_key);
997                        }
998                    }
999                }
1000            }
1001            StartsWith => {
1002                let left_key = document_type.serialize_value_for_key(
1003                    self.field.as_str(),
1004                    &self.value,
1005                    platform_version,
1006                )?;
1007                let mut right_key = left_key.clone();
1008                let last_char = right_key.last_mut().ok_or({
1009                    Error::Query(QuerySyntaxError::InvalidStartsWithClause(
1010                        "starts with must have at least one character",
1011                    ))
1012                })?;
1013                *last_char += 1;
1014                match starts_at_key_option {
1015                    None => query.insert_range(left_key..right_key),
1016                    Some((starts_at_key, included)) => {
1017                        if left_to_right {
1018                            if starts_at_key < left_key || (included && starts_at_key == left_key) {
1019                                query.insert_range(left_key..right_key)
1020                            } else if starts_at_key == left_key {
1021                                query.insert_range_after_to(left_key..right_key)
1022                            } else if starts_at_key > left_key && starts_at_key < right_key {
1023                                if included {
1024                                    query.insert_range(starts_at_key..right_key);
1025                                } else {
1026                                    query.insert_range_after_to(starts_at_key..right_key);
1027                                }
1028                            }
1029                        } else if starts_at_key >= right_key {
1030                            query.insert_range(left_key..right_key)
1031                        } else if starts_at_key > left_key && starts_at_key < right_key {
1032                            if included {
1033                                query.insert_range_inclusive(left_key..=starts_at_key);
1034                            } else {
1035                                query.insert_range(left_key..starts_at_key);
1036                            }
1037                        } else if starts_at_key == left_key && included {
1038                            query.insert_key(left_key);
1039                        }
1040                    }
1041                }
1042            }
1043            // Mapped to `Equal` and `In` by `as_key_operator` above.
1044            Contains | ContainsAny => {
1045                return Err(Error::Drive(DriveError::CorruptedCodeExecution(
1046                    "an element operator keys as an equality or an in",
1047                )))
1048            }
1049        }
1050        Ok(query)
1051    }
1052
1053    pub(crate) fn build_where_clauses_from_operations(
1054        binary_operation: &ast::Expr,
1055        document_type: &DocumentType,
1056        where_clauses: &mut Vec<WhereClause>,
1057    ) -> Result<(), Error> {
1058        match &binary_operation {
1059            ast::Expr::InList {
1060                expr,
1061                list,
1062                negated,
1063            } => {
1064                if *negated {
1065                    return Err(Error::Query(QuerySyntaxError::Unsupported(
1066                        "Invalid query: negated in clause not supported".to_string(),
1067                    )));
1068                }
1069
1070                let field_name: String = if let ast::Expr::Identifier(ident) = &**expr {
1071                    ident.value.clone()
1072                } else {
1073                    return Err(Error::Query(QuerySyntaxError::InvalidInClause(
1074                        "Invalid query: in clause should start with an identifier".to_string(),
1075                    )));
1076                };
1077
1078                let property_type = if let Some(ty) = meta_field_property_type(&field_name) {
1079                    Cow::Owned(ty)
1080                } else {
1081                    let property = document_type
1082                        .flattened_properties()
1083                        .get(&field_name)
1084                        .ok_or_else(|| {
1085                            Error::Query(QuerySyntaxError::InvalidSQL(format!(
1086                                "Invalid query: property named {} not in document type",
1087                                field_name
1088                            )))
1089                        })?;
1090                    Cow::Borrowed(&property.property_type)
1091                };
1092
1093                let mut in_values: Vec<Value> = Vec::new();
1094                for value in list {
1095                    if let ast::Expr::Value(sql_value) = value {
1096                        let platform_value =
1097                            sql_value_to_platform_value(sql_value.clone()).ok_or({
1098                                Error::Query(QuerySyntaxError::InvalidSQL(
1099                                    "Invalid query: unexpected value type".to_string(),
1100                                ))
1101                            })?;
1102                        let transformed_value = if let Value::Text(text_value) = &platform_value {
1103                            property_type.value_from_string(text_value)?
1104                        } else {
1105                            platform_value
1106                        };
1107
1108                        in_values.push(transformed_value);
1109                    } else {
1110                        return Err(Error::Query(QuerySyntaxError::InvalidSQL(
1111                            "Invalid query: expected a list of sql values".to_string(),
1112                        )));
1113                    }
1114                }
1115
1116                where_clauses.push(WhereClause {
1117                    field: field_name,
1118                    operator: In,
1119                    value: Value::Array(in_values),
1120                });
1121
1122                Ok(())
1123            }
1124            ast::Expr::Like {
1125                negated,
1126                expr,
1127                pattern,
1128                escape_char: _,
1129            } => {
1130                let where_operator = StartsWith;
1131                if *negated {
1132                    return Err(Error::Query(QuerySyntaxError::Unsupported(
1133                        "Negated Like not supported".to_string(),
1134                    )));
1135                }
1136
1137                let field_name: String = if let ast::Expr::Identifier(ident) = &**expr {
1138                    ident.value.clone()
1139                } else {
1140                    panic!("unreachable: confirmed it's identifier variant");
1141                };
1142
1143                let transformed_value = if let ast::Expr::Value(value) = &**pattern {
1144                    let platform_value = sql_value_to_platform_value(value.clone()).ok_or({
1145                        Error::Query(QuerySyntaxError::InvalidSQL(
1146                            "Invalid query: unexpected value type".to_string(),
1147                        ))
1148                    })?;
1149
1150                    // make sure the value is of the right format i.e. prefix%
1151                    let inner_text = platform_value.as_text().ok_or({
1152                        Error::Query(QuerySyntaxError::InvalidStartsWithClause(
1153                            "Invalid query: startsWith takes text",
1154                        ))
1155                    })?;
1156                    let match_locations: Vec<_> = inner_text.match_indices('%').collect();
1157                    if match_locations.len() == 1 && match_locations[0].0 == inner_text.len() - 1 {
1158                        Value::Text(String::from(&inner_text[..(inner_text.len() - 1)]))
1159                    } else {
1160                        return Err(Error::Query(QuerySyntaxError::Unsupported(
1161                            "Invalid query: like can only be used to represent startswith"
1162                                .to_string(),
1163                        )));
1164                    }
1165                } else {
1166                    panic!("unreachable: confirmed it's value variant");
1167                };
1168
1169                where_clauses.push(WhereClause {
1170                    field: field_name,
1171                    operator: where_operator,
1172                    value: transformed_value,
1173                });
1174                Ok(())
1175            }
1176            ast::Expr::BinaryOp { left, op, right } => {
1177                if *op == ast::BinaryOperator::And {
1178                    Self::build_where_clauses_from_operations(left, document_type, where_clauses)?;
1179                    Self::build_where_clauses_from_operations(right, document_type, where_clauses)?;
1180                } else {
1181                    let mut where_operator =
1182                        WhereOperator::from_sql_operator(op.clone()).ok_or(Error::Query(
1183                            QuerySyntaxError::Unsupported("Unknown operator".to_string()),
1184                        ))?;
1185
1186                    let identifier;
1187                    let value_expr;
1188
1189                    if matches!(&**left, ast::Expr::Identifier(_))
1190                        && matches!(&**right, ast::Expr::Value(_))
1191                    {
1192                        identifier = &**left;
1193                        value_expr = &**right;
1194                    } else if matches!(&**right, ast::Expr::Identifier(_))
1195                        && matches!(&**left, ast::Expr::Value(_))
1196                    {
1197                        identifier = &**right;
1198                        value_expr = &**left;
1199                        where_operator = where_operator.flip()?;
1200                    } else {
1201                        return Err(Error::Query(QuerySyntaxError::InvalidSQL(
1202                            "Invalid query: where clause should have field name and value"
1203                                .to_string(),
1204                        )));
1205                    }
1206
1207                    let field_name: String = if let ast::Expr::Identifier(ident) = identifier {
1208                        ident.value.clone()
1209                    } else {
1210                        panic!("unreachable: confirmed it's identifier variant");
1211                    };
1212
1213                    let property_type = if let Some(ty) = meta_field_property_type(&field_name) {
1214                        Cow::Owned(ty)
1215                    } else {
1216                        let property = document_type
1217                            .flattened_properties()
1218                            .get(&field_name)
1219                            .ok_or_else(|| {
1220                                Error::Query(QuerySyntaxError::InvalidSQL(format!(
1221                                    "Invalid query: property named {} not in document type",
1222                                    field_name
1223                                )))
1224                            })?;
1225                        Cow::Borrowed(&property.property_type)
1226                    };
1227
1228                    let transformed_value = if let ast::Expr::Value(value) = value_expr {
1229                        let platform_value = sql_value_to_platform_value(value.clone()).ok_or({
1230                            Error::Query(QuerySyntaxError::InvalidSQL(
1231                                "Invalid query: unexpected value type".to_string(),
1232                            ))
1233                        })?;
1234
1235                        if let Value::Text(text_value) = &platform_value {
1236                            property_type.value_from_string(text_value)?
1237                        } else {
1238                            platform_value
1239                        }
1240                    } else {
1241                        panic!("unreachable: confirmed it's value variant");
1242                    };
1243
1244                    where_clauses.push(WhereClause {
1245                        field: field_name,
1246                        operator: where_operator,
1247                        value: transformed_value,
1248                    });
1249                }
1250                Ok(())
1251            }
1252            _ => Err(Error::Query(QuerySyntaxError::InvalidSQL(
1253                "Issue parsing sql: invalid selection format".to_string(),
1254            ))),
1255        }
1256    }
1257
1258    /// Evaluate this WhereClause against a provided `Value`
1259    pub fn matches_value(&self, value: &Value) -> bool {
1260        self.operator.eval(value, &self.value)
1261    }
1262
1263    /// Judge a clause that names a typed array or reads its elements (protocol
1264    /// version 14): a typed array is read with `contains` or `containsAny` and
1265    /// nothing else, those two read a typed array, and each value they name
1266    /// must key as an element of the array's element type. A value is judged
1267    /// the way a value of any other indexed property is, by whether it
1268    /// serializes as an index key of the type, so an identifier element may
1269    /// be given as its bytes or its base58 text. A clause on any other field
1270    /// passes.
1271    #[cfg(any(feature = "server", feature = "verify"))]
1272    pub fn validate_array_element_clause(
1273        &self,
1274        document_type: DocumentTypeRef,
1275        platform_version: &PlatformVersion,
1276    ) -> Result<(), Error> {
1277        let names_typed_array =
1278            typed_array_property(document_type.flattened_properties(), &self.field).is_some();
1279        if let Some(error) = self.element_operator_mismatch(names_typed_array) {
1280            return Err(Error::Query(error));
1281        }
1282        if !names_typed_array {
1283            return Ok(());
1284        }
1285        self.validate_element_values(|value| {
1286            document_type
1287                .serialize_value_for_key(&self.field, value, platform_version)
1288                .is_ok()
1289        })
1290        .map_err(Error::Query)
1291    }
1292
1293    /// The refusal of a clause that names a typed array without an element
1294    /// operator, or uses one on any other field.
1295    #[cfg(any(feature = "server", feature = "verify"))]
1296    fn element_operator_mismatch(&self, names_typed_array: bool) -> Option<QuerySyntaxError> {
1297        (names_typed_array != self.operator.reads_array_elements()).then_some(
1298            QuerySyntaxError::InvalidWhereClauseComponents(if names_typed_array {
1299                "a typed array is read with contains or containsAny, and no other operator"
1300            } else {
1301                "contains and containsAny read the elements of a typed array"
1302            }),
1303        )
1304    }
1305
1306    /// Judge the values of an element clause, `keys_as_element` telling
1307    /// whether one value keys as an element of the array: `contains` names
1308    /// one element, `containsAny` a list of them given as an `in` list is (1
1309    /// to 100 distinct values, or bytes for u8 elements). Documents queries and
1310    /// subscription filters judge element values by this one rule rather than
1311    /// by the operators the element type admits as a property, so a boolean
1312    /// array is read with `containsAny` though a boolean property takes no `in`.
1313    #[cfg(any(feature = "server", feature = "verify"))]
1314    fn validate_element_values(
1315        &self,
1316        keys_as_element: impl Fn(&Value) -> bool,
1317    ) -> Result<(), QuerySyntaxError> {
1318        let listed;
1319        let values: &[Value] = if self.operator == ContainsAny {
1320            let in_values = self.in_values();
1321            if let Some(error) = in_values.errors.into_iter().next() {
1322                return Err(error);
1323            }
1324            listed = in_values.data.unwrap_or_default();
1325            &listed
1326        } else {
1327            std::slice::from_ref(&self.value)
1328        };
1329        if values
1330            .iter()
1331            .all(|value| !value.is_null() && keys_as_element(value))
1332        {
1333            Ok(())
1334        } else {
1335            Err(QuerySyntaxError::InvalidWhereClauseComponents(
1336                "invalid value type for an element of the typed array",
1337            ))
1338        }
1339    }
1340
1341    /// Validate this where clause against the document schema
1342    #[cfg(any(feature = "server", feature = "verify"))]
1343    pub fn validate_against_schema(
1344        &self,
1345        document_type: DocumentTypeRef,
1346    ) -> QuerySyntaxSimpleValidationResult {
1347        // First determine the property type of self.field
1348        let property_type_cow = if let Some(meta_ty) = meta_field_property_type(&self.field) {
1349            Cow::Owned(meta_ty)
1350        } else {
1351            // Check that the field exists in the schema
1352            let Some(property) = document_type.flattened_properties().get(&self.field) else {
1353                return QuerySyntaxSimpleValidationResult::new_with_error(
1354                    QuerySyntaxError::InvalidWhereClauseComponents("unknown field in where clause"),
1355                );
1356            };
1357            Cow::Borrowed(&property.property_type)
1358        };
1359
1360        // A typed array is read with an element operator and nothing else, and
1361        // each value it names is judged as `validate_array_element_clause`
1362        // judges it for documents queries: it must key as an element, as
1363        // generation 1 of `serialize_value_for_key` keys one. Any other clause
1364        // is judged by the operator rules of its field's type.
1365        let property_type = property_type_cow.as_ref();
1366        let element_type = match property_type {
1367            DocumentPropertyType::TypedArray(typed_array) => Some(&typed_array.item_type),
1368            _ => None,
1369        };
1370        if let Some(error) = self.element_operator_mismatch(element_type.is_some()) {
1371            return QuerySyntaxSimpleValidationResult::new_with_error(error);
1372        }
1373        if let Some(element_type) = element_type {
1374            return match self.validate_element_values(|value| {
1375                element_type
1376                    .encode_value_for_tree_keys(value)
1377                    .is_ok_and(|key| key.len() <= MAX_INDEX_SIZE)
1378            }) {
1379                Ok(()) => QuerySyntaxSimpleValidationResult::new(),
1380                Err(error) => QuerySyntaxSimpleValidationResult::new_with_error(error),
1381            };
1382        }
1383
1384        self.validate_against_property_type(property_type)
1385    }
1386
1387    /// Validate the operator and value of this where clause against the type
1388    /// of the field it names: the value shape, the bounds and the value types.
1389    #[cfg(any(feature = "server", feature = "verify"))]
1390    fn validate_against_property_type(
1391        &self,
1392        property_type: &DocumentPropertyType,
1393    ) -> QuerySyntaxSimpleValidationResult {
1394        if !allowed_ops_for_type(property_type).contains(&self.operator) {
1395            return QuerySyntaxSimpleValidationResult::new_with_error(
1396                QuerySyntaxError::InvalidWhereClauseComponents(
1397                    "operator not allowed for field type",
1398                ),
1399            );
1400        }
1401
1402        // Check starts_with value is not empty
1403        if self.operator == StartsWith {
1404            if let Value::Text(s) = &self.value {
1405                if s.is_empty() {
1406                    return QuerySyntaxSimpleValidationResult::new_with_error(
1407                        QuerySyntaxError::StartsWithIllegalString(
1408                            "starts_with can not start with an empty string",
1409                        ),
1410                    );
1411                }
1412            }
1413        }
1414
1415        // Check in clause values
1416        if self.operator == In {
1417            // Ensure array value, length bounds and no duplicates
1418            let result = self.in_values();
1419            if !result.is_valid() {
1420                return QuerySyntaxSimpleValidationResult::new_with_errors(result.errors);
1421            }
1422            // If value provided as Bytes, only allow for U8 numeric fields
1423            if matches!(self.value, Value::Bytes(_))
1424                && !matches!(property_type, DocumentPropertyType::U8)
1425            {
1426                return QuerySyntaxSimpleValidationResult::new_with_error(
1427                    QuerySyntaxError::InvalidWhereClauseComponents(
1428                        "IN Bytes only allowed for U8 fields",
1429                    ),
1430                );
1431            }
1432        }
1433
1434        // Check value shape is correct for operator and field type
1435        if !self.operator.value_shape_ok(&self.value, property_type) {
1436            return QuerySyntaxSimpleValidationResult::new_with_error(
1437                QuerySyntaxError::InvalidWhereClauseComponents("invalid value shape for operator"),
1438            );
1439        }
1440
1441        // For Between variants, ensure bounds are in ascending order to avoid surprising matches
1442        match self.operator {
1443            Between | BetweenExcludeBounds | BetweenExcludeLeft | BetweenExcludeRight => {
1444                if let Value::Array(bounds) = &self.value {
1445                    if bounds.len() == 2 {
1446                        match bounds[0].partial_cmp(&bounds[1]) {
1447                            Some(Ordering::Less) => {}
1448                            _ => {
1449                                return QuerySyntaxSimpleValidationResult::new_with_error(
1450                                    QuerySyntaxError::InvalidBetweenClause(
1451                                        "when using between operator bounds must be strictly ascending",
1452                                    ),
1453                                );
1454                            }
1455                        }
1456                    }
1457                }
1458            }
1459            _ => {}
1460        }
1461
1462        // Check value type matches field type for equality and IN operators
1463        let value_type_matches = |prop_ty: &DocumentPropertyType, v: &Value| -> bool {
1464            use DocumentPropertyType as T;
1465            match prop_ty {
1466                T::String(_) => matches!(v, Value::Text(_)),
1467                T::Identifier | T::IdentifierWithReference(_) => matches!(v, Value::Identifier(_)),
1468                T::Boolean => matches!(v, Value::Bool(_)),
1469                T::ByteArray(_) => matches!(v, Value::Bytes(_)),
1470                T::F64 => matches!(v, Value::Float(_)),
1471                T::Date => matches!(
1472                    v,
1473                    Value::U64(_)
1474                        | Value::I64(_)
1475                        | Value::U32(_)
1476                        | Value::I32(_)
1477                        | Value::U16(_)
1478                        | Value::I16(_)
1479                        | Value::U8(_)
1480                        | Value::I8(_)
1481                ),
1482                T::U8 | T::U16 | T::U32 | T::KeyIdWithReference(_) | T::U64 | T::U128 => {
1483                    matches!(
1484                        v,
1485                        Value::U8(_)
1486                            | Value::U16(_)
1487                            | Value::U32(_)
1488                            | Value::U64(_)
1489                            | Value::U128(_)
1490                    )
1491                }
1492                T::I8 | T::I16 | T::I32 | T::I64 | T::I128 => matches!(
1493                    v,
1494                    Value::I8(_) | Value::I16(_) | Value::I32(_) | Value::I64(_) | Value::I128(_)
1495                ),
1496                // No validation for object/array types as operators are disallowed
1497                T::Object(_) | T::Array(_) | T::VariableTypeArray(_) | T::TypedArray(_) => false,
1498            }
1499        };
1500
1501        // For equality, allow some type coercion (e.g. integer types)
1502        match self.operator {
1503            Equal => {
1504                use DocumentPropertyType as T;
1505                let ok = match property_type {
1506                    // Accept any integer-like value for integer fields (signed/unsigned), reject floats
1507                    T::U8
1508                    | T::U16
1509                    | T::U32
1510                    | T::KeyIdWithReference(_)
1511                    | T::U64
1512                    | T::U128
1513                    | T::I8
1514                    | T::I16
1515                    | T::I32
1516                    | T::I64
1517                    | T::I128 => {
1518                        matches!(
1519                            self.value,
1520                            Value::U128(_)
1521                                | Value::I128(_)
1522                                | Value::U64(_)
1523                                | Value::I64(_)
1524                                | Value::U32(_)
1525                                | Value::I32(_)
1526                                | Value::U16(_)
1527                                | Value::I16(_)
1528                                | Value::U8(_)
1529                                | Value::I8(_)
1530                        )
1531                    }
1532                    T::F64 => matches!(self.value, Value::Float(_)),
1533                    T::Date => matches!(
1534                        self.value,
1535                        Value::U64(_)
1536                            | Value::I64(_)
1537                            | Value::U32(_)
1538                            | Value::I32(_)
1539                            | Value::U16(_)
1540                            | Value::I16(_)
1541                            | Value::U8(_)
1542                            | Value::I8(_)
1543                    ),
1544                    T::String(_) => matches!(self.value, Value::Text(_)),
1545                    T::Identifier | T::IdentifierWithReference(_) => {
1546                        matches!(self.value, Value::Identifier(_))
1547                    }
1548                    T::ByteArray(_) => matches!(self.value, Value::Bytes(_)),
1549                    T::Boolean => matches!(self.value, Value::Bool(_)),
1550                    // Not applicable for object/array/variable arrays
1551                    T::Object(_) | T::Array(_) | T::VariableTypeArray(_) | T::TypedArray(_) => {
1552                        false
1553                    }
1554                };
1555                if !ok {
1556                    return QuerySyntaxSimpleValidationResult::new_with_error(
1557                        QuerySyntaxError::InvalidWhereClauseComponents(
1558                            "invalid value type for equality",
1559                        ),
1560                    );
1561                }
1562            }
1563            In => {
1564                if let Value::Array(arr) = &self.value {
1565                    if !arr.iter().all(|v| value_type_matches(property_type, v)) {
1566                        return QuerySyntaxSimpleValidationResult::new_with_error(
1567                            QuerySyntaxError::InvalidWhereClauseComponents(
1568                                "invalid value type in IN clause",
1569                            ),
1570                        );
1571                    }
1572                }
1573            }
1574            _ => {}
1575        }
1576
1577        QuerySyntaxSimpleValidationResult::new()
1578    }
1579}
1580
1581impl From<WhereClause> for Value {
1582    fn from(value: WhereClause) -> Self {
1583        Value::Array(vec![value.field.into(), value.operator.into(), value.value])
1584    }
1585}
1586
1587/// Value-only clause used when there is no field lookup involved
1588#[derive(Clone, Debug, PartialEq)]
1589#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1590pub struct ValueClause {
1591    /// Operator
1592    pub operator: WhereOperator,
1593    /// Value
1594    pub value: Value,
1595}
1596
1597impl ValueClause {
1598    /// Evaluate this clause against a provided `Value`
1599    pub fn matches_value(&self, value: &Value) -> bool {
1600        self.operator.eval(value, &self.value)
1601    }
1602}
1603
1604/// Returns the set of allowed operators for a given property type
1605#[cfg(any(feature = "server", feature = "verify"))]
1606pub fn allowed_ops_for_type(property_type: &DocumentPropertyType) -> &'static [WhereOperator] {
1607    match property_type {
1608        DocumentPropertyType::U8
1609        | DocumentPropertyType::I8
1610        | DocumentPropertyType::U16
1611        | DocumentPropertyType::I16
1612        | DocumentPropertyType::U32
1613        | DocumentPropertyType::KeyIdWithReference(_)
1614        | DocumentPropertyType::I32
1615        | DocumentPropertyType::U64
1616        | DocumentPropertyType::I64
1617        | DocumentPropertyType::U128
1618        | DocumentPropertyType::I128
1619        | DocumentPropertyType::F64
1620        | DocumentPropertyType::Date => &[
1621            Equal,
1622            In,
1623            GreaterThan,
1624            GreaterThanOrEquals,
1625            LessThan,
1626            LessThanOrEquals,
1627            Between,
1628            BetweenExcludeBounds,
1629            BetweenExcludeLeft,
1630            BetweenExcludeRight,
1631        ],
1632        DocumentPropertyType::String(_) => &[
1633            Equal,
1634            In,
1635            StartsWith,
1636            GreaterThan,
1637            GreaterThanOrEquals,
1638            LessThan,
1639            LessThanOrEquals,
1640            Between,
1641            BetweenExcludeBounds,
1642            BetweenExcludeLeft,
1643            BetweenExcludeRight,
1644        ],
1645        DocumentPropertyType::Identifier | DocumentPropertyType::IdentifierWithReference(_) => {
1646            &[Equal, In]
1647        }
1648        DocumentPropertyType::ByteArray(_) => &[Equal, In],
1649        DocumentPropertyType::Boolean => &[Equal],
1650        // A typed array is read by its elements (protocol version 14): only an
1651        // index naming it can serve the clause, and none could before.
1652        DocumentPropertyType::TypedArray(_) => &[Contains, ContainsAny],
1653        DocumentPropertyType::Object(_)
1654        | DocumentPropertyType::Array(_)
1655        | DocumentPropertyType::VariableTypeArray(_) => &[],
1656    }
1657}
1658
1659#[cfg(any(feature = "server", feature = "verify"))]
1660fn is_numeric_value(value: &Value) -> bool {
1661    matches!(
1662        value,
1663        Value::U128(_)
1664            | Value::I128(_)
1665            | Value::U64(_)
1666            | Value::I64(_)
1667            | Value::U32(_)
1668            | Value::I32(_)
1669            | Value::U16(_)
1670            | Value::I16(_)
1671            | Value::U8(_)
1672            | Value::I8(_)
1673            | Value::Float(_)
1674    )
1675}
1676
1677/// Map known meta/system fields to their corresponding property types.
1678/// Meta fields are top-level and always start with `$`.
1679fn meta_field_property_type(field: &str) -> Option<DocumentPropertyType> {
1680    match field {
1681        // Identifiers
1682        "$id" | "$ownerId" | "$dataContractId" | "$creatorId" | "$moderatedBy" => {
1683            Some(DocumentPropertyType::Identifier)
1684        }
1685        // Dates (millis since epoch)
1686        "$createdAt" | "$updatedAt" | "$transferredAt" | "$moderatedAt" => {
1687            Some(DocumentPropertyType::Date)
1688        }
1689        // Block heights and core block heights
1690        "$createdAtBlockHeight" | "$updatedAtBlockHeight" | "$transferredAtBlockHeight" => {
1691            Some(DocumentPropertyType::U64)
1692        }
1693        "$createdAtCoreBlockHeight"
1694        | "$updatedAtCoreBlockHeight"
1695        | "$transferredAtCoreBlockHeight" => Some(DocumentPropertyType::U32),
1696        // Revision and protocol version are integers
1697        "$revision" | "$protocolVersion" => Some(DocumentPropertyType::U64),
1698        // Type name is a string
1699        "$type" => Some(DocumentPropertyType::String(
1700            dpp::data_contract::document_type::StringPropertySizes {
1701                min_length: None,
1702                max_length: None,
1703                max_bytes: None,
1704            },
1705        )),
1706        _ => None,
1707    }
1708}
1709
1710#[cfg(feature = "server")]
1711#[cfg(test)]
1712#[allow(clippy::approx_constant)]
1713mod tests {
1714    use crate::error::query::QuerySyntaxError;
1715    use crate::query::conditions::WhereClause;
1716    use crate::query::conditions::{
1717        Between, BetweenExcludeBounds, BetweenExcludeLeft, BetweenExcludeRight, Equal, GreaterThan,
1718        GreaterThanOrEquals, In, LessThan, LessThanOrEquals, ValueClause,
1719    };
1720    use crate::query::InternalClauses;
1721    use dpp::data_contract::accessors::v0::DataContractV0Getters;
1722    use dpp::data_contract::document_type::methods::DocumentTypeV0Methods;
1723    use dpp::document::DocumentV0;
1724    use dpp::platform_value::Value;
1725    use dpp::prelude::Identifier;
1726    use dpp::tests::fixtures::get_data_contract_fixture;
1727    use dpp::version::PlatformVersion;
1728    use dpp::version::LATEST_PLATFORM_VERSION;
1729    use grovedb::Query;
1730    use std::collections::BTreeMap;
1731
1732    fn cursor_document(field: &str, value: Value) -> dpp::document::Document {
1733        DocumentV0 {
1734            contract_version: None,
1735            id: Identifier::from([3u8; 32]),
1736            owner_id: Identifier::from([4u8; 32]),
1737            properties: BTreeMap::from([(field.to_string(), value)]),
1738            revision: None,
1739            created_at: None,
1740            updated_at: None,
1741            transferred_at: None,
1742            created_at_block_height: None,
1743            updated_at_block_height: None,
1744            transferred_at_block_height: None,
1745            created_at_core_block_height: None,
1746            updated_at_core_block_height: None,
1747            transferred_at_core_block_height: None,
1748            creator_id: None,
1749            moderated_at: None,
1750            moderated_by: None,
1751        }
1752        .into()
1753    }
1754
1755    #[test]
1756    fn ascending_less_than_ranges_start_at_the_cursor() {
1757        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1758        let contract = fixture.data_contract_owned();
1759        let document_type = contract
1760            .document_type_for_name("niceDocument")
1761            .expect("document type exists");
1762        let cursor_value = Value::Text("m".to_string());
1763        let upper_value = Value::Text("z".to_string());
1764        let cursor_key = document_type
1765            .serialize_value_for_key("name", &cursor_value, LATEST_PLATFORM_VERSION)
1766            .unwrap();
1767        let upper_key = document_type
1768            .serialize_value_for_key("name", &upper_value, LATEST_PLATFORM_VERSION)
1769            .unwrap();
1770
1771        for (operator, cursor_included) in [
1772            (LessThan, true),
1773            (LessThan, false),
1774            (LessThanOrEquals, true),
1775            (LessThanOrEquals, false),
1776        ] {
1777            let clause = WhereClause {
1778                field: "name".to_string(),
1779                operator,
1780                value: upper_value.clone(),
1781            };
1782            let start_at = Some((
1783                cursor_document("name", cursor_value.clone()),
1784                cursor_included,
1785            ));
1786            let actual = clause
1787                .to_path_query(document_type, &start_at, true, LATEST_PLATFORM_VERSION)
1788                .unwrap();
1789            let mut expected = Query::new_with_direction(true);
1790
1791            match (operator, cursor_included) {
1792                (LessThan, true) => expected.insert_range(cursor_key.clone()..upper_key.clone()),
1793                (LessThan, false) => {
1794                    expected.insert_range_after_to(cursor_key.clone()..upper_key.clone())
1795                }
1796                (LessThanOrEquals, true) => {
1797                    expected.insert_range_inclusive(cursor_key.clone()..=upper_key.clone())
1798                }
1799                (LessThanOrEquals, false) => {
1800                    expected.insert_range_after_to_inclusive(cursor_key.clone()..=upper_key.clone())
1801                }
1802                _ => unreachable!(),
1803            }
1804
1805            assert_eq!(actual.items, expected.items);
1806        }
1807    }
1808
1809    #[test]
1810    fn test_allowed_sup_query_pairs() {
1811        let allowed_pairs_test_cases = [
1812            [GreaterThan, LessThan],
1813            [GreaterThan, LessThanOrEquals],
1814            [GreaterThanOrEquals, LessThanOrEquals],
1815        ];
1816        for query_pair in allowed_pairs_test_cases {
1817            let where_clauses = vec![
1818                WhereClause {
1819                    field: "a".to_string(),
1820                    operator: *query_pair.first().unwrap(),
1821                    value: Value::Float(0.0),
1822                },
1823                WhereClause {
1824                    field: "a".to_string(),
1825                    operator: *query_pair.get(1).unwrap(),
1826                    value: Value::Float(1.0),
1827                },
1828            ];
1829            let (_, range_clause, _) =
1830                WhereClause::group_clauses(&where_clauses, PlatformVersion::latest())
1831                    .expect("expected to have groupable pair");
1832            range_clause.expect("expected to have range clause returned");
1833        }
1834    }
1835
1836    #[test]
1837    fn test_allowed_inf_query_pairs() {
1838        let allowed_pairs_test_cases = [
1839            [LessThan, GreaterThan],
1840            [LessThan, GreaterThanOrEquals],
1841            [LessThanOrEquals, GreaterThanOrEquals],
1842        ];
1843        for query_pair in allowed_pairs_test_cases {
1844            let where_clauses = vec![
1845                WhereClause {
1846                    field: "a".to_string(),
1847                    operator: *query_pair.first().unwrap(),
1848                    value: Value::Float(1.0),
1849                },
1850                WhereClause {
1851                    field: "a".to_string(),
1852                    operator: *query_pair.get(1).unwrap(),
1853                    value: Value::Float(0.0),
1854                },
1855            ];
1856            let (_, range_clause, _) =
1857                WhereClause::group_clauses(&where_clauses, PlatformVersion::latest())
1858                    .expect("expected to have groupable pair");
1859            range_clause.expect("expected to have range clause returned");
1860        }
1861    }
1862
1863    #[test]
1864    fn test_query_pairs_incoherent_same_value() {
1865        let allowed_pairs_test_cases = [[LessThan, GreaterThan], [GreaterThan, LessThan]];
1866        for query_pair in allowed_pairs_test_cases {
1867            let where_clauses = vec![
1868                WhereClause {
1869                    field: "a".to_string(),
1870                    operator: *query_pair.first().unwrap(),
1871                    value: Value::Float(1.0),
1872                },
1873                WhereClause {
1874                    field: "a".to_string(),
1875                    operator: *query_pair.get(1).unwrap(),
1876                    value: Value::Float(1.0),
1877                },
1878            ];
1879            WhereClause::group_clauses(&where_clauses, PlatformVersion::latest())
1880                .expect_err("expected to have an error returned");
1881        }
1882    }
1883
1884    #[test]
1885    fn test_different_fields_grouping_causes_error() {
1886        let where_clauses = vec![
1887            WhereClause {
1888                field: "a".to_string(),
1889                operator: LessThan,
1890                value: Value::Float(0.0),
1891            },
1892            WhereClause {
1893                field: "b".to_string(),
1894                operator: GreaterThan,
1895                value: Value::Float(1.0),
1896            },
1897        ];
1898        WhereClause::group_clauses(&where_clauses, PlatformVersion::latest())
1899            .expect_err("different fields should not be groupable");
1900    }
1901
1902    #[test]
1903    fn test_restricted_query_pairs_causes_error() {
1904        let restricted_pairs_test_cases = [
1905            [Equal, LessThan],
1906            [Equal, GreaterThan],
1907            [In, LessThan],
1908            [Equal, GreaterThan],
1909            [LessThanOrEquals, LessThanOrEquals],
1910            [LessThan, LessThan],
1911            [LessThan, LessThanOrEquals],
1912            [GreaterThan, GreaterThan],
1913            [GreaterThan, GreaterThanOrEquals],
1914            [GreaterThanOrEquals, GreaterThanOrEquals],
1915            [Equal, Equal],
1916        ];
1917        for query_pair in restricted_pairs_test_cases {
1918            let where_clauses = vec![
1919                WhereClause {
1920                    field: "a".to_string(),
1921                    operator: *query_pair.first().unwrap(),
1922                    value: Value::Float(0.0),
1923                },
1924                WhereClause {
1925                    field: "a".to_string(),
1926                    operator: *query_pair.get(1).unwrap(),
1927                    value: Value::Float(1.0),
1928                },
1929            ];
1930            WhereClause::group_clauses(&where_clauses, PlatformVersion::latest())
1931                .expect_err("expected to not have a groupable pair");
1932        }
1933    }
1934
1935    #[test]
1936    fn validate_rejects_equality_with_wrong_type_for_string_field() {
1937        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1938        let contract = fixture.data_contract_owned();
1939        let doc_type = contract
1940            .document_type_for_name("niceDocument")
1941            .expect("doc type exists");
1942
1943        let clause = WhereClause {
1944            field: "name".to_string(),
1945            operator: Equal,
1946            value: Value::Identifier([1u8; 32]),
1947        };
1948        let res = clause.validate_against_schema(doc_type);
1949        assert!(res.is_err());
1950        assert!(matches!(
1951            res.first_error(),
1952            Some(QuerySyntaxError::InvalidWhereClauseComponents(_))
1953        ));
1954    }
1955
1956    #[test]
1957    fn validate_rejects_in_with_wrong_element_types() {
1958        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1959        let contract = fixture.data_contract_owned();
1960        let doc_type = contract
1961            .document_type_for_name("indexedDocument")
1962            .expect("doc type exists");
1963
1964        let clause = WhereClause {
1965            field: "firstName".to_string(),
1966            operator: In,
1967            value: Value::Array(vec![
1968                Value::Text("alice".to_string()),
1969                Value::Identifier([2u8; 32]),
1970            ]),
1971        };
1972        let res = clause.validate_against_schema(doc_type);
1973        assert!(res.is_err());
1974        assert!(matches!(
1975            res.first_error(),
1976            Some(QuerySyntaxError::InvalidWhereClauseComponents(_))
1977        ));
1978    }
1979
1980    #[test]
1981    fn validate_rejects_primary_key_in_with_non_identifiers() {
1982        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1983        let contract = fixture.data_contract_owned();
1984        let doc_type = contract
1985            .document_type_for_name("niceDocument")
1986            .expect("doc type exists");
1987
1988        let clauses = InternalClauses {
1989            primary_key_in_clause: Some(WhereClause {
1990                field: "$id".to_string(),
1991                operator: In,
1992                value: Value::Array(vec![
1993                    Value::Text("a".to_string()),
1994                    Value::Text("b".to_string()),
1995                ]),
1996            }),
1997            ..Default::default()
1998        };
1999
2000        let res = clauses.validate_against_schema(doc_type);
2001        assert!(res.is_err());
2002        assert!(matches!(
2003            res.first_error(),
2004            Some(QuerySyntaxError::InvalidWhereClauseComponents(_))
2005        ));
2006    }
2007
2008    #[test]
2009    fn validate_rejects_date_with_float_equality() {
2010        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2011        let contract = fixture.data_contract_owned();
2012        let doc_type = contract
2013            .document_type_for_name("uniqueDates")
2014            .expect("doc type exists");
2015
2016        let clause = WhereClause {
2017            field: "$createdAt".to_string(),
2018            operator: Equal,
2019            value: Value::Float(1.23),
2020        };
2021        let res = clause.validate_against_schema(doc_type);
2022        assert!(res.is_err());
2023        assert!(matches!(
2024            res.first_error(),
2025            Some(QuerySyntaxError::InvalidWhereClauseComponents(_))
2026        ));
2027    }
2028
2029    #[test]
2030    fn validate_rejects_in_bytes_for_string_field() {
2031        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2032        let contract = fixture.data_contract_owned();
2033        let doc_type = contract
2034            .document_type_for_name("niceDocument")
2035            .expect("doc type exists");
2036
2037        // IN with Bytes should be rejected on string fields
2038        let clause = WhereClause {
2039            field: "name".to_string(),
2040            operator: In,
2041            value: Value::Bytes(vec![1, 2, 3]),
2042        };
2043        let res = clause.validate_against_schema(doc_type);
2044        assert!(res.is_err());
2045    }
2046
2047    #[test]
2048    fn validate_accepts_meta_owner_id_in_identifiers() {
2049        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2050        let contract = fixture.data_contract_owned();
2051        let doc_type = contract
2052            .document_type_for_name("niceDocument")
2053            .expect("doc type exists");
2054
2055        let clause = WhereClause {
2056            field: "$ownerId".to_string(),
2057            operator: In,
2058            value: Value::Array(vec![
2059                Value::Identifier([1u8; 32]),
2060                Value::Identifier([2u8; 32]),
2061            ]),
2062        };
2063        let res = clause.validate_against_schema(doc_type);
2064        assert!(res.is_valid());
2065    }
2066
2067    #[test]
2068    fn validate_accepts_meta_created_at_between_integers() {
2069        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2070        let contract = fixture.data_contract_owned();
2071        let doc_type = contract
2072            .document_type_for_name("uniqueDates")
2073            .expect("doc type exists");
2074
2075        let clause = WhereClause {
2076            field: "$createdAt".to_string(),
2077            operator: crate::query::conditions::Between,
2078            value: Value::Array(vec![Value::U64(1000), Value::U64(2000)]),
2079        };
2080        let res = clause.validate_against_schema(doc_type);
2081        assert!(res.is_valid());
2082    }
2083
2084    #[test]
2085    fn validate_rejects_between_variants_with_equal_bounds() {
2086        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2087        let contract = fixture.data_contract_owned();
2088        let doc_type = contract
2089            .document_type_for_name("uniqueDates")
2090            .expect("doc type exists");
2091
2092        for operator in [
2093            Between,
2094            BetweenExcludeBounds,
2095            BetweenExcludeLeft,
2096            BetweenExcludeRight,
2097        ] {
2098            let clause = WhereClause {
2099                field: "$createdAt".to_string(),
2100                operator,
2101                value: Value::Array(vec![Value::U64(1000), Value::U64(1000)]),
2102            };
2103
2104            let res = clause.validate_against_schema(doc_type);
2105            assert!(
2106                res.is_err(),
2107                "{operator:?} should reject equal bounds during validation"
2108            );
2109            assert!(matches!(
2110                res.first_error(),
2111                Some(QuerySyntaxError::InvalidBetweenClause(_))
2112            ));
2113        }
2114    }
2115
2116    #[test]
2117    fn value_clause_between_variants_do_not_match_equal_bounds() {
2118        let equal_bounds = Value::Array(vec![Value::U64(1000), Value::U64(1000)]);
2119        let value_to_test = Value::U64(1000);
2120
2121        for operator in [
2122            Between,
2123            BetweenExcludeBounds,
2124            BetweenExcludeLeft,
2125            BetweenExcludeRight,
2126        ] {
2127            let clause = ValueClause {
2128                operator,
2129                value: equal_bounds.clone(),
2130            };
2131
2132            assert!(
2133                !clause.matches_value(&value_to_test),
2134                "{operator:?} should not match when bounds are equal"
2135            );
2136        }
2137    }
2138
2139    #[test]
2140    fn validate_rejects_meta_revision_float_equality() {
2141        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2142        let contract = fixture.data_contract_owned();
2143        let doc_type = contract
2144            .document_type_for_name("niceDocument")
2145            .expect("doc type exists");
2146
2147        let clause = WhereClause {
2148            field: "$revision".to_string(),
2149            operator: Equal,
2150            value: Value::Float(3.15),
2151        };
2152        let res = clause.validate_against_schema(doc_type);
2153        assert!(res.is_err());
2154    }
2155
2156    #[test]
2157    fn validate_accepts_meta_created_at_block_height_range() {
2158        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2159        let contract = fixture.data_contract_owned();
2160        let doc_type = contract
2161            .document_type_for_name("uniqueDates")
2162            .expect("doc type exists");
2163
2164        let clause = WhereClause {
2165            field: "$createdAtBlockHeight".to_string(),
2166            operator: GreaterThanOrEquals,
2167            value: Value::U64(100),
2168        };
2169        let res = clause.validate_against_schema(doc_type);
2170        assert!(res.is_valid());
2171    }
2172
2173    #[test]
2174    fn validate_accepts_meta_data_contract_id_equality() {
2175        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2176        let contract = fixture.data_contract_owned();
2177        let doc_type = contract
2178            .document_type_for_name("niceDocument")
2179            .expect("doc type exists");
2180
2181        let clause = WhereClause {
2182            field: "$dataContractId".to_string(),
2183            operator: Equal,
2184            value: Value::Identifier([3u8; 32]),
2185        };
2186        let res = clause.validate_against_schema(doc_type);
2187        assert!(res.is_valid());
2188    }
2189
2190    // ---- WhereOperator::allows_flip ----
2191
2192    #[test]
2193    fn allows_flip_returns_true_for_comparison_operators() {
2194        assert!(Equal.allows_flip());
2195        assert!(GreaterThan.allows_flip());
2196        assert!(GreaterThanOrEquals.allows_flip());
2197        assert!(LessThan.allows_flip());
2198        assert!(LessThanOrEquals.allows_flip());
2199    }
2200
2201    #[test]
2202    fn allows_flip_returns_false_for_non_flippable_operators() {
2203        assert!(!Between.allows_flip());
2204        assert!(!BetweenExcludeBounds.allows_flip());
2205        assert!(!BetweenExcludeLeft.allows_flip());
2206        assert!(!BetweenExcludeRight.allows_flip());
2207        assert!(!In.allows_flip());
2208        assert!(!super::StartsWith.allows_flip());
2209    }
2210
2211    // ---- WhereOperator::flip ----
2212
2213    #[test]
2214    fn flip_equal_stays_equal() {
2215        assert_eq!(Equal.flip().unwrap(), Equal);
2216    }
2217
2218    #[test]
2219    fn flip_greater_than_becomes_less_than() {
2220        assert_eq!(GreaterThan.flip().unwrap(), LessThan);
2221    }
2222
2223    #[test]
2224    fn flip_greater_than_or_equals_becomes_less_than_or_equals() {
2225        assert_eq!(GreaterThanOrEquals.flip().unwrap(), LessThanOrEquals);
2226    }
2227
2228    #[test]
2229    fn flip_less_than_becomes_greater_than() {
2230        assert_eq!(LessThan.flip().unwrap(), GreaterThan);
2231    }
2232
2233    #[test]
2234    fn flip_less_than_or_equals_becomes_greater_than_or_equals() {
2235        assert_eq!(LessThanOrEquals.flip().unwrap(), GreaterThanOrEquals);
2236    }
2237
2238    #[test]
2239    fn flip_between_returns_error() {
2240        assert!(Between.flip().is_err());
2241    }
2242
2243    #[test]
2244    fn flip_between_exclude_bounds_returns_error() {
2245        assert!(BetweenExcludeBounds.flip().is_err());
2246    }
2247
2248    #[test]
2249    fn flip_between_exclude_left_returns_error() {
2250        assert!(BetweenExcludeLeft.flip().is_err());
2251    }
2252
2253    #[test]
2254    fn flip_between_exclude_right_returns_error() {
2255        assert!(BetweenExcludeRight.flip().is_err());
2256    }
2257
2258    #[test]
2259    fn flip_in_returns_error() {
2260        assert!(In.flip().is_err());
2261    }
2262
2263    #[test]
2264    fn flip_starts_with_returns_error() {
2265        assert!(super::StartsWith.flip().is_err());
2266    }
2267
2268    // ---- WhereOperator::is_range ----
2269
2270    #[test]
2271    fn is_range_false_for_equal() {
2272        assert!(!Equal.is_range());
2273    }
2274
2275    #[test]
2276    fn is_range_true_for_all_range_operators() {
2277        assert!(GreaterThan.is_range());
2278        assert!(GreaterThanOrEquals.is_range());
2279        assert!(LessThan.is_range());
2280        assert!(LessThanOrEquals.is_range());
2281        assert!(Between.is_range());
2282        assert!(BetweenExcludeBounds.is_range());
2283        assert!(BetweenExcludeLeft.is_range());
2284        assert!(BetweenExcludeRight.is_range());
2285        assert!(In.is_range());
2286        assert!(super::StartsWith.is_range());
2287    }
2288
2289    // ---- WhereOperator::from_string ----
2290
2291    #[test]
2292    fn from_string_parses_equality_operators() {
2293        use super::WhereOperator;
2294        assert_eq!(WhereOperator::from_string("="), Some(Equal));
2295        assert_eq!(WhereOperator::from_string("=="), Some(Equal));
2296    }
2297
2298    #[test]
2299    fn from_string_parses_comparison_operators() {
2300        use super::WhereOperator;
2301        assert_eq!(WhereOperator::from_string(">"), Some(GreaterThan));
2302        assert_eq!(WhereOperator::from_string(">="), Some(GreaterThanOrEquals));
2303        assert_eq!(WhereOperator::from_string("<"), Some(LessThan));
2304        assert_eq!(WhereOperator::from_string("<="), Some(LessThanOrEquals));
2305    }
2306
2307    #[test]
2308    fn from_string_parses_between_variants() {
2309        use super::WhereOperator;
2310        assert_eq!(WhereOperator::from_string("Between"), Some(Between));
2311        assert_eq!(WhereOperator::from_string("between"), Some(Between));
2312        assert_eq!(
2313            WhereOperator::from_string("BetweenExcludeBounds"),
2314            Some(BetweenExcludeBounds)
2315        );
2316        assert_eq!(
2317            WhereOperator::from_string("betweenExcludeBounds"),
2318            Some(BetweenExcludeBounds)
2319        );
2320        assert_eq!(
2321            WhereOperator::from_string("betweenexcludebounds"),
2322            Some(BetweenExcludeBounds)
2323        );
2324        assert_eq!(
2325            WhereOperator::from_string("between_exclude_bounds"),
2326            Some(BetweenExcludeBounds)
2327        );
2328        assert_eq!(
2329            WhereOperator::from_string("BetweenExcludeLeft"),
2330            Some(BetweenExcludeLeft)
2331        );
2332        assert_eq!(
2333            WhereOperator::from_string("betweenExcludeLeft"),
2334            Some(BetweenExcludeLeft)
2335        );
2336        assert_eq!(
2337            WhereOperator::from_string("betweenexcludeleft"),
2338            Some(BetweenExcludeLeft)
2339        );
2340        assert_eq!(
2341            WhereOperator::from_string("between_exclude_left"),
2342            Some(BetweenExcludeLeft)
2343        );
2344        assert_eq!(
2345            WhereOperator::from_string("BetweenExcludeRight"),
2346            Some(BetweenExcludeRight)
2347        );
2348        assert_eq!(
2349            WhereOperator::from_string("betweenExcludeRight"),
2350            Some(BetweenExcludeRight)
2351        );
2352        assert_eq!(
2353            WhereOperator::from_string("betweenexcluderight"),
2354            Some(BetweenExcludeRight)
2355        );
2356        assert_eq!(
2357            WhereOperator::from_string("between_exclude_right"),
2358            Some(BetweenExcludeRight)
2359        );
2360    }
2361
2362    #[test]
2363    fn from_string_parses_in_operator() {
2364        use super::WhereOperator;
2365        assert_eq!(WhereOperator::from_string("In"), Some(In));
2366        assert_eq!(WhereOperator::from_string("in"), Some(In));
2367    }
2368
2369    #[test]
2370    fn from_string_parses_starts_with_operator() {
2371        use super::WhereOperator;
2372        assert_eq!(
2373            WhereOperator::from_string("StartsWith"),
2374            Some(super::StartsWith)
2375        );
2376        assert_eq!(
2377            WhereOperator::from_string("startsWith"),
2378            Some(super::StartsWith)
2379        );
2380        assert_eq!(
2381            WhereOperator::from_string("startswith"),
2382            Some(super::StartsWith)
2383        );
2384        assert_eq!(
2385            WhereOperator::from_string("starts_with"),
2386            Some(super::StartsWith)
2387        );
2388    }
2389
2390    #[test]
2391    fn from_string_returns_none_for_unknown() {
2392        use super::WhereOperator;
2393        assert_eq!(WhereOperator::from_string("LIKE"), None);
2394        assert_eq!(WhereOperator::from_string("!="), None);
2395        assert_eq!(WhereOperator::from_string(""), None);
2396    }
2397
2398    // ---- WhereOperator::from_sql_operator ----
2399
2400    #[test]
2401    fn from_sql_operator_maps_known_operators() {
2402        use super::WhereOperator;
2403        use sqlparser::ast::BinaryOperator;
2404        assert_eq!(
2405            WhereOperator::from_sql_operator(BinaryOperator::Eq),
2406            Some(Equal)
2407        );
2408        assert_eq!(
2409            WhereOperator::from_sql_operator(BinaryOperator::Gt),
2410            Some(GreaterThan)
2411        );
2412        assert_eq!(
2413            WhereOperator::from_sql_operator(BinaryOperator::GtEq),
2414            Some(GreaterThanOrEquals)
2415        );
2416        assert_eq!(
2417            WhereOperator::from_sql_operator(BinaryOperator::Lt),
2418            Some(LessThan)
2419        );
2420        assert_eq!(
2421            WhereOperator::from_sql_operator(BinaryOperator::LtEq),
2422            Some(LessThanOrEquals)
2423        );
2424    }
2425
2426    #[test]
2427    fn from_sql_operator_returns_none_for_unsupported() {
2428        use super::WhereOperator;
2429        use sqlparser::ast::BinaryOperator;
2430        assert_eq!(
2431            WhereOperator::from_sql_operator(BinaryOperator::NotEq),
2432            None
2433        );
2434        assert_eq!(WhereOperator::from_sql_operator(BinaryOperator::Plus), None);
2435    }
2436
2437    // ---- WhereOperator::eval ----
2438
2439    #[test]
2440    fn eval_equal_matches_identical_values() {
2441        assert!(Equal.eval(&Value::I64(42), &Value::I64(42)));
2442        assert!(!Equal.eval(&Value::I64(42), &Value::I64(43)));
2443    }
2444
2445    #[test]
2446    fn eval_greater_than() {
2447        assert!(GreaterThan.eval(&Value::I64(10), &Value::I64(5)));
2448        assert!(!GreaterThan.eval(&Value::I64(5), &Value::I64(10)));
2449        assert!(!GreaterThan.eval(&Value::I64(5), &Value::I64(5)));
2450    }
2451
2452    #[test]
2453    fn eval_greater_than_or_equals() {
2454        assert!(GreaterThanOrEquals.eval(&Value::I64(10), &Value::I64(5)));
2455        assert!(GreaterThanOrEquals.eval(&Value::I64(5), &Value::I64(5)));
2456        assert!(!GreaterThanOrEquals.eval(&Value::I64(4), &Value::I64(5)));
2457    }
2458
2459    #[test]
2460    fn eval_less_than() {
2461        assert!(LessThan.eval(&Value::I64(3), &Value::I64(5)));
2462        assert!(!LessThan.eval(&Value::I64(5), &Value::I64(3)));
2463        assert!(!LessThan.eval(&Value::I64(5), &Value::I64(5)));
2464    }
2465
2466    #[test]
2467    fn eval_less_than_or_equals() {
2468        assert!(LessThanOrEquals.eval(&Value::I64(3), &Value::I64(5)));
2469        assert!(LessThanOrEquals.eval(&Value::I64(5), &Value::I64(5)));
2470        assert!(!LessThanOrEquals.eval(&Value::I64(6), &Value::I64(5)));
2471    }
2472
2473    #[test]
2474    fn eval_in_with_array() {
2475        let arr = Value::Array(vec![Value::I64(1), Value::I64(2), Value::I64(3)]);
2476        assert!(In.eval(&Value::I64(2), &arr));
2477        assert!(!In.eval(&Value::I64(4), &arr));
2478    }
2479
2480    #[test]
2481    fn eval_in_with_bytes() {
2482        let bytes = Value::Bytes(vec![10, 20, 30]);
2483        assert!(In.eval(&Value::U8(20), &bytes));
2484        assert!(!In.eval(&Value::U8(40), &bytes));
2485        // Non-U8 value against Bytes should return false
2486        assert!(!In.eval(&Value::I64(20), &bytes));
2487    }
2488
2489    #[test]
2490    fn eval_in_with_non_collection_returns_false() {
2491        assert!(!In.eval(&Value::I64(1), &Value::I64(1)));
2492    }
2493
2494    #[test]
2495    fn eval_between_inclusive() {
2496        let bounds = Value::Array(vec![Value::I64(10), Value::I64(20)]);
2497        assert!(Between.eval(&Value::I64(10), &bounds));
2498        assert!(Between.eval(&Value::I64(15), &bounds));
2499        assert!(Between.eval(&Value::I64(20), &bounds));
2500        assert!(!Between.eval(&Value::I64(9), &bounds));
2501        assert!(!Between.eval(&Value::I64(21), &bounds));
2502    }
2503
2504    #[test]
2505    fn eval_between_exclude_bounds() {
2506        let bounds = Value::Array(vec![Value::I64(10), Value::I64(20)]);
2507        assert!(!BetweenExcludeBounds.eval(&Value::I64(10), &bounds));
2508        assert!(BetweenExcludeBounds.eval(&Value::I64(15), &bounds));
2509        assert!(!BetweenExcludeBounds.eval(&Value::I64(20), &bounds));
2510    }
2511
2512    #[test]
2513    fn eval_between_exclude_left() {
2514        let bounds = Value::Array(vec![Value::I64(10), Value::I64(20)]);
2515        assert!(!BetweenExcludeLeft.eval(&Value::I64(10), &bounds));
2516        assert!(BetweenExcludeLeft.eval(&Value::I64(15), &bounds));
2517        assert!(BetweenExcludeLeft.eval(&Value::I64(20), &bounds));
2518    }
2519
2520    #[test]
2521    fn eval_between_exclude_right() {
2522        let bounds = Value::Array(vec![Value::I64(10), Value::I64(20)]);
2523        assert!(BetweenExcludeRight.eval(&Value::I64(10), &bounds));
2524        assert!(BetweenExcludeRight.eval(&Value::I64(15), &bounds));
2525        assert!(!BetweenExcludeRight.eval(&Value::I64(20), &bounds));
2526    }
2527
2528    #[test]
2529    fn eval_between_with_wrong_bound_order_returns_false() {
2530        // Bounds in descending order should not match anything
2531        let bounds = Value::Array(vec![Value::I64(20), Value::I64(10)]);
2532        assert!(!Between.eval(&Value::I64(15), &bounds));
2533        assert!(!BetweenExcludeBounds.eval(&Value::I64(15), &bounds));
2534        assert!(!BetweenExcludeLeft.eval(&Value::I64(15), &bounds));
2535        assert!(!BetweenExcludeRight.eval(&Value::I64(15), &bounds));
2536    }
2537
2538    #[test]
2539    fn eval_between_with_non_array_returns_false() {
2540        assert!(!Between.eval(&Value::I64(5), &Value::I64(10)));
2541    }
2542
2543    #[test]
2544    fn eval_between_with_wrong_array_len_returns_false() {
2545        let single = Value::Array(vec![Value::I64(10)]);
2546        assert!(!Between.eval(&Value::I64(10), &single));
2547    }
2548
2549    #[test]
2550    fn eval_starts_with_text() {
2551        assert!(super::StartsWith.eval(
2552            &Value::Text("hello world".to_string()),
2553            &Value::Text("hello".to_string())
2554        ));
2555        assert!(!super::StartsWith.eval(
2556            &Value::Text("hello world".to_string()),
2557            &Value::Text("world".to_string())
2558        ));
2559    }
2560
2561    #[test]
2562    fn eval_starts_with_non_text_returns_false() {
2563        assert!(!super::StartsWith.eval(&Value::I64(123), &Value::Text("1".to_string())));
2564        assert!(!super::StartsWith.eval(&Value::Text("hello".to_string()), &Value::I64(1)));
2565    }
2566
2567    // ---- WhereOperator Display ----
2568
2569    #[test]
2570    fn display_formatting_for_all_operators() {
2571        assert_eq!(format!("{}", Equal), "=");
2572        assert_eq!(format!("{}", GreaterThan), ">");
2573        assert_eq!(format!("{}", GreaterThanOrEquals), ">=");
2574        assert_eq!(format!("{}", LessThan), "<");
2575        assert_eq!(format!("{}", LessThanOrEquals), "<=");
2576        assert_eq!(format!("{}", Between), "Between");
2577        assert_eq!(format!("{}", BetweenExcludeBounds), "BetweenExcludeBounds");
2578        assert_eq!(format!("{}", BetweenExcludeLeft), "BetweenExcludeLeft");
2579        assert_eq!(format!("{}", BetweenExcludeRight), "BetweenExcludeRight");
2580        assert_eq!(format!("{}", In), "In");
2581        assert_eq!(format!("{}", super::StartsWith), "StartsWith");
2582    }
2583
2584    // ---- WhereOperator -> Value conversion ----
2585
2586    #[test]
2587    fn where_operator_into_value() {
2588        let val: Value = Equal.into();
2589        assert_eq!(val, Value::Text("=".to_string()));
2590
2591        let val: Value = In.into();
2592        assert_eq!(val, Value::Text("In".to_string()));
2593    }
2594
2595    // ---- WhereClause::is_identifier ----
2596
2597    #[test]
2598    fn is_identifier_returns_true_for_dollar_id() {
2599        let clause = WhereClause {
2600            field: "$id".to_string(),
2601            operator: Equal,
2602            value: Value::I64(1),
2603        };
2604        assert!(clause.is_identifier());
2605    }
2606
2607    #[test]
2608    fn is_identifier_returns_false_for_other_fields() {
2609        let clause = WhereClause {
2610            field: "name".to_string(),
2611            operator: Equal,
2612            value: Value::I64(1),
2613        };
2614        assert!(!clause.is_identifier());
2615
2616        let clause = WhereClause {
2617            field: "$ownerId".to_string(),
2618            operator: Equal,
2619            value: Value::I64(1),
2620        };
2621        assert!(!clause.is_identifier());
2622    }
2623
2624    // ---- WhereClause::in_values ----
2625
2626    #[test]
2627    fn in_values_with_array() {
2628        let clause = WhereClause {
2629            field: "f".to_string(),
2630            operator: In,
2631            value: Value::Array(vec![Value::I64(1), Value::I64(2)]),
2632        };
2633        let result = clause.in_values();
2634        assert!(result.is_valid());
2635        let data = result.into_data().expect("should have data");
2636        assert_eq!(data.len(), 2);
2637    }
2638
2639    #[test]
2640    fn in_values_with_bytes() {
2641        let clause = WhereClause {
2642            field: "f".to_string(),
2643            operator: In,
2644            value: Value::Bytes(vec![10, 20]),
2645        };
2646        let result = clause.in_values();
2647        assert!(result.is_valid());
2648        let data = result.into_data().expect("should have data");
2649        assert_eq!(data.len(), 2);
2650        assert_eq!(data[0], Value::U8(10));
2651        assert_eq!(data[1], Value::U8(20));
2652    }
2653
2654    #[test]
2655    fn in_values_non_array_returns_error() {
2656        let clause = WhereClause {
2657            field: "f".to_string(),
2658            operator: In,
2659            value: Value::I64(42),
2660        };
2661        let result = clause.in_values();
2662        assert!(!result.is_valid());
2663    }
2664
2665    #[test]
2666    fn in_values_empty_array_returns_error() {
2667        let clause = WhereClause {
2668            field: "f".to_string(),
2669            operator: In,
2670            value: Value::Array(vec![]),
2671        };
2672        let result = clause.in_values();
2673        assert!(!result.is_valid());
2674    }
2675
2676    #[test]
2677    fn in_values_too_many_returns_error() {
2678        let values: Vec<Value> = (0..101).map(Value::I64).collect();
2679        let clause = WhereClause {
2680            field: "f".to_string(),
2681            operator: In,
2682            value: Value::Array(values),
2683        };
2684        let result = clause.in_values();
2685        assert!(!result.is_valid());
2686    }
2687
2688    #[test]
2689    fn in_values_with_duplicates_returns_error() {
2690        let clause = WhereClause {
2691            field: "f".to_string(),
2692            operator: In,
2693            value: Value::Array(vec![Value::I64(1), Value::I64(1)]),
2694        };
2695        let result = clause.in_values();
2696        assert!(!result.is_valid());
2697    }
2698
2699    // ---- WhereClause::less_than ----
2700
2701    #[test]
2702    fn less_than_with_i128_values() {
2703        let a = WhereClause {
2704            field: "f".to_string(),
2705            operator: Equal,
2706            value: Value::I128(5),
2707        };
2708        let b = WhereClause {
2709            field: "f".to_string(),
2710            operator: Equal,
2711            value: Value::I128(10),
2712        };
2713        assert!(a.less_than(&b, false).unwrap());
2714        assert!(a.less_than(&b, true).unwrap());
2715        assert!(!b.less_than(&a, false).unwrap());
2716        assert!(a.less_than(&a, true).unwrap()); // le
2717        assert!(!a.less_than(&a, false).unwrap()); // lt
2718    }
2719
2720    #[test]
2721    fn less_than_with_u128_values() {
2722        let a = WhereClause {
2723            field: "f".to_string(),
2724            operator: Equal,
2725            value: Value::U128(1),
2726        };
2727        let b = WhereClause {
2728            field: "f".to_string(),
2729            operator: Equal,
2730            value: Value::U128(2),
2731        };
2732        assert!(a.less_than(&b, false).unwrap());
2733        assert!(!b.less_than(&a, false).unwrap());
2734    }
2735
2736    #[test]
2737    fn less_than_with_i64_values() {
2738        let a = WhereClause {
2739            field: "f".to_string(),
2740            operator: Equal,
2741            value: Value::I64(-5),
2742        };
2743        let b = WhereClause {
2744            field: "f".to_string(),
2745            operator: Equal,
2746            value: Value::I64(10),
2747        };
2748        assert!(a.less_than(&b, false).unwrap());
2749    }
2750
2751    #[test]
2752    fn less_than_with_u64_values() {
2753        let a = WhereClause {
2754            field: "f".to_string(),
2755            operator: Equal,
2756            value: Value::U64(3),
2757        };
2758        let b = WhereClause {
2759            field: "f".to_string(),
2760            operator: Equal,
2761            value: Value::U64(7),
2762        };
2763        assert!(a.less_than(&b, false).unwrap());
2764    }
2765
2766    #[test]
2767    fn less_than_with_i32_values() {
2768        let a = WhereClause {
2769            field: "f".to_string(),
2770            operator: Equal,
2771            value: Value::I32(1),
2772        };
2773        let b = WhereClause {
2774            field: "f".to_string(),
2775            operator: Equal,
2776            value: Value::I32(2),
2777        };
2778        assert!(a.less_than(&b, false).unwrap());
2779    }
2780
2781    #[test]
2782    fn less_than_with_u32_values() {
2783        let a = WhereClause {
2784            field: "f".to_string(),
2785            operator: Equal,
2786            value: Value::U32(1),
2787        };
2788        let b = WhereClause {
2789            field: "f".to_string(),
2790            operator: Equal,
2791            value: Value::U32(2),
2792        };
2793        assert!(a.less_than(&b, false).unwrap());
2794    }
2795
2796    #[test]
2797    fn less_than_with_i16_values() {
2798        let a = WhereClause {
2799            field: "f".to_string(),
2800            operator: Equal,
2801            value: Value::I16(1),
2802        };
2803        let b = WhereClause {
2804            field: "f".to_string(),
2805            operator: Equal,
2806            value: Value::I16(2),
2807        };
2808        assert!(a.less_than(&b, false).unwrap());
2809        assert!(a.less_than(&b, true).unwrap());
2810    }
2811
2812    #[test]
2813    fn less_than_with_u16_values() {
2814        let a = WhereClause {
2815            field: "f".to_string(),
2816            operator: Equal,
2817            value: Value::U16(1),
2818        };
2819        let b = WhereClause {
2820            field: "f".to_string(),
2821            operator: Equal,
2822            value: Value::U16(2),
2823        };
2824        assert!(a.less_than(&b, false).unwrap());
2825    }
2826
2827    #[test]
2828    fn less_than_with_i8_values() {
2829        let a = WhereClause {
2830            field: "f".to_string(),
2831            operator: Equal,
2832            value: Value::I8(1),
2833        };
2834        let b = WhereClause {
2835            field: "f".to_string(),
2836            operator: Equal,
2837            value: Value::I8(2),
2838        };
2839        assert!(a.less_than(&b, false).unwrap());
2840    }
2841
2842    #[test]
2843    fn less_than_with_u8_values() {
2844        let a = WhereClause {
2845            field: "f".to_string(),
2846            operator: Equal,
2847            value: Value::U8(1),
2848        };
2849        let b = WhereClause {
2850            field: "f".to_string(),
2851            operator: Equal,
2852            value: Value::U8(2),
2853        };
2854        assert!(a.less_than(&b, false).unwrap());
2855    }
2856
2857    #[test]
2858    fn less_than_with_bytes_values() {
2859        let a = WhereClause {
2860            field: "f".to_string(),
2861            operator: Equal,
2862            value: Value::Bytes(vec![1, 2]),
2863        };
2864        let b = WhereClause {
2865            field: "f".to_string(),
2866            operator: Equal,
2867            value: Value::Bytes(vec![1, 3]),
2868        };
2869        assert!(a.less_than(&b, false).unwrap());
2870    }
2871
2872    #[test]
2873    fn less_than_with_float_values() {
2874        let a = WhereClause {
2875            field: "f".to_string(),
2876            operator: Equal,
2877            value: Value::Float(1.5),
2878        };
2879        let b = WhereClause {
2880            field: "f".to_string(),
2881            operator: Equal,
2882            value: Value::Float(2.5),
2883        };
2884        assert!(a.less_than(&b, false).unwrap());
2885        assert!(a.less_than(&b, true).unwrap());
2886    }
2887
2888    #[test]
2889    fn less_than_with_text_values() {
2890        let a = WhereClause {
2891            field: "f".to_string(),
2892            operator: Equal,
2893            value: Value::Text("abc".to_string()),
2894        };
2895        let b = WhereClause {
2896            field: "f".to_string(),
2897            operator: Equal,
2898            value: Value::Text("xyz".to_string()),
2899        };
2900        assert!(a.less_than(&b, false).unwrap());
2901    }
2902
2903    #[test]
2904    fn less_than_with_mismatched_types_returns_error() {
2905        let a = WhereClause {
2906            field: "f".to_string(),
2907            operator: Equal,
2908            value: Value::I64(1),
2909        };
2910        let b = WhereClause {
2911            field: "f".to_string(),
2912            operator: Equal,
2913            value: Value::Text("abc".to_string()),
2914        };
2915        assert!(a.less_than(&b, false).is_err());
2916    }
2917
2918    // ---- WhereClause::from_components ----
2919
2920    #[test]
2921    fn from_components_valid_clause() {
2922        let components = vec![
2923            Value::Text("name".to_string()),
2924            Value::Text("=".to_string()),
2925            Value::Text("alice".to_string()),
2926        ];
2927        let clause = WhereClause::from_components(&components).unwrap();
2928        assert_eq!(clause.field, "name");
2929        assert_eq!(clause.operator, Equal);
2930        assert_eq!(clause.value, Value::Text("alice".to_string()));
2931    }
2932
2933    #[test]
2934    fn from_components_wrong_count_returns_error() {
2935        let components = vec![
2936            Value::Text("name".to_string()),
2937            Value::Text("=".to_string()),
2938        ];
2939        assert!(WhereClause::from_components(&components).is_err());
2940
2941        let components = vec![
2942            Value::Text("name".to_string()),
2943            Value::Text("=".to_string()),
2944            Value::I64(1),
2945            Value::I64(2),
2946        ];
2947        assert!(WhereClause::from_components(&components).is_err());
2948    }
2949
2950    #[test]
2951    fn from_components_non_string_field_returns_error() {
2952        let components = vec![Value::I64(123), Value::Text("=".to_string()), Value::I64(1)];
2953        assert!(WhereClause::from_components(&components).is_err());
2954    }
2955
2956    #[test]
2957    fn from_components_non_string_operator_returns_error() {
2958        let components = vec![
2959            Value::Text("name".to_string()),
2960            Value::I64(1),
2961            Value::I64(1),
2962        ];
2963        assert!(WhereClause::from_components(&components).is_err());
2964    }
2965
2966    #[test]
2967    fn from_components_unknown_operator_returns_error() {
2968        let components = vec![
2969            Value::Text("name".to_string()),
2970            Value::Text("LIKE".to_string()),
2971            Value::I64(1),
2972        ];
2973        assert!(WhereClause::from_components(&components).is_err());
2974    }
2975
2976    #[test]
2977    fn from_components_with_in_operator() {
2978        let components = vec![
2979            Value::Text("status".to_string()),
2980            Value::Text("in".to_string()),
2981            Value::Array(vec![Value::I64(1), Value::I64(2)]),
2982        ];
2983        let clause = WhereClause::from_components(&components).unwrap();
2984        assert_eq!(clause.operator, In);
2985    }
2986
2987    #[test]
2988    fn from_components_with_starts_with_operator() {
2989        let components = vec![
2990            Value::Text("name".to_string()),
2991            Value::Text("startsWith".to_string()),
2992            Value::Text("alice".to_string()),
2993        ];
2994        let clause = WhereClause::from_components(&components).unwrap();
2995        assert_eq!(clause.operator, super::StartsWith);
2996    }
2997
2998    // ---- WhereClause -> Value conversion ----
2999
3000    #[test]
3001    fn where_clause_into_value() {
3002        let clause = WhereClause {
3003            field: "name".to_string(),
3004            operator: Equal,
3005            value: Value::Text("alice".to_string()),
3006        };
3007        let val: Value = clause.into();
3008        match val {
3009            Value::Array(arr) => {
3010                assert_eq!(arr.len(), 3);
3011                assert_eq!(arr[0], Value::Text("name".to_string()));
3012                assert_eq!(arr[1], Value::Text("=".to_string()));
3013                assert_eq!(arr[2], Value::Text("alice".to_string()));
3014            }
3015            _ => panic!("expected Array"),
3016        }
3017    }
3018
3019    // ---- ValueClause::matches_value ----
3020
3021    #[test]
3022    fn value_clause_matches_value_equal() {
3023        let clause = ValueClause {
3024            operator: Equal,
3025            value: Value::I64(42),
3026        };
3027        assert!(clause.matches_value(&Value::I64(42)));
3028        assert!(!clause.matches_value(&Value::I64(43)));
3029    }
3030
3031    #[test]
3032    fn value_clause_matches_value_greater_than() {
3033        let clause = ValueClause {
3034            operator: GreaterThan,
3035            value: Value::I64(10),
3036        };
3037        assert!(clause.matches_value(&Value::I64(20)));
3038        assert!(!clause.matches_value(&Value::I64(5)));
3039    }
3040
3041    #[test]
3042    fn value_clause_matches_value_in() {
3043        let clause = ValueClause {
3044            operator: In,
3045            value: Value::Array(vec![Value::I64(1), Value::I64(2), Value::I64(3)]),
3046        };
3047        assert!(clause.matches_value(&Value::I64(2)));
3048        assert!(!clause.matches_value(&Value::I64(4)));
3049    }
3050
3051    #[test]
3052    fn value_clause_matches_value_starts_with() {
3053        let clause = ValueClause {
3054            operator: super::StartsWith,
3055            value: Value::Text("hello".to_string()),
3056        };
3057        assert!(clause.matches_value(&Value::Text("hello world".to_string())));
3058        assert!(!clause.matches_value(&Value::Text("world hello".to_string())));
3059    }
3060
3061    // ---- WhereClause::matches_value ----
3062
3063    #[test]
3064    fn where_clause_matches_value_delegates_to_eval() {
3065        let clause = WhereClause {
3066            field: "age".to_string(),
3067            operator: GreaterThanOrEquals,
3068            value: Value::I64(18),
3069        };
3070        assert!(clause.matches_value(&Value::I64(18)));
3071        assert!(clause.matches_value(&Value::I64(25)));
3072        assert!(!clause.matches_value(&Value::I64(17)));
3073    }
3074
3075    // ---- group_clauses: additional coverage ----
3076
3077    #[test]
3078    fn group_clauses_empty_input() {
3079        let clauses: Vec<WhereClause> = vec![];
3080        let (eq, range, in_c) = WhereClause::group_clauses(&clauses, PlatformVersion::latest())
3081            .expect("empty should succeed");
3082        assert!(eq.is_empty());
3083        assert!(range.is_none());
3084        assert!(in_c.is_empty());
3085    }
3086
3087    #[test]
3088    fn group_clauses_single_equality() {
3089        let clauses = vec![WhereClause {
3090            field: "name".to_string(),
3091            operator: Equal,
3092            value: Value::Text("alice".to_string()),
3093        }];
3094        let (eq, range, in_c) =
3095            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3096        assert_eq!(eq.len(), 1);
3097        assert!(eq.contains_key("name"));
3098        assert!(range.is_none());
3099        assert!(in_c.is_empty());
3100    }
3101
3102    #[test]
3103    fn group_clauses_equality_on_id_is_excluded_from_equals() {
3104        let clauses = vec![WhereClause {
3105            field: "$id".to_string(),
3106            operator: Equal,
3107            value: Value::I64(1),
3108        }];
3109        let (eq, range, in_c) =
3110            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3111        // $id equality is excluded from the equal_clauses map
3112        assert!(eq.is_empty());
3113        assert!(range.is_none());
3114        assert!(in_c.is_empty());
3115    }
3116
3117    #[test]
3118    fn group_clauses_in_on_id_is_excluded_from_in_clause() {
3119        let clauses = vec![WhereClause {
3120            field: "$id".to_string(),
3121            operator: In,
3122            value: Value::Array(vec![Value::I64(1), Value::I64(2)]),
3123        }];
3124        let (eq, range, in_c) =
3125            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3126        assert!(eq.is_empty());
3127        assert!(range.is_none());
3128        assert!(in_c.is_empty());
3129    }
3130
3131    #[test]
3132    fn group_clauses_single_in() {
3133        let clauses = vec![WhereClause {
3134            field: "status".to_string(),
3135            operator: In,
3136            value: Value::Array(vec![Value::I64(1), Value::I64(2)]),
3137        }];
3138        let (eq, range, in_c) =
3139            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3140        assert!(eq.is_empty());
3141        assert!(range.is_none());
3142        assert_eq!(in_c.len(), 1);
3143        assert_eq!(in_c[0].field, "status");
3144    }
3145
3146    #[test]
3147    fn group_clauses_multiple_in_on_distinct_fields_groups_structurally() {
3148        // Whether more than one in clause is accepted is decided at
3149        // path-query lowering (protocol version 14+); the grammar groups
3150        // them structurally in query order.
3151        let clauses = vec![
3152            WhereClause {
3153                field: "a".to_string(),
3154                operator: In,
3155                value: Value::Array(vec![Value::I64(1)]),
3156            },
3157            WhereClause {
3158                field: "b".to_string(),
3159                operator: In,
3160                value: Value::Array(vec![Value::I64(2)]),
3161            },
3162        ];
3163        let (eq, range, in_c) =
3164            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3165        assert!(eq.is_empty());
3166        assert!(range.is_none());
3167        assert_eq!(in_c.len(), 2);
3168        assert_eq!(in_c[0].field, "a");
3169        assert_eq!(in_c[1].field, "b");
3170    }
3171
3172    #[test]
3173    fn group_clauses_multiple_in_v13_reports_multiple_in_before_any_other_check() {
3174        // The v0 grammar (protocol versions up to 13) rejects any query
3175        // with more than one non-primary-key in clause with
3176        // MultipleInClauses before duplicate-field, overlap, or range
3177        // checks — the error surface historical nodes committed to.
3178        let platform_version_13 =
3179            PlatformVersion::get(13).expect("protocol version 13 should exist");
3180        let shapes: Vec<Vec<WhereClause>> = vec![
3181            // duplicate in fields
3182            vec![
3183                WhereClause {
3184                    field: "a".to_string(),
3185                    operator: In,
3186                    value: Value::Array(vec![Value::I64(1)]),
3187                },
3188                WhereClause {
3189                    field: "a".to_string(),
3190                    operator: In,
3191                    value: Value::Array(vec![Value::I64(2)]),
3192                },
3193            ],
3194            // in overlapping an equality
3195            vec![
3196                WhereClause {
3197                    field: "a".to_string(),
3198                    operator: Equal,
3199                    value: Value::I64(1),
3200                },
3201                WhereClause {
3202                    field: "a".to_string(),
3203                    operator: In,
3204                    value: Value::Array(vec![Value::I64(2)]),
3205                },
3206                WhereClause {
3207                    field: "b".to_string(),
3208                    operator: In,
3209                    value: Value::Array(vec![Value::I64(3)]),
3210                },
3211            ],
3212            // two ranges on different fields next to two ins
3213            vec![
3214                WhereClause {
3215                    field: "a".to_string(),
3216                    operator: In,
3217                    value: Value::Array(vec![Value::I64(1)]),
3218                },
3219                WhereClause {
3220                    field: "b".to_string(),
3221                    operator: In,
3222                    value: Value::Array(vec![Value::I64(2)]),
3223                },
3224                WhereClause {
3225                    field: "c".to_string(),
3226                    operator: GreaterThan,
3227                    value: Value::I64(5),
3228                },
3229                WhereClause {
3230                    field: "d".to_string(),
3231                    operator: super::LessThan,
3232                    value: Value::I64(3),
3233                },
3234            ],
3235        ];
3236        for clauses in shapes {
3237            let error = WhereClause::group_clauses(&clauses, platform_version_13)
3238                .expect_err("multi-in shapes must be rejected at protocol version 13");
3239            assert!(
3240                matches!(
3241                    error,
3242                    crate::error::Error::Query(QuerySyntaxError::MultipleInClauses(_))
3243                ),
3244                "expected MultipleInClauses, got {error:?}"
3245            );
3246        }
3247    }
3248
3249    #[test]
3250    fn group_clauses_multiple_in_on_same_field_returns_error() {
3251        let clauses = vec![
3252            WhereClause {
3253                field: "a".to_string(),
3254                operator: In,
3255                value: Value::Array(vec![Value::I64(1)]),
3256            },
3257            WhereClause {
3258                field: "a".to_string(),
3259                operator: In,
3260                value: Value::Array(vec![Value::I64(2)]),
3261            },
3262        ];
3263        assert!(WhereClause::group_clauses(&clauses, PlatformVersion::latest()).is_err());
3264    }
3265
3266    #[test]
3267    fn group_clauses_in_same_field_as_equality_returns_error() {
3268        let clauses = vec![
3269            WhereClause {
3270                field: "status".to_string(),
3271                operator: Equal,
3272                value: Value::I64(1),
3273            },
3274            WhereClause {
3275                field: "status".to_string(),
3276                operator: In,
3277                value: Value::Array(vec![Value::I64(2)]),
3278            },
3279        ];
3280        assert!(WhereClause::group_clauses(&clauses, PlatformVersion::latest()).is_err());
3281    }
3282
3283    #[test]
3284    fn group_clauses_duplicate_equality_same_field_returns_error() {
3285        let clauses = vec![
3286            WhereClause {
3287                field: "name".to_string(),
3288                operator: Equal,
3289                value: Value::Text("alice".to_string()),
3290            },
3291            WhereClause {
3292                field: "name".to_string(),
3293                operator: Equal,
3294                value: Value::Text("bob".to_string()),
3295            },
3296        ];
3297        assert!(WhereClause::group_clauses(&clauses, PlatformVersion::latest()).is_err());
3298    }
3299
3300    #[test]
3301    fn group_clauses_single_range_operator() {
3302        let clauses = vec![WhereClause {
3303            field: "age".to_string(),
3304            operator: GreaterThan,
3305            value: Value::I64(18),
3306        }];
3307        let (eq, range, in_c) =
3308            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3309        assert!(eq.is_empty());
3310        assert!(range.is_some());
3311        assert_eq!(range.unwrap().operator, GreaterThan);
3312        assert!(in_c.is_empty());
3313    }
3314
3315    #[test]
3316    fn group_clauses_single_non_groupable_range_between() {
3317        let clauses = vec![WhereClause {
3318            field: "age".to_string(),
3319            operator: Between,
3320            value: Value::Array(vec![Value::Float(0.0), Value::Float(100.0)]),
3321        }];
3322        let (eq, range, in_c) =
3323            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3324        assert!(eq.is_empty());
3325        assert!(range.is_some());
3326        assert_eq!(range.unwrap().operator, Between);
3327        assert!(in_c.is_empty());
3328    }
3329
3330    #[test]
3331    fn group_clauses_starts_with_empty_string_returns_error() {
3332        let clauses = vec![WhereClause {
3333            field: "name".to_string(),
3334            operator: super::StartsWith,
3335            value: Value::Text("".to_string()),
3336        }];
3337        assert!(WhereClause::group_clauses(&clauses, PlatformVersion::latest()).is_err());
3338    }
3339
3340    #[test]
3341    fn group_clauses_starts_with_valid_string() {
3342        let clauses = vec![WhereClause {
3343            field: "name".to_string(),
3344            operator: super::StartsWith,
3345            value: Value::Text("al".to_string()),
3346        }];
3347        let (eq, range, in_c) =
3348            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3349        assert!(eq.is_empty());
3350        assert!(range.is_some());
3351        assert_eq!(range.unwrap().operator, super::StartsWith);
3352        assert!(in_c.is_empty());
3353    }
3354
3355    #[test]
3356    fn group_clauses_non_groupable_range_same_field_as_equality_returns_error() {
3357        let clauses = vec![
3358            WhereClause {
3359                field: "name".to_string(),
3360                operator: Equal,
3361                value: Value::Text("alice".to_string()),
3362            },
3363            WhereClause {
3364                field: "name".to_string(),
3365                operator: super::StartsWith,
3366                value: Value::Text("al".to_string()),
3367            },
3368        ];
3369        assert!(WhereClause::group_clauses(&clauses, PlatformVersion::latest()).is_err());
3370    }
3371
3372    #[test]
3373    fn group_clauses_multiple_non_groupable_ranges_returns_error() {
3374        let clauses = vec![
3375            WhereClause {
3376                field: "a".to_string(),
3377                operator: Between,
3378                value: Value::Array(vec![Value::Float(0.0), Value::Float(10.0)]),
3379            },
3380            WhereClause {
3381                field: "b".to_string(),
3382                operator: super::StartsWith,
3383                value: Value::Text("x".to_string()),
3384            },
3385        ];
3386        assert!(WhereClause::group_clauses(&clauses, PlatformVersion::latest()).is_err());
3387    }
3388
3389    #[test]
3390    fn group_clauses_mixed_groupable_and_non_groupable_returns_error() {
3391        let clauses = vec![
3392            WhereClause {
3393                field: "a".to_string(),
3394                operator: GreaterThan,
3395                value: Value::Float(0.0),
3396            },
3397            WhereClause {
3398                field: "b".to_string(),
3399                operator: Between,
3400                value: Value::Array(vec![Value::Float(0.0), Value::Float(10.0)]),
3401            },
3402        ];
3403        assert!(WhereClause::group_clauses(&clauses, PlatformVersion::latest()).is_err());
3404    }
3405
3406    #[test]
3407    fn group_clauses_three_groupable_ranges_returns_error() {
3408        let clauses = vec![
3409            WhereClause {
3410                field: "a".to_string(),
3411                operator: GreaterThan,
3412                value: Value::Float(0.0),
3413            },
3414            WhereClause {
3415                field: "a".to_string(),
3416                operator: LessThan,
3417                value: Value::Float(10.0),
3418            },
3419            WhereClause {
3420                field: "a".to_string(),
3421                operator: GreaterThanOrEquals,
3422                value: Value::Float(5.0),
3423            },
3424        ];
3425        assert!(WhereClause::group_clauses(&clauses, PlatformVersion::latest()).is_err());
3426    }
3427
3428    #[test]
3429    fn group_clauses_range_same_field_as_equality_returns_error() {
3430        let clauses = vec![
3431            WhereClause {
3432                field: "age".to_string(),
3433                operator: Equal,
3434                value: Value::I64(25),
3435            },
3436            WhereClause {
3437                field: "age".to_string(),
3438                operator: GreaterThan,
3439                value: Value::I64(18),
3440            },
3441        ];
3442        assert!(WhereClause::group_clauses(&clauses, PlatformVersion::latest()).is_err());
3443    }
3444
3445    #[test]
3446    fn group_clauses_two_ranges_combined_into_between() {
3447        let clauses = vec![
3448            WhereClause {
3449                field: "age".to_string(),
3450                operator: GreaterThanOrEquals,
3451                value: Value::Float(10.0),
3452            },
3453            WhereClause {
3454                field: "age".to_string(),
3455                operator: LessThanOrEquals,
3456                value: Value::Float(20.0),
3457            },
3458        ];
3459        let (_, range, _) =
3460            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3461        let r = range.unwrap();
3462        assert_eq!(r.operator, Between);
3463        assert_eq!(r.field, "age");
3464    }
3465
3466    #[test]
3467    fn group_clauses_two_ranges_combined_into_between_exclude_right() {
3468        let clauses = vec![
3469            WhereClause {
3470                field: "age".to_string(),
3471                operator: GreaterThanOrEquals,
3472                value: Value::Float(10.0),
3473            },
3474            WhereClause {
3475                field: "age".to_string(),
3476                operator: LessThan,
3477                value: Value::Float(20.0),
3478            },
3479        ];
3480        let (_, range, _) =
3481            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3482        assert_eq!(range.unwrap().operator, BetweenExcludeRight);
3483    }
3484
3485    #[test]
3486    fn group_clauses_two_ranges_combined_into_between_exclude_left() {
3487        let clauses = vec![
3488            WhereClause {
3489                field: "age".to_string(),
3490                operator: GreaterThan,
3491                value: Value::Float(10.0),
3492            },
3493            WhereClause {
3494                field: "age".to_string(),
3495                operator: LessThanOrEquals,
3496                value: Value::Float(20.0),
3497            },
3498        ];
3499        let (_, range, _) =
3500            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3501        assert_eq!(range.unwrap().operator, BetweenExcludeLeft);
3502    }
3503
3504    #[test]
3505    fn group_clauses_two_ranges_combined_into_between_exclude_bounds() {
3506        let clauses = vec![
3507            WhereClause {
3508                field: "age".to_string(),
3509                operator: GreaterThan,
3510                value: Value::Float(10.0),
3511            },
3512            WhereClause {
3513                field: "age".to_string(),
3514                operator: LessThan,
3515                value: Value::Float(20.0),
3516            },
3517        ];
3518        let (_, range, _) =
3519            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3520        assert_eq!(range.unwrap().operator, BetweenExcludeBounds);
3521    }
3522
3523    #[test]
3524    fn group_clauses_equality_plus_in_on_different_fields() {
3525        let clauses = vec![
3526            WhereClause {
3527                field: "name".to_string(),
3528                operator: Equal,
3529                value: Value::Text("alice".to_string()),
3530            },
3531            WhereClause {
3532                field: "status".to_string(),
3533                operator: In,
3534                value: Value::Array(vec![Value::I64(1), Value::I64(2)]),
3535            },
3536        ];
3537        let (eq, _, in_c) =
3538            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3539        assert_eq!(eq.len(), 1);
3540        assert_eq!(in_c.len(), 1);
3541    }
3542
3543    #[test]
3544    fn group_clauses_equality_plus_range_on_different_fields() {
3545        let clauses = vec![
3546            WhereClause {
3547                field: "name".to_string(),
3548                operator: Equal,
3549                value: Value::Text("alice".to_string()),
3550            },
3551            WhereClause {
3552                field: "age".to_string(),
3553                operator: GreaterThan,
3554                value: Value::Float(18.0),
3555            },
3556        ];
3557        let (eq, range, in_c) =
3558            WhereClause::group_clauses(&clauses, PlatformVersion::latest()).unwrap();
3559        assert_eq!(eq.len(), 1);
3560        assert!(range.is_some());
3561        assert!(in_c.is_empty());
3562    }
3563
3564    // ---- meta_field_property_type ----
3565
3566    #[test]
3567    fn meta_field_property_type_all_identifiers() {
3568        use super::meta_field_property_type;
3569        use dpp::data_contract::document_type::DocumentPropertyType;
3570
3571        for field in ["$id", "$ownerId", "$dataContractId", "$creatorId"] {
3572            let pt = meta_field_property_type(field);
3573            assert!(
3574                matches!(
3575                    pt,
3576                    Some(
3577                        DocumentPropertyType::Identifier
3578                            | DocumentPropertyType::IdentifierWithReference(_)
3579                    )
3580                ),
3581                "expected Identifier for {field}"
3582            );
3583        }
3584    }
3585
3586    #[test]
3587    fn meta_field_property_type_dates() {
3588        use super::meta_field_property_type;
3589        use dpp::data_contract::document_type::DocumentPropertyType;
3590
3591        for field in ["$createdAt", "$updatedAt", "$transferredAt"] {
3592            let pt = meta_field_property_type(field);
3593            assert!(
3594                matches!(pt, Some(DocumentPropertyType::Date)),
3595                "expected Date for {field}"
3596            );
3597        }
3598    }
3599
3600    #[test]
3601    fn meta_field_property_type_block_heights() {
3602        use super::meta_field_property_type;
3603        use dpp::data_contract::document_type::DocumentPropertyType;
3604
3605        for field in [
3606            "$createdAtBlockHeight",
3607            "$updatedAtBlockHeight",
3608            "$transferredAtBlockHeight",
3609        ] {
3610            let pt = meta_field_property_type(field);
3611            assert!(
3612                matches!(pt, Some(DocumentPropertyType::U64)),
3613                "expected U64 for {field}"
3614            );
3615        }
3616    }
3617
3618    #[test]
3619    fn meta_field_property_type_core_block_heights() {
3620        use super::meta_field_property_type;
3621        use dpp::data_contract::document_type::DocumentPropertyType;
3622
3623        for field in [
3624            "$createdAtCoreBlockHeight",
3625            "$updatedAtCoreBlockHeight",
3626            "$transferredAtCoreBlockHeight",
3627        ] {
3628            let pt = meta_field_property_type(field);
3629            assert!(
3630                matches!(pt, Some(DocumentPropertyType::U32)),
3631                "expected U32 for {field}"
3632            );
3633        }
3634    }
3635
3636    #[test]
3637    fn meta_field_property_type_revision_and_protocol_version() {
3638        use super::meta_field_property_type;
3639        use dpp::data_contract::document_type::DocumentPropertyType;
3640
3641        assert!(matches!(
3642            meta_field_property_type("$revision"),
3643            Some(DocumentPropertyType::U64)
3644        ));
3645        assert!(matches!(
3646            meta_field_property_type("$protocolVersion"),
3647            Some(DocumentPropertyType::U64)
3648        ));
3649    }
3650
3651    #[test]
3652    fn meta_field_property_type_type_field() {
3653        use super::meta_field_property_type;
3654        use dpp::data_contract::document_type::DocumentPropertyType;
3655
3656        assert!(matches!(
3657            meta_field_property_type("$type"),
3658            Some(DocumentPropertyType::String(_))
3659        ));
3660    }
3661
3662    #[test]
3663    fn meta_field_property_type_unknown_returns_none() {
3664        use super::meta_field_property_type;
3665
3666        assert!(meta_field_property_type("unknown").is_none());
3667        assert!(meta_field_property_type("$nonexistent").is_none());
3668    }
3669
3670    // ---- allowed_ops_for_type ----
3671
3672    #[test]
3673    fn allowed_ops_for_numeric_types_include_ranges() {
3674        use super::allowed_ops_for_type;
3675        use dpp::data_contract::document_type::DocumentPropertyType;
3676
3677        for ty in [
3678            DocumentPropertyType::U8,
3679            DocumentPropertyType::I8,
3680            DocumentPropertyType::U16,
3681            DocumentPropertyType::I16,
3682            DocumentPropertyType::U32,
3683            DocumentPropertyType::I32,
3684            DocumentPropertyType::U64,
3685            DocumentPropertyType::I64,
3686            DocumentPropertyType::U128,
3687            DocumentPropertyType::I128,
3688            DocumentPropertyType::F64,
3689            DocumentPropertyType::Date,
3690        ] {
3691            let ops = allowed_ops_for_type(&ty);
3692            assert!(ops.contains(&Equal), "numeric type should allow Equal");
3693            assert!(ops.contains(&In), "numeric type should allow In");
3694            assert!(
3695                ops.contains(&GreaterThan),
3696                "numeric type should allow GreaterThan"
3697            );
3698            assert!(ops.contains(&Between), "numeric type should allow Between");
3699            assert!(
3700                !ops.contains(&super::StartsWith),
3701                "numeric type should not allow StartsWith"
3702            );
3703        }
3704    }
3705
3706    #[test]
3707    fn allowed_ops_for_string_includes_starts_with() {
3708        use super::allowed_ops_for_type;
3709        use dpp::data_contract::document_type::{DocumentPropertyType, StringPropertySizes};
3710
3711        let ty = DocumentPropertyType::String(StringPropertySizes {
3712            min_length: None,
3713            max_length: None,
3714            max_bytes: None,
3715        });
3716        let ops = allowed_ops_for_type(&ty);
3717        assert!(ops.contains(&super::StartsWith));
3718        assert!(ops.contains(&Equal));
3719        assert!(ops.contains(&In));
3720        assert!(ops.contains(&GreaterThan));
3721    }
3722
3723    #[test]
3724    fn allowed_ops_for_identifier_only_equal_and_in() {
3725        use super::allowed_ops_for_type;
3726        use dpp::data_contract::document_type::DocumentPropertyType;
3727
3728        let ops = allowed_ops_for_type(&DocumentPropertyType::Identifier);
3729        assert_eq!(ops, &[Equal, In]);
3730    }
3731
3732    #[test]
3733    fn allowed_ops_for_boolean_only_equal() {
3734        use super::allowed_ops_for_type;
3735        use dpp::data_contract::document_type::DocumentPropertyType;
3736
3737        let ops = allowed_ops_for_type(&DocumentPropertyType::Boolean);
3738        assert_eq!(ops, &[Equal]);
3739    }
3740
3741    #[test]
3742    fn allowed_ops_for_object_is_empty() {
3743        use super::allowed_ops_for_type;
3744        use dpp::data_contract::document_type::DocumentPropertyType;
3745
3746        let ops = allowed_ops_for_type(&DocumentPropertyType::Object(Default::default()));
3747        assert!(ops.is_empty());
3748    }
3749
3750    // ---- value_shape_ok ----
3751
3752    #[test]
3753    fn value_shape_ok_equal_always_true() {
3754        use super::WhereOperator;
3755        use dpp::data_contract::document_type::DocumentPropertyType;
3756
3757        // Equal accepts any value shape
3758        assert!(WhereOperator::Equal.value_shape_ok(&Value::I64(1), &DocumentPropertyType::U64));
3759        assert!(WhereOperator::Equal
3760            .value_shape_ok(&Value::Text("x".into()), &DocumentPropertyType::Boolean));
3761    }
3762
3763    #[test]
3764    fn value_shape_ok_in_requires_array_or_bytes() {
3765        use super::WhereOperator;
3766        use dpp::data_contract::document_type::DocumentPropertyType;
3767
3768        assert!(WhereOperator::In.value_shape_ok(
3769            &Value::Array(vec![Value::I64(1)]),
3770            &DocumentPropertyType::U64
3771        ));
3772        assert!(WhereOperator::In.value_shape_ok(&Value::Bytes(vec![1]), &DocumentPropertyType::U8));
3773        assert!(!WhereOperator::In.value_shape_ok(&Value::I64(1), &DocumentPropertyType::U64));
3774    }
3775
3776    #[test]
3777    fn value_shape_ok_starts_with_requires_text() {
3778        use super::WhereOperator;
3779        use dpp::data_contract::document_type::{DocumentPropertyType, StringPropertySizes};
3780
3781        let str_ty = DocumentPropertyType::String(StringPropertySizes {
3782            min_length: None,
3783            max_length: None,
3784            max_bytes: None,
3785        });
3786        assert!(WhereOperator::StartsWith.value_shape_ok(&Value::Text("abc".into()), &str_ty));
3787        assert!(!WhereOperator::StartsWith.value_shape_ok(&Value::I64(1), &str_ty));
3788    }
3789
3790    #[test]
3791    fn value_shape_ok_range_for_f64_requires_numeric() {
3792        use super::WhereOperator;
3793        use dpp::data_contract::document_type::DocumentPropertyType;
3794
3795        assert!(WhereOperator::GreaterThan
3796            .value_shape_ok(&Value::Float(1.0), &DocumentPropertyType::F64));
3797        assert!(
3798            WhereOperator::GreaterThan.value_shape_ok(&Value::I64(1), &DocumentPropertyType::F64)
3799        );
3800        assert!(!WhereOperator::GreaterThan
3801            .value_shape_ok(&Value::Text("x".into()), &DocumentPropertyType::F64));
3802    }
3803
3804    #[test]
3805    fn value_shape_ok_range_for_string_requires_text() {
3806        use super::WhereOperator;
3807        use dpp::data_contract::document_type::{DocumentPropertyType, StringPropertySizes};
3808
3809        let str_ty = DocumentPropertyType::String(StringPropertySizes {
3810            min_length: None,
3811            max_length: None,
3812            max_bytes: None,
3813        });
3814        assert!(WhereOperator::LessThan.value_shape_ok(&Value::Text("a".into()), &str_ty));
3815        assert!(!WhereOperator::LessThan.value_shape_ok(&Value::I64(1), &str_ty));
3816    }
3817
3818    #[test]
3819    fn value_shape_ok_range_for_integer_requires_integer() {
3820        use super::WhereOperator;
3821        use dpp::data_contract::document_type::DocumentPropertyType;
3822
3823        assert!(
3824            WhereOperator::GreaterThan.value_shape_ok(&Value::U64(1), &DocumentPropertyType::U64)
3825        );
3826        assert!(
3827            WhereOperator::GreaterThan.value_shape_ok(&Value::I32(1), &DocumentPropertyType::I32)
3828        );
3829        assert!(!WhereOperator::GreaterThan
3830            .value_shape_ok(&Value::Float(1.0), &DocumentPropertyType::U64));
3831        assert!(!WhereOperator::GreaterThan
3832            .value_shape_ok(&Value::Text("x".into()), &DocumentPropertyType::U64));
3833    }
3834
3835    #[test]
3836    fn value_shape_ok_between_requires_array_of_two() {
3837        use super::WhereOperator;
3838        use dpp::data_contract::document_type::DocumentPropertyType;
3839
3840        let good = Value::Array(vec![Value::I64(1), Value::I64(10)]);
3841        assert!(WhereOperator::Between.value_shape_ok(&good, &DocumentPropertyType::I64));
3842
3843        let bad_len = Value::Array(vec![Value::I64(1)]);
3844        assert!(!WhereOperator::Between.value_shape_ok(&bad_len, &DocumentPropertyType::I64));
3845
3846        let not_array = Value::I64(5);
3847        assert!(!WhereOperator::Between.value_shape_ok(&not_array, &DocumentPropertyType::I64));
3848
3849        // All between variants
3850        assert!(
3851            WhereOperator::BetweenExcludeBounds.value_shape_ok(&good, &DocumentPropertyType::I64)
3852        );
3853        assert!(WhereOperator::BetweenExcludeLeft.value_shape_ok(&good, &DocumentPropertyType::I64));
3854        assert!(
3855            WhereOperator::BetweenExcludeRight.value_shape_ok(&good, &DocumentPropertyType::I64)
3856        );
3857    }
3858
3859    // ---- validate_against_schema: additional coverage ----
3860
3861    #[test]
3862    fn validate_rejects_unknown_field() {
3863        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3864        let contract = fixture.data_contract_owned();
3865        let doc_type = contract
3866            .document_type_for_name("niceDocument")
3867            .expect("doc type exists");
3868
3869        let clause = WhereClause {
3870            field: "nonexistentField".to_string(),
3871            operator: Equal,
3872            value: Value::I64(1),
3873        };
3874        let res = clause.validate_against_schema(doc_type);
3875        assert!(res.is_err());
3876    }
3877
3878    #[test]
3879    fn validate_rejects_disallowed_operator_for_boolean() {
3880        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3881        let contract = fixture.data_contract_owned();
3882        let doc_type = contract
3883            .document_type_for_name("niceDocument")
3884            .expect("doc type exists");
3885
3886        // Boolean only allows Equal, not GreaterThan -- but we need a boolean field.
3887        // Check if we can use a meta field or if there is one in the doc type.
3888        // $type is a String, so let's validate that startsWith is allowed for $type
3889        let clause = WhereClause {
3890            field: "$type".to_string(),
3891            operator: super::StartsWith,
3892            value: Value::Text("nice".to_string()),
3893        };
3894        let res = clause.validate_against_schema(doc_type);
3895        assert!(res.is_valid());
3896    }
3897
3898    #[test]
3899    fn validate_rejects_starts_with_empty_string() {
3900        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3901        let contract = fixture.data_contract_owned();
3902        let doc_type = contract
3903            .document_type_for_name("niceDocument")
3904            .expect("doc type exists");
3905
3906        let clause = WhereClause {
3907            field: "$type".to_string(),
3908            operator: super::StartsWith,
3909            value: Value::Text("".to_string()),
3910        };
3911        let res = clause.validate_against_schema(doc_type);
3912        assert!(res.is_err());
3913        assert!(matches!(
3914            res.first_error(),
3915            Some(QuerySyntaxError::StartsWithIllegalString(_))
3916        ));
3917    }
3918
3919    #[test]
3920    fn validate_rejects_in_with_empty_array() {
3921        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3922        let contract = fixture.data_contract_owned();
3923        let doc_type = contract
3924            .document_type_for_name("niceDocument")
3925            .expect("doc type exists");
3926
3927        let clause = WhereClause {
3928            field: "$ownerId".to_string(),
3929            operator: In,
3930            value: Value::Array(vec![]),
3931        };
3932        let res = clause.validate_against_schema(doc_type);
3933        assert!(res.is_err());
3934    }
3935
3936    #[test]
3937    fn validate_rejects_in_with_duplicates() {
3938        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3939        let contract = fixture.data_contract_owned();
3940        let doc_type = contract
3941            .document_type_for_name("niceDocument")
3942            .expect("doc type exists");
3943
3944        let clause = WhereClause {
3945            field: "$ownerId".to_string(),
3946            operator: In,
3947            value: Value::Array(vec![
3948                Value::Identifier([1u8; 32]),
3949                Value::Identifier([1u8; 32]),
3950            ]),
3951        };
3952        let res = clause.validate_against_schema(doc_type);
3953        assert!(res.is_err());
3954    }
3955
3956    #[test]
3957    fn validate_rejects_between_with_descending_bounds() {
3958        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3959        let contract = fixture.data_contract_owned();
3960        let doc_type = contract
3961            .document_type_for_name("uniqueDates")
3962            .expect("doc type exists");
3963
3964        let clause = WhereClause {
3965            field: "$createdAt".to_string(),
3966            operator: Between,
3967            value: Value::Array(vec![Value::U64(2000), Value::U64(1000)]),
3968        };
3969        let res = clause.validate_against_schema(doc_type);
3970        assert!(res.is_err());
3971        assert!(matches!(
3972            res.first_error(),
3973            Some(QuerySyntaxError::InvalidBetweenClause(_))
3974        ));
3975    }
3976
3977    #[test]
3978    fn validate_rejects_range_operator_not_allowed_for_identifier() {
3979        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3980        let contract = fixture.data_contract_owned();
3981        let doc_type = contract
3982            .document_type_for_name("niceDocument")
3983            .expect("doc type exists");
3984
3985        let clause = WhereClause {
3986            field: "$ownerId".to_string(),
3987            operator: GreaterThan,
3988            value: Value::Identifier([1u8; 32]),
3989        };
3990        let res = clause.validate_against_schema(doc_type);
3991        assert!(res.is_err());
3992    }
3993
3994    #[test]
3995    fn validate_accepts_valid_integer_equality() {
3996        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3997        let contract = fixture.data_contract_owned();
3998        let doc_type = contract
3999            .document_type_for_name("niceDocument")
4000            .expect("doc type exists");
4001
4002        let clause = WhereClause {
4003            field: "$revision".to_string(),
4004            operator: Equal,
4005            value: Value::U64(5),
4006        };
4007        let res = clause.validate_against_schema(doc_type);
4008        assert!(res.is_valid());
4009    }
4010
4011    // ---- sql_value_to_platform_value ----
4012
4013    #[test]
4014    fn sql_value_boolean_true() {
4015        use super::sql_value_to_platform_value;
4016        let result = sql_value_to_platform_value(sqlparser::ast::Value::Boolean(true));
4017        assert_eq!(result, Some(Value::Bool(true)));
4018    }
4019
4020    #[test]
4021    fn sql_value_boolean_false() {
4022        use super::sql_value_to_platform_value;
4023        let result = sql_value_to_platform_value(sqlparser::ast::Value::Boolean(false));
4024        assert_eq!(result, Some(Value::Bool(false)));
4025    }
4026
4027    #[test]
4028    fn sql_value_number_integer() {
4029        use super::sql_value_to_platform_value;
4030        let result =
4031            sql_value_to_platform_value(sqlparser::ast::Value::Number("42".to_string(), false));
4032        assert_eq!(result, Some(Value::I64(42)));
4033    }
4034
4035    #[test]
4036    fn sql_value_number_negative_integer() {
4037        use super::sql_value_to_platform_value;
4038        let result =
4039            sql_value_to_platform_value(sqlparser::ast::Value::Number("-7".to_string(), false));
4040        assert_eq!(result, Some(Value::I64(-7)));
4041    }
4042
4043    #[test]
4044    fn sql_value_number_float() {
4045        use super::sql_value_to_platform_value;
4046        let result =
4047            sql_value_to_platform_value(sqlparser::ast::Value::Number("3.14".to_string(), false));
4048        assert_eq!(result, Some(Value::Float(3.14)));
4049    }
4050
4051    #[test]
4052    fn sql_value_number_unparseable_returns_none() {
4053        use super::sql_value_to_platform_value;
4054        // A string that cannot parse as i64
4055        let result = sql_value_to_platform_value(sqlparser::ast::Value::Number(
4056            "not_a_number".to_string(),
4057            false,
4058        ));
4059        assert_eq!(result, None);
4060    }
4061
4062    #[test]
4063    fn sql_value_single_quoted_string() {
4064        use super::sql_value_to_platform_value;
4065        let result = sql_value_to_platform_value(sqlparser::ast::Value::SingleQuotedString(
4066            "hello".to_string(),
4067        ));
4068        assert_eq!(result, Some(Value::Text("hello".to_string())));
4069    }
4070
4071    #[test]
4072    fn sql_value_double_quoted_string() {
4073        use super::sql_value_to_platform_value;
4074        let result = sql_value_to_platform_value(sqlparser::ast::Value::DoubleQuotedString(
4075            "world".to_string(),
4076        ));
4077        assert_eq!(result, Some(Value::Text("world".to_string())));
4078    }
4079
4080    #[test]
4081    fn sql_value_hex_string_literal() {
4082        use super::sql_value_to_platform_value;
4083        let result = sql_value_to_platform_value(sqlparser::ast::Value::HexStringLiteral(
4084            "0xABCD".to_string(),
4085        ));
4086        assert_eq!(result, Some(Value::Text("0xABCD".to_string())));
4087    }
4088
4089    #[test]
4090    fn sql_value_national_string_literal() {
4091        use super::sql_value_to_platform_value;
4092        let result = sql_value_to_platform_value(sqlparser::ast::Value::NationalStringLiteral(
4093            "n_str".to_string(),
4094        ));
4095        assert_eq!(result, Some(Value::Text("n_str".to_string())));
4096    }
4097
4098    #[test]
4099    fn sql_value_null_returns_none() {
4100        use super::sql_value_to_platform_value;
4101        let result = sql_value_to_platform_value(sqlparser::ast::Value::Null);
4102        assert_eq!(result, None);
4103    }
4104
4105    #[test]
4106    fn sql_value_placeholder_returns_none() {
4107        use super::sql_value_to_platform_value;
4108        let result =
4109            sql_value_to_platform_value(sqlparser::ast::Value::Placeholder("?".to_string()));
4110        assert_eq!(result, None);
4111    }
4112
4113    // ---- WhereClause::from_components: additional operator coverage ----
4114
4115    #[test]
4116    fn from_components_with_between_operator() {
4117        let components = vec![
4118            Value::Text("age".to_string()),
4119            Value::Text("between".to_string()),
4120            Value::Array(vec![Value::I64(10), Value::I64(20)]),
4121        ];
4122        let clause = WhereClause::from_components(&components).unwrap();
4123        assert_eq!(clause.field, "age");
4124        assert_eq!(clause.operator, Between);
4125        assert_eq!(
4126            clause.value,
4127            Value::Array(vec![Value::I64(10), Value::I64(20)])
4128        );
4129    }
4130
4131    #[test]
4132    fn from_components_with_between_exclude_bounds_operator() {
4133        let components = vec![
4134            Value::Text("score".to_string()),
4135            Value::Text("betweenExcludeBounds".to_string()),
4136            Value::Array(vec![Value::Float(1.0), Value::Float(9.0)]),
4137        ];
4138        let clause = WhereClause::from_components(&components).unwrap();
4139        assert_eq!(clause.operator, BetweenExcludeBounds);
4140    }
4141
4142    #[test]
4143    fn from_components_with_greater_than_or_equals() {
4144        let components = vec![
4145            Value::Text("price".to_string()),
4146            Value::Text(">=".to_string()),
4147            Value::U64(100),
4148        ];
4149        let clause = WhereClause::from_components(&components).unwrap();
4150        assert_eq!(clause.operator, GreaterThanOrEquals);
4151        assert_eq!(clause.value, Value::U64(100));
4152    }
4153
4154    #[test]
4155    fn from_components_with_less_than() {
4156        let components = vec![
4157            Value::Text("height".to_string()),
4158            Value::Text("<".to_string()),
4159            Value::I64(200),
4160        ];
4161        let clause = WhereClause::from_components(&components).unwrap();
4162        assert_eq!(clause.operator, LessThan);
4163    }
4164
4165    #[test]
4166    fn from_components_with_less_than_or_equals() {
4167        let components = vec![
4168            Value::Text("height".to_string()),
4169            Value::Text("<=".to_string()),
4170            Value::I64(200),
4171        ];
4172        let clause = WhereClause::from_components(&components).unwrap();
4173        assert_eq!(clause.operator, LessThanOrEquals);
4174    }
4175
4176    #[test]
4177    fn from_components_preserves_value_type() {
4178        // Ensure the value is cloned as-is, including complex types
4179        let components = vec![
4180            Value::Text("tags".to_string()),
4181            Value::Text("in".to_string()),
4182            Value::Array(vec![
4183                Value::Text("a".to_string()),
4184                Value::Text("b".to_string()),
4185                Value::Text("c".to_string()),
4186            ]),
4187        ];
4188        let clause = WhereClause::from_components(&components).unwrap();
4189        assert_eq!(clause.operator, In);
4190        if let Value::Array(arr) = &clause.value {
4191            assert_eq!(arr.len(), 3);
4192        } else {
4193            panic!("expected Array value");
4194        }
4195    }
4196
4197    #[test]
4198    fn from_components_empty_returns_error() {
4199        let components: Vec<Value> = vec![];
4200        assert!(WhereClause::from_components(&components).is_err());
4201    }
4202
4203    #[test]
4204    fn from_components_single_element_returns_error() {
4205        let components = vec![Value::Text("name".to_string())];
4206        assert!(WhereClause::from_components(&components).is_err());
4207    }
4208
4209    // ---- WhereClause::less_than: additional equal-value coverage ----
4210
4211    #[test]
4212    fn less_than_u64_equal_values_with_allow_eq() {
4213        let a = WhereClause {
4214            field: "f".to_string(),
4215            operator: Equal,
4216            value: Value::U64(10),
4217        };
4218        assert!(a.less_than(&a, true).unwrap()); // le
4219        assert!(!a.less_than(&a, false).unwrap()); // lt
4220    }
4221
4222    #[test]
4223    fn less_than_u32_equal_values_with_allow_eq() {
4224        let a = WhereClause {
4225            field: "f".to_string(),
4226            operator: Equal,
4227            value: Value::U32(5),
4228        };
4229        assert!(a.less_than(&a, true).unwrap());
4230        assert!(!a.less_than(&a, false).unwrap());
4231    }
4232
4233    #[test]
4234    fn less_than_i32_equal_values_with_allow_eq() {
4235        let a = WhereClause {
4236            field: "f".to_string(),
4237            operator: Equal,
4238            value: Value::I32(-3),
4239        };
4240        assert!(a.less_than(&a, true).unwrap());
4241        assert!(!a.less_than(&a, false).unwrap());
4242    }
4243
4244    #[test]
4245    fn less_than_u16_equal_values_with_allow_eq() {
4246        let a = WhereClause {
4247            field: "f".to_string(),
4248            operator: Equal,
4249            value: Value::U16(100),
4250        };
4251        assert!(a.less_than(&a, true).unwrap());
4252        assert!(!a.less_than(&a, false).unwrap());
4253    }
4254
4255    #[test]
4256    fn less_than_u8_equal_values_with_allow_eq() {
4257        let a = WhereClause {
4258            field: "f".to_string(),
4259            operator: Equal,
4260            value: Value::U8(7),
4261        };
4262        assert!(a.less_than(&a, true).unwrap());
4263        assert!(!a.less_than(&a, false).unwrap());
4264    }
4265
4266    #[test]
4267    fn less_than_i8_equal_values_with_allow_eq() {
4268        let a = WhereClause {
4269            field: "f".to_string(),
4270            operator: Equal,
4271            value: Value::I8(-1),
4272        };
4273        assert!(a.less_than(&a, true).unwrap());
4274        assert!(!a.less_than(&a, false).unwrap());
4275    }
4276
4277    #[test]
4278    fn less_than_u128_equal_values_with_allow_eq() {
4279        let a = WhereClause {
4280            field: "f".to_string(),
4281            operator: Equal,
4282            value: Value::U128(999),
4283        };
4284        assert!(a.less_than(&a, true).unwrap());
4285        assert!(!a.less_than(&a, false).unwrap());
4286    }
4287
4288    #[test]
4289    fn less_than_bytes_equal_values_with_allow_eq() {
4290        let a = WhereClause {
4291            field: "f".to_string(),
4292            operator: Equal,
4293            value: Value::Bytes(vec![1, 2, 3]),
4294        };
4295        assert!(a.less_than(&a, true).unwrap());
4296        assert!(!a.less_than(&a, false).unwrap());
4297    }
4298
4299    #[test]
4300    fn less_than_text_equal_values_with_allow_eq() {
4301        let a = WhereClause {
4302            field: "f".to_string(),
4303            operator: Equal,
4304            value: Value::Text("same".to_string()),
4305        };
4306        assert!(a.less_than(&a, true).unwrap());
4307        assert!(!a.less_than(&a, false).unwrap());
4308    }
4309
4310    #[test]
4311    fn less_than_float_equal_values_with_allow_eq() {
4312        let a = WhereClause {
4313            field: "f".to_string(),
4314            operator: Equal,
4315            value: Value::Float(2.5),
4316        };
4317        assert!(a.less_than(&a, true).unwrap());
4318        assert!(!a.less_than(&a, false).unwrap());
4319    }
4320
4321    #[test]
4322    fn less_than_mismatched_integer_types_returns_error() {
4323        let a = WhereClause {
4324            field: "f".to_string(),
4325            operator: Equal,
4326            value: Value::U64(1),
4327        };
4328        let b = WhereClause {
4329            field: "f".to_string(),
4330            operator: Equal,
4331            value: Value::I64(1),
4332        };
4333        assert!(a.less_than(&b, false).is_err());
4334    }
4335
4336    #[test]
4337    fn less_than_bool_vs_bool_returns_error() {
4338        let a = WhereClause {
4339            field: "f".to_string(),
4340            operator: Equal,
4341            value: Value::Bool(true),
4342        };
4343        let b = WhereClause {
4344            field: "f".to_string(),
4345            operator: Equal,
4346            value: Value::Bool(false),
4347        };
4348        assert!(a.less_than(&b, false).is_err());
4349    }
4350
4351    // ---- value_shape_ok: additional coverage ----
4352
4353    #[test]
4354    fn value_shape_ok_between_with_three_elements_rejected() {
4355        use super::WhereOperator;
4356        use dpp::data_contract::document_type::DocumentPropertyType;
4357
4358        let three = Value::Array(vec![Value::I64(1), Value::I64(5), Value::I64(10)]);
4359        assert!(!WhereOperator::Between.value_shape_ok(&three, &DocumentPropertyType::I64));
4360    }
4361
4362    #[test]
4363    fn value_shape_ok_between_with_empty_array_rejected() {
4364        use super::WhereOperator;
4365        use dpp::data_contract::document_type::DocumentPropertyType;
4366
4367        let empty = Value::Array(vec![]);
4368        assert!(!WhereOperator::Between.value_shape_ok(&empty, &DocumentPropertyType::I64));
4369    }
4370
4371    #[test]
4372    fn value_shape_ok_between_for_f64_property_requires_numeric_elements() {
4373        use super::WhereOperator;
4374        use dpp::data_contract::document_type::DocumentPropertyType;
4375
4376        let good = Value::Array(vec![Value::Float(1.0), Value::Float(10.0)]);
4377        assert!(WhereOperator::Between.value_shape_ok(&good, &DocumentPropertyType::F64));
4378
4379        let also_good = Value::Array(vec![Value::I64(1), Value::I64(10)]);
4380        assert!(WhereOperator::Between.value_shape_ok(&also_good, &DocumentPropertyType::F64));
4381
4382        let bad = Value::Array(vec![Value::Text("a".into()), Value::Text("b".into())]);
4383        assert!(!WhereOperator::Between.value_shape_ok(&bad, &DocumentPropertyType::F64));
4384    }
4385
4386    #[test]
4387    fn value_shape_ok_between_for_string_property_requires_text_elements() {
4388        use super::WhereOperator;
4389        use dpp::data_contract::document_type::{DocumentPropertyType, StringPropertySizes};
4390
4391        let str_ty = DocumentPropertyType::String(StringPropertySizes {
4392            min_length: None,
4393            max_length: None,
4394            max_bytes: None,
4395        });
4396
4397        let good = Value::Array(vec![Value::Text("aaa".into()), Value::Text("zzz".into())]);
4398        assert!(WhereOperator::Between.value_shape_ok(&good, &str_ty));
4399
4400        let bad = Value::Array(vec![Value::I64(1), Value::I64(10)]);
4401        assert!(!WhereOperator::Between.value_shape_ok(&bad, &str_ty));
4402    }
4403
4404    #[test]
4405    fn value_shape_ok_between_exclude_left_with_non_array_rejected() {
4406        use super::WhereOperator;
4407        use dpp::data_contract::document_type::DocumentPropertyType;
4408
4409        assert!(!WhereOperator::BetweenExcludeLeft
4410            .value_shape_ok(&Value::I64(5), &DocumentPropertyType::I64));
4411    }
4412
4413    #[test]
4414    fn value_shape_ok_between_exclude_right_with_non_array_rejected() {
4415        use super::WhereOperator;
4416        use dpp::data_contract::document_type::DocumentPropertyType;
4417
4418        assert!(!WhereOperator::BetweenExcludeRight
4419            .value_shape_ok(&Value::I64(5), &DocumentPropertyType::I64));
4420    }
4421
4422    #[test]
4423    fn value_shape_ok_between_exclude_bounds_with_non_array_rejected() {
4424        use super::WhereOperator;
4425        use dpp::data_contract::document_type::DocumentPropertyType;
4426
4427        assert!(!WhereOperator::BetweenExcludeBounds
4428            .value_shape_ok(&Value::I64(5), &DocumentPropertyType::I64));
4429    }
4430
4431    #[test]
4432    fn value_shape_ok_range_accepts_all_integer_widths() {
4433        use super::WhereOperator;
4434        use dpp::data_contract::document_type::DocumentPropertyType;
4435
4436        // Each integer value variant should be accepted for its corresponding property type
4437        let cases: Vec<(Value, DocumentPropertyType)> = vec![
4438            (Value::U8(1), DocumentPropertyType::U8),
4439            (Value::I8(-1), DocumentPropertyType::I8),
4440            (Value::U16(1), DocumentPropertyType::U16),
4441            (Value::I16(-1), DocumentPropertyType::I16),
4442            (Value::U32(1), DocumentPropertyType::U32),
4443            (Value::I32(-1), DocumentPropertyType::I32),
4444            (Value::U64(1), DocumentPropertyType::U64),
4445            (Value::I64(-1), DocumentPropertyType::I64),
4446            (Value::U128(1), DocumentPropertyType::U128),
4447            (Value::I128(-1), DocumentPropertyType::I128),
4448        ];
4449        for (val, ty) in cases {
4450            assert!(
4451                WhereOperator::GreaterThan.value_shape_ok(&val, &ty),
4452                "GreaterThan should accept integer value for {:?}",
4453                ty
4454            );
4455            assert!(
4456                WhereOperator::LessThanOrEquals.value_shape_ok(&val, &ty),
4457                "LessThanOrEquals should accept integer value for {:?}",
4458                ty
4459            );
4460        }
4461    }
4462
4463    #[test]
4464    fn value_shape_ok_range_rejects_bool_for_integer_type() {
4465        use super::WhereOperator;
4466        use dpp::data_contract::document_type::DocumentPropertyType;
4467
4468        assert!(!WhereOperator::GreaterThan
4469            .value_shape_ok(&Value::Bool(true), &DocumentPropertyType::U64));
4470    }
4471
4472    #[test]
4473    fn value_shape_ok_in_rejects_text() {
4474        use super::WhereOperator;
4475        use dpp::data_contract::document_type::DocumentPropertyType;
4476
4477        assert!(!WhereOperator::In
4478            .value_shape_ok(&Value::Text("not-array".into()), &DocumentPropertyType::U64));
4479    }
4480
4481    // ---- ValueClause::matches_value: additional operator coverage ----
4482
4483    #[test]
4484    fn value_clause_matches_value_less_than() {
4485        let clause = ValueClause {
4486            operator: LessThan,
4487            value: Value::I64(50),
4488        };
4489        assert!(clause.matches_value(&Value::I64(30)));
4490        assert!(!clause.matches_value(&Value::I64(50)));
4491        assert!(!clause.matches_value(&Value::I64(60)));
4492    }
4493
4494    #[test]
4495    fn value_clause_matches_value_less_than_or_equals() {
4496        let clause = ValueClause {
4497            operator: LessThanOrEquals,
4498            value: Value::I64(50),
4499        };
4500        assert!(clause.matches_value(&Value::I64(30)));
4501        assert!(clause.matches_value(&Value::I64(50)));
4502        assert!(!clause.matches_value(&Value::I64(51)));
4503    }
4504
4505    #[test]
4506    fn value_clause_matches_value_greater_than_or_equals() {
4507        let clause = ValueClause {
4508            operator: GreaterThanOrEquals,
4509            value: Value::I64(10),
4510        };
4511        assert!(clause.matches_value(&Value::I64(10)));
4512        assert!(clause.matches_value(&Value::I64(100)));
4513        assert!(!clause.matches_value(&Value::I64(9)));
4514    }
4515
4516    #[test]
4517    fn value_clause_matches_between_inclusive() {
4518        let clause = ValueClause {
4519            operator: Between,
4520            value: Value::Array(vec![Value::U64(10), Value::U64(20)]),
4521        };
4522        assert!(clause.matches_value(&Value::U64(10)));
4523        assert!(clause.matches_value(&Value::U64(15)));
4524        assert!(clause.matches_value(&Value::U64(20)));
4525        assert!(!clause.matches_value(&Value::U64(9)));
4526        assert!(!clause.matches_value(&Value::U64(21)));
4527    }
4528
4529    #[test]
4530    fn value_clause_matches_between_exclude_bounds() {
4531        let clause = ValueClause {
4532            operator: BetweenExcludeBounds,
4533            value: Value::Array(vec![Value::U64(10), Value::U64(20)]),
4534        };
4535        assert!(!clause.matches_value(&Value::U64(10)));
4536        assert!(clause.matches_value(&Value::U64(15)));
4537        assert!(!clause.matches_value(&Value::U64(20)));
4538    }
4539
4540    #[test]
4541    fn value_clause_matches_between_exclude_left() {
4542        let clause = ValueClause {
4543            operator: BetweenExcludeLeft,
4544            value: Value::Array(vec![Value::U64(10), Value::U64(20)]),
4545        };
4546        assert!(!clause.matches_value(&Value::U64(10)));
4547        assert!(clause.matches_value(&Value::U64(11)));
4548        assert!(clause.matches_value(&Value::U64(20)));
4549    }
4550
4551    #[test]
4552    fn value_clause_matches_between_exclude_right() {
4553        let clause = ValueClause {
4554            operator: BetweenExcludeRight,
4555            value: Value::Array(vec![Value::U64(10), Value::U64(20)]),
4556        };
4557        assert!(clause.matches_value(&Value::U64(10)));
4558        assert!(clause.matches_value(&Value::U64(19)));
4559        assert!(!clause.matches_value(&Value::U64(20)));
4560    }
4561
4562    #[test]
4563    fn value_clause_in_with_bytes() {
4564        let clause = ValueClause {
4565            operator: In,
4566            value: Value::Bytes(vec![5, 10, 15]),
4567        };
4568        assert!(clause.matches_value(&Value::U8(10)));
4569        assert!(!clause.matches_value(&Value::U8(20)));
4570        // Non-U8 against Bytes returns false
4571        assert!(!clause.matches_value(&Value::I64(10)));
4572    }
4573
4574    #[test]
4575    fn value_clause_starts_with_non_text_returns_false() {
4576        let clause = ValueClause {
4577            operator: super::StartsWith,
4578            value: Value::Text("he".to_string()),
4579        };
4580        assert!(!clause.matches_value(&Value::I64(42)));
4581    }
4582
4583    // ---- WhereClause::matches_value: additional coverage ----
4584
4585    #[test]
4586    fn where_clause_matches_value_between() {
4587        let clause = WhereClause {
4588            field: "price".to_string(),
4589            operator: Between,
4590            value: Value::Array(vec![Value::U64(100), Value::U64(500)]),
4591        };
4592        assert!(clause.matches_value(&Value::U64(100)));
4593        assert!(clause.matches_value(&Value::U64(300)));
4594        assert!(clause.matches_value(&Value::U64(500)));
4595        assert!(!clause.matches_value(&Value::U64(99)));
4596        assert!(!clause.matches_value(&Value::U64(501)));
4597    }
4598
4599    #[test]
4600    fn where_clause_matches_value_in() {
4601        let clause = WhereClause {
4602            field: "status".to_string(),
4603            operator: In,
4604            value: Value::Array(vec![
4605                Value::Text("a".to_string()),
4606                Value::Text("b".to_string()),
4607            ]),
4608        };
4609        assert!(clause.matches_value(&Value::Text("a".to_string())));
4610        assert!(clause.matches_value(&Value::Text("b".to_string())));
4611        assert!(!clause.matches_value(&Value::Text("c".to_string())));
4612    }
4613
4614    #[test]
4615    fn where_clause_matches_value_starts_with() {
4616        let clause = WhereClause {
4617            field: "name".to_string(),
4618            operator: super::StartsWith,
4619            value: Value::Text("pre".to_string()),
4620        };
4621        assert!(clause.matches_value(&Value::Text("prefix_value".to_string())));
4622        assert!(!clause.matches_value(&Value::Text("no_match".to_string())));
4623    }
4624
4625    // ---- eval: additional coverage for text comparison operators ----
4626
4627    #[test]
4628    fn eval_greater_than_with_text() {
4629        assert!(GreaterThan.eval(
4630            &Value::Text("banana".to_string()),
4631            &Value::Text("apple".to_string())
4632        ));
4633        assert!(!GreaterThan.eval(
4634            &Value::Text("apple".to_string()),
4635            &Value::Text("banana".to_string())
4636        ));
4637    }
4638
4639    #[test]
4640    fn eval_less_than_with_text() {
4641        assert!(LessThan.eval(
4642            &Value::Text("apple".to_string()),
4643            &Value::Text("banana".to_string())
4644        ));
4645        assert!(!LessThan.eval(
4646            &Value::Text("banana".to_string()),
4647            &Value::Text("apple".to_string())
4648        ));
4649    }
4650
4651    #[test]
4652    fn eval_between_with_text() {
4653        let bounds = Value::Array(vec![
4654            Value::Text("b".to_string()),
4655            Value::Text("d".to_string()),
4656        ]);
4657        assert!(Between.eval(&Value::Text("b".to_string()), &bounds));
4658        assert!(Between.eval(&Value::Text("c".to_string()), &bounds));
4659        assert!(Between.eval(&Value::Text("d".to_string()), &bounds));
4660        assert!(!Between.eval(&Value::Text("a".to_string()), &bounds));
4661        assert!(!Between.eval(&Value::Text("e".to_string()), &bounds));
4662    }
4663
4664    #[test]
4665    fn eval_equal_with_text() {
4666        assert!(Equal.eval(
4667            &Value::Text("same".to_string()),
4668            &Value::Text("same".to_string())
4669        ));
4670        assert!(!Equal.eval(
4671            &Value::Text("one".to_string()),
4672            &Value::Text("two".to_string())
4673        ));
4674    }
4675
4676    #[test]
4677    fn eval_in_with_empty_array_returns_false() {
4678        let arr = Value::Array(vec![]);
4679        assert!(!In.eval(&Value::I64(1), &arr));
4680    }
4681
4682    #[test]
4683    fn eval_starts_with_empty_prefix_matches_everything() {
4684        assert!(super::StartsWith.eval(
4685            &Value::Text("anything".to_string()),
4686            &Value::Text("".to_string())
4687        ));
4688    }
4689}