1use anyhow::Result;
2use serde_json::Value;
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum WhereExpr {
6 And(Box<WhereExpr>, Box<WhereExpr>),
8 Or(Box<WhereExpr>, Box<WhereExpr>),
9 Not(Box<WhereExpr>),
10
11 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 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 Contains(String, String),
31 StartsWith(String, String),
32 EndsWith(String, String),
33 ContainsIgnoreCase(String, String), StartsWithIgnoreCase(String, String), EndsWithIgnoreCase(String, String), ToLower(String, ComparisonOp, String), ToUpper(String, ComparisonOp, String), IsNullOrEmpty(String), 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 fn try_parse_number(s: &str) -> Option<f64> {
73 s.parse::<f64>().ok().filter(|n| n.is_finite())
75 }
76
77 fn try_coerce_numeric(left: &WhereValue, right: &WhereValue) -> Option<(f64, f64)> {
80 match (left, right) {
81 (WhereValue::Number(n1), WhereValue::Number(n2)) => Some((*n1, *n2)),
83
84 (WhereValue::String(s), WhereValue::Number(n)) => {
86 Self::try_parse_number(s).map(|parsed| (parsed, *n))
87 }
88
89 (WhereValue::Number(n), WhereValue::String(s)) => {
91 Self::try_parse_number(s).map(|parsed| (*n, parsed))
92 }
93
94 (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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
238 return Ok((n1 - n2).abs() < f64::EPSILON);
239 }
240
241 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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
270 return Ok(n1 > n2);
271 }
272
273 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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
289 return Ok(n1 >= n2);
290 }
291
292 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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
308 return Ok(n1 < n2);
309 }
310
311 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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
327 return Ok(n1 <= n2);
328 }
329
330 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 if let (Some((v, l)), Some((_v2, u))) = (
346 WhereValue::try_coerce_numeric(&val, lower),
347 WhereValue::try_coerce_numeric(&val, upper),
348 ) {
349 return Ok(v >= l && v <= u);
351 }
352
353 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) }
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 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 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) }
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 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) }
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) }
462 }
463
464 WhereExpr::Contains(column, search) => {
465 if let Some(field_value) = row.get(column) {
466 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(), };
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) }
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 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 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 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 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 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 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 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 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); let expr3 = WhereExpr::Not(Box::new(WhereExpr::IsNullOrEmpty("name".to_string())));
744 assert_eq!(evaluate_where_expr(&expr3, &row).unwrap(), false);
745 }
746}