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