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 #[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 fn try_parse_number(s: &str) -> Option<f64> {
74 s.parse::<f64>().ok().filter(|n| n.is_finite())
76 }
77
78 fn try_coerce_numeric(left: &WhereValue, right: &WhereValue) -> Option<(f64, f64)> {
81 match (left, right) {
82 (WhereValue::Number(n1), WhereValue::Number(n2)) => Some((*n1, *n2)),
84
85 (WhereValue::String(s), WhereValue::Number(n)) => {
87 Self::try_parse_number(s).map(|parsed| (parsed, *n))
88 }
89
90 (WhereValue::Number(n), WhereValue::String(s)) => {
92 Self::try_parse_number(s).map(|parsed| (*n, parsed))
93 }
94
95 (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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
240 return Ok((n1 - n2).abs() < f64::EPSILON);
241 }
242
243 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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
272 return Ok(n1 > n2);
273 }
274
275 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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
291 return Ok(n1 >= n2);
292 }
293
294 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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
310 return Ok(n1 < n2);
311 }
312
313 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 if let Some((n1, n2)) = WhereValue::try_coerce_numeric(&left, value) {
329 return Ok(n1 <= n2);
330 }
331
332 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 if let (Some((v, l)), Some((_v2, u))) = (
348 WhereValue::try_coerce_numeric(&val, lower),
349 WhereValue::try_coerce_numeric(&val, upper),
350 ) {
351 return Ok(v >= l && v <= u);
353 }
354
355 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) }
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 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 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) }
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 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) }
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) }
464 }
465
466 WhereExpr::Contains(column, search) => {
467 if let Some(field_value) = row.get(column) {
468 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(), };
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) }
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 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 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 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 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 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 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 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 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()); let expr3 = WhereExpr::Not(Box::new(WhereExpr::IsNullOrEmpty("name".to_string())));
746 assert!(!evaluate_where_expr(&expr3, &row).unwrap());
747 }
748}