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