Skip to main content

drive/query/
conditions.rs

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