sql_cli/sql/
where_ast.rs

1use anyhow::Result;
2use serde_json::Value;
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum WhereExpr {
6    // Logical operators
7    And(Box<WhereExpr>, Box<WhereExpr>),
8    Or(Box<WhereExpr>, Box<WhereExpr>),
9    Not(Box<WhereExpr>),
10
11    // Comparison operators
12    Equal(String, WhereValue),
13    NotEqual(String, WhereValue),
14    GreaterThan(String, WhereValue),
15    GreaterThanOrEqual(String, WhereValue),
16    LessThan(String, WhereValue),
17    LessThanOrEqual(String, WhereValue),
18
19    // Special operators
20    Between(String, WhereValue, WhereValue),
21    In(String, Vec<WhereValue>),
22    NotIn(String, Vec<WhereValue>),
23    InIgnoreCase(String, Vec<WhereValue>),
24    NotInIgnoreCase(String, Vec<WhereValue>),
25    Like(String, String),
26    IsNull(String),
27    IsNotNull(String),
28
29    // String methods
30    Contains(String, String),
31    StartsWith(String, String),
32    EndsWith(String, String),
33    ContainsIgnoreCase(String, String), // Case-insensitive contains
34    StartsWithIgnoreCase(String, String), // Case-insensitive starts with
35    EndsWithIgnoreCase(String, String), // Case-insensitive ends with
36    ToLower(String, ComparisonOp, String), // column.ToLower() == "value"
37    ToUpper(String, ComparisonOp, String), // column.ToUpper() == "VALUE"
38    IsNullOrEmpty(String),              // String.IsNullOrEmpty(column)
39
40    // Numeric methods
41    Length(String, ComparisonOp, i64),
42}
43
44#[derive(Debug, Clone, PartialEq)]
45pub enum ComparisonOp {
46    Equal,
47    NotEqual,
48    GreaterThan,
49    GreaterThanOrEqual,
50    LessThan,
51    LessThanOrEqual,
52}
53
54#[derive(Debug, Clone, PartialEq)]
55pub enum WhereValue {
56    String(String),
57    Number(f64),
58    Null,
59}
60
61impl WhereValue {
62    #[must_use]
63    pub fn from_json(value: &Value) -> Self {
64        match value {
65            Value::String(s) => WhereValue::String(s.clone()),
66            Value::Number(n) => WhereValue::Number(n.as_f64().unwrap_or(0.0)),
67            Value::Null => WhereValue::Null,
68            _ => WhereValue::Null,
69        }
70    }
71
72    /// Try to parse a string as a number, supporting scientific notation
73    fn try_parse_number(s: &str) -> Option<f64> {
74        // Parse the string as f64, which handles scientific notation (1e-4, 1.5E+3, etc.)
75        s.parse::<f64>().ok().filter(|n| n.is_finite())
76    }
77
78    /// Try to coerce values for numeric comparison
79    /// Returns (`left_value`, `right_value`) if coercion is possible
80    fn try_coerce_numeric(left: &WhereValue, right: &WhereValue) -> Option<(f64, f64)> {
81        match (left, right) {
82            // Both are already numbers
83            (WhereValue::Number(n1), WhereValue::Number(n2)) => Some((*n1, *n2)),
84
85            // Left is string, right is number - try to parse left
86            (WhereValue::String(s), WhereValue::Number(n)) => {
87                Self::try_parse_number(s).map(|parsed| (parsed, *n))
88            }
89
90            // Left is number, right is string - try to parse right
91            (WhereValue::Number(n), WhereValue::String(s)) => {
92                Self::try_parse_number(s).map(|parsed| (*n, parsed))
93            }
94
95            // Both are strings - try to parse both for numeric comparison
96            (WhereValue::String(s1), WhereValue::String(s2)) => {
97                match (Self::try_parse_number(s1), Self::try_parse_number(s2)) {
98                    (Some(n1), Some(n2)) => Some((n1, n2)),
99                    _ => None,
100                }
101            }
102
103            _ => None,
104        }
105    }
106}
107
108#[must_use]
109pub fn format_where_ast(expr: &WhereExpr, indent: usize) -> String {
110    let indent_str = "  ".repeat(indent);
111    match expr {
112        WhereExpr::And(left, right) => {
113            format!(
114                "{}AND\n{}\n{}",
115                indent_str,
116                format_where_ast(left, indent + 1),
117                format_where_ast(right, indent + 1)
118            )
119        }
120        WhereExpr::Or(left, right) => {
121            format!(
122                "{}OR\n{}\n{}",
123                indent_str,
124                format_where_ast(left, indent + 1),
125                format_where_ast(right, indent + 1)
126            )
127        }
128        WhereExpr::Not(inner) => {
129            format!("{}NOT\n{}", indent_str, format_where_ast(inner, indent + 1))
130        }
131        WhereExpr::Equal(col, val) => {
132            format!("{indent_str}EQUAL({col}, {val:?})")
133        }
134        WhereExpr::NotEqual(col, val) => {
135            format!("{indent_str}NOT_EQUAL({col}, {val:?})")
136        }
137        WhereExpr::GreaterThan(col, val) => {
138            format!("{indent_str}GREATER_THAN({col}, {val:?})")
139        }
140        WhereExpr::GreaterThanOrEqual(col, val) => {
141            format!("{indent_str}GREATER_THAN_OR_EQUAL({col}, {val:?})")
142        }
143        WhereExpr::LessThan(col, val) => {
144            format!("{indent_str}LESS_THAN({col}, {val:?})")
145        }
146        WhereExpr::LessThanOrEqual(col, val) => {
147            format!("{indent_str}LESS_THAN_OR_EQUAL({col}, {val:?})")
148        }
149        WhereExpr::Between(col, lower, upper) => {
150            format!("{indent_str}BETWEEN({col}, {lower:?}, {upper:?})")
151        }
152        WhereExpr::In(col, values) => {
153            format!("{indent_str}IN({col}, {values:?})")
154        }
155        WhereExpr::NotIn(col, values) => {
156            format!("{indent_str}NOT_IN({col}, {values:?})")
157        }
158        WhereExpr::InIgnoreCase(col, values) => {
159            format!("{indent_str}IN_IGNORE_CASE({col}, {values:?})")
160        }
161        WhereExpr::NotInIgnoreCase(col, values) => {
162            format!("{indent_str}NOT_IN_IGNORE_CASE({col}, {values:?})")
163        }
164        WhereExpr::Like(col, pattern) => {
165            format!("{indent_str}LIKE({col}, \"{pattern}\")")
166        }
167        WhereExpr::IsNull(col) => {
168            format!("{indent_str}IS_NULL({col})")
169        }
170        WhereExpr::IsNotNull(col) => {
171            format!("{indent_str}IS_NOT_NULL({col})")
172        }
173        WhereExpr::Contains(col, search) => {
174            format!("{indent_str}CONTAINS({col}, \"{search}\")")
175        }
176        WhereExpr::StartsWith(col, prefix) => {
177            format!("{indent_str}STARTS_WITH({col}, \"{prefix}\")")
178        }
179        WhereExpr::EndsWith(col, suffix) => {
180            format!("{indent_str}ENDS_WITH({col}, \"{suffix}\")")
181        }
182        WhereExpr::ContainsIgnoreCase(col, search) => {
183            format!("{indent_str}CONTAINS_IGNORE_CASE({col}, \"{search}\")")
184        }
185        WhereExpr::StartsWithIgnoreCase(col, prefix) => {
186            format!("{indent_str}STARTS_WITH_IGNORE_CASE({col}, \"{prefix}\")")
187        }
188        WhereExpr::EndsWithIgnoreCase(col, suffix) => {
189            format!("{indent_str}ENDS_WITH_IGNORE_CASE({col}, \"{suffix}\")")
190        }
191        WhereExpr::ToLower(col, op, value) => {
192            format!("{indent_str}TO_LOWER({col}, {op:?}, \"{value}\")")
193        }
194        WhereExpr::ToUpper(col, op, value) => {
195            format!("{indent_str}TO_UPPER({col}, {op:?}, \"{value}\")")
196        }
197        WhereExpr::Length(col, op, value) => {
198            format!("{indent_str}LENGTH({col}, {op:?}, {value})")
199        }
200        WhereExpr::IsNullOrEmpty(col) => {
201            format!("{indent_str}IS_NULL_OR_EMPTY({col})")
202        }
203    }
204}
205
206pub fn evaluate_where_expr(expr: &WhereExpr, row: &Value) -> Result<bool> {
207    evaluate_where_expr_with_options(expr, row, false)
208}
209
210pub fn evaluate_where_expr_with_options(
211    expr: &WhereExpr,
212    row: &Value,
213    case_insensitive: bool,
214) -> Result<bool> {
215    match expr {
216        WhereExpr::And(left, right) => {
217            Ok(
218                evaluate_where_expr_with_options(left, row, case_insensitive)?
219                    && evaluate_where_expr_with_options(right, row, case_insensitive)?,
220            )
221        }
222        WhereExpr::Or(left, right) => {
223            Ok(
224                evaluate_where_expr_with_options(left, row, case_insensitive)?
225                    || evaluate_where_expr_with_options(right, row, case_insensitive)?,
226            )
227        }
228        WhereExpr::Not(inner) => Ok(!evaluate_where_expr_with_options(
229            inner,
230            row,
231            case_insensitive,
232        )?),
233
234        WhereExpr::Equal(column, value) => {
235            if let Some(field_value) = row.get(column) {
236                let left = WhereValue::from_json(field_value);
237
238                // Try numeric comparison first for potential numeric values
239                if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
240                    return Ok((n1 - n2).abs() < f64::EPSILON);
241                }
242
243                // Fall back to string matching (with optional case insensitivity)
244                match (&left, value) {
245                    (WhereValue::String(s1), WhereValue::String(s2)) => {
246                        if case_insensitive {
247                            Ok(s1.to_lowercase() == s2.to_lowercase())
248                        } else {
249                            Ok(s1 == s2)
250                        }
251                    }
252                    (WhereValue::Null, WhereValue::Null) => Ok(true),
253                    _ => Ok(false),
254                }
255            } else {
256                Ok(matches!(value, WhereValue::Null))
257            }
258        }
259
260        WhereExpr::NotEqual(column, value) => Ok(!evaluate_where_expr_with_options(
261            &WhereExpr::Equal(column.clone(), value.clone()),
262            row,
263            case_insensitive,
264        )?),
265
266        WhereExpr::GreaterThan(column, value) => {
267            if let Some(field_value) = row.get(column) {
268                let left = WhereValue::from_json(field_value);
269
270                // Try numeric comparison first
271                if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
272                    return Ok(n1 > n2);
273                }
274
275                // Fall back to string comparison
276                match (&left, value) {
277                    (WhereValue::String(s1), WhereValue::String(s2)) => Ok(s1 > s2),
278                    _ => Ok(false),
279                }
280            } else {
281                Ok(false)
282            }
283        }
284
285        WhereExpr::GreaterThanOrEqual(column, value) => {
286            if let Some(field_value) = row.get(column) {
287                let left = WhereValue::from_json(field_value);
288
289                // Try numeric comparison first
290                if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
291                    return Ok(n1 >= n2);
292                }
293
294                // Fall back to string comparison
295                match (&left, value) {
296                    (WhereValue::String(s1), WhereValue::String(s2)) => Ok(s1 >= s2),
297                    _ => Ok(false),
298                }
299            } else {
300                Ok(false)
301            }
302        }
303
304        WhereExpr::LessThan(column, value) => {
305            if let Some(field_value) = row.get(column) {
306                let left = WhereValue::from_json(field_value);
307
308                // Try numeric comparison first
309                if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
310                    return Ok(n1 < n2);
311                }
312
313                // Fall back to string comparison
314                match (&left, value) {
315                    (WhereValue::String(s1), WhereValue::String(s2)) => Ok(s1 < s2),
316                    _ => Ok(false),
317                }
318            } else {
319                Ok(false)
320            }
321        }
322
323        WhereExpr::LessThanOrEqual(column, value) => {
324            if let Some(field_value) = row.get(column) {
325                let left = WhereValue::from_json(field_value);
326
327                // Try numeric comparison first
328                if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
329                    return Ok(n1 <= n2);
330                }
331
332                // Fall back to string comparison
333                match (&left, value) {
334                    (WhereValue::String(s1), WhereValue::String(s2)) => Ok(s1 <= s2),
335                    _ => Ok(false),
336                }
337            } else {
338                Ok(false)
339            }
340        }
341
342        WhereExpr::Between(column, lower, upper) => {
343            if let Some(field_value) = row.get(column) {
344                let val = WhereValue::from_json(field_value);
345
346                // Try numeric comparison first
347                if let (Some((v, l)), Some((_v2, u))) = (
348                    WhereValue::try_coerce_numeric(&val, lower),
349                    WhereValue::try_coerce_numeric(&val, upper),
350                ) {
351                    // Both coercions succeeded, use the value from first coercion
352                    return Ok(v >= l && v <= u);
353                }
354
355                // Fall back to string comparison
356                match (&val, lower, upper) {
357                    (WhereValue::String(s), WhereValue::String(l), WhereValue::String(u)) => {
358                        Ok(s >= l && s <= u)
359                    }
360                    _ => Ok(false),
361                }
362            } else {
363                Ok(false)
364            }
365        }
366
367        WhereExpr::In(column, values) => {
368            if let Some(field_value) = row.get(column) {
369                let val = WhereValue::from_json(field_value);
370                Ok(values.contains(&val))
371            } else {
372                Ok(false)
373            }
374        }
375
376        WhereExpr::NotIn(column, values) => {
377            if let Some(field_value) = row.get(column) {
378                let val = WhereValue::from_json(field_value);
379                Ok(!values.contains(&val))
380            } else {
381                Ok(true) // NULL is not in any list
382            }
383        }
384
385        WhereExpr::InIgnoreCase(column, values) => {
386            if let Some(field_value) = row.get(column) {
387                let val = WhereValue::from_json(field_value);
388                // For case-insensitive comparison, convert to lowercase
389                if let WhereValue::String(ref field_str) = val {
390                    let field_lower = field_str.to_lowercase();
391                    Ok(values.iter().any(|v| {
392                        if let WhereValue::String(s) = v {
393                            s.to_lowercase() == field_lower
394                        } else {
395                            v == &val
396                        }
397                    }))
398                } else {
399                    Ok(values.contains(&val))
400                }
401            } else {
402                Ok(false)
403            }
404        }
405
406        WhereExpr::NotInIgnoreCase(column, values) => {
407            if let Some(field_value) = row.get(column) {
408                let val = WhereValue::from_json(field_value);
409                // For case-insensitive comparison, convert to lowercase
410                if let WhereValue::String(ref field_str) = val {
411                    let field_lower = field_str.to_lowercase();
412                    Ok(!values.iter().any(|v| {
413                        if let WhereValue::String(s) = v {
414                            s.to_lowercase() == field_lower
415                        } else {
416                            v == &val
417                        }
418                    }))
419                } else {
420                    Ok(!values.contains(&val))
421                }
422            } else {
423                Ok(true) // NULL is not in any list
424            }
425        }
426
427        WhereExpr::Like(column, pattern) => {
428            if let Some(field_value) = row.get(column) {
429                let str_value = match field_value {
430                    Value::String(s) => s.clone(),
431                    Value::Number(n) => n.to_string(),
432                    Value::Bool(b) => b.to_string(),
433                    Value::Null => return Ok(false),
434                    _ => field_value.to_string(),
435                };
436
437                // Simple LIKE implementation: % = any chars, _ = single char
438                let regex_pattern = pattern.replace('%', ".*").replace('_', ".");
439
440                if let Ok(regex) = regex::Regex::new(&format!("^{regex_pattern}$")) {
441                    Ok(regex.is_match(&str_value))
442                } else {
443                    Ok(false)
444                }
445            } else {
446                Ok(false)
447            }
448        }
449
450        WhereExpr::IsNull(column) => {
451            if let Some(field_value) = row.get(column) {
452                Ok(field_value.is_null())
453            } else {
454                Ok(true) // Missing field is considered NULL
455            }
456        }
457
458        WhereExpr::IsNotNull(column) => {
459            if let Some(field_value) = row.get(column) {
460                Ok(!field_value.is_null())
461            } else {
462                Ok(false) // Missing field is considered NULL
463            }
464        }
465
466        WhereExpr::Contains(column, search) => {
467            if let Some(field_value) = row.get(column) {
468                // Try as string first, then coerce other types to string
469                let str_value = match field_value {
470                    Value::String(s) => s.clone(),
471                    Value::Number(n) => n.to_string(),
472                    Value::Bool(b) => b.to_string(),
473                    Value::Null => return Ok(false),
474                    _ => field_value.to_string(), // For arrays/objects, use JSON representation
475                };
476                Ok(str_value.contains(search))
477            } else {
478                Ok(false)
479            }
480        }
481
482        WhereExpr::StartsWith(column, prefix) => {
483            if let Some(field_value) = row.get(column) {
484                let str_value = match field_value {
485                    Value::String(s) => s.clone(),
486                    Value::Number(n) => n.to_string(),
487                    Value::Bool(b) => b.to_string(),
488                    Value::Null => return Ok(false),
489                    _ => field_value.to_string(),
490                };
491                Ok(str_value.starts_with(prefix))
492            } else {
493                Ok(false)
494            }
495        }
496
497        WhereExpr::EndsWith(column, suffix) => {
498            if let Some(field_value) = row.get(column) {
499                let str_value = match field_value {
500                    Value::String(s) => s.clone(),
501                    Value::Number(n) => n.to_string(),
502                    Value::Bool(b) => b.to_string(),
503                    Value::Null => return Ok(false),
504                    _ => field_value.to_string(),
505                };
506                Ok(str_value.ends_with(suffix))
507            } else {
508                Ok(false)
509            }
510        }
511
512        WhereExpr::ContainsIgnoreCase(column, search) => {
513            if let Some(field_value) = row.get(column) {
514                let str_value = match field_value {
515                    Value::String(s) => s.clone(),
516                    Value::Number(n) => n.to_string(),
517                    Value::Bool(b) => b.to_string(),
518                    Value::Null => return Ok(false),
519                    _ => field_value.to_string(),
520                };
521                Ok(str_value.to_lowercase().contains(&search.to_lowercase()))
522            } else {
523                Ok(false)
524            }
525        }
526
527        WhereExpr::StartsWithIgnoreCase(column, prefix) => {
528            if let Some(field_value) = row.get(column) {
529                let str_value = match field_value {
530                    Value::String(s) => s.clone(),
531                    Value::Number(n) => n.to_string(),
532                    Value::Bool(b) => b.to_string(),
533                    Value::Null => return Ok(false),
534                    _ => field_value.to_string(),
535                };
536                Ok(str_value.to_lowercase().starts_with(&prefix.to_lowercase()))
537            } else {
538                Ok(false)
539            }
540        }
541
542        WhereExpr::EndsWithIgnoreCase(column, suffix) => {
543            if let Some(field_value) = row.get(column) {
544                let str_value = match field_value {
545                    Value::String(s) => s.clone(),
546                    Value::Number(n) => n.to_string(),
547                    Value::Bool(b) => b.to_string(),
548                    Value::Null => return Ok(false),
549                    _ => field_value.to_string(),
550                };
551                Ok(str_value.to_lowercase().ends_with(&suffix.to_lowercase()))
552            } else {
553                Ok(false)
554            }
555        }
556
557        WhereExpr::ToLower(column, op, value) => {
558            if let Some(field_value) = row.get(column) {
559                if let Some(s) = field_value.as_str() {
560                    let lower_s = s.to_lowercase();
561                    Ok(match op {
562                        ComparisonOp::Equal => lower_s == *value,
563                        ComparisonOp::NotEqual => lower_s != *value,
564                        ComparisonOp::GreaterThan => lower_s > *value,
565                        ComparisonOp::GreaterThanOrEqual => lower_s >= *value,
566                        ComparisonOp::LessThan => lower_s < *value,
567                        ComparisonOp::LessThanOrEqual => lower_s <= *value,
568                    })
569                } else {
570                    Ok(false)
571                }
572            } else {
573                Ok(false)
574            }
575        }
576
577        WhereExpr::ToUpper(column, op, value) => {
578            if let Some(field_value) = row.get(column) {
579                if let Some(s) = field_value.as_str() {
580                    let upper_s = s.to_uppercase();
581                    Ok(match op {
582                        ComparisonOp::Equal => upper_s == *value,
583                        ComparisonOp::NotEqual => upper_s != *value,
584                        ComparisonOp::GreaterThan => upper_s > *value,
585                        ComparisonOp::GreaterThanOrEqual => upper_s >= *value,
586                        ComparisonOp::LessThan => upper_s < *value,
587                        ComparisonOp::LessThanOrEqual => upper_s <= *value,
588                    })
589                } else {
590                    Ok(false)
591                }
592            } else {
593                Ok(false)
594            }
595        }
596
597        WhereExpr::Length(column, op, value) => {
598            if let Some(field_value) = row.get(column) {
599                if let Some(s) = field_value.as_str() {
600                    let len = s.len() as i64;
601                    Ok(match op {
602                        ComparisonOp::Equal => len == *value,
603                        ComparisonOp::NotEqual => len != *value,
604                        ComparisonOp::GreaterThan => len > *value,
605                        ComparisonOp::GreaterThanOrEqual => len >= *value,
606                        ComparisonOp::LessThan => len < *value,
607                        ComparisonOp::LessThanOrEqual => len <= *value,
608                    })
609                } else {
610                    Ok(false)
611                }
612            } else {
613                Ok(false)
614            }
615        }
616
617        WhereExpr::IsNullOrEmpty(column) => {
618            if let Some(field_value) = row.get(column) {
619                if field_value.is_null() {
620                    Ok(true)
621                } else if let Some(s) = field_value.as_str() {
622                    Ok(s.is_empty())
623                } else {
624                    Ok(false)
625                }
626            } else {
627                Ok(true) // Missing field is considered null/empty
628            }
629        }
630    }
631}
632
633#[cfg(test)]
634mod tests {
635    use super::*;
636    use serde_json::json;
637
638    #[test]
639    fn test_is_null_or_empty_with_null() {
640        let row = json!({
641            "name": null,
642            "age": 25
643        });
644
645        // Test that null values return true
646        let expr = WhereExpr::IsNullOrEmpty("name".to_string());
647        assert!(evaluate_where_expr(&expr, &row).unwrap());
648    }
649
650    #[test]
651    fn test_is_null_or_empty_with_empty_string() {
652        let row = json!({
653            "name": "",
654            "age": 25
655        });
656
657        // Test that empty strings return true
658        let expr = WhereExpr::IsNullOrEmpty("name".to_string());
659        assert!(evaluate_where_expr(&expr, &row).unwrap());
660    }
661
662    #[test]
663    fn test_is_null_or_empty_with_non_empty_string() {
664        let row = json!({
665            "name": "John",
666            "age": 25
667        });
668
669        // Test that non-empty strings return false
670        let expr = WhereExpr::IsNullOrEmpty("name".to_string());
671        assert!(!evaluate_where_expr(&expr, &row).unwrap());
672    }
673
674    #[test]
675    fn test_is_null_or_empty_with_missing_field() {
676        let row = json!({
677            "age": 25
678        });
679
680        // Test that missing fields are considered null/empty
681        let expr = WhereExpr::IsNullOrEmpty("name".to_string());
682        assert!(evaluate_where_expr(&expr, &row).unwrap());
683    }
684
685    #[test]
686    fn test_is_null_or_empty_with_whitespace() {
687        let row = json!({
688            "name": "   ",
689            "description": " \t\n "
690        });
691
692        // Test that whitespace-only strings are NOT considered empty
693        // (following standard IsNullOrEmpty behavior)
694        let expr = WhereExpr::IsNullOrEmpty("name".to_string());
695        assert!(!evaluate_where_expr(&expr, &row).unwrap());
696
697        let expr2 = WhereExpr::IsNullOrEmpty("description".to_string());
698        assert!(!evaluate_where_expr(&expr2, &row).unwrap());
699    }
700
701    #[test]
702    fn test_is_null_or_empty_with_number_field() {
703        let row = json!({
704            "count": 0,
705            "price": 100.5
706        });
707
708        // Test that numeric fields return false (not strings)
709        let expr = WhereExpr::IsNullOrEmpty("count".to_string());
710        assert!(!evaluate_where_expr(&expr, &row).unwrap());
711
712        let expr2 = WhereExpr::IsNullOrEmpty("price".to_string());
713        assert!(!evaluate_where_expr(&expr2, &row).unwrap());
714    }
715
716    #[test]
717    fn test_is_null_or_empty_in_complex_expression() {
718        let row = json!({
719            "name": "",
720            "age": 25,
721            "city": "New York"
722        });
723
724        // Test: name.IsNullOrEmpty() AND age > 20
725        let expr = WhereExpr::And(
726            Box::new(WhereExpr::IsNullOrEmpty("name".to_string())),
727            Box::new(WhereExpr::GreaterThan(
728                "age".to_string(),
729                WhereValue::Number(20.0),
730            )),
731        );
732        assert!(evaluate_where_expr(&expr, &row).unwrap());
733
734        // Test: name.IsNullOrEmpty() OR city = "Boston"
735        let expr2 = WhereExpr::Or(
736            Box::new(WhereExpr::IsNullOrEmpty("name".to_string())),
737            Box::new(WhereExpr::Equal(
738                "city".to_string(),
739                WhereValue::String("Boston".to_string()),
740            )),
741        );
742        assert!(evaluate_where_expr(&expr2, &row).unwrap()); // true because name is empty
743
744        // Test: NOT name.IsNullOrEmpty()
745        let expr3 = WhereExpr::Not(Box::new(WhereExpr::IsNullOrEmpty("name".to_string())));
746        assert!(!evaluate_where_expr(&expr3, &row).unwrap());
747    }
748}