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