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)]
1704mod tests {
1705    use crate::error::query::QuerySyntaxError;
1706    use crate::query::conditions::WhereClause;
1707    use crate::query::conditions::{
1708        Between, BetweenExcludeBounds, BetweenExcludeLeft, BetweenExcludeRight, Equal, GreaterThan,
1709        GreaterThanOrEquals, In, LessThan, LessThanOrEquals, ValueClause,
1710    };
1711    use crate::query::InternalClauses;
1712    use dpp::data_contract::accessors::v0::DataContractV0Getters;
1713    use dpp::platform_value::Value;
1714    use dpp::tests::fixtures::get_data_contract_fixture;
1715    use dpp::version::LATEST_PLATFORM_VERSION;
1716
1717    #[test]
1718    fn test_allowed_sup_query_pairs() {
1719        let allowed_pairs_test_cases = [
1720            [GreaterThan, LessThan],
1721            [GreaterThan, LessThanOrEquals],
1722            [GreaterThanOrEquals, LessThanOrEquals],
1723        ];
1724        for query_pair in allowed_pairs_test_cases {
1725            let where_clauses = vec![
1726                WhereClause {
1727                    field: "a".to_string(),
1728                    operator: *query_pair.first().unwrap(),
1729                    value: Value::Float(0.0),
1730                },
1731                WhereClause {
1732                    field: "a".to_string(),
1733                    operator: *query_pair.get(1).unwrap(),
1734                    value: Value::Float(1.0),
1735                },
1736            ];
1737            let (_, range_clause, _) = WhereClause::group_clauses(&where_clauses)
1738                .expect("expected to have groupable pair");
1739            range_clause.expect("expected to have range clause returned");
1740        }
1741    }
1742
1743    #[test]
1744    fn test_allowed_inf_query_pairs() {
1745        let allowed_pairs_test_cases = [
1746            [LessThan, GreaterThan],
1747            [LessThan, GreaterThanOrEquals],
1748            [LessThanOrEquals, GreaterThanOrEquals],
1749        ];
1750        for query_pair in allowed_pairs_test_cases {
1751            let where_clauses = vec![
1752                WhereClause {
1753                    field: "a".to_string(),
1754                    operator: *query_pair.first().unwrap(),
1755                    value: Value::Float(1.0),
1756                },
1757                WhereClause {
1758                    field: "a".to_string(),
1759                    operator: *query_pair.get(1).unwrap(),
1760                    value: Value::Float(0.0),
1761                },
1762            ];
1763            let (_, range_clause, _) = WhereClause::group_clauses(&where_clauses)
1764                .expect("expected to have groupable pair");
1765            range_clause.expect("expected to have range clause returned");
1766        }
1767    }
1768
1769    #[test]
1770    fn test_query_pairs_incoherent_same_value() {
1771        let allowed_pairs_test_cases = [[LessThan, GreaterThan], [GreaterThan, LessThan]];
1772        for query_pair in allowed_pairs_test_cases {
1773            let where_clauses = vec![
1774                WhereClause {
1775                    field: "a".to_string(),
1776                    operator: *query_pair.first().unwrap(),
1777                    value: Value::Float(1.0),
1778                },
1779                WhereClause {
1780                    field: "a".to_string(),
1781                    operator: *query_pair.get(1).unwrap(),
1782                    value: Value::Float(1.0),
1783                },
1784            ];
1785            WhereClause::group_clauses(&where_clauses)
1786                .expect_err("expected to have an error returned");
1787        }
1788    }
1789
1790    #[test]
1791    fn test_different_fields_grouping_causes_error() {
1792        let where_clauses = vec![
1793            WhereClause {
1794                field: "a".to_string(),
1795                operator: LessThan,
1796                value: Value::Float(0.0),
1797            },
1798            WhereClause {
1799                field: "b".to_string(),
1800                operator: GreaterThan,
1801                value: Value::Float(1.0),
1802            },
1803        ];
1804        WhereClause::group_clauses(&where_clauses)
1805            .expect_err("different fields should not be groupable");
1806    }
1807
1808    #[test]
1809    fn test_restricted_query_pairs_causes_error() {
1810        let restricted_pairs_test_cases = [
1811            [Equal, LessThan],
1812            [Equal, GreaterThan],
1813            [In, LessThan],
1814            [Equal, GreaterThan],
1815            [LessThanOrEquals, LessThanOrEquals],
1816            [LessThan, LessThan],
1817            [LessThan, LessThanOrEquals],
1818            [GreaterThan, GreaterThan],
1819            [GreaterThan, GreaterThanOrEquals],
1820            [GreaterThanOrEquals, GreaterThanOrEquals],
1821            [Equal, Equal],
1822        ];
1823        for query_pair in restricted_pairs_test_cases {
1824            let where_clauses = vec![
1825                WhereClause {
1826                    field: "a".to_string(),
1827                    operator: *query_pair.first().unwrap(),
1828                    value: Value::Float(0.0),
1829                },
1830                WhereClause {
1831                    field: "a".to_string(),
1832                    operator: *query_pair.get(1).unwrap(),
1833                    value: Value::Float(1.0),
1834                },
1835            ];
1836            WhereClause::group_clauses(&where_clauses)
1837                .expect_err("expected to not have a groupable pair");
1838        }
1839    }
1840
1841    #[test]
1842    fn validate_rejects_equality_with_wrong_type_for_string_field() {
1843        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1844        let contract = fixture.data_contract_owned();
1845        let doc_type = contract
1846            .document_type_for_name("niceDocument")
1847            .expect("doc type exists");
1848
1849        let clause = WhereClause {
1850            field: "name".to_string(),
1851            operator: Equal,
1852            value: Value::Identifier([1u8; 32]),
1853        };
1854        let res = clause.validate_against_schema(doc_type);
1855        assert!(res.is_err());
1856        assert!(matches!(
1857            res.first_error(),
1858            Some(QuerySyntaxError::InvalidWhereClauseComponents(_))
1859        ));
1860    }
1861
1862    #[test]
1863    fn validate_rejects_in_with_wrong_element_types() {
1864        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1865        let contract = fixture.data_contract_owned();
1866        let doc_type = contract
1867            .document_type_for_name("indexedDocument")
1868            .expect("doc type exists");
1869
1870        let clause = WhereClause {
1871            field: "firstName".to_string(),
1872            operator: In,
1873            value: Value::Array(vec![
1874                Value::Text("alice".to_string()),
1875                Value::Identifier([2u8; 32]),
1876            ]),
1877        };
1878        let res = clause.validate_against_schema(doc_type);
1879        assert!(res.is_err());
1880        assert!(matches!(
1881            res.first_error(),
1882            Some(QuerySyntaxError::InvalidWhereClauseComponents(_))
1883        ));
1884    }
1885
1886    #[test]
1887    fn validate_rejects_primary_key_in_with_non_identifiers() {
1888        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1889        let contract = fixture.data_contract_owned();
1890        let doc_type = contract
1891            .document_type_for_name("niceDocument")
1892            .expect("doc type exists");
1893
1894        let clauses = InternalClauses {
1895            primary_key_in_clause: Some(WhereClause {
1896                field: "$id".to_string(),
1897                operator: In,
1898                value: Value::Array(vec![
1899                    Value::Text("a".to_string()),
1900                    Value::Text("b".to_string()),
1901                ]),
1902            }),
1903            ..Default::default()
1904        };
1905
1906        let res = clauses.validate_against_schema(doc_type);
1907        assert!(res.is_err());
1908        assert!(matches!(
1909            res.first_error(),
1910            Some(QuerySyntaxError::InvalidWhereClauseComponents(_))
1911        ));
1912    }
1913
1914    #[test]
1915    fn validate_rejects_date_with_float_equality() {
1916        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1917        let contract = fixture.data_contract_owned();
1918        let doc_type = contract
1919            .document_type_for_name("uniqueDates")
1920            .expect("doc type exists");
1921
1922        let clause = WhereClause {
1923            field: "$createdAt".to_string(),
1924            operator: Equal,
1925            value: Value::Float(1.23),
1926        };
1927        let res = clause.validate_against_schema(doc_type);
1928        assert!(res.is_err());
1929        assert!(matches!(
1930            res.first_error(),
1931            Some(QuerySyntaxError::InvalidWhereClauseComponents(_))
1932        ));
1933    }
1934
1935    #[test]
1936    fn validate_rejects_in_bytes_for_string_field() {
1937        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1938        let contract = fixture.data_contract_owned();
1939        let doc_type = contract
1940            .document_type_for_name("niceDocument")
1941            .expect("doc type exists");
1942
1943        // IN with Bytes should be rejected on string fields
1944        let clause = WhereClause {
1945            field: "name".to_string(),
1946            operator: In,
1947            value: Value::Bytes(vec![1, 2, 3]),
1948        };
1949        let res = clause.validate_against_schema(doc_type);
1950        assert!(res.is_err());
1951    }
1952
1953    #[test]
1954    fn validate_accepts_meta_owner_id_in_identifiers() {
1955        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1956        let contract = fixture.data_contract_owned();
1957        let doc_type = contract
1958            .document_type_for_name("niceDocument")
1959            .expect("doc type exists");
1960
1961        let clause = WhereClause {
1962            field: "$ownerId".to_string(),
1963            operator: In,
1964            value: Value::Array(vec![
1965                Value::Identifier([1u8; 32]),
1966                Value::Identifier([2u8; 32]),
1967            ]),
1968        };
1969        let res = clause.validate_against_schema(doc_type);
1970        assert!(res.is_valid());
1971    }
1972
1973    #[test]
1974    fn validate_accepts_meta_created_at_between_integers() {
1975        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1976        let contract = fixture.data_contract_owned();
1977        let doc_type = contract
1978            .document_type_for_name("uniqueDates")
1979            .expect("doc type exists");
1980
1981        let clause = WhereClause {
1982            field: "$createdAt".to_string(),
1983            operator: crate::query::conditions::Between,
1984            value: Value::Array(vec![Value::U64(1000), Value::U64(2000)]),
1985        };
1986        let res = clause.validate_against_schema(doc_type);
1987        assert!(res.is_valid());
1988    }
1989
1990    #[test]
1991    fn validate_rejects_between_variants_with_equal_bounds() {
1992        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1993        let contract = fixture.data_contract_owned();
1994        let doc_type = contract
1995            .document_type_for_name("uniqueDates")
1996            .expect("doc type exists");
1997
1998        for operator in [
1999            Between,
2000            BetweenExcludeBounds,
2001            BetweenExcludeLeft,
2002            BetweenExcludeRight,
2003        ] {
2004            let clause = WhereClause {
2005                field: "$createdAt".to_string(),
2006                operator,
2007                value: Value::Array(vec![Value::U64(1000), Value::U64(1000)]),
2008            };
2009
2010            let res = clause.validate_against_schema(doc_type);
2011            assert!(
2012                res.is_err(),
2013                "{operator:?} should reject equal bounds during validation"
2014            );
2015            assert!(matches!(
2016                res.first_error(),
2017                Some(QuerySyntaxError::InvalidBetweenClause(_))
2018            ));
2019        }
2020    }
2021
2022    #[test]
2023    fn value_clause_between_variants_do_not_match_equal_bounds() {
2024        let equal_bounds = Value::Array(vec![Value::U64(1000), Value::U64(1000)]);
2025        let value_to_test = Value::U64(1000);
2026
2027        for operator in [
2028            Between,
2029            BetweenExcludeBounds,
2030            BetweenExcludeLeft,
2031            BetweenExcludeRight,
2032        ] {
2033            let clause = ValueClause {
2034                operator,
2035                value: equal_bounds.clone(),
2036            };
2037
2038            assert!(
2039                !clause.matches_value(&value_to_test),
2040                "{operator:?} should not match when bounds are equal"
2041            );
2042        }
2043    }
2044
2045    #[test]
2046    fn validate_rejects_meta_revision_float_equality() {
2047        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2048        let contract = fixture.data_contract_owned();
2049        let doc_type = contract
2050            .document_type_for_name("niceDocument")
2051            .expect("doc type exists");
2052
2053        let clause = WhereClause {
2054            field: "$revision".to_string(),
2055            operator: Equal,
2056            value: Value::Float(3.15),
2057        };
2058        let res = clause.validate_against_schema(doc_type);
2059        assert!(res.is_err());
2060    }
2061
2062    #[test]
2063    fn validate_accepts_meta_created_at_block_height_range() {
2064        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2065        let contract = fixture.data_contract_owned();
2066        let doc_type = contract
2067            .document_type_for_name("uniqueDates")
2068            .expect("doc type exists");
2069
2070        let clause = WhereClause {
2071            field: "$createdAtBlockHeight".to_string(),
2072            operator: GreaterThanOrEquals,
2073            value: Value::U64(100),
2074        };
2075        let res = clause.validate_against_schema(doc_type);
2076        assert!(res.is_valid());
2077    }
2078
2079    #[test]
2080    fn validate_accepts_meta_data_contract_id_equality() {
2081        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2082        let contract = fixture.data_contract_owned();
2083        let doc_type = contract
2084            .document_type_for_name("niceDocument")
2085            .expect("doc type exists");
2086
2087        let clause = WhereClause {
2088            field: "$dataContractId".to_string(),
2089            operator: Equal,
2090            value: Value::Identifier([3u8; 32]),
2091        };
2092        let res = clause.validate_against_schema(doc_type);
2093        assert!(res.is_valid());
2094    }
2095
2096    // ---- WhereOperator::allows_flip ----
2097
2098    #[test]
2099    fn allows_flip_returns_true_for_comparison_operators() {
2100        assert!(Equal.allows_flip());
2101        assert!(GreaterThan.allows_flip());
2102        assert!(GreaterThanOrEquals.allows_flip());
2103        assert!(LessThan.allows_flip());
2104        assert!(LessThanOrEquals.allows_flip());
2105    }
2106
2107    #[test]
2108    fn allows_flip_returns_false_for_non_flippable_operators() {
2109        assert!(!Between.allows_flip());
2110        assert!(!BetweenExcludeBounds.allows_flip());
2111        assert!(!BetweenExcludeLeft.allows_flip());
2112        assert!(!BetweenExcludeRight.allows_flip());
2113        assert!(!In.allows_flip());
2114        assert!(!super::StartsWith.allows_flip());
2115    }
2116
2117    // ---- WhereOperator::flip ----
2118
2119    #[test]
2120    fn flip_equal_stays_equal() {
2121        assert_eq!(Equal.flip().unwrap(), Equal);
2122    }
2123
2124    #[test]
2125    fn flip_greater_than_becomes_less_than() {
2126        assert_eq!(GreaterThan.flip().unwrap(), LessThan);
2127    }
2128
2129    #[test]
2130    fn flip_greater_than_or_equals_becomes_less_than_or_equals() {
2131        assert_eq!(GreaterThanOrEquals.flip().unwrap(), LessThanOrEquals);
2132    }
2133
2134    #[test]
2135    fn flip_less_than_becomes_greater_than() {
2136        assert_eq!(LessThan.flip().unwrap(), GreaterThan);
2137    }
2138
2139    #[test]
2140    fn flip_less_than_or_equals_becomes_greater_than_or_equals() {
2141        assert_eq!(LessThanOrEquals.flip().unwrap(), GreaterThanOrEquals);
2142    }
2143
2144    #[test]
2145    fn flip_between_returns_error() {
2146        assert!(Between.flip().is_err());
2147    }
2148
2149    #[test]
2150    fn flip_between_exclude_bounds_returns_error() {
2151        assert!(BetweenExcludeBounds.flip().is_err());
2152    }
2153
2154    #[test]
2155    fn flip_between_exclude_left_returns_error() {
2156        assert!(BetweenExcludeLeft.flip().is_err());
2157    }
2158
2159    #[test]
2160    fn flip_between_exclude_right_returns_error() {
2161        assert!(BetweenExcludeRight.flip().is_err());
2162    }
2163
2164    #[test]
2165    fn flip_in_returns_error() {
2166        assert!(In.flip().is_err());
2167    }
2168
2169    #[test]
2170    fn flip_starts_with_returns_error() {
2171        assert!(super::StartsWith.flip().is_err());
2172    }
2173
2174    // ---- WhereOperator::is_range ----
2175
2176    #[test]
2177    fn is_range_false_for_equal() {
2178        assert!(!Equal.is_range());
2179    }
2180
2181    #[test]
2182    fn is_range_true_for_all_range_operators() {
2183        assert!(GreaterThan.is_range());
2184        assert!(GreaterThanOrEquals.is_range());
2185        assert!(LessThan.is_range());
2186        assert!(LessThanOrEquals.is_range());
2187        assert!(Between.is_range());
2188        assert!(BetweenExcludeBounds.is_range());
2189        assert!(BetweenExcludeLeft.is_range());
2190        assert!(BetweenExcludeRight.is_range());
2191        assert!(In.is_range());
2192        assert!(super::StartsWith.is_range());
2193    }
2194
2195    // ---- WhereOperator::from_string ----
2196
2197    #[test]
2198    fn from_string_parses_equality_operators() {
2199        use super::WhereOperator;
2200        assert_eq!(WhereOperator::from_string("="), Some(Equal));
2201        assert_eq!(WhereOperator::from_string("=="), Some(Equal));
2202    }
2203
2204    #[test]
2205    fn from_string_parses_comparison_operators() {
2206        use super::WhereOperator;
2207        assert_eq!(WhereOperator::from_string(">"), Some(GreaterThan));
2208        assert_eq!(WhereOperator::from_string(">="), Some(GreaterThanOrEquals));
2209        assert_eq!(WhereOperator::from_string("<"), Some(LessThan));
2210        assert_eq!(WhereOperator::from_string("<="), Some(LessThanOrEquals));
2211    }
2212
2213    #[test]
2214    fn from_string_parses_between_variants() {
2215        use super::WhereOperator;
2216        assert_eq!(WhereOperator::from_string("Between"), Some(Between));
2217        assert_eq!(WhereOperator::from_string("between"), Some(Between));
2218        assert_eq!(
2219            WhereOperator::from_string("BetweenExcludeBounds"),
2220            Some(BetweenExcludeBounds)
2221        );
2222        assert_eq!(
2223            WhereOperator::from_string("betweenExcludeBounds"),
2224            Some(BetweenExcludeBounds)
2225        );
2226        assert_eq!(
2227            WhereOperator::from_string("betweenexcludebounds"),
2228            Some(BetweenExcludeBounds)
2229        );
2230        assert_eq!(
2231            WhereOperator::from_string("between_exclude_bounds"),
2232            Some(BetweenExcludeBounds)
2233        );
2234        assert_eq!(
2235            WhereOperator::from_string("BetweenExcludeLeft"),
2236            Some(BetweenExcludeLeft)
2237        );
2238        assert_eq!(
2239            WhereOperator::from_string("betweenExcludeLeft"),
2240            Some(BetweenExcludeLeft)
2241        );
2242        assert_eq!(
2243            WhereOperator::from_string("betweenexcludeleft"),
2244            Some(BetweenExcludeLeft)
2245        );
2246        assert_eq!(
2247            WhereOperator::from_string("between_exclude_left"),
2248            Some(BetweenExcludeLeft)
2249        );
2250        assert_eq!(
2251            WhereOperator::from_string("BetweenExcludeRight"),
2252            Some(BetweenExcludeRight)
2253        );
2254        assert_eq!(
2255            WhereOperator::from_string("betweenExcludeRight"),
2256            Some(BetweenExcludeRight)
2257        );
2258        assert_eq!(
2259            WhereOperator::from_string("betweenexcluderight"),
2260            Some(BetweenExcludeRight)
2261        );
2262        assert_eq!(
2263            WhereOperator::from_string("between_exclude_right"),
2264            Some(BetweenExcludeRight)
2265        );
2266    }
2267
2268    #[test]
2269    fn from_string_parses_in_operator() {
2270        use super::WhereOperator;
2271        assert_eq!(WhereOperator::from_string("In"), Some(In));
2272        assert_eq!(WhereOperator::from_string("in"), Some(In));
2273    }
2274
2275    #[test]
2276    fn from_string_parses_starts_with_operator() {
2277        use super::WhereOperator;
2278        assert_eq!(
2279            WhereOperator::from_string("StartsWith"),
2280            Some(super::StartsWith)
2281        );
2282        assert_eq!(
2283            WhereOperator::from_string("startsWith"),
2284            Some(super::StartsWith)
2285        );
2286        assert_eq!(
2287            WhereOperator::from_string("startswith"),
2288            Some(super::StartsWith)
2289        );
2290        assert_eq!(
2291            WhereOperator::from_string("starts_with"),
2292            Some(super::StartsWith)
2293        );
2294    }
2295
2296    #[test]
2297    fn from_string_returns_none_for_unknown() {
2298        use super::WhereOperator;
2299        assert_eq!(WhereOperator::from_string("LIKE"), None);
2300        assert_eq!(WhereOperator::from_string("!="), None);
2301        assert_eq!(WhereOperator::from_string(""), None);
2302    }
2303
2304    // ---- WhereOperator::from_sql_operator ----
2305
2306    #[test]
2307    fn from_sql_operator_maps_known_operators() {
2308        use super::WhereOperator;
2309        use sqlparser::ast::BinaryOperator;
2310        assert_eq!(
2311            WhereOperator::from_sql_operator(BinaryOperator::Eq),
2312            Some(Equal)
2313        );
2314        assert_eq!(
2315            WhereOperator::from_sql_operator(BinaryOperator::Gt),
2316            Some(GreaterThan)
2317        );
2318        assert_eq!(
2319            WhereOperator::from_sql_operator(BinaryOperator::GtEq),
2320            Some(GreaterThanOrEquals)
2321        );
2322        assert_eq!(
2323            WhereOperator::from_sql_operator(BinaryOperator::Lt),
2324            Some(LessThan)
2325        );
2326        assert_eq!(
2327            WhereOperator::from_sql_operator(BinaryOperator::LtEq),
2328            Some(LessThanOrEquals)
2329        );
2330    }
2331
2332    #[test]
2333    fn from_sql_operator_returns_none_for_unsupported() {
2334        use super::WhereOperator;
2335        use sqlparser::ast::BinaryOperator;
2336        assert_eq!(
2337            WhereOperator::from_sql_operator(BinaryOperator::NotEq),
2338            None
2339        );
2340        assert_eq!(WhereOperator::from_sql_operator(BinaryOperator::Plus), None);
2341    }
2342
2343    // ---- WhereOperator::eval ----
2344
2345    #[test]
2346    fn eval_equal_matches_identical_values() {
2347        assert!(Equal.eval(&Value::I64(42), &Value::I64(42)));
2348        assert!(!Equal.eval(&Value::I64(42), &Value::I64(43)));
2349    }
2350
2351    #[test]
2352    fn eval_greater_than() {
2353        assert!(GreaterThan.eval(&Value::I64(10), &Value::I64(5)));
2354        assert!(!GreaterThan.eval(&Value::I64(5), &Value::I64(10)));
2355        assert!(!GreaterThan.eval(&Value::I64(5), &Value::I64(5)));
2356    }
2357
2358    #[test]
2359    fn eval_greater_than_or_equals() {
2360        assert!(GreaterThanOrEquals.eval(&Value::I64(10), &Value::I64(5)));
2361        assert!(GreaterThanOrEquals.eval(&Value::I64(5), &Value::I64(5)));
2362        assert!(!GreaterThanOrEquals.eval(&Value::I64(4), &Value::I64(5)));
2363    }
2364
2365    #[test]
2366    fn eval_less_than() {
2367        assert!(LessThan.eval(&Value::I64(3), &Value::I64(5)));
2368        assert!(!LessThan.eval(&Value::I64(5), &Value::I64(3)));
2369        assert!(!LessThan.eval(&Value::I64(5), &Value::I64(5)));
2370    }
2371
2372    #[test]
2373    fn eval_less_than_or_equals() {
2374        assert!(LessThanOrEquals.eval(&Value::I64(3), &Value::I64(5)));
2375        assert!(LessThanOrEquals.eval(&Value::I64(5), &Value::I64(5)));
2376        assert!(!LessThanOrEquals.eval(&Value::I64(6), &Value::I64(5)));
2377    }
2378
2379    #[test]
2380    fn eval_in_with_array() {
2381        let arr = Value::Array(vec![Value::I64(1), Value::I64(2), Value::I64(3)]);
2382        assert!(In.eval(&Value::I64(2), &arr));
2383        assert!(!In.eval(&Value::I64(4), &arr));
2384    }
2385
2386    #[test]
2387    fn eval_in_with_bytes() {
2388        let bytes = Value::Bytes(vec![10, 20, 30]);
2389        assert!(In.eval(&Value::U8(20), &bytes));
2390        assert!(!In.eval(&Value::U8(40), &bytes));
2391        // Non-U8 value against Bytes should return false
2392        assert!(!In.eval(&Value::I64(20), &bytes));
2393    }
2394
2395    #[test]
2396    fn eval_in_with_non_collection_returns_false() {
2397        assert!(!In.eval(&Value::I64(1), &Value::I64(1)));
2398    }
2399
2400    #[test]
2401    fn eval_between_inclusive() {
2402        let bounds = Value::Array(vec![Value::I64(10), Value::I64(20)]);
2403        assert!(Between.eval(&Value::I64(10), &bounds));
2404        assert!(Between.eval(&Value::I64(15), &bounds));
2405        assert!(Between.eval(&Value::I64(20), &bounds));
2406        assert!(!Between.eval(&Value::I64(9), &bounds));
2407        assert!(!Between.eval(&Value::I64(21), &bounds));
2408    }
2409
2410    #[test]
2411    fn eval_between_exclude_bounds() {
2412        let bounds = Value::Array(vec![Value::I64(10), Value::I64(20)]);
2413        assert!(!BetweenExcludeBounds.eval(&Value::I64(10), &bounds));
2414        assert!(BetweenExcludeBounds.eval(&Value::I64(15), &bounds));
2415        assert!(!BetweenExcludeBounds.eval(&Value::I64(20), &bounds));
2416    }
2417
2418    #[test]
2419    fn eval_between_exclude_left() {
2420        let bounds = Value::Array(vec![Value::I64(10), Value::I64(20)]);
2421        assert!(!BetweenExcludeLeft.eval(&Value::I64(10), &bounds));
2422        assert!(BetweenExcludeLeft.eval(&Value::I64(15), &bounds));
2423        assert!(BetweenExcludeLeft.eval(&Value::I64(20), &bounds));
2424    }
2425
2426    #[test]
2427    fn eval_between_exclude_right() {
2428        let bounds = Value::Array(vec![Value::I64(10), Value::I64(20)]);
2429        assert!(BetweenExcludeRight.eval(&Value::I64(10), &bounds));
2430        assert!(BetweenExcludeRight.eval(&Value::I64(15), &bounds));
2431        assert!(!BetweenExcludeRight.eval(&Value::I64(20), &bounds));
2432    }
2433
2434    #[test]
2435    fn eval_between_with_wrong_bound_order_returns_false() {
2436        // Bounds in descending order should not match anything
2437        let bounds = Value::Array(vec![Value::I64(20), Value::I64(10)]);
2438        assert!(!Between.eval(&Value::I64(15), &bounds));
2439        assert!(!BetweenExcludeBounds.eval(&Value::I64(15), &bounds));
2440        assert!(!BetweenExcludeLeft.eval(&Value::I64(15), &bounds));
2441        assert!(!BetweenExcludeRight.eval(&Value::I64(15), &bounds));
2442    }
2443
2444    #[test]
2445    fn eval_between_with_non_array_returns_false() {
2446        assert!(!Between.eval(&Value::I64(5), &Value::I64(10)));
2447    }
2448
2449    #[test]
2450    fn eval_between_with_wrong_array_len_returns_false() {
2451        let single = Value::Array(vec![Value::I64(10)]);
2452        assert!(!Between.eval(&Value::I64(10), &single));
2453    }
2454
2455    #[test]
2456    fn eval_starts_with_text() {
2457        assert!(super::StartsWith.eval(
2458            &Value::Text("hello world".to_string()),
2459            &Value::Text("hello".to_string())
2460        ));
2461        assert!(!super::StartsWith.eval(
2462            &Value::Text("hello world".to_string()),
2463            &Value::Text("world".to_string())
2464        ));
2465    }
2466
2467    #[test]
2468    fn eval_starts_with_non_text_returns_false() {
2469        assert!(!super::StartsWith.eval(&Value::I64(123), &Value::Text("1".to_string())));
2470        assert!(!super::StartsWith.eval(&Value::Text("hello".to_string()), &Value::I64(1)));
2471    }
2472
2473    // ---- WhereOperator Display ----
2474
2475    #[test]
2476    fn display_formatting_for_all_operators() {
2477        assert_eq!(format!("{}", Equal), "=");
2478        assert_eq!(format!("{}", GreaterThan), ">");
2479        assert_eq!(format!("{}", GreaterThanOrEquals), ">=");
2480        assert_eq!(format!("{}", LessThan), "<");
2481        assert_eq!(format!("{}", LessThanOrEquals), "<=");
2482        assert_eq!(format!("{}", Between), "Between");
2483        assert_eq!(format!("{}", BetweenExcludeBounds), "BetweenExcludeBounds");
2484        assert_eq!(format!("{}", BetweenExcludeLeft), "BetweenExcludeLeft");
2485        assert_eq!(format!("{}", BetweenExcludeRight), "BetweenExcludeRight");
2486        assert_eq!(format!("{}", In), "In");
2487        assert_eq!(format!("{}", super::StartsWith), "StartsWith");
2488    }
2489
2490    // ---- WhereOperator -> Value conversion ----
2491
2492    #[test]
2493    fn where_operator_into_value() {
2494        let val: Value = Equal.into();
2495        assert_eq!(val, Value::Text("=".to_string()));
2496
2497        let val: Value = In.into();
2498        assert_eq!(val, Value::Text("In".to_string()));
2499    }
2500
2501    // ---- WhereClause::is_identifier ----
2502
2503    #[test]
2504    fn is_identifier_returns_true_for_dollar_id() {
2505        let clause = WhereClause {
2506            field: "$id".to_string(),
2507            operator: Equal,
2508            value: Value::I64(1),
2509        };
2510        assert!(clause.is_identifier());
2511    }
2512
2513    #[test]
2514    fn is_identifier_returns_false_for_other_fields() {
2515        let clause = WhereClause {
2516            field: "name".to_string(),
2517            operator: Equal,
2518            value: Value::I64(1),
2519        };
2520        assert!(!clause.is_identifier());
2521
2522        let clause = WhereClause {
2523            field: "$ownerId".to_string(),
2524            operator: Equal,
2525            value: Value::I64(1),
2526        };
2527        assert!(!clause.is_identifier());
2528    }
2529
2530    // ---- WhereClause::in_values ----
2531
2532    #[test]
2533    fn in_values_with_array() {
2534        let clause = WhereClause {
2535            field: "f".to_string(),
2536            operator: In,
2537            value: Value::Array(vec![Value::I64(1), Value::I64(2)]),
2538        };
2539        let result = clause.in_values();
2540        assert!(result.is_valid());
2541        let data = result.into_data().expect("should have data");
2542        assert_eq!(data.len(), 2);
2543    }
2544
2545    #[test]
2546    fn in_values_with_bytes() {
2547        let clause = WhereClause {
2548            field: "f".to_string(),
2549            operator: In,
2550            value: Value::Bytes(vec![10, 20]),
2551        };
2552        let result = clause.in_values();
2553        assert!(result.is_valid());
2554        let data = result.into_data().expect("should have data");
2555        assert_eq!(data.len(), 2);
2556        assert_eq!(data[0], Value::U8(10));
2557        assert_eq!(data[1], Value::U8(20));
2558    }
2559
2560    #[test]
2561    fn in_values_non_array_returns_error() {
2562        let clause = WhereClause {
2563            field: "f".to_string(),
2564            operator: In,
2565            value: Value::I64(42),
2566        };
2567        let result = clause.in_values();
2568        assert!(!result.is_valid());
2569    }
2570
2571    #[test]
2572    fn in_values_empty_array_returns_error() {
2573        let clause = WhereClause {
2574            field: "f".to_string(),
2575            operator: In,
2576            value: Value::Array(vec![]),
2577        };
2578        let result = clause.in_values();
2579        assert!(!result.is_valid());
2580    }
2581
2582    #[test]
2583    fn in_values_too_many_returns_error() {
2584        let values: Vec<Value> = (0..101).map(|i| Value::I64(i)).collect();
2585        let clause = WhereClause {
2586            field: "f".to_string(),
2587            operator: In,
2588            value: Value::Array(values),
2589        };
2590        let result = clause.in_values();
2591        assert!(!result.is_valid());
2592    }
2593
2594    #[test]
2595    fn in_values_with_duplicates_returns_error() {
2596        let clause = WhereClause {
2597            field: "f".to_string(),
2598            operator: In,
2599            value: Value::Array(vec![Value::I64(1), Value::I64(1)]),
2600        };
2601        let result = clause.in_values();
2602        assert!(!result.is_valid());
2603    }
2604
2605    // ---- WhereClause::less_than ----
2606
2607    #[test]
2608    fn less_than_with_i128_values() {
2609        let a = WhereClause {
2610            field: "f".to_string(),
2611            operator: Equal,
2612            value: Value::I128(5),
2613        };
2614        let b = WhereClause {
2615            field: "f".to_string(),
2616            operator: Equal,
2617            value: Value::I128(10),
2618        };
2619        assert!(a.less_than(&b, false).unwrap());
2620        assert!(a.less_than(&b, true).unwrap());
2621        assert!(!b.less_than(&a, false).unwrap());
2622        assert!(a.less_than(&a, true).unwrap()); // le
2623        assert!(!a.less_than(&a, false).unwrap()); // lt
2624    }
2625
2626    #[test]
2627    fn less_than_with_u128_values() {
2628        let a = WhereClause {
2629            field: "f".to_string(),
2630            operator: Equal,
2631            value: Value::U128(1),
2632        };
2633        let b = WhereClause {
2634            field: "f".to_string(),
2635            operator: Equal,
2636            value: Value::U128(2),
2637        };
2638        assert!(a.less_than(&b, false).unwrap());
2639        assert!(!b.less_than(&a, false).unwrap());
2640    }
2641
2642    #[test]
2643    fn less_than_with_i64_values() {
2644        let a = WhereClause {
2645            field: "f".to_string(),
2646            operator: Equal,
2647            value: Value::I64(-5),
2648        };
2649        let b = WhereClause {
2650            field: "f".to_string(),
2651            operator: Equal,
2652            value: Value::I64(10),
2653        };
2654        assert!(a.less_than(&b, false).unwrap());
2655    }
2656
2657    #[test]
2658    fn less_than_with_u64_values() {
2659        let a = WhereClause {
2660            field: "f".to_string(),
2661            operator: Equal,
2662            value: Value::U64(3),
2663        };
2664        let b = WhereClause {
2665            field: "f".to_string(),
2666            operator: Equal,
2667            value: Value::U64(7),
2668        };
2669        assert!(a.less_than(&b, false).unwrap());
2670    }
2671
2672    #[test]
2673    fn less_than_with_i32_values() {
2674        let a = WhereClause {
2675            field: "f".to_string(),
2676            operator: Equal,
2677            value: Value::I32(1),
2678        };
2679        let b = WhereClause {
2680            field: "f".to_string(),
2681            operator: Equal,
2682            value: Value::I32(2),
2683        };
2684        assert!(a.less_than(&b, false).unwrap());
2685    }
2686
2687    #[test]
2688    fn less_than_with_u32_values() {
2689        let a = WhereClause {
2690            field: "f".to_string(),
2691            operator: Equal,
2692            value: Value::U32(1),
2693        };
2694        let b = WhereClause {
2695            field: "f".to_string(),
2696            operator: Equal,
2697            value: Value::U32(2),
2698        };
2699        assert!(a.less_than(&b, false).unwrap());
2700    }
2701
2702    #[test]
2703    fn less_than_with_i16_values() {
2704        let a = WhereClause {
2705            field: "f".to_string(),
2706            operator: Equal,
2707            value: Value::I16(1),
2708        };
2709        let b = WhereClause {
2710            field: "f".to_string(),
2711            operator: Equal,
2712            value: Value::I16(2),
2713        };
2714        assert!(a.less_than(&b, false).unwrap());
2715        assert!(a.less_than(&b, true).unwrap());
2716    }
2717
2718    #[test]
2719    fn less_than_with_u16_values() {
2720        let a = WhereClause {
2721            field: "f".to_string(),
2722            operator: Equal,
2723            value: Value::U16(1),
2724        };
2725        let b = WhereClause {
2726            field: "f".to_string(),
2727            operator: Equal,
2728            value: Value::U16(2),
2729        };
2730        assert!(a.less_than(&b, false).unwrap());
2731    }
2732
2733    #[test]
2734    fn less_than_with_i8_values() {
2735        let a = WhereClause {
2736            field: "f".to_string(),
2737            operator: Equal,
2738            value: Value::I8(1),
2739        };
2740        let b = WhereClause {
2741            field: "f".to_string(),
2742            operator: Equal,
2743            value: Value::I8(2),
2744        };
2745        assert!(a.less_than(&b, false).unwrap());
2746    }
2747
2748    #[test]
2749    fn less_than_with_u8_values() {
2750        let a = WhereClause {
2751            field: "f".to_string(),
2752            operator: Equal,
2753            value: Value::U8(1),
2754        };
2755        let b = WhereClause {
2756            field: "f".to_string(),
2757            operator: Equal,
2758            value: Value::U8(2),
2759        };
2760        assert!(a.less_than(&b, false).unwrap());
2761    }
2762
2763    #[test]
2764    fn less_than_with_bytes_values() {
2765        let a = WhereClause {
2766            field: "f".to_string(),
2767            operator: Equal,
2768            value: Value::Bytes(vec![1, 2]),
2769        };
2770        let b = WhereClause {
2771            field: "f".to_string(),
2772            operator: Equal,
2773            value: Value::Bytes(vec![1, 3]),
2774        };
2775        assert!(a.less_than(&b, false).unwrap());
2776    }
2777
2778    #[test]
2779    fn less_than_with_float_values() {
2780        let a = WhereClause {
2781            field: "f".to_string(),
2782            operator: Equal,
2783            value: Value::Float(1.5),
2784        };
2785        let b = WhereClause {
2786            field: "f".to_string(),
2787            operator: Equal,
2788            value: Value::Float(2.5),
2789        };
2790        assert!(a.less_than(&b, false).unwrap());
2791        assert!(a.less_than(&b, true).unwrap());
2792    }
2793
2794    #[test]
2795    fn less_than_with_text_values() {
2796        let a = WhereClause {
2797            field: "f".to_string(),
2798            operator: Equal,
2799            value: Value::Text("abc".to_string()),
2800        };
2801        let b = WhereClause {
2802            field: "f".to_string(),
2803            operator: Equal,
2804            value: Value::Text("xyz".to_string()),
2805        };
2806        assert!(a.less_than(&b, false).unwrap());
2807    }
2808
2809    #[test]
2810    fn less_than_with_mismatched_types_returns_error() {
2811        let a = WhereClause {
2812            field: "f".to_string(),
2813            operator: Equal,
2814            value: Value::I64(1),
2815        };
2816        let b = WhereClause {
2817            field: "f".to_string(),
2818            operator: Equal,
2819            value: Value::Text("abc".to_string()),
2820        };
2821        assert!(a.less_than(&b, false).is_err());
2822    }
2823
2824    // ---- WhereClause::from_components ----
2825
2826    #[test]
2827    fn from_components_valid_clause() {
2828        let components = vec![
2829            Value::Text("name".to_string()),
2830            Value::Text("=".to_string()),
2831            Value::Text("alice".to_string()),
2832        ];
2833        let clause = WhereClause::from_components(&components).unwrap();
2834        assert_eq!(clause.field, "name");
2835        assert_eq!(clause.operator, Equal);
2836        assert_eq!(clause.value, Value::Text("alice".to_string()));
2837    }
2838
2839    #[test]
2840    fn from_components_wrong_count_returns_error() {
2841        let components = vec![
2842            Value::Text("name".to_string()),
2843            Value::Text("=".to_string()),
2844        ];
2845        assert!(WhereClause::from_components(&components).is_err());
2846
2847        let components = vec![
2848            Value::Text("name".to_string()),
2849            Value::Text("=".to_string()),
2850            Value::I64(1),
2851            Value::I64(2),
2852        ];
2853        assert!(WhereClause::from_components(&components).is_err());
2854    }
2855
2856    #[test]
2857    fn from_components_non_string_field_returns_error() {
2858        let components = vec![Value::I64(123), Value::Text("=".to_string()), Value::I64(1)];
2859        assert!(WhereClause::from_components(&components).is_err());
2860    }
2861
2862    #[test]
2863    fn from_components_non_string_operator_returns_error() {
2864        let components = vec![
2865            Value::Text("name".to_string()),
2866            Value::I64(1),
2867            Value::I64(1),
2868        ];
2869        assert!(WhereClause::from_components(&components).is_err());
2870    }
2871
2872    #[test]
2873    fn from_components_unknown_operator_returns_error() {
2874        let components = vec![
2875            Value::Text("name".to_string()),
2876            Value::Text("LIKE".to_string()),
2877            Value::I64(1),
2878        ];
2879        assert!(WhereClause::from_components(&components).is_err());
2880    }
2881
2882    #[test]
2883    fn from_components_with_in_operator() {
2884        let components = vec![
2885            Value::Text("status".to_string()),
2886            Value::Text("in".to_string()),
2887            Value::Array(vec![Value::I64(1), Value::I64(2)]),
2888        ];
2889        let clause = WhereClause::from_components(&components).unwrap();
2890        assert_eq!(clause.operator, In);
2891    }
2892
2893    #[test]
2894    fn from_components_with_starts_with_operator() {
2895        let components = vec![
2896            Value::Text("name".to_string()),
2897            Value::Text("startsWith".to_string()),
2898            Value::Text("alice".to_string()),
2899        ];
2900        let clause = WhereClause::from_components(&components).unwrap();
2901        assert_eq!(clause.operator, super::StartsWith);
2902    }
2903
2904    // ---- WhereClause -> Value conversion ----
2905
2906    #[test]
2907    fn where_clause_into_value() {
2908        let clause = WhereClause {
2909            field: "name".to_string(),
2910            operator: Equal,
2911            value: Value::Text("alice".to_string()),
2912        };
2913        let val: Value = clause.into();
2914        match val {
2915            Value::Array(arr) => {
2916                assert_eq!(arr.len(), 3);
2917                assert_eq!(arr[0], Value::Text("name".to_string()));
2918                assert_eq!(arr[1], Value::Text("=".to_string()));
2919                assert_eq!(arr[2], Value::Text("alice".to_string()));
2920            }
2921            _ => panic!("expected Array"),
2922        }
2923    }
2924
2925    // ---- ValueClause::matches_value ----
2926
2927    #[test]
2928    fn value_clause_matches_value_equal() {
2929        let clause = ValueClause {
2930            operator: Equal,
2931            value: Value::I64(42),
2932        };
2933        assert!(clause.matches_value(&Value::I64(42)));
2934        assert!(!clause.matches_value(&Value::I64(43)));
2935    }
2936
2937    #[test]
2938    fn value_clause_matches_value_greater_than() {
2939        let clause = ValueClause {
2940            operator: GreaterThan,
2941            value: Value::I64(10),
2942        };
2943        assert!(clause.matches_value(&Value::I64(20)));
2944        assert!(!clause.matches_value(&Value::I64(5)));
2945    }
2946
2947    #[test]
2948    fn value_clause_matches_value_in() {
2949        let clause = ValueClause {
2950            operator: In,
2951            value: Value::Array(vec![Value::I64(1), Value::I64(2), Value::I64(3)]),
2952        };
2953        assert!(clause.matches_value(&Value::I64(2)));
2954        assert!(!clause.matches_value(&Value::I64(4)));
2955    }
2956
2957    #[test]
2958    fn value_clause_matches_value_starts_with() {
2959        let clause = ValueClause {
2960            operator: super::StartsWith,
2961            value: Value::Text("hello".to_string()),
2962        };
2963        assert!(clause.matches_value(&Value::Text("hello world".to_string())));
2964        assert!(!clause.matches_value(&Value::Text("world hello".to_string())));
2965    }
2966
2967    // ---- WhereClause::matches_value ----
2968
2969    #[test]
2970    fn where_clause_matches_value_delegates_to_eval() {
2971        let clause = WhereClause {
2972            field: "age".to_string(),
2973            operator: GreaterThanOrEquals,
2974            value: Value::I64(18),
2975        };
2976        assert!(clause.matches_value(&Value::I64(18)));
2977        assert!(clause.matches_value(&Value::I64(25)));
2978        assert!(!clause.matches_value(&Value::I64(17)));
2979    }
2980
2981    // ---- group_clauses: additional coverage ----
2982
2983    #[test]
2984    fn group_clauses_empty_input() {
2985        let clauses: Vec<WhereClause> = vec![];
2986        let (eq, range, in_c) = WhereClause::group_clauses(&clauses).expect("empty should succeed");
2987        assert!(eq.is_empty());
2988        assert!(range.is_none());
2989        assert!(in_c.is_none());
2990    }
2991
2992    #[test]
2993    fn group_clauses_single_equality() {
2994        let clauses = vec![WhereClause {
2995            field: "name".to_string(),
2996            operator: Equal,
2997            value: Value::Text("alice".to_string()),
2998        }];
2999        let (eq, range, in_c) = WhereClause::group_clauses(&clauses).unwrap();
3000        assert_eq!(eq.len(), 1);
3001        assert!(eq.contains_key("name"));
3002        assert!(range.is_none());
3003        assert!(in_c.is_none());
3004    }
3005
3006    #[test]
3007    fn group_clauses_equality_on_id_is_excluded_from_equals() {
3008        let clauses = vec![WhereClause {
3009            field: "$id".to_string(),
3010            operator: Equal,
3011            value: Value::I64(1),
3012        }];
3013        let (eq, range, in_c) = WhereClause::group_clauses(&clauses).unwrap();
3014        // $id equality is excluded from the equal_clauses map
3015        assert!(eq.is_empty());
3016        assert!(range.is_none());
3017        assert!(in_c.is_none());
3018    }
3019
3020    #[test]
3021    fn group_clauses_in_on_id_is_excluded_from_in_clause() {
3022        let clauses = vec![WhereClause {
3023            field: "$id".to_string(),
3024            operator: In,
3025            value: Value::Array(vec![Value::I64(1), Value::I64(2)]),
3026        }];
3027        let (eq, range, in_c) = WhereClause::group_clauses(&clauses).unwrap();
3028        assert!(eq.is_empty());
3029        assert!(range.is_none());
3030        assert!(in_c.is_none());
3031    }
3032
3033    #[test]
3034    fn group_clauses_single_in() {
3035        let clauses = vec![WhereClause {
3036            field: "status".to_string(),
3037            operator: In,
3038            value: Value::Array(vec![Value::I64(1), Value::I64(2)]),
3039        }];
3040        let (eq, range, in_c) = WhereClause::group_clauses(&clauses).unwrap();
3041        assert!(eq.is_empty());
3042        assert!(range.is_none());
3043        assert!(in_c.is_some());
3044        assert_eq!(in_c.unwrap().field, "status");
3045    }
3046
3047    #[test]
3048    fn group_clauses_multiple_in_returns_error() {
3049        let clauses = vec![
3050            WhereClause {
3051                field: "a".to_string(),
3052                operator: In,
3053                value: Value::Array(vec![Value::I64(1)]),
3054            },
3055            WhereClause {
3056                field: "b".to_string(),
3057                operator: In,
3058                value: Value::Array(vec![Value::I64(2)]),
3059            },
3060        ];
3061        assert!(WhereClause::group_clauses(&clauses).is_err());
3062    }
3063
3064    #[test]
3065    fn group_clauses_in_same_field_as_equality_returns_error() {
3066        let clauses = vec![
3067            WhereClause {
3068                field: "status".to_string(),
3069                operator: Equal,
3070                value: Value::I64(1),
3071            },
3072            WhereClause {
3073                field: "status".to_string(),
3074                operator: In,
3075                value: Value::Array(vec![Value::I64(2)]),
3076            },
3077        ];
3078        assert!(WhereClause::group_clauses(&clauses).is_err());
3079    }
3080
3081    #[test]
3082    fn group_clauses_duplicate_equality_same_field_returns_error() {
3083        let clauses = vec![
3084            WhereClause {
3085                field: "name".to_string(),
3086                operator: Equal,
3087                value: Value::Text("alice".to_string()),
3088            },
3089            WhereClause {
3090                field: "name".to_string(),
3091                operator: Equal,
3092                value: Value::Text("bob".to_string()),
3093            },
3094        ];
3095        assert!(WhereClause::group_clauses(&clauses).is_err());
3096    }
3097
3098    #[test]
3099    fn group_clauses_single_range_operator() {
3100        let clauses = vec![WhereClause {
3101            field: "age".to_string(),
3102            operator: GreaterThan,
3103            value: Value::I64(18),
3104        }];
3105        let (eq, range, in_c) = WhereClause::group_clauses(&clauses).unwrap();
3106        assert!(eq.is_empty());
3107        assert!(range.is_some());
3108        assert_eq!(range.unwrap().operator, GreaterThan);
3109        assert!(in_c.is_none());
3110    }
3111
3112    #[test]
3113    fn group_clauses_single_non_groupable_range_between() {
3114        let clauses = vec![WhereClause {
3115            field: "age".to_string(),
3116            operator: Between,
3117            value: Value::Array(vec![Value::Float(0.0), Value::Float(100.0)]),
3118        }];
3119        let (eq, range, in_c) = WhereClause::group_clauses(&clauses).unwrap();
3120        assert!(eq.is_empty());
3121        assert!(range.is_some());
3122        assert_eq!(range.unwrap().operator, Between);
3123        assert!(in_c.is_none());
3124    }
3125
3126    #[test]
3127    fn group_clauses_starts_with_empty_string_returns_error() {
3128        let clauses = vec![WhereClause {
3129            field: "name".to_string(),
3130            operator: super::StartsWith,
3131            value: Value::Text("".to_string()),
3132        }];
3133        assert!(WhereClause::group_clauses(&clauses).is_err());
3134    }
3135
3136    #[test]
3137    fn group_clauses_starts_with_valid_string() {
3138        let clauses = vec![WhereClause {
3139            field: "name".to_string(),
3140            operator: super::StartsWith,
3141            value: Value::Text("al".to_string()),
3142        }];
3143        let (eq, range, in_c) = WhereClause::group_clauses(&clauses).unwrap();
3144        assert!(eq.is_empty());
3145        assert!(range.is_some());
3146        assert_eq!(range.unwrap().operator, super::StartsWith);
3147        assert!(in_c.is_none());
3148    }
3149
3150    #[test]
3151    fn group_clauses_non_groupable_range_same_field_as_equality_returns_error() {
3152        let clauses = vec![
3153            WhereClause {
3154                field: "name".to_string(),
3155                operator: Equal,
3156                value: Value::Text("alice".to_string()),
3157            },
3158            WhereClause {
3159                field: "name".to_string(),
3160                operator: super::StartsWith,
3161                value: Value::Text("al".to_string()),
3162            },
3163        ];
3164        assert!(WhereClause::group_clauses(&clauses).is_err());
3165    }
3166
3167    #[test]
3168    fn group_clauses_multiple_non_groupable_ranges_returns_error() {
3169        let clauses = vec![
3170            WhereClause {
3171                field: "a".to_string(),
3172                operator: Between,
3173                value: Value::Array(vec![Value::Float(0.0), Value::Float(10.0)]),
3174            },
3175            WhereClause {
3176                field: "b".to_string(),
3177                operator: super::StartsWith,
3178                value: Value::Text("x".to_string()),
3179            },
3180        ];
3181        assert!(WhereClause::group_clauses(&clauses).is_err());
3182    }
3183
3184    #[test]
3185    fn group_clauses_mixed_groupable_and_non_groupable_returns_error() {
3186        let clauses = vec![
3187            WhereClause {
3188                field: "a".to_string(),
3189                operator: GreaterThan,
3190                value: Value::Float(0.0),
3191            },
3192            WhereClause {
3193                field: "b".to_string(),
3194                operator: Between,
3195                value: Value::Array(vec![Value::Float(0.0), Value::Float(10.0)]),
3196            },
3197        ];
3198        assert!(WhereClause::group_clauses(&clauses).is_err());
3199    }
3200
3201    #[test]
3202    fn group_clauses_three_groupable_ranges_returns_error() {
3203        let clauses = vec![
3204            WhereClause {
3205                field: "a".to_string(),
3206                operator: GreaterThan,
3207                value: Value::Float(0.0),
3208            },
3209            WhereClause {
3210                field: "a".to_string(),
3211                operator: LessThan,
3212                value: Value::Float(10.0),
3213            },
3214            WhereClause {
3215                field: "a".to_string(),
3216                operator: GreaterThanOrEquals,
3217                value: Value::Float(5.0),
3218            },
3219        ];
3220        assert!(WhereClause::group_clauses(&clauses).is_err());
3221    }
3222
3223    #[test]
3224    fn group_clauses_range_same_field_as_equality_returns_error() {
3225        let clauses = vec![
3226            WhereClause {
3227                field: "age".to_string(),
3228                operator: Equal,
3229                value: Value::I64(25),
3230            },
3231            WhereClause {
3232                field: "age".to_string(),
3233                operator: GreaterThan,
3234                value: Value::I64(18),
3235            },
3236        ];
3237        assert!(WhereClause::group_clauses(&clauses).is_err());
3238    }
3239
3240    #[test]
3241    fn group_clauses_two_ranges_combined_into_between() {
3242        let clauses = vec![
3243            WhereClause {
3244                field: "age".to_string(),
3245                operator: GreaterThanOrEquals,
3246                value: Value::Float(10.0),
3247            },
3248            WhereClause {
3249                field: "age".to_string(),
3250                operator: LessThanOrEquals,
3251                value: Value::Float(20.0),
3252            },
3253        ];
3254        let (_, range, _) = WhereClause::group_clauses(&clauses).unwrap();
3255        let r = range.unwrap();
3256        assert_eq!(r.operator, Between);
3257        assert_eq!(r.field, "age");
3258    }
3259
3260    #[test]
3261    fn group_clauses_two_ranges_combined_into_between_exclude_right() {
3262        let clauses = vec![
3263            WhereClause {
3264                field: "age".to_string(),
3265                operator: GreaterThanOrEquals,
3266                value: Value::Float(10.0),
3267            },
3268            WhereClause {
3269                field: "age".to_string(),
3270                operator: LessThan,
3271                value: Value::Float(20.0),
3272            },
3273        ];
3274        let (_, range, _) = WhereClause::group_clauses(&clauses).unwrap();
3275        assert_eq!(range.unwrap().operator, BetweenExcludeRight);
3276    }
3277
3278    #[test]
3279    fn group_clauses_two_ranges_combined_into_between_exclude_left() {
3280        let clauses = vec![
3281            WhereClause {
3282                field: "age".to_string(),
3283                operator: GreaterThan,
3284                value: Value::Float(10.0),
3285            },
3286            WhereClause {
3287                field: "age".to_string(),
3288                operator: LessThanOrEquals,
3289                value: Value::Float(20.0),
3290            },
3291        ];
3292        let (_, range, _) = WhereClause::group_clauses(&clauses).unwrap();
3293        assert_eq!(range.unwrap().operator, BetweenExcludeLeft);
3294    }
3295
3296    #[test]
3297    fn group_clauses_two_ranges_combined_into_between_exclude_bounds() {
3298        let clauses = vec![
3299            WhereClause {
3300                field: "age".to_string(),
3301                operator: GreaterThan,
3302                value: Value::Float(10.0),
3303            },
3304            WhereClause {
3305                field: "age".to_string(),
3306                operator: LessThan,
3307                value: Value::Float(20.0),
3308            },
3309        ];
3310        let (_, range, _) = WhereClause::group_clauses(&clauses).unwrap();
3311        assert_eq!(range.unwrap().operator, BetweenExcludeBounds);
3312    }
3313
3314    #[test]
3315    fn group_clauses_equality_plus_in_on_different_fields() {
3316        let clauses = vec![
3317            WhereClause {
3318                field: "name".to_string(),
3319                operator: Equal,
3320                value: Value::Text("alice".to_string()),
3321            },
3322            WhereClause {
3323                field: "status".to_string(),
3324                operator: In,
3325                value: Value::Array(vec![Value::I64(1), Value::I64(2)]),
3326            },
3327        ];
3328        let (eq, _, in_c) = WhereClause::group_clauses(&clauses).unwrap();
3329        assert_eq!(eq.len(), 1);
3330        assert!(in_c.is_some());
3331    }
3332
3333    #[test]
3334    fn group_clauses_equality_plus_range_on_different_fields() {
3335        let clauses = vec![
3336            WhereClause {
3337                field: "name".to_string(),
3338                operator: Equal,
3339                value: Value::Text("alice".to_string()),
3340            },
3341            WhereClause {
3342                field: "age".to_string(),
3343                operator: GreaterThan,
3344                value: Value::Float(18.0),
3345            },
3346        ];
3347        let (eq, range, in_c) = WhereClause::group_clauses(&clauses).unwrap();
3348        assert_eq!(eq.len(), 1);
3349        assert!(range.is_some());
3350        assert!(in_c.is_none());
3351    }
3352
3353    // ---- meta_field_property_type ----
3354
3355    #[test]
3356    fn meta_field_property_type_all_identifiers() {
3357        use super::meta_field_property_type;
3358        use dpp::data_contract::document_type::DocumentPropertyType;
3359
3360        for field in ["$id", "$ownerId", "$dataContractId", "$creatorId"] {
3361            let pt = meta_field_property_type(field);
3362            assert!(
3363                matches!(pt, Some(DocumentPropertyType::Identifier)),
3364                "expected Identifier for {field}"
3365            );
3366        }
3367    }
3368
3369    #[test]
3370    fn meta_field_property_type_dates() {
3371        use super::meta_field_property_type;
3372        use dpp::data_contract::document_type::DocumentPropertyType;
3373
3374        for field in ["$createdAt", "$updatedAt", "$transferredAt"] {
3375            let pt = meta_field_property_type(field);
3376            assert!(
3377                matches!(pt, Some(DocumentPropertyType::Date)),
3378                "expected Date for {field}"
3379            );
3380        }
3381    }
3382
3383    #[test]
3384    fn meta_field_property_type_block_heights() {
3385        use super::meta_field_property_type;
3386        use dpp::data_contract::document_type::DocumentPropertyType;
3387
3388        for field in [
3389            "$createdAtBlockHeight",
3390            "$updatedAtBlockHeight",
3391            "$transferredAtBlockHeight",
3392        ] {
3393            let pt = meta_field_property_type(field);
3394            assert!(
3395                matches!(pt, Some(DocumentPropertyType::U64)),
3396                "expected U64 for {field}"
3397            );
3398        }
3399    }
3400
3401    #[test]
3402    fn meta_field_property_type_core_block_heights() {
3403        use super::meta_field_property_type;
3404        use dpp::data_contract::document_type::DocumentPropertyType;
3405
3406        for field in [
3407            "$createdAtCoreBlockHeight",
3408            "$updatedAtCoreBlockHeight",
3409            "$transferredAtCoreBlockHeight",
3410        ] {
3411            let pt = meta_field_property_type(field);
3412            assert!(
3413                matches!(pt, Some(DocumentPropertyType::U32)),
3414                "expected U32 for {field}"
3415            );
3416        }
3417    }
3418
3419    #[test]
3420    fn meta_field_property_type_revision_and_protocol_version() {
3421        use super::meta_field_property_type;
3422        use dpp::data_contract::document_type::DocumentPropertyType;
3423
3424        assert!(matches!(
3425            meta_field_property_type("$revision"),
3426            Some(DocumentPropertyType::U64)
3427        ));
3428        assert!(matches!(
3429            meta_field_property_type("$protocolVersion"),
3430            Some(DocumentPropertyType::U64)
3431        ));
3432    }
3433
3434    #[test]
3435    fn meta_field_property_type_type_field() {
3436        use super::meta_field_property_type;
3437        use dpp::data_contract::document_type::DocumentPropertyType;
3438
3439        assert!(matches!(
3440            meta_field_property_type("$type"),
3441            Some(DocumentPropertyType::String(_))
3442        ));
3443    }
3444
3445    #[test]
3446    fn meta_field_property_type_unknown_returns_none() {
3447        use super::meta_field_property_type;
3448
3449        assert!(meta_field_property_type("unknown").is_none());
3450        assert!(meta_field_property_type("$nonexistent").is_none());
3451    }
3452
3453    // ---- allowed_ops_for_type ----
3454
3455    #[test]
3456    fn allowed_ops_for_numeric_types_include_ranges() {
3457        use super::allowed_ops_for_type;
3458        use dpp::data_contract::document_type::DocumentPropertyType;
3459
3460        for ty in [
3461            DocumentPropertyType::U8,
3462            DocumentPropertyType::I8,
3463            DocumentPropertyType::U16,
3464            DocumentPropertyType::I16,
3465            DocumentPropertyType::U32,
3466            DocumentPropertyType::I32,
3467            DocumentPropertyType::U64,
3468            DocumentPropertyType::I64,
3469            DocumentPropertyType::U128,
3470            DocumentPropertyType::I128,
3471            DocumentPropertyType::F64,
3472            DocumentPropertyType::Date,
3473        ] {
3474            let ops = allowed_ops_for_type(&ty);
3475            assert!(ops.contains(&Equal), "numeric type should allow Equal");
3476            assert!(ops.contains(&In), "numeric type should allow In");
3477            assert!(
3478                ops.contains(&GreaterThan),
3479                "numeric type should allow GreaterThan"
3480            );
3481            assert!(ops.contains(&Between), "numeric type should allow Between");
3482            assert!(
3483                !ops.contains(&super::StartsWith),
3484                "numeric type should not allow StartsWith"
3485            );
3486        }
3487    }
3488
3489    #[test]
3490    fn allowed_ops_for_string_includes_starts_with() {
3491        use super::allowed_ops_for_type;
3492        use dpp::data_contract::document_type::{DocumentPropertyType, StringPropertySizes};
3493
3494        let ty = DocumentPropertyType::String(StringPropertySizes {
3495            min_length: None,
3496            max_length: None,
3497        });
3498        let ops = allowed_ops_for_type(&ty);
3499        assert!(ops.contains(&super::StartsWith));
3500        assert!(ops.contains(&Equal));
3501        assert!(ops.contains(&In));
3502        assert!(ops.contains(&GreaterThan));
3503    }
3504
3505    #[test]
3506    fn allowed_ops_for_identifier_only_equal_and_in() {
3507        use super::allowed_ops_for_type;
3508        use dpp::data_contract::document_type::DocumentPropertyType;
3509
3510        let ops = allowed_ops_for_type(&DocumentPropertyType::Identifier);
3511        assert_eq!(ops, &[Equal, In]);
3512    }
3513
3514    #[test]
3515    fn allowed_ops_for_boolean_only_equal() {
3516        use super::allowed_ops_for_type;
3517        use dpp::data_contract::document_type::DocumentPropertyType;
3518
3519        let ops = allowed_ops_for_type(&DocumentPropertyType::Boolean);
3520        assert_eq!(ops, &[Equal]);
3521    }
3522
3523    #[test]
3524    fn allowed_ops_for_object_is_empty() {
3525        use super::allowed_ops_for_type;
3526        use dpp::data_contract::document_type::DocumentPropertyType;
3527
3528        let ops = allowed_ops_for_type(&DocumentPropertyType::Object(Default::default()));
3529        assert!(ops.is_empty());
3530    }
3531
3532    // ---- value_shape_ok ----
3533
3534    #[test]
3535    fn value_shape_ok_equal_always_true() {
3536        use super::WhereOperator;
3537        use dpp::data_contract::document_type::DocumentPropertyType;
3538
3539        // Equal accepts any value shape
3540        assert!(WhereOperator::Equal.value_shape_ok(&Value::I64(1), &DocumentPropertyType::U64));
3541        assert!(WhereOperator::Equal
3542            .value_shape_ok(&Value::Text("x".into()), &DocumentPropertyType::Boolean));
3543    }
3544
3545    #[test]
3546    fn value_shape_ok_in_requires_array_or_bytes() {
3547        use super::WhereOperator;
3548        use dpp::data_contract::document_type::DocumentPropertyType;
3549
3550        assert!(WhereOperator::In.value_shape_ok(
3551            &Value::Array(vec![Value::I64(1)]),
3552            &DocumentPropertyType::U64
3553        ));
3554        assert!(WhereOperator::In.value_shape_ok(&Value::Bytes(vec![1]), &DocumentPropertyType::U8));
3555        assert!(!WhereOperator::In.value_shape_ok(&Value::I64(1), &DocumentPropertyType::U64));
3556    }
3557
3558    #[test]
3559    fn value_shape_ok_starts_with_requires_text() {
3560        use super::WhereOperator;
3561        use dpp::data_contract::document_type::{DocumentPropertyType, StringPropertySizes};
3562
3563        let str_ty = DocumentPropertyType::String(StringPropertySizes {
3564            min_length: None,
3565            max_length: None,
3566        });
3567        assert!(WhereOperator::StartsWith.value_shape_ok(&Value::Text("abc".into()), &str_ty));
3568        assert!(!WhereOperator::StartsWith.value_shape_ok(&Value::I64(1), &str_ty));
3569    }
3570
3571    #[test]
3572    fn value_shape_ok_range_for_f64_requires_numeric() {
3573        use super::WhereOperator;
3574        use dpp::data_contract::document_type::DocumentPropertyType;
3575
3576        assert!(WhereOperator::GreaterThan
3577            .value_shape_ok(&Value::Float(1.0), &DocumentPropertyType::F64));
3578        assert!(
3579            WhereOperator::GreaterThan.value_shape_ok(&Value::I64(1), &DocumentPropertyType::F64)
3580        );
3581        assert!(!WhereOperator::GreaterThan
3582            .value_shape_ok(&Value::Text("x".into()), &DocumentPropertyType::F64));
3583    }
3584
3585    #[test]
3586    fn value_shape_ok_range_for_string_requires_text() {
3587        use super::WhereOperator;
3588        use dpp::data_contract::document_type::{DocumentPropertyType, StringPropertySizes};
3589
3590        let str_ty = DocumentPropertyType::String(StringPropertySizes {
3591            min_length: None,
3592            max_length: None,
3593        });
3594        assert!(WhereOperator::LessThan.value_shape_ok(&Value::Text("a".into()), &str_ty));
3595        assert!(!WhereOperator::LessThan.value_shape_ok(&Value::I64(1), &str_ty));
3596    }
3597
3598    #[test]
3599    fn value_shape_ok_range_for_integer_requires_integer() {
3600        use super::WhereOperator;
3601        use dpp::data_contract::document_type::DocumentPropertyType;
3602
3603        assert!(
3604            WhereOperator::GreaterThan.value_shape_ok(&Value::U64(1), &DocumentPropertyType::U64)
3605        );
3606        assert!(
3607            WhereOperator::GreaterThan.value_shape_ok(&Value::I32(1), &DocumentPropertyType::I32)
3608        );
3609        assert!(!WhereOperator::GreaterThan
3610            .value_shape_ok(&Value::Float(1.0), &DocumentPropertyType::U64));
3611        assert!(!WhereOperator::GreaterThan
3612            .value_shape_ok(&Value::Text("x".into()), &DocumentPropertyType::U64));
3613    }
3614
3615    #[test]
3616    fn value_shape_ok_between_requires_array_of_two() {
3617        use super::WhereOperator;
3618        use dpp::data_contract::document_type::DocumentPropertyType;
3619
3620        let good = Value::Array(vec![Value::I64(1), Value::I64(10)]);
3621        assert!(WhereOperator::Between.value_shape_ok(&good, &DocumentPropertyType::I64));
3622
3623        let bad_len = Value::Array(vec![Value::I64(1)]);
3624        assert!(!WhereOperator::Between.value_shape_ok(&bad_len, &DocumentPropertyType::I64));
3625
3626        let not_array = Value::I64(5);
3627        assert!(!WhereOperator::Between.value_shape_ok(&not_array, &DocumentPropertyType::I64));
3628
3629        // All between variants
3630        assert!(
3631            WhereOperator::BetweenExcludeBounds.value_shape_ok(&good, &DocumentPropertyType::I64)
3632        );
3633        assert!(WhereOperator::BetweenExcludeLeft.value_shape_ok(&good, &DocumentPropertyType::I64));
3634        assert!(
3635            WhereOperator::BetweenExcludeRight.value_shape_ok(&good, &DocumentPropertyType::I64)
3636        );
3637    }
3638
3639    // ---- validate_against_schema: additional coverage ----
3640
3641    #[test]
3642    fn validate_rejects_unknown_field() {
3643        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3644        let contract = fixture.data_contract_owned();
3645        let doc_type = contract
3646            .document_type_for_name("niceDocument")
3647            .expect("doc type exists");
3648
3649        let clause = WhereClause {
3650            field: "nonexistentField".to_string(),
3651            operator: Equal,
3652            value: Value::I64(1),
3653        };
3654        let res = clause.validate_against_schema(doc_type);
3655        assert!(res.is_err());
3656    }
3657
3658    #[test]
3659    fn validate_rejects_disallowed_operator_for_boolean() {
3660        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3661        let contract = fixture.data_contract_owned();
3662        let doc_type = contract
3663            .document_type_for_name("niceDocument")
3664            .expect("doc type exists");
3665
3666        // Boolean only allows Equal, not GreaterThan -- but we need a boolean field.
3667        // Check if we can use a meta field or if there is one in the doc type.
3668        // $type is a String, so let's validate that startsWith is allowed for $type
3669        let clause = WhereClause {
3670            field: "$type".to_string(),
3671            operator: super::StartsWith,
3672            value: Value::Text("nice".to_string()),
3673        };
3674        let res = clause.validate_against_schema(doc_type);
3675        assert!(res.is_valid());
3676    }
3677
3678    #[test]
3679    fn validate_rejects_starts_with_empty_string() {
3680        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3681        let contract = fixture.data_contract_owned();
3682        let doc_type = contract
3683            .document_type_for_name("niceDocument")
3684            .expect("doc type exists");
3685
3686        let clause = WhereClause {
3687            field: "$type".to_string(),
3688            operator: super::StartsWith,
3689            value: Value::Text("".to_string()),
3690        };
3691        let res = clause.validate_against_schema(doc_type);
3692        assert!(res.is_err());
3693        assert!(matches!(
3694            res.first_error(),
3695            Some(QuerySyntaxError::StartsWithIllegalString(_))
3696        ));
3697    }
3698
3699    #[test]
3700    fn validate_rejects_in_with_empty_array() {
3701        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3702        let contract = fixture.data_contract_owned();
3703        let doc_type = contract
3704            .document_type_for_name("niceDocument")
3705            .expect("doc type exists");
3706
3707        let clause = WhereClause {
3708            field: "$ownerId".to_string(),
3709            operator: In,
3710            value: Value::Array(vec![]),
3711        };
3712        let res = clause.validate_against_schema(doc_type);
3713        assert!(res.is_err());
3714    }
3715
3716    #[test]
3717    fn validate_rejects_in_with_duplicates() {
3718        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3719        let contract = fixture.data_contract_owned();
3720        let doc_type = contract
3721            .document_type_for_name("niceDocument")
3722            .expect("doc type exists");
3723
3724        let clause = WhereClause {
3725            field: "$ownerId".to_string(),
3726            operator: In,
3727            value: Value::Array(vec![
3728                Value::Identifier([1u8; 32]),
3729                Value::Identifier([1u8; 32]),
3730            ]),
3731        };
3732        let res = clause.validate_against_schema(doc_type);
3733        assert!(res.is_err());
3734    }
3735
3736    #[test]
3737    fn validate_rejects_between_with_descending_bounds() {
3738        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3739        let contract = fixture.data_contract_owned();
3740        let doc_type = contract
3741            .document_type_for_name("uniqueDates")
3742            .expect("doc type exists");
3743
3744        let clause = WhereClause {
3745            field: "$createdAt".to_string(),
3746            operator: Between,
3747            value: Value::Array(vec![Value::U64(2000), Value::U64(1000)]),
3748        };
3749        let res = clause.validate_against_schema(doc_type);
3750        assert!(res.is_err());
3751        assert!(matches!(
3752            res.first_error(),
3753            Some(QuerySyntaxError::InvalidBetweenClause(_))
3754        ));
3755    }
3756
3757    #[test]
3758    fn validate_rejects_range_operator_not_allowed_for_identifier() {
3759        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3760        let contract = fixture.data_contract_owned();
3761        let doc_type = contract
3762            .document_type_for_name("niceDocument")
3763            .expect("doc type exists");
3764
3765        let clause = WhereClause {
3766            field: "$ownerId".to_string(),
3767            operator: GreaterThan,
3768            value: Value::Identifier([1u8; 32]),
3769        };
3770        let res = clause.validate_against_schema(doc_type);
3771        assert!(res.is_err());
3772    }
3773
3774    #[test]
3775    fn validate_accepts_valid_integer_equality() {
3776        let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
3777        let contract = fixture.data_contract_owned();
3778        let doc_type = contract
3779            .document_type_for_name("niceDocument")
3780            .expect("doc type exists");
3781
3782        let clause = WhereClause {
3783            field: "$revision".to_string(),
3784            operator: Equal,
3785            value: Value::U64(5),
3786        };
3787        let res = clause.validate_against_schema(doc_type);
3788        assert!(res.is_valid());
3789    }
3790}