1use anyhow::{Result, anyhow};
10use std::cmp::Ordering;
11use uni_common::{TemporalValue, Value};
12
13use crate::query::datetime::{
14 CypherDuration, TemporalType, add_cypher_duration_to_date, add_cypher_duration_to_datetime,
15 add_cypher_duration_to_localdatetime, add_cypher_duration_to_localtime,
16 add_cypher_duration_to_time, classify_temporal, eval_datetime_function, is_duration_value,
17 parse_datetime_utc, parse_duration_from_value, parse_duration_to_cypher,
18};
19use crate::query::spatial::eval_spatial_function;
20use uni_cypher::ast::BinaryOp;
21
22pub fn eval_binary_op(left: &Value, op: &BinaryOp, right: &Value) -> Result<Value> {
27 if !matches!(op, BinaryOp::And | BinaryOp::Or) && (left.is_null() || right.is_null()) {
29 return Ok(Value::Null);
30 }
31
32 match op {
33 BinaryOp::Eq => Ok(match cypher_eq(left, right) {
34 Some(b) => Value::Bool(b),
35 None => Value::Null,
36 }),
37 BinaryOp::NotEq => Ok(match cypher_eq(left, right) {
38 Some(b) => Value::Bool(!b),
39 None => Value::Null,
40 }),
41 BinaryOp::And => {
42 match (left.as_bool(), right.as_bool()) {
44 (Some(false), _) | (_, Some(false)) => Ok(Value::Bool(false)),
45 (Some(true), Some(true)) => Ok(Value::Bool(true)),
46 _ if left.is_null() || right.is_null() => Ok(Value::Null),
47 _ => Err(anyhow!(
48 "InvalidArgumentType: Expected bool for AND operands"
49 )),
50 }
51 }
52 BinaryOp::Or => {
53 match (left.as_bool(), right.as_bool()) {
55 (Some(true), _) | (_, Some(true)) => Ok(Value::Bool(true)),
56 (Some(false), Some(false)) => Ok(Value::Bool(false)),
57 _ if left.is_null() || right.is_null() => Ok(Value::Null),
58 _ => Err(anyhow!(
59 "InvalidArgumentType: Expected bool for OR operands"
60 )),
61 }
62 }
63 BinaryOp::Xor => {
64 match (left.as_bool(), right.as_bool()) {
66 (Some(l), Some(r)) => Ok(Value::Bool(l ^ r)),
67 _ if left.is_null() || right.is_null() => Ok(Value::Null),
68 _ => Err(anyhow!(
69 "InvalidArgumentType: Expected bool for XOR operands"
70 )),
71 }
72 }
73 BinaryOp::Gt => eval_comparison(left, right, |ordering| ordering.is_gt()),
74 BinaryOp::Lt => eval_comparison(left, right, |ordering| ordering.is_lt()),
75 BinaryOp::GtEq => eval_comparison(left, right, |ordering| ordering.is_ge()),
76 BinaryOp::LtEq => eval_comparison(left, right, |ordering| ordering.is_le()),
77 BinaryOp::Contains => eval_string_predicate(left, right, "CONTAINS", |l, r| l.contains(r)),
78 BinaryOp::StartsWith => {
79 eval_string_predicate(left, right, "STARTS WITH", |l, r| l.starts_with(r))
80 }
81 BinaryOp::EndsWith => {
82 eval_string_predicate(left, right, "ENDS WITH", |l, r| l.ends_with(r))
83 }
84 BinaryOp::Add => eval_add(left, right),
85 BinaryOp::Sub => eval_sub(left, right),
86 BinaryOp::Mul => eval_mul(left, right),
87 BinaryOp::Div => eval_div(left, right),
88 BinaryOp::Mod => eval_numeric_op(left, right, |a, b| a % b),
89 BinaryOp::Pow => eval_numeric_op(left, right, |a, b| a.powf(b)),
90 BinaryOp::Regex => {
91 let l = left
92 .as_str()
93 .ok_or_else(|| anyhow!("Left operand of =~ must be a string"))?;
94 let pattern = right
95 .as_str()
96 .ok_or_else(|| anyhow!("Right operand of =~ must be a regex pattern string"))?;
97 let re = regex::Regex::new(pattern)
98 .map_err(|e| anyhow!("Invalid regex pattern '{}': {}", pattern, e))?;
99 Ok(Value::Bool(re.is_match(l)))
100 }
101 BinaryOp::ApproxEq => eval_vector_similarity(left, right),
102 }
103}
104
105pub fn cypher_eq(left: &Value, right: &Value) -> Option<bool> {
108 if left.is_null() || right.is_null() {
109 return None;
110 }
111
112 if let (Some(l), Some(r)) = (left.as_i64(), right.as_i64()) {
114 return Some(l == r);
115 }
116
117 if let (Some(l), Some(r)) = (left.as_f64(), right.as_f64()) {
119 if l.is_nan() || r.is_nan() {
120 return Some(false);
121 }
122 return Some(l == r);
123 }
124
125 if let (Value::List(l), Value::List(r)) = (left, right) {
127 if l.len() != r.len() {
128 return Some(false);
129 }
130 let mut has_null = false;
131 for (lv, rv) in l.iter().zip(r.iter()) {
132 match cypher_eq(lv, rv) {
133 Some(false) => return Some(false),
134 None => has_null = true,
135 Some(true) => {}
136 }
137 }
138 return if has_null { None } else { Some(true) };
139 }
140
141 if let (Value::Map(l), Value::Map(r)) = (left, right) {
143 if let (Some(vid_l), Some(vid_r)) = (l.get("_vid"), r.get("_vid")) {
145 return Some(vid_l == vid_r);
146 }
147 if let (Some(eid_l), Some(eid_r)) = (l.get("_eid"), r.get("_eid")) {
149 return Some(eid_l == eid_r);
150 }
151
152 if l.len() != r.len() {
153 return Some(false);
154 }
155
156 let mut has_null = false;
157 for (k, lv) in l {
158 if let Some(rv) = r.get(k) {
159 match cypher_eq(lv, rv) {
160 Some(false) => return Some(false),
161 None => has_null = true,
162 Some(true) => {}
163 }
164 } else {
165 return Some(false);
166 }
167 }
168 return if has_null { None } else { Some(true) };
169 }
170
171 Some(left == right)
173}
174
175pub fn eval_in_op(left: &Value, right: &Value) -> Result<Value> {
177 if let Value::List(arr) = right {
178 let mut has_null = false;
179 for item in arr {
181 match cypher_eq(left, item) {
182 Some(true) => return Ok(Value::Bool(true)),
183 None => has_null = true,
184 _ => {}
185 }
186 }
187
188 if let Value::Map(map) = left
192 && let Some(vid_val) = map.get("_vid")
193 && let Some(vid_u64) = vid_val.as_u64()
194 {
195 let vid = uni_common::core::id::Vid::from(vid_u64);
196 let vid_str = vid.to_string();
197 for item in arr {
198 match item {
199 Value::String(s) if s == &vid_str => return Ok(Value::Bool(true)),
200 Value::Int(n) if *n as u64 == vid_u64 => return Ok(Value::Bool(true)),
201 _ => {}
202 }
203 }
204 }
205
206 if has_null {
207 Ok(Value::Null)
208 } else {
209 Ok(Value::Bool(false))
210 }
211 } else {
212 Err(anyhow!("Right side of IN must be a list"))
213 }
214}
215
216fn eval_string_predicate(
217 left: &Value,
218 right: &Value,
219 op_name: &str,
220 check: fn(&str, &str) -> bool,
221) -> Result<Value> {
222 let l = left
223 .as_str()
224 .ok_or_else(|| anyhow!("Left side of {} must be a string", op_name))?;
225 let r = right
226 .as_str()
227 .ok_or_else(|| anyhow!("Right side of {} must be a string", op_name))?;
228 Ok(Value::Bool(check(l, r)))
229}
230
231fn eval_numeric_op<F>(left: &Value, right: &Value, op: F) -> Result<Value>
232where
233 F: Fn(f64, f64) -> f64,
234{
235 if left.is_null() || right.is_null() {
237 return Ok(Value::Null);
238 }
239 let (l, r) = match (left.as_f64(), right.as_f64()) {
240 (Some(l), Some(r)) => (l, r),
241 _ => return Err(anyhow!("Arithmetic operation requires numbers")),
242 };
243 let result = op(l, r);
244 if !result.is_nan()
246 && !result.is_infinite()
247 && result.fract() == 0.0
248 && left.is_i64()
249 && right.is_i64()
250 {
251 Ok(Value::Int(result as i64))
252 } else {
253 Ok(Value::Float(result))
254 }
255}
256
257fn add_temporal_duration_to_value(val: &Value, dur: &CypherDuration) -> Result<Value> {
264 match val {
265 Value::Temporal(tv) => add_temporal_duration_typed(tv, dur),
266 Value::Map(map) => {
267 if let Some(tv) = temporal_from_map_wrapper(map) {
268 add_temporal_duration_typed(&tv, dur)
269 } else {
270 Err(anyhow!("Expected temporal value for duration arithmetic"))
271 }
272 }
273 Value::String(s) => {
274 if let Some(tv) = temporal_from_json_wrapper_str(s) {
275 return add_temporal_duration_typed(&tv, dur);
276 }
277 let ttype = classify_temporal(s)
278 .ok_or_else(|| anyhow!("Cannot classify temporal value: {}", s))?;
279 let result_str = match ttype {
280 TemporalType::Date => add_cypher_duration_to_date(s, dur)?,
281 TemporalType::LocalTime => add_cypher_duration_to_localtime(s, dur)?,
282 TemporalType::Time => add_cypher_duration_to_time(s, dur)?,
283 TemporalType::LocalDateTime => add_cypher_duration_to_localdatetime(s, dur)?,
284 TemporalType::DateTime => add_cypher_duration_to_datetime(s, dur)?,
285 TemporalType::Duration => {
286 return Err(anyhow!("Cannot add duration to Duration this way"));
287 }
288 TemporalType::Btic => {
289 return Err(anyhow!(
290 "TypeError: Cannot add duration to BTIC interval. \
291 BTIC represents a time interval, not a point. \
292 Use btic_span() to combine intervals or construct a new btic() literal."
293 ));
294 }
295 };
296 Ok(Value::String(result_str))
297 }
298 _ => Err(anyhow!("Expected temporal value for duration arithmetic")),
299 }
300}
301
302fn add_temporal_duration_typed(tv: &TemporalValue, dur: &CypherDuration) -> Result<Value> {
304 let s = tv.to_string();
307 let ttype = tv.temporal_type();
308 let result_str = match ttype {
309 TemporalType::Date => add_cypher_duration_to_date(&s, dur)?,
310 TemporalType::LocalTime => add_cypher_duration_to_localtime(&s, dur)?,
311 TemporalType::Time => add_cypher_duration_to_time(&s, dur)?,
312 TemporalType::LocalDateTime => add_cypher_duration_to_localdatetime(&s, dur)?,
313 TemporalType::DateTime => add_cypher_duration_to_datetime(&s, dur)?,
314 TemporalType::Duration => {
315 return Err(anyhow!("Cannot add duration to Duration this way"));
316 }
317 TemporalType::Btic => {
318 return Err(anyhow!(
319 "TypeError: Cannot add duration to BTIC interval. \
320 BTIC represents a time interval, not a point. \
321 Use btic_span() to combine intervals or construct a new btic() literal."
322 ));
323 }
324 };
325 let args = [Value::String(result_str)];
327 match ttype {
328 TemporalType::Date => eval_datetime_function("DATE", &args),
329 TemporalType::LocalTime => eval_datetime_function("LOCALTIME", &args),
330 TemporalType::Time => eval_datetime_function("TIME", &args),
331 TemporalType::LocalDateTime => eval_datetime_function("LOCALDATETIME", &args),
332 TemporalType::DateTime => eval_datetime_function("DATETIME", &args),
333 TemporalType::Duration | TemporalType::Btic => unreachable!(),
334 }
335}
336
337fn eval_add(left: &Value, right: &Value) -> Result<Value> {
339 if left.is_null() || right.is_null() {
341 return Ok(Value::Null);
342 }
343
344 match (left, right) {
346 (Value::List(l), Value::List(r)) => {
347 let mut result = l.clone();
348 result.extend(r.iter().cloned());
349 return Ok(Value::List(result));
350 }
351 (Value::List(l), _) => {
352 let mut result = l.clone();
353 result.push(right.clone());
354 return Ok(Value::List(result));
355 }
356 (_, Value::List(r)) => {
357 let mut result = vec![left.clone()];
358 result.extend(r.iter().cloned());
359 return Ok(Value::List(result));
360 }
361 _ => {}
362 }
363
364 if let (Some(l), Some(r)) = (left.as_f64(), right.as_f64()) {
366 if left.is_i64() && right.is_i64() {
367 return Ok(Value::Int(left.as_i64().unwrap() + right.as_i64().unwrap()));
368 }
369 return Ok(Value::Float(l + r));
370 }
371
372 if let Value::String(s) = left
374 && classify_temporal(s).is_some_and(|t| t != TemporalType::Duration)
375 && let Ok(dur) = parse_duration_from_value(right)
376 {
377 return add_temporal_duration_to_value(left, &dur);
378 }
379 if let Value::String(s) = right
380 && classify_temporal(s).is_some_and(|t| t != TemporalType::Duration)
381 && let Ok(dur) = parse_duration_from_value(left)
382 {
383 return add_temporal_duration_to_value(right, &dur);
384 }
385
386 if let Some(tv) = temporal_from_value(left)
388 && !matches!(tv, TemporalValue::Duration { .. })
389 && (is_duration_value(right) || right.is_number())
390 {
391 let dur = parse_duration_from_value(right)?;
392 return add_temporal_duration_typed(&tv, &dur);
393 }
394 if let Some(tv) = temporal_from_value(right)
396 && !matches!(tv, TemporalValue::Duration { .. })
397 && (is_duration_value(left) || left.is_number())
398 {
399 let dur = parse_duration_from_value(left)?;
400 return add_temporal_duration_typed(&tv, &dur);
401 }
402 if let (
404 Some(TemporalValue::Duration {
405 months: m1,
406 days: d1,
407 nanos: n1,
408 }),
409 Some(TemporalValue::Duration {
410 months: m2,
411 days: d2,
412 nanos: n2,
413 }),
414 ) = (temporal_from_value(left), temporal_from_value(right))
415 {
416 return Ok(Value::Temporal(TemporalValue::Duration {
417 months: m1 + m2,
418 days: d1 + d2,
419 nanos: n1 + n2,
420 }));
421 }
422
423 if let (Value::String(l), Value::String(r)) = (left, right) {
425 let l_type = classify_temporal(l);
426 let r_type = classify_temporal(r);
427
428 match (l_type, r_type) {
429 (Some(lt), Some(TemporalType::Duration)) if lt != TemporalType::Duration => {
431 let dur = parse_duration_to_cypher(r)?;
432 return add_temporal_duration_to_value(left, &dur);
433 }
434 (Some(TemporalType::Duration), Some(rt)) if rt != TemporalType::Duration => {
436 let dur = parse_duration_to_cypher(l)?;
437 return add_temporal_duration_to_value(right, &dur);
438 }
439 (Some(TemporalType::Duration), Some(TemporalType::Duration)) => {
441 let d1 = parse_duration_to_cypher(l)?;
442 let d2 = parse_duration_to_cypher(r)?;
443 return Ok(Value::String(d1.add(&d2).to_iso8601()));
444 }
445 _ => return Ok(Value::String(format!("{}{}", l, r))),
447 }
448 }
449
450 if let Value::String(_) = left
452 && right.is_number()
453 && classify_value_temporal(left).is_some_and(|t| t != TemporalType::Duration)
454 {
455 let dur = parse_duration_from_value(right)?;
456 return add_temporal_duration_to_value(left, &dur);
457 }
458 if let Value::String(_) = right
460 && left.is_number()
461 && classify_value_temporal(right).is_some_and(|t| t != TemporalType::Duration)
462 {
463 let dur = parse_duration_from_value(left)?;
464 return add_temporal_duration_to_value(right, &dur);
465 }
466
467 Err(anyhow!(
468 "Invalid types for addition: left={:?}, right={:?}",
469 left,
470 right
471 ))
472}
473
474fn classify_value_temporal(val: &Value) -> Option<TemporalType> {
476 match val {
477 Value::Temporal(tv) => Some(tv.temporal_type()),
478 Value::String(s) => classify_temporal(s),
479 _ => None,
480 }
481}
482
483fn eval_sub(left: &Value, right: &Value) -> Result<Value> {
485 if left.is_null() || right.is_null() {
487 return Ok(Value::Null);
488 }
489
490 if let Value::Temporal(tv) = left
492 && !matches!(tv, TemporalValue::Duration { .. })
493 {
494 if let Value::Temporal(TemporalValue::Duration {
495 months,
496 days,
497 nanos,
498 }) = right
499 {
500 let dur = CypherDuration::new(-months, -days, -nanos);
501 return add_temporal_duration_typed(tv, &dur);
502 }
503 if is_duration_value(right) || right.is_number() {
504 let dur = parse_duration_from_value(right)?.negate();
505 return add_temporal_duration_typed(tv, &dur);
506 }
507 }
508 if let (
510 Value::Temporal(TemporalValue::Duration {
511 months: m1,
512 days: d1,
513 nanos: n1,
514 }),
515 Value::Temporal(TemporalValue::Duration {
516 months: m2,
517 days: d2,
518 nanos: n2,
519 }),
520 ) = (left, right)
521 {
522 return Ok(Value::Temporal(TemporalValue::Duration {
523 months: m1 - m2,
524 days: d1 - d2,
525 nanos: n1 - n2,
526 }));
527 }
528 if let (Value::Temporal(l), Value::Temporal(r)) = (left, right)
530 && l.temporal_type() == r.temporal_type()
531 && l.temporal_type() != TemporalType::Duration
532 {
533 let args = [left.clone(), right.clone()];
534 return crate::query::datetime::eval_datetime_function("DURATION.BETWEEN", &args);
535 }
536
537 if let (Value::String(l), Value::String(r)) = (left, right) {
539 let l_type = classify_temporal(l);
540 let r_type = classify_temporal(r);
541
542 match (l_type, r_type) {
543 (Some(lt), Some(TemporalType::Duration)) if lt != TemporalType::Duration => {
544 let dur = parse_duration_to_cypher(r)?.negate();
545 return add_temporal_duration_to_value(left, &dur);
546 }
547 (Some(TemporalType::Duration), Some(TemporalType::Duration)) => {
548 let d1 = parse_duration_to_cypher(l)?;
549 let d2 = parse_duration_to_cypher(r)?;
550 return Ok(Value::String(d1.sub(&d2).to_iso8601()));
551 }
552 (Some(lt), Some(rt))
553 if lt != TemporalType::Duration && rt != TemporalType::Duration && lt == rt =>
554 {
555 let args = [left.clone(), right.clone()];
556 return crate::query::datetime::eval_datetime_function("DURATION.BETWEEN", &args);
557 }
558 _ => {}
559 }
560 }
561
562 if let Value::String(_) = left
564 && right.is_number()
565 && classify_value_temporal(left).is_some_and(|t| t != TemporalType::Duration)
566 {
567 let dur = parse_duration_from_value(right)?.negate();
568 return add_temporal_duration_to_value(left, &dur);
569 }
570
571 eval_numeric_op(left, right, |a, b| a - b)
572}
573
574fn extract_cypher_duration(val: &Value) -> Option<Result<(CypherDuration, bool)>> {
578 match val {
579 Value::Temporal(TemporalValue::Duration {
580 months,
581 days,
582 nanos,
583 }) => Some(Ok((CypherDuration::new(*months, *days, *nanos), true))),
584 Value::String(s) if is_duration_value(val) => {
585 Some(parse_duration_to_cypher(s).map(|d| (d, false)))
586 }
587 _ => None,
588 }
589}
590
591fn duration_to_value(result: CypherDuration, is_temporal: bool) -> Value {
596 if is_temporal {
597 result.to_temporal_value()
598 } else {
599 Value::String(result.to_iso8601())
600 }
601}
602
603fn eval_mul(left: &Value, right: &Value) -> Result<Value> {
605 if left.is_null() || right.is_null() {
606 return Ok(Value::Null);
607 }
608
609 if let Some(dur_result) = extract_cypher_duration(left)
611 && let Some(factor) = right.as_f64()
612 {
613 let (dur, is_temporal) = dur_result?;
614 return Ok(duration_to_value(dur.multiply(factor), is_temporal));
615 }
616 if let Some(dur_result) = extract_cypher_duration(right)
617 && let Some(factor) = left.as_f64()
618 {
619 let (dur, is_temporal) = dur_result?;
620 return Ok(duration_to_value(dur.multiply(factor), is_temporal));
621 }
622
623 eval_numeric_op(left, right, |a, b| a * b)
624}
625
626fn eval_div(left: &Value, right: &Value) -> Result<Value> {
628 if left.is_null() || right.is_null() {
629 return Ok(Value::Null);
630 }
631
632 if let Some(dur_result) = extract_cypher_duration(left)
634 && let Some(divisor) = right.as_f64()
635 {
636 let (dur, is_temporal) = dur_result?;
637 return Ok(duration_to_value(dur.divide(divisor), is_temporal));
638 }
639
640 if let (Value::Int(l), Value::Int(r)) = (left, right) {
642 return if *r == 0 {
643 Err(anyhow!("Division by zero"))
644 } else {
645 Ok(Value::Int(l / r))
646 };
647 }
648
649 eval_numeric_op(left, right, |a, b| a / b)
650}
651
652fn eval_comparison<F>(left: &Value, right: &Value, check: F) -> Result<Value>
658where
659 F: Fn(Ordering) -> bool,
660{
661 if left.is_null() || right.is_null() {
663 return Ok(Value::Null);
664 }
665
666 let left_nan = left.as_f64().is_some_and(|f| f.is_nan());
668 let right_nan = right.as_f64().is_some_and(|f| f.is_nan());
669 if left_nan || right_nan {
670 if left_nan && right_nan {
671 return Ok(Value::Bool(false));
672 }
673 let other = if left_nan { right } else { left };
674 if other.as_f64().is_some() {
675 return Ok(Value::Bool(false)); }
677 return Ok(Value::Null); }
679
680 let ord = cypher_partial_cmp(left, right);
681 match ord {
682 Some(o) => Ok(Value::Bool(check(o))),
683 None => Ok(Value::Null),
684 }
685}
686
687fn cypher_partial_cmp(left: &Value, right: &Value) -> Option<Ordering> {
689 if left.is_null() || right.is_null() {
690 return None;
691 }
692
693 let left_temporal = temporal_from_value(left);
694 let right_temporal = temporal_from_value(right);
695 if let (Some(l), Some(r)) = (&left_temporal, &right_temporal) {
696 return temporal_partial_cmp(l, r);
697 }
698 if let (Some(_), Value::String(rs)) = (&left_temporal, right) {
699 let ls = left.to_string();
700 if let (Some(lt), Some(rt)) = (classify_temporal(&ls), classify_temporal(rs))
701 && lt == rt
702 {
703 return temporal_string_cmp(&ls, rs, lt);
704 }
705 return None;
706 }
707 if let (Value::String(ls), Some(_)) = (left, &right_temporal) {
708 let rs = right.to_string();
709 if let (Some(lt), Some(rt)) = (classify_temporal(ls), classify_temporal(&rs))
710 && lt == rt
711 {
712 return temporal_string_cmp(ls, &rs, lt);
713 }
714 return None;
715 }
716
717 if let (Some(l), Some(r)) = (left.as_i64(), right.as_i64()) {
719 return Some(l.cmp(&r));
720 }
721
722 if let (Some(l), Some(r)) = (left.as_f64(), right.as_f64()) {
724 return l.partial_cmp(&r);
725 }
726
727 if let (Some(l), Some(r)) = (left.as_str(), right.as_str()) {
729 if let (Some(lt), Some(rt)) = (classify_temporal(l), classify_temporal(r))
731 && lt == rt
732 {
733 let res = temporal_string_cmp(l, r, lt);
734 if res.is_some() {
735 return res;
736 }
737 }
738 return l.partial_cmp(r);
739 }
740
741 if let (Some(l), Some(r)) = (left.as_bool(), right.as_bool()) {
743 return l.partial_cmp(&r);
744 }
745
746 if let (Value::List(l), Value::List(r)) = (left, right) {
748 for (lv, rv) in l.iter().zip(r.iter()) {
749 match cypher_partial_cmp(lv, rv) {
750 Some(Ordering::Equal) => continue,
751 other => return other,
752 }
753 }
754 return l.len().partial_cmp(&r.len());
755 }
756
757 None
759}
760
761fn temporal_partial_cmp(left: &TemporalValue, right: &TemporalValue) -> Option<Ordering> {
763 match (left, right) {
764 (
765 TemporalValue::Date {
766 days_since_epoch: l,
767 },
768 TemporalValue::Date {
769 days_since_epoch: r,
770 },
771 ) => Some(l.cmp(r)),
772 (
773 TemporalValue::LocalTime {
774 nanos_since_midnight: l,
775 },
776 TemporalValue::LocalTime {
777 nanos_since_midnight: r,
778 },
779 ) => Some(l.cmp(r)),
780 (
781 TemporalValue::Time {
782 nanos_since_midnight: lm,
783 offset_seconds: lo,
784 },
785 TemporalValue::Time {
786 nanos_since_midnight: rm,
787 offset_seconds: ro,
788 },
789 ) => {
790 let l_utc = *lm as i128 - (*lo as i128) * 1_000_000_000;
792 let r_utc = *rm as i128 - (*ro as i128) * 1_000_000_000;
793 Some(l_utc.cmp(&r_utc))
794 }
795 (
796 TemporalValue::LocalDateTime {
797 nanos_since_epoch: l,
798 },
799 TemporalValue::LocalDateTime {
800 nanos_since_epoch: r,
801 },
802 ) => Some(l.cmp(r)),
803 (
804 TemporalValue::DateTime {
805 nanos_since_epoch: l,
806 ..
807 },
808 TemporalValue::DateTime {
809 nanos_since_epoch: r,
810 ..
811 },
812 ) => {
813 Some(l.cmp(r))
815 }
816 (
818 TemporalValue::Btic {
819 lo: l_lo,
820 hi: l_hi,
821 meta: l_meta,
822 },
823 TemporalValue::Btic {
824 lo: r_lo,
825 hi: r_hi,
826 meta: r_meta,
827 },
828 ) => match (
829 uni_btic::Btic::new(*l_lo, *l_hi, *l_meta),
830 uni_btic::Btic::new(*r_lo, *r_hi, *r_meta),
831 ) {
832 (Ok(l), Ok(r)) => Some(l.cmp(&r)),
833 _ => None,
834 },
835 (TemporalValue::Duration { .. }, TemporalValue::Duration { .. }) => None,
837 _ => None,
839 }
840}
841
842pub(crate) fn temporal_from_value(v: &Value) -> Option<TemporalValue> {
849 match v {
850 Value::Temporal(tv) => Some(tv.clone()),
851 Value::Map(map) => temporal_from_map_wrapper(map),
852 Value::String(s) => {
853 temporal_from_json_wrapper_str(s).or_else(|| temporal_from_human_readable_str(s))
854 }
855 _ => None,
856 }
857}
858
859pub(crate) fn temporal_from_human_readable_str(s: &str) -> Option<TemporalValue> {
862 let fn_name = match classify_temporal(s)? {
863 TemporalType::Date => "DATE",
864 TemporalType::LocalTime => "LOCALTIME",
865 TemporalType::Time => "TIME",
866 TemporalType::LocalDateTime => "LOCALDATETIME",
867 TemporalType::DateTime => "DATETIME",
868 TemporalType::Duration => "DURATION",
869 TemporalType::Btic => return None, };
871 match eval_datetime_function(fn_name, &[Value::String(s.to_string())]).ok()? {
872 Value::Temporal(tv) => Some(tv),
873 _ => None,
874 }
875}
876
877pub(crate) fn temporal_from_map_wrapper(
883 map: &std::collections::HashMap<String, Value>,
884) -> Option<TemporalValue> {
885 if map.len() != 1 {
886 return None;
887 }
888
889 let as_i32 = |v: &Value| v.as_i64().and_then(|n| i32::try_from(n).ok());
890 let as_i64 = |v: &Value| v.as_i64();
891
892 if let Some(Value::Map(inner)) = map.get("Date") {
893 let days = inner.get("days_since_epoch").and_then(as_i32)?;
894 return Some(TemporalValue::Date {
895 days_since_epoch: days,
896 });
897 }
898 if let Some(Value::Map(inner)) = map.get("LocalTime") {
899 let nanos = inner.get("nanos_since_midnight").and_then(as_i64)?;
900 return Some(TemporalValue::LocalTime {
901 nanos_since_midnight: nanos,
902 });
903 }
904 if let Some(Value::Map(inner)) = map.get("Time") {
905 let nanos = inner.get("nanos_since_midnight").and_then(as_i64)?;
906 let offset = inner.get("offset_seconds").and_then(as_i32)?;
907 return Some(TemporalValue::Time {
908 nanos_since_midnight: nanos,
909 offset_seconds: offset,
910 });
911 }
912 if let Some(Value::Map(inner)) = map.get("LocalDateTime") {
913 let nanos = inner.get("nanos_since_epoch").and_then(as_i64)?;
914 return Some(TemporalValue::LocalDateTime {
915 nanos_since_epoch: nanos,
916 });
917 }
918 if let Some(Value::Map(inner)) = map.get("DateTime") {
919 let nanos = inner.get("nanos_since_epoch").and_then(as_i64)?;
920 let offset = inner.get("offset_seconds").and_then(as_i32)?;
921 let timezone_name = match inner.get("timezone_name") {
922 Some(Value::String(s)) => Some(s.clone()),
923 _ => None,
924 };
925 return Some(TemporalValue::DateTime {
926 nanos_since_epoch: nanos,
927 offset_seconds: offset,
928 timezone_name,
929 });
930 }
931 if let Some(Value::Map(inner)) = map.get("Duration") {
932 let months = inner.get("months").and_then(as_i64)?;
933 let days = inner.get("days").and_then(as_i64)?;
934 let nanos = inner.get("nanos").and_then(as_i64)?;
935 return Some(TemporalValue::Duration {
936 months,
937 days,
938 nanos,
939 });
940 }
941 None
942}
943
944fn temporal_from_json_wrapper_str(s: &str) -> Option<TemporalValue> {
945 let parsed: serde_json::Value = serde_json::from_str(s).ok()?;
946 let obj = parsed.as_object()?;
947 if obj.len() != 1 {
948 return None;
949 }
950
951 let as_i32 = |o: &serde_json::Map<String, serde_json::Value>, key: &str| {
952 o.get(key)
953 .and_then(serde_json::Value::as_i64)
954 .and_then(|n| i32::try_from(n).ok())
955 };
956 let as_i64 = |o: &serde_json::Map<String, serde_json::Value>, key: &str| {
957 o.get(key).and_then(serde_json::Value::as_i64)
958 };
959
960 if let Some(inner) = obj.get("Date").and_then(serde_json::Value::as_object) {
961 return Some(TemporalValue::Date {
962 days_since_epoch: as_i32(inner, "days_since_epoch")?,
963 });
964 }
965 if let Some(inner) = obj.get("LocalTime").and_then(serde_json::Value::as_object) {
966 return Some(TemporalValue::LocalTime {
967 nanos_since_midnight: as_i64(inner, "nanos_since_midnight")?,
968 });
969 }
970 if let Some(inner) = obj.get("Time").and_then(serde_json::Value::as_object) {
971 return Some(TemporalValue::Time {
972 nanos_since_midnight: as_i64(inner, "nanos_since_midnight")?,
973 offset_seconds: as_i32(inner, "offset_seconds")?,
974 });
975 }
976 if let Some(inner) = obj
977 .get("LocalDateTime")
978 .and_then(serde_json::Value::as_object)
979 {
980 return Some(TemporalValue::LocalDateTime {
981 nanos_since_epoch: as_i64(inner, "nanos_since_epoch")?,
982 });
983 }
984 if let Some(inner) = obj.get("DateTime").and_then(serde_json::Value::as_object) {
985 return Some(TemporalValue::DateTime {
986 nanos_since_epoch: as_i64(inner, "nanos_since_epoch")?,
987 offset_seconds: as_i32(inner, "offset_seconds")?,
988 timezone_name: inner
989 .get("timezone_name")
990 .and_then(serde_json::Value::as_str)
991 .map(str::to_string),
992 });
993 }
994 if let Some(inner) = obj.get("Duration").and_then(serde_json::Value::as_object) {
995 return Some(TemporalValue::Duration {
996 months: as_i64(inner, "months")?,
997 days: as_i64(inner, "days")?,
998 nanos: as_i64(inner, "nanos")?,
999 });
1000 }
1001 None
1002}
1003
1004fn temporal_string_cmp(l: &str, r: &str, ttype: TemporalType) -> Option<Ordering> {
1006 match ttype {
1007 TemporalType::Date => {
1008 let ld = chrono::NaiveDate::parse_from_str(l, "%Y-%m-%d").ok();
1009 let rd = chrono::NaiveDate::parse_from_str(r, "%Y-%m-%d").ok();
1010 ld.and_then(|l| rd.map(|r| l.cmp(&r)))
1011 }
1012 TemporalType::LocalTime => {
1013 let lt = parse_time_for_cmp(l).ok();
1014 let rt = parse_time_for_cmp(r).ok();
1015 lt.and_then(|l| rt.map(|r| l.cmp(&r)))
1016 }
1017 TemporalType::Time => {
1018 let ln = time_with_tz_to_utc_nanos(l).ok();
1019 let rn = time_with_tz_to_utc_nanos(r).ok();
1020 ln.and_then(|l| rn.map(|r| l.cmp(&r)))
1021 }
1022 TemporalType::LocalDateTime => {
1023 let ldt = parse_local_datetime_for_cmp(l).ok();
1024 let rdt = parse_local_datetime_for_cmp(r).ok();
1025 ldt.and_then(|l| rdt.map(|r| l.cmp(&r)))
1026 }
1027 TemporalType::DateTime => {
1028 let ldt = parse_datetime_utc(l).ok();
1029 let rdt = parse_datetime_utc(r).ok();
1030 ldt.and_then(|l| rdt.map(|r| l.cmp(&r)))
1031 }
1032 TemporalType::Duration => None, TemporalType::Btic => None, }
1035}
1036
1037fn parse_time_for_cmp(s: &str) -> Result<chrono::NaiveTime> {
1039 chrono::NaiveTime::parse_from_str(s, "%H:%M:%S%.f")
1040 .or_else(|_| chrono::NaiveTime::parse_from_str(s, "%H:%M:%S"))
1041 .or_else(|_| chrono::NaiveTime::parse_from_str(s, "%H:%M"))
1042 .map_err(|_| anyhow!("Cannot parse time: {}", s))
1043}
1044
1045fn parse_local_datetime_for_cmp(s: &str) -> Result<chrono::NaiveDateTime> {
1047 chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f")
1048 .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S"))
1049 .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M"))
1050 .map_err(|_| anyhow!("Cannot parse localdatetime: {}", s))
1051}
1052
1053const NANOS_PER_SECOND_CMP: i64 = 1_000_000_000;
1054
1055fn time_with_tz_to_utc_nanos(s: &str) -> Result<i64> {
1057 use chrono::Timelike;
1058 let (_, time, tz_info) = crate::query::datetime::parse_datetime_with_tz(s)?;
1059 let local_nanos = time.hour() as i64 * 3_600 * NANOS_PER_SECOND_CMP
1060 + time.minute() as i64 * 60 * NANOS_PER_SECOND_CMP
1061 + time.second() as i64 * NANOS_PER_SECOND_CMP
1062 + time.nanosecond() as i64;
1063
1064 let offset_secs: i64 = match tz_info {
1066 Some(ref tz) => {
1067 let today = chrono::NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
1068 let ndt = chrono::NaiveDateTime::new(today, time);
1069 tz.offset_for_local(&ndt)?.local_minus_utc() as i64
1070 }
1071 None => 0,
1072 };
1073
1074 Ok(local_nanos - offset_secs * NANOS_PER_SECOND_CMP)
1075}
1076
1077fn eval_size(arg: &Value) -> Result<Value> {
1082 match arg {
1083 Value::List(arr) => Ok(Value::Int(arr.len() as i64)),
1084 Value::Map(map) => Ok(Value::Int(map.len() as i64)),
1085 Value::String(s) => Ok(Value::Int(s.len() as i64)),
1086 Value::Null => Ok(Value::Null),
1087 _ => Err(anyhow!("size() expects a List, Map, or String")),
1088 }
1089}
1090
1091fn eval_keys(arg: &Value) -> Result<Value> {
1092 match arg {
1093 Value::Map(map) => {
1094 let is_entity =
1098 map.contains_key("_vid") || map.contains_key("_eid") || map.contains_key("_labels");
1099 let mut keys: Vec<&String> = map
1100 .iter()
1101 .filter(|(k, v)| !k.starts_with('_') && (!is_entity || !v.is_null()))
1102 .map(|(k, _)| k)
1103 .collect();
1104 keys.sort();
1105 Ok(Value::List(
1106 keys.into_iter().map(|k| Value::String(k.clone())).collect(),
1107 ))
1108 }
1109 Value::Null => Ok(Value::Null),
1110 _ => Err(anyhow!("keys() expects a Map")),
1111 }
1112}
1113
1114fn eval_head(arg: &Value) -> Result<Value> {
1115 match arg {
1116 Value::List(arr) => Ok(arr.first().cloned().unwrap_or(Value::Null)),
1117 Value::Null => Ok(Value::Null),
1118 _ => Err(anyhow!("head() expects a List")),
1119 }
1120}
1121
1122fn eval_tail(arg: &Value) -> Result<Value> {
1123 match arg {
1124 Value::List(arr) => Ok(Value::List(arr.get(1..).unwrap_or_default().to_vec())),
1125 Value::Null => Ok(Value::Null),
1126 _ => Err(anyhow!("tail() expects a List")),
1127 }
1128}
1129
1130fn eval_last(arg: &Value) -> Result<Value> {
1131 match arg {
1132 Value::List(arr) => Ok(arr.last().cloned().unwrap_or(Value::Null)),
1133 Value::Null => Ok(Value::Null),
1134 _ => Err(anyhow!("last() expects a List")),
1135 }
1136}
1137
1138fn eval_length(arg: &Value) -> Result<Value> {
1139 match arg {
1140 Value::List(arr) => Ok(Value::Int(arr.len() as i64)),
1141 Value::String(s) => Ok(Value::Int(s.len() as i64)),
1142 Value::Path(p) => Ok(Value::Int(p.edges.len() as i64)),
1143 Value::Map(map) => {
1144 if map.contains_key("nodes")
1146 && map.contains_key("relationships")
1147 && let Some(Value::List(rels)) = map.get("relationships")
1148 {
1149 return Ok(Value::Int(rels.len() as i64));
1150 }
1151 Ok(Value::Null)
1152 }
1153 Value::Null => Ok(Value::Null),
1154 _ => Err(anyhow!("length() expects a List, String, or Path")),
1155 }
1156}
1157
1158fn eval_nodes(arg: &Value) -> Result<Value> {
1159 match arg {
1160 Value::Path(p) => Ok(Value::List(
1161 p.nodes.iter().map(|n| Value::Node(n.clone())).collect(),
1162 )),
1163 Value::Map(map) => {
1164 if let Some(nodes) = map.get("nodes") {
1165 Ok(nodes.clone())
1166 } else {
1167 Ok(Value::Null)
1168 }
1169 }
1170 Value::Null => Ok(Value::Null),
1171 _ => Err(anyhow!("nodes() expects a Path")),
1172 }
1173}
1174
1175fn eval_relationships(arg: &Value) -> Result<Value> {
1176 match arg {
1177 Value::Path(p) => Ok(Value::List(
1178 p.edges.iter().map(|e| Value::Edge(e.clone())).collect(),
1179 )),
1180 Value::Map(map) => {
1181 if let Some(rels) = map.get("relationships") {
1182 Ok(rels.clone())
1183 } else {
1184 Ok(Value::Null)
1185 }
1186 }
1187 Value::Null => Ok(Value::Null),
1188 _ => Err(anyhow!("relationships() expects a Path")),
1189 }
1190}
1191
1192fn eval_list_function(name: &str, args: &[Value]) -> Result<Value> {
1194 if args.len() != 1 {
1195 return Err(anyhow!("{}() requires 1 argument", name));
1196 }
1197 match name {
1198 "SIZE" => eval_size(&args[0]),
1199 "KEYS" => eval_keys(&args[0]),
1200 "HEAD" => eval_head(&args[0]),
1201 "TAIL" => eval_tail(&args[0]),
1202 "LAST" => eval_last(&args[0]),
1203 "LENGTH" => eval_length(&args[0]),
1204 "NODES" => eval_nodes(&args[0]),
1205 "RELATIONSHIPS" => eval_relationships(&args[0]),
1206 _ => Err(anyhow!("Unknown list function: {}", name)),
1207 }
1208}
1209
1210fn eval_tointeger(arg: &Value) -> Result<Value> {
1215 match arg {
1216 Value::Int(i) => Ok(Value::Int(*i)),
1217 Value::Float(f) => Ok(Value::Int(*f as i64)),
1218 Value::String(s) => Ok(s.parse::<i64>().map(Value::Int).unwrap_or(Value::Null)),
1219 Value::Null => Ok(Value::Null),
1220 _ => Err(anyhow!(
1221 "InvalidArgumentValue: toInteger() cannot convert type"
1222 )),
1223 }
1224}
1225
1226fn eval_tofloat(arg: &Value) -> Result<Value> {
1227 match arg {
1228 Value::Int(i) => Ok(Value::Float(*i as f64)),
1229 Value::Float(f) => Ok(Value::Float(*f)),
1230 Value::String(s) => Ok(s.parse::<f64>().map(Value::Float).unwrap_or(Value::Null)),
1231 Value::Null => Ok(Value::Null),
1232 _ => Err(anyhow!(
1233 "InvalidArgumentValue: toFloat() cannot convert type"
1234 )),
1235 }
1236}
1237
1238fn eval_tostring(arg: &Value) -> Result<Value> {
1239 match arg {
1240 Value::String(s) => Ok(Value::String(s.clone())),
1241 Value::Int(i) => Ok(Value::String(i.to_string())),
1242 Value::Float(f) => {
1243 if f.fract() == 0.0 && f.is_finite() {
1245 Ok(Value::String(format!("{f:.1}")))
1246 } else {
1247 Ok(Value::String(f.to_string()))
1248 }
1249 }
1250 Value::Bool(b) => Ok(Value::String(b.to_string())),
1251 Value::Null => Ok(Value::Null),
1252 other => Ok(Value::String(other.to_string())),
1253 }
1254}
1255
1256fn eval_toboolean(arg: &Value) -> Result<Value> {
1257 match arg {
1258 Value::Bool(b) => Ok(Value::Bool(*b)),
1259 Value::String(s) => {
1260 let lower = s.to_lowercase();
1261 if lower == "true" {
1262 Ok(Value::Bool(true))
1263 } else if lower == "false" {
1264 Ok(Value::Bool(false))
1265 } else {
1266 Ok(Value::Null)
1267 }
1268 }
1269 Value::Null => Ok(Value::Null),
1270 _ => Err(anyhow!(
1271 "InvalidArgumentValue: toBoolean() cannot convert type"
1272 )),
1273 }
1274}
1275
1276fn eval_type_function(name: &str, args: &[Value]) -> Result<Value> {
1278 if args.len() != 1 {
1279 return Err(anyhow!("{}() requires 1 argument", name));
1280 }
1281 match name {
1282 "TOINTEGER" | "TOINT" => eval_tointeger(&args[0]),
1283 "TOFLOAT" => eval_tofloat(&args[0]),
1284 "TOSTRING" => eval_tostring(&args[0]),
1285 "TOBOOLEAN" | "TOBOOL" => eval_toboolean(&args[0]),
1286 _ => Err(anyhow!("Unknown type function: {}", name)),
1287 }
1288}
1289
1290fn eval_abs(arg: &Value) -> Result<Value> {
1295 match arg {
1296 Value::Int(i) => Ok(Value::Int(i.abs())),
1297 Value::Float(f) => Ok(Value::Float(f.abs())),
1298 Value::Null => Ok(Value::Null),
1299 _ => Err(anyhow!("abs() expects a number")),
1300 }
1301}
1302
1303fn eval_sqrt(arg: &Value) -> Result<Value> {
1304 match arg {
1305 v if v.is_number() => {
1306 let f = v.as_f64().unwrap();
1307 if f < 0.0 {
1308 Ok(Value::Null)
1309 } else {
1310 Ok(Value::Float(f.sqrt()))
1311 }
1312 }
1313 Value::Null => Ok(Value::Null),
1314 _ => Err(anyhow!("sqrt() expects a number")),
1315 }
1316}
1317
1318fn eval_sign(arg: &Value) -> Result<Value> {
1319 match arg {
1320 Value::Int(i) => Ok(Value::Int(i.signum())),
1321 Value::Float(f) => Ok(Value::Int(f.signum() as i64)),
1322 Value::Null => Ok(Value::Null),
1323 _ => Err(anyhow!("sign() expects a number")),
1324 }
1325}
1326
1327fn eval_power(args: &[Value]) -> Result<Value> {
1328 if args.len() != 2 {
1329 return Err(anyhow!("power() requires 2 arguments"));
1330 }
1331 match (&args[0], &args[1]) {
1332 (a, b) if a.is_number() && b.is_number() => {
1333 let base = a.as_f64().unwrap();
1334 let exp = b.as_f64().unwrap();
1335 Ok(Value::Float(base.powf(exp)))
1336 }
1337 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
1338 _ => Err(anyhow!("power() expects numeric arguments")),
1339 }
1340}
1341
1342fn eval_unary_numeric_op<F>(arg: &Value, func_name: &str, op: F) -> Result<Value>
1344where
1345 F: Fn(f64) -> f64,
1346{
1347 match arg {
1348 Value::Int(i) => Ok(Value::Float(op(*i as f64))),
1349 Value::Float(f) => Ok(Value::Float(op(*f))),
1350 Value::Null => Ok(Value::Null),
1351 _ => Err(anyhow!("{}() expects a number", func_name)),
1352 }
1353}
1354
1355fn eval_atan2(args: &[Value]) -> Result<Value> {
1356 if args.len() != 2 {
1357 return Err(anyhow!("atan2() requires 2 arguments"));
1358 }
1359 match (&args[0], &args[1]) {
1360 (a, b) if a.is_number() && b.is_number() => {
1361 let y_val = a.as_f64().unwrap();
1362 let x_val = b.as_f64().unwrap();
1363 Ok(Value::Float(y_val.atan2(x_val)))
1364 }
1365 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
1366 _ => Err(anyhow!("atan2() expects numeric arguments")),
1367 }
1368}
1369
1370fn require_one_arg<'a>(name: &str, args: &'a [Value]) -> Result<&'a Value> {
1372 if args.len() != 1 {
1373 return Err(anyhow!("{} requires 1 argument", name));
1374 }
1375 Ok(&args[0])
1376}
1377
1378fn eval_math_function(name: &str, args: &[Value]) -> Result<Value> {
1383 match name {
1384 "ABS" => eval_abs(require_one_arg(name, args)?),
1386 "CEIL" => eval_unary_numeric_op(require_one_arg(name, args)?, "ceil", f64::ceil),
1387 "FLOOR" => eval_unary_numeric_op(require_one_arg(name, args)?, "floor", f64::floor),
1388 "ROUND" => eval_unary_numeric_op(require_one_arg(name, args)?, "round", f64::round),
1389 "SQRT" => eval_sqrt(require_one_arg(name, args)?),
1390 "SIGN" => eval_sign(require_one_arg(name, args)?),
1391 "LOG" => eval_unary_numeric_op(require_one_arg(name, args)?, "log", f64::ln),
1392 "LOG10" => eval_unary_numeric_op(require_one_arg(name, args)?, "log10", f64::log10),
1393 "EXP" => eval_unary_numeric_op(require_one_arg(name, args)?, "exp", f64::exp),
1394 "SIN" => eval_unary_numeric_op(require_one_arg(name, args)?, "sin", f64::sin),
1395 "COS" => eval_unary_numeric_op(require_one_arg(name, args)?, "cos", f64::cos),
1396 "TAN" => eval_unary_numeric_op(require_one_arg(name, args)?, "tan", f64::tan),
1397 "ASIN" => eval_unary_numeric_op(require_one_arg(name, args)?, "asin", f64::asin),
1398 "ACOS" => eval_unary_numeric_op(require_one_arg(name, args)?, "acos", f64::acos),
1399 "ATAN" => eval_unary_numeric_op(require_one_arg(name, args)?, "atan", f64::atan),
1400 "DEGREES" => {
1401 eval_unary_numeric_op(require_one_arg(name, args)?, "degrees", f64::to_degrees)
1402 }
1403 "RADIANS" => {
1404 eval_unary_numeric_op(require_one_arg(name, args)?, "radians", f64::to_radians)
1405 }
1406 "HAVERSIN" => eval_unary_numeric_op(require_one_arg(name, args)?, "haversin", |f| {
1407 (1.0 - f.cos()) / 2.0
1408 }),
1409 "POWER" | "POW" => eval_power(args),
1411 "ATAN2" => eval_atan2(args),
1412 "PI" => {
1414 if !args.is_empty() {
1415 return Err(anyhow!("PI takes no arguments"));
1416 }
1417 Ok(Value::Float(std::f64::consts::PI))
1418 }
1419 "E" => {
1420 if !args.is_empty() {
1421 return Err(anyhow!("E takes no arguments"));
1422 }
1423 Ok(Value::Float(std::f64::consts::E))
1424 }
1425 "RAND" => {
1426 if !args.is_empty() {
1427 return Err(anyhow!("RAND takes no arguments"));
1428 }
1429 use rand::Rng;
1430 let mut rng = rand::thread_rng();
1431 Ok(Value::Float(rng.gen_range(0.0..1.0)))
1432 }
1433 _ => Err(anyhow!("Unknown math function: {}", name)),
1434 }
1435}
1436
1437fn eval_unary_string_op<F>(arg: &Value, func_name: &str, op: F) -> Result<Value>
1443where
1444 F: FnOnce(&str) -> String,
1445{
1446 match arg {
1447 Value::String(s) => Ok(Value::String(op(s))),
1448 Value::Null => Ok(Value::Null),
1449 _ => Err(anyhow!("{}() expects a string", func_name)),
1450 }
1451}
1452
1453fn eval_toupper(args: &[Value]) -> Result<Value> {
1454 let arg = require_one_arg("toUpper", args)?;
1455 eval_unary_string_op(arg, "toUpper", |s| s.to_uppercase())
1456}
1457
1458fn eval_tolower(args: &[Value]) -> Result<Value> {
1459 let arg = require_one_arg("toLower", args)?;
1460 eval_unary_string_op(arg, "toLower", |s| s.to_lowercase())
1461}
1462
1463fn eval_trim(args: &[Value]) -> Result<Value> {
1464 let arg = require_one_arg("trim", args)?;
1465 eval_unary_string_op(arg, "trim", |s| s.trim().to_string())
1466}
1467
1468fn eval_ltrim(args: &[Value]) -> Result<Value> {
1469 let arg = require_one_arg("ltrim", args)?;
1470 eval_unary_string_op(arg, "ltrim", |s| s.trim_start().to_string())
1471}
1472
1473fn eval_rtrim(args: &[Value]) -> Result<Value> {
1474 let arg = require_one_arg("rtrim", args)?;
1475 eval_unary_string_op(arg, "rtrim", |s| s.trim_end().to_string())
1476}
1477
1478fn eval_reverse(args: &[Value]) -> Result<Value> {
1479 let arg = require_one_arg("reverse", args)?;
1480 match arg {
1481 Value::String(s) => Ok(Value::String(s.chars().rev().collect())),
1482 Value::List(arr) => Ok(Value::List(arr.iter().rev().cloned().collect())),
1483 Value::Null => Ok(Value::Null),
1484 _ => Err(anyhow!("reverse() expects a string or list")),
1485 }
1486}
1487
1488fn eval_replace(args: &[Value]) -> Result<Value> {
1489 if args.len() != 3 {
1490 return Err(anyhow!("replace() requires 3 arguments"));
1491 }
1492 match (&args[0], &args[1], &args[2]) {
1493 (Value::String(s), Value::String(search), Value::String(replacement)) => Ok(Value::String(
1494 s.replace(search.as_str(), replacement.as_str()),
1495 )),
1496 (Value::Null, _, _) => Ok(Value::Null),
1497 _ => Err(anyhow!("replace() expects string arguments")),
1498 }
1499}
1500
1501pub(crate) fn eval_split(args: &[Value]) -> Result<Value> {
1502 if args.len() != 2 {
1503 return Err(anyhow!("split() requires 2 arguments"));
1504 }
1505 match (&args[0], &args[1]) {
1506 (Value::String(s), Value::String(delimiter)) => {
1507 let parts: Vec<Value> = s
1508 .split(delimiter.as_str())
1509 .map(|p| Value::String(p.to_string()))
1510 .collect();
1511 Ok(Value::List(parts))
1512 }
1513 (Value::Null, _) => Ok(Value::Null),
1514 _ => Err(anyhow!("split() expects string arguments")),
1515 }
1516}
1517
1518fn eval_substring(args: &[Value]) -> Result<Value> {
1519 if args.len() < 2 || args.len() > 3 {
1520 return Err(anyhow!("substring() requires 2 or 3 arguments"));
1521 }
1522 match &args[0] {
1523 Value::String(s) => {
1524 let start = args[1]
1525 .as_i64()
1526 .ok_or_else(|| anyhow!("substring() start must be an integer"))?
1527 as usize;
1528 let len = if args.len() == 3 {
1529 args[2]
1530 .as_i64()
1531 .ok_or_else(|| anyhow!("substring() length must be an integer"))?
1532 as usize
1533 } else {
1534 s.len().saturating_sub(start)
1535 };
1536 let chars: Vec<char> = s.chars().collect();
1537 let end = (start + len).min(chars.len());
1538 let result: String = chars[start.min(chars.len())..end].iter().collect();
1539 Ok(Value::String(result))
1540 }
1541 Value::Null => Ok(Value::Null),
1542 _ => Err(anyhow!("substring() expects a string")),
1543 }
1544}
1545
1546fn eval_left(args: &[Value]) -> Result<Value> {
1547 if args.len() != 2 {
1548 return Err(anyhow!("left() requires 2 arguments"));
1549 }
1550 match (&args[0], &args[1]) {
1551 (Value::String(s), n) if n.is_number() => {
1552 let len = n.as_i64().unwrap_or(0) as usize;
1553 Ok(Value::String(s.chars().take(len).collect()))
1554 }
1555 (Value::Null, _) => Ok(Value::Null),
1556 _ => Err(anyhow!("left() expects a string and integer")),
1557 }
1558}
1559
1560fn eval_right(args: &[Value]) -> Result<Value> {
1561 if args.len() != 2 {
1562 return Err(anyhow!("right() requires 2 arguments"));
1563 }
1564 match (&args[0], &args[1]) {
1565 (Value::String(s), n) if n.is_number() => {
1566 let len = n.as_i64().unwrap_or(0) as usize;
1567 let chars: Vec<char> = s.chars().collect();
1568 let start = chars.len().saturating_sub(len);
1569 Ok(Value::String(chars[start..].iter().collect()))
1570 }
1571 (Value::Null, _) => Ok(Value::Null),
1572 _ => Err(anyhow!("right() expects a string and integer")),
1573 }
1574}
1575
1576fn eval_pad(func_name: &str, args: &[Value], pad_left: bool) -> Result<Value> {
1578 if args.len() < 2 || args.len() > 3 {
1579 return Err(anyhow!("{}() requires 2 or 3 arguments", func_name));
1580 }
1581 let s = match &args[0] {
1582 Value::String(s) => s,
1583 Value::Null => return Ok(Value::Null),
1584 _ => {
1585 return Err(anyhow!(
1586 "{}() expects a string as first argument",
1587 func_name
1588 ));
1589 }
1590 };
1591 let len = match &args[1] {
1592 Value::Int(n) => *n as usize,
1593 Value::Float(f) => *f as i64 as usize,
1594 Value::Null => return Ok(Value::Null),
1595 _ => {
1596 return Err(anyhow!(
1597 "{}() expects an integer as second argument",
1598 func_name
1599 ));
1600 }
1601 };
1602 if len > 1_000_000 {
1603 return Err(anyhow!(
1604 "{}() length exceeds maximum limit of 1,000,000",
1605 func_name
1606 ));
1607 }
1608 let pad_str = if args.len() == 3 {
1609 match &args[2] {
1610 Value::String(p) => p.as_str(),
1611 Value::Null => return Ok(Value::Null),
1612 _ => {
1613 return Err(anyhow!(
1614 "{}() expects a string as third argument",
1615 func_name
1616 ));
1617 }
1618 }
1619 } else {
1620 " "
1621 };
1622
1623 let s_chars: Vec<char> = s.chars().collect();
1624 if s_chars.len() >= len {
1625 return Ok(Value::String(s_chars.into_iter().take(len).collect()));
1626 }
1627
1628 let pad_chars: Vec<char> = pad_str.chars().collect();
1629 if pad_chars.is_empty() {
1630 return Ok(Value::String(s.clone()));
1631 }
1632
1633 let needed = len - s_chars.len();
1634 let full_pads = needed / pad_chars.len();
1635 let partial_pad = needed % pad_chars.len();
1636
1637 let mut padding = String::with_capacity(needed);
1638 for _ in 0..full_pads {
1639 padding.push_str(pad_str);
1640 }
1641 padding.extend(pad_chars.into_iter().take(partial_pad));
1642
1643 let result = if pad_left {
1644 format!("{}{}", padding, s)
1645 } else {
1646 format!("{}{}", s, padding)
1647 };
1648 Ok(Value::String(result))
1649}
1650
1651fn eval_lpad(args: &[Value]) -> Result<Value> {
1652 eval_pad("lpad", args, true)
1653}
1654
1655fn eval_rpad(args: &[Value]) -> Result<Value> {
1656 eval_pad("rpad", args, false)
1657}
1658
1659fn eval_string_function(name: &str, args: &[Value]) -> Result<Value> {
1661 match name {
1662 "TOUPPER" | "UPPER" => eval_toupper(args),
1663 "TOLOWER" | "LOWER" => eval_tolower(args),
1664 "TRIM" => eval_trim(args),
1665 "LTRIM" => eval_ltrim(args),
1666 "RTRIM" => eval_rtrim(args),
1667 "REVERSE" => eval_reverse(args),
1668 "REPLACE" => eval_replace(args),
1669 "SPLIT" => eval_split(args),
1670 "SUBSTRING" => eval_substring(args),
1671 "LEFT" => eval_left(args),
1672 "RIGHT" => eval_right(args),
1673 "LPAD" => eval_lpad(args),
1674 "RPAD" => eval_rpad(args),
1675 _ => Err(anyhow!("Unknown string function: {}", name)),
1676 }
1677}
1678
1679fn eval_range_function(args: &[Value]) -> Result<Value> {
1681 if args.len() < 2 || args.len() > 3 {
1682 return Err(anyhow!("range() requires 2 or 3 arguments"));
1683 }
1684 let start = args[0]
1685 .as_i64()
1686 .ok_or_else(|| anyhow!("range() start must be an integer"))?;
1687 let end = args[1]
1688 .as_i64()
1689 .ok_or_else(|| anyhow!("range() end must be an integer"))?;
1690 let step = if args.len() == 3 {
1691 args[2]
1692 .as_i64()
1693 .ok_or_else(|| anyhow!("range() step must be an integer"))?
1694 } else {
1695 1
1696 };
1697 if step == 0 {
1698 return Err(anyhow!("range() step cannot be zero"));
1699 }
1700 let mut result = Vec::new();
1701 let mut i = start;
1702 if step > 0 {
1703 while i <= end {
1704 result.push(Value::Int(i));
1705 i += step;
1706 }
1707 } else {
1708 while i >= end {
1709 result.push(Value::Int(i));
1710 i += step;
1711 }
1712 }
1713 Ok(Value::List(result))
1714}
1715
1716pub fn eval_scalar_function(
1721 name: &str,
1722 args: &[Value],
1723 custom_fns: Option<&super::executor::custom_functions::CustomFunctionRegistry>,
1724) -> Result<Value> {
1725 let name_upper = name.to_uppercase();
1726
1727 match name_upper.as_str() {
1728 "COALESCE" => {
1730 for arg in args {
1731 if !arg.is_null() {
1732 return Ok(arg.clone());
1733 }
1734 }
1735 Ok(Value::Null)
1736 }
1737 "NULLIF" => {
1738 if args.len() != 2 {
1739 return Err(anyhow!("NULLIF requires 2 arguments"));
1740 }
1741 if args[0] == args[1] {
1742 Ok(Value::Null)
1743 } else {
1744 Ok(args[0].clone())
1745 }
1746 }
1747
1748 "SIZE" | "KEYS" | "HEAD" | "TAIL" | "LAST" | "LENGTH" | "NODES" | "RELATIONSHIPS" => {
1750 eval_list_function(&name_upper, args)
1751 }
1752
1753 "TOINTEGER" | "TOINT" | "TOFLOAT" | "TOSTRING" | "TOBOOLEAN" | "TOBOOL" => {
1755 eval_type_function(&name_upper, args)
1756 }
1757
1758 "ABS" | "CEIL" | "FLOOR" | "ROUND" | "SQRT" | "SIGN" | "LOG" | "LOG10" | "EXP"
1760 | "POWER" | "POW" | "SIN" | "COS" | "TAN" | "ASIN" | "ACOS" | "ATAN" | "ATAN2"
1761 | "DEGREES" | "RADIANS" | "HAVERSIN" | "PI" | "E" | "RAND" => {
1762 eval_math_function(&name_upper, args)
1763 }
1764
1765 "TOUPPER" | "UPPER" | "TOLOWER" | "LOWER" | "TRIM" | "LTRIM" | "RTRIM" | "REVERSE"
1767 | "REPLACE" | "SPLIT" | "SUBSTRING" | "LEFT" | "RIGHT" | "LPAD" | "RPAD" => {
1768 eval_string_function(&name_upper, args)
1769 }
1770
1771 "DATE"
1773 | "TIME"
1774 | "DATETIME"
1775 | "LOCALDATETIME"
1776 | "LOCALTIME"
1777 | "DURATION"
1778 | "BTIC"
1779 | "YEAR"
1780 | "MONTH"
1781 | "DAY"
1782 | "HOUR"
1783 | "MINUTE"
1784 | "SECOND"
1785 | "DATETIME.FROMEPOCH"
1786 | "DATETIME.FROMEPOCHMILLIS"
1787 | "DATE.TRUNCATE"
1788 | "TIME.TRUNCATE"
1789 | "DATETIME.TRUNCATE"
1790 | "LOCALDATETIME.TRUNCATE"
1791 | "LOCALTIME.TRUNCATE"
1792 | "DATETIME.TRANSACTION"
1793 | "DATETIME.STATEMENT"
1794 | "DATETIME.REALTIME"
1795 | "DATE.TRANSACTION"
1796 | "DATE.STATEMENT"
1797 | "DATE.REALTIME"
1798 | "TIME.TRANSACTION"
1799 | "TIME.STATEMENT"
1800 | "TIME.REALTIME"
1801 | "LOCALTIME.TRANSACTION"
1802 | "LOCALTIME.STATEMENT"
1803 | "LOCALTIME.REALTIME"
1804 | "LOCALDATETIME.TRANSACTION"
1805 | "LOCALDATETIME.STATEMENT"
1806 | "LOCALDATETIME.REALTIME"
1807 | "DURATION.BETWEEN"
1808 | "DURATION.INMONTHS"
1809 | "DURATION.INDAYS"
1810 | "DURATION.INSECONDS" => eval_datetime_function(&name_upper, args),
1811
1812 "POINT" | "DISTANCE" | "POINT.WITHINBBOX" => eval_spatial_function(&name_upper, args),
1814
1815 "RANGE" => eval_range_function(args),
1816
1817 "UNI.TEMPORAL.VALIDAT" => eval_valid_at(args),
1818
1819 "VECTOR_DISTANCE" => {
1820 if args.len() < 2 || args.len() > 3 {
1821 return Err(anyhow!("vector_distance requires 2 or 3 arguments"));
1822 }
1823 let metric = if args.len() == 3 {
1824 args[2].as_str().ok_or(anyhow!("metric must be string"))?
1825 } else {
1826 "cosine"
1827 };
1828 eval_vector_distance(&args[0], &args[1], metric)
1829 }
1830
1831 "UNI_BITWISE_OR"
1833 | "UNI_BITWISE_AND"
1834 | "UNI_BITWISE_XOR"
1835 | "UNI_BITWISE_NOT"
1836 | "UNI_BITWISE_SHIFTLEFT"
1837 | "UNI_BITWISE_SHIFTRIGHT" => eval_bitwise_function(&name_upper, args),
1838
1839 "SIMILAR_TO" => {
1842 if args.len() < 2 {
1843 return Err(anyhow!("similar_to requires at least 2 arguments"));
1844 }
1845 crate::query::similar_to::eval_similar_to_pure(&args[0], &args[1])
1846 }
1847
1848 "VECTOR_SIMILARITY" => {
1849 if args.len() != 2 {
1850 return Err(anyhow!("vector_similarity takes 2 arguments"));
1851 }
1852 eval_vector_similarity(&args[0], &args[1])
1853 }
1854
1855 _ if is_btic_function(&name_upper) => eval_btic_function(&name_upper, args),
1857
1858 _ => {
1859 if let Some(func) = custom_fns.and_then(|r| r.get(name)) {
1861 return func(args).map_err(|e| anyhow!("{}", e));
1862 }
1863 Err(anyhow!("Function {} not implemented or is aggregate", name))
1864 }
1865 }
1866}
1867
1868fn eval_valid_at(args: &[Value]) -> Result<Value> {
1876 if args.len() != 4 {
1877 return Err(anyhow!(
1878 "validAt requires 4 arguments: node, start_prop, end_prop, time"
1879 ));
1880 }
1881
1882 let node_map = match &args[0] {
1883 Value::Map(map) => map,
1884 Value::Null => return Ok(Value::Bool(false)),
1885 _ => {
1886 return Err(anyhow!(
1887 "validAt expects a Node or Edge (Object) as first argument"
1888 ));
1889 }
1890 };
1891
1892 let start_prop = args[1]
1893 .as_str()
1894 .ok_or_else(|| anyhow!("start_prop must be a string"))?;
1895 let end_prop = args[2]
1896 .as_str()
1897 .ok_or_else(|| anyhow!("end_prop must be a string"))?;
1898
1899 let time_str = match &args[3] {
1900 Value::String(s) => s,
1901 _ => return Err(anyhow!("time argument must be a datetime string")),
1902 };
1903
1904 let query_time = parse_datetime_utc(time_str)
1905 .map_err(|_| anyhow!("Invalid query time format: {}", time_str))?;
1906
1907 let valid_from_val = node_map.get(start_prop);
1908 let valid_from = match valid_from_val {
1909 Some(Value::String(s)) => parse_datetime_utc(s)
1910 .map_err(|_| anyhow!("Invalid datetime in property {}: {}", start_prop, s))?,
1911 Some(Value::Null) | None => return Ok(Value::Bool(false)),
1912 _ => return Err(anyhow!("Property {} must be a datetime string", start_prop)),
1913 };
1914
1915 let valid_to_val = node_map.get(end_prop);
1916 let valid_to = match valid_to_val {
1917 Some(Value::String(s)) => Some(
1918 parse_datetime_utc(s)
1919 .map_err(|_| anyhow!("Invalid datetime in property {}: {}", end_prop, s))?,
1920 ),
1921 Some(Value::Null) | None => None,
1922 _ => {
1923 return Err(anyhow!(
1924 "Property {} must be a datetime string or null",
1925 end_prop
1926 ));
1927 }
1928 };
1929
1930 let is_valid = valid_from <= query_time && valid_to.map(|vt| query_time < vt).unwrap_or(true);
1932
1933 Ok(Value::Bool(is_valid))
1934}
1935
1936pub fn eval_vector_similarity(v1: &Value, v2: &Value) -> Result<Value> {
1938 let (arr1, arr2) = match (v1, v2) {
1939 (Value::List(a1), Value::List(a2)) => (a1, a2),
1940 _ => return Err(anyhow!("vector_similarity arguments must be arrays")),
1941 };
1942
1943 if arr1.len() != arr2.len() {
1944 return Err(anyhow!(
1945 "Vector dimensions mismatch: {} vs {}",
1946 arr1.len(),
1947 arr2.len()
1948 ));
1949 }
1950
1951 let mut dot = 0.0;
1952 let mut norm1_sq = 0.0;
1953 let mut norm2_sq = 0.0;
1954
1955 for (v1_elem, v2_elem) in arr1.iter().zip(arr2.iter()) {
1956 let f1 = v1_elem
1957 .as_f64()
1958 .ok_or_else(|| anyhow!("Vector element not a number"))?;
1959 let f2 = v2_elem
1960 .as_f64()
1961 .ok_or_else(|| anyhow!("Vector element not a number"))?;
1962 dot += f1 * f2;
1963 norm1_sq += f1 * f1;
1964 norm2_sq += f2 * f2;
1965 }
1966
1967 let mag1 = norm1_sq.sqrt();
1968 let mag2 = norm2_sq.sqrt();
1969
1970 let sim = if mag1 == 0.0 || mag2 == 0.0 {
1971 0.0
1972 } else {
1973 dot / (mag1 * mag2)
1974 };
1975
1976 Ok(Value::Float(sim))
1977}
1978
1979pub fn eval_vector_distance(v1: &Value, v2: &Value, metric: &str) -> Result<Value> {
1981 let (arr1, arr2) = match (v1, v2) {
1982 (Value::List(a1), Value::List(a2)) => (a1, a2),
1983 _ => return Err(anyhow!("vector_distance arguments must be arrays")),
1984 };
1985
1986 if arr1.len() != arr2.len() {
1987 return Err(anyhow!(
1988 "Vector dimensions mismatch: {} vs {}",
1989 arr1.len(),
1990 arr2.len()
1991 ));
1992 }
1993
1994 let iter1 = arr1
1996 .iter()
1997 .map(|v| v.as_f64().ok_or(anyhow!("Vector element not a number")));
1998 let iter2 = arr2
1999 .iter()
2000 .map(|v| v.as_f64().ok_or(anyhow!("Vector element not a number")));
2001
2002 match metric.to_lowercase().as_str() {
2003 "cosine" => {
2004 let mut dot = 0.0;
2006 let mut norm1_sq = 0.0;
2007 let mut norm2_sq = 0.0;
2008
2009 for (r1, r2) in iter1.zip(iter2) {
2010 let f1 = r1?;
2011 let f2 = r2?;
2012 dot += f1 * f2;
2013 norm1_sq += f1 * f1;
2014 norm2_sq += f2 * f2;
2015 }
2016
2017 let mag1 = norm1_sq.sqrt();
2018 let mag2 = norm2_sq.sqrt();
2019
2020 if mag1 == 0.0 || mag2 == 0.0 {
2021 Ok(Value::Float(1.0))
2022 } else {
2023 let sim = dot / (mag1 * mag2);
2024 let sim = sim.clamp(-1.0, 1.0);
2026 Ok(Value::Float(1.0 - sim))
2027 }
2028 }
2029 "euclidean" | "l2" => {
2030 let mut sum_sq_diff = 0.0;
2031 for (r1, r2) in iter1.zip(iter2) {
2032 let f1 = r1?;
2033 let f2 = r2?;
2034 let diff = f1 - f2;
2035 sum_sq_diff += diff * diff;
2036 }
2037 Ok(Value::Float(sum_sq_diff.sqrt()))
2038 }
2039 "dot" | "inner_product" => {
2040 let mut dot = 0.0;
2041 for (r1, r2) in iter1.zip(iter2) {
2042 let f1 = r1?;
2043 let f2 = r2?;
2044 dot += f1 * f2;
2045 }
2046 Ok(Value::Float(1.0 - dot))
2047 }
2048 _ => Err(anyhow!("Unknown metric: {}", metric)),
2049 }
2050}
2051
2052pub fn is_scalar_function(name: &str) -> bool {
2054 let name_upper = name.to_uppercase();
2055 matches!(
2056 name_upper.as_str(),
2057 "COALESCE"
2058 | "NULLIF"
2059 | "SIZE"
2060 | "KEYS"
2061 | "HEAD"
2062 | "TAIL"
2063 | "LAST"
2064 | "LENGTH"
2065 | "NODES"
2066 | "RELATIONSHIPS"
2067 | "TOINTEGER"
2068 | "TOINT"
2069 | "TOFLOAT"
2070 | "TOSTRING"
2071 | "TOBOOLEAN"
2072 | "TOBOOL"
2073 | "ABS"
2074 | "CEIL"
2075 | "FLOOR"
2076 | "ROUND"
2077 | "SQRT"
2078 | "SIGN"
2079 | "LOG"
2080 | "LOG10"
2081 | "EXP"
2082 | "POWER"
2083 | "POW"
2084 | "SIN"
2085 | "COS"
2086 | "TAN"
2087 | "ASIN"
2088 | "ACOS"
2089 | "ATAN"
2090 | "ATAN2"
2091 | "DEGREES"
2092 | "RADIANS"
2093 | "HAVERSIN"
2094 | "PI"
2095 | "E"
2096 | "RAND"
2097 | "TOUPPER"
2098 | "UPPER"
2099 | "TOLOWER"
2100 | "LOWER"
2101 | "TRIM"
2102 | "LTRIM"
2103 | "RTRIM"
2104 | "REVERSE"
2105 | "REPLACE"
2106 | "SPLIT"
2107 | "SUBSTRING"
2108 | "LEFT"
2109 | "RIGHT"
2110 | "LPAD"
2111 | "RPAD"
2112 | "RANGE"
2113 | "UNI.VALIDAT"
2114 | "VALIDAT"
2115 | "SIMILAR_TO"
2116 | "VECTOR_SIMILARITY"
2117 | "VECTOR_DISTANCE"
2118 | "DATE"
2119 | "TIME"
2120 | "DATETIME"
2121 | "DURATION"
2122 | "YEAR"
2123 | "MONTH"
2124 | "DAY"
2125 | "HOUR"
2126 | "MINUTE"
2127 | "SECOND"
2128 | "ID"
2129 | "ELEMENTID"
2130 | "TYPE"
2131 | "LABELS"
2132 | "PROPERTIES"
2133 | "STARTNODE"
2134 | "ENDNODE"
2135 | "ANY"
2136 | "ALL"
2137 | "NONE"
2138 | "SINGLE"
2139 ) || is_btic_function(&name_upper)
2140}
2141
2142pub fn is_btic_function(name_upper: &str) -> bool {
2144 matches!(
2145 name_upper,
2146 "BTIC_LO"
2147 | "BTIC_HI"
2148 | "BTIC_DURATION"
2149 | "BTIC_CONTAINS_POINT"
2150 | "BTIC_OVERLAPS"
2151 | "BTIC_IS_INSTANT"
2152 | "BTIC_IS_UNBOUNDED"
2153 | "BTIC_IS_FINITE"
2154 | "BTIC_GRANULARITY"
2155 | "BTIC_LO_GRANULARITY"
2156 | "BTIC_HI_GRANULARITY"
2157 | "BTIC_CERTAINTY"
2158 | "BTIC_LO_CERTAINTY"
2159 | "BTIC_HI_CERTAINTY"
2160 | "BTIC_CONTAINS"
2161 | "BTIC_BEFORE"
2162 | "BTIC_AFTER"
2163 | "BTIC_MEETS"
2164 | "BTIC_ADJACENT"
2165 | "BTIC_DISJOINT"
2166 | "BTIC_EQUALS"
2167 | "BTIC_STARTS"
2168 | "BTIC_DURING"
2169 | "BTIC_FINISHES"
2170 | "BTIC_INTERSECTION"
2171 | "BTIC_SPAN"
2172 | "BTIC_GAP"
2173 )
2174}
2175
2176pub fn eval_btic_function(name: &str, args: &[Value]) -> Result<Value> {
2178 match name {
2179 "BTIC_LO" => {
2180 let Some((lo, _, _)) = require_btic_1arg(name, args)? else {
2181 return Ok(Value::Null);
2182 };
2183 if lo == i64::MIN {
2184 return Ok(Value::Null);
2185 }
2186 Ok(ms_to_utc_datetime(lo))
2187 }
2188 "BTIC_HI" => {
2189 let Some((_, hi, _)) = require_btic_1arg(name, args)? else {
2190 return Ok(Value::Null);
2191 };
2192 if hi == i64::MAX {
2193 return Ok(Value::Null);
2194 }
2195 Ok(ms_to_utc_datetime(hi))
2196 }
2197 "BTIC_DURATION" => {
2198 let Some((lo, hi, _)) = require_btic_1arg(name, args)? else {
2199 return Ok(Value::Null);
2200 };
2201 if lo == i64::MIN || hi == i64::MAX {
2202 Ok(Value::Null)
2203 } else {
2204 Ok(Value::Int(hi - lo))
2205 }
2206 }
2207 "BTIC_IS_INSTANT" => {
2208 let Some((lo, hi, _)) = require_btic_1arg(name, args)? else {
2209 return Ok(Value::Null);
2210 };
2211 Ok(Value::Bool(hi == lo + 1))
2212 }
2213 "BTIC_IS_UNBOUNDED" => {
2214 let Some((lo, hi, _)) = require_btic_1arg(name, args)? else {
2215 return Ok(Value::Null);
2216 };
2217 Ok(Value::Bool(lo == i64::MIN || hi == i64::MAX))
2218 }
2219 "BTIC_IS_FINITE" => {
2220 let Some((lo, hi, _)) = require_btic_1arg(name, args)? else {
2221 return Ok(Value::Null);
2222 };
2223 Ok(Value::Bool(lo != i64::MIN && hi != i64::MAX))
2224 }
2225 "BTIC_GRANULARITY" | "BTIC_LO_GRANULARITY" => {
2226 let Some(btic) = require_btic_1arg_parsed(name, args)? else {
2227 return Ok(Value::Null);
2228 };
2229 Ok(Value::String(btic.lo_granularity().name().to_string()))
2230 }
2231 "BTIC_HI_GRANULARITY" => {
2232 let Some(btic) = require_btic_1arg_parsed(name, args)? else {
2233 return Ok(Value::Null);
2234 };
2235 Ok(Value::String(btic.hi_granularity().name().to_string()))
2236 }
2237 "BTIC_CERTAINTY" | "BTIC_LO_CERTAINTY" => {
2238 let Some(btic) = require_btic_1arg_parsed(name, args)? else {
2239 return Ok(Value::Null);
2240 };
2241 if name == "BTIC_CERTAINTY" {
2242 let lc = btic.lo_certainty();
2243 let hc = btic.hi_certainty();
2244 Ok(Value::String(lc.least_certain(hc).name().to_string()))
2245 } else {
2246 Ok(Value::String(btic.lo_certainty().name().to_string()))
2247 }
2248 }
2249 "BTIC_HI_CERTAINTY" => {
2250 let Some(btic) = require_btic_1arg_parsed(name, args)? else {
2251 return Ok(Value::Null);
2252 };
2253 Ok(Value::String(btic.hi_certainty().name().to_string()))
2254 }
2255 "BTIC_CONTAINS_POINT" => {
2256 if args.len() != 2 {
2257 return Err(anyhow!("btic_contains_point requires 2 arguments"));
2258 }
2259 if args[0].is_null() || args[1].is_null() {
2260 return Ok(Value::Null);
2261 }
2262 match &args[0] {
2263 Value::Temporal(TemporalValue::Btic { lo, hi, .. }) => {
2264 let point_ms = extract_point_ms(&args[1])?;
2265 Ok(Value::Bool(*lo <= point_ms && point_ms < *hi))
2266 }
2267 _ => Err(anyhow!("btic_contains_point: first arg must be BTIC")),
2268 }
2269 }
2270 "BTIC_OVERLAPS" | "BTIC_CONTAINS" | "BTIC_BEFORE" | "BTIC_AFTER" | "BTIC_MEETS"
2272 | "BTIC_ADJACENT" | "BTIC_DISJOINT" | "BTIC_EQUALS" | "BTIC_STARTS" | "BTIC_DURING"
2273 | "BTIC_FINISHES" => eval_btic_binary_predicate(name, args),
2274 "BTIC_INTERSECTION" => eval_btic_set_op(name, args, uni_btic::set_ops::intersection),
2276 "BTIC_SPAN" => eval_btic_set_op(name, args, |a, b| Some(uni_btic::set_ops::span(a, b))),
2277 "BTIC_GAP" => eval_btic_set_op(name, args, uni_btic::set_ops::gap),
2278 _ => Err(anyhow!("unknown BTIC function: {}", name)),
2279 }
2280}
2281
2282fn require_btic_1arg(name: &str, args: &[Value]) -> Result<Option<(i64, i64, u64)>> {
2285 if args.len() != 1 {
2286 return Err(anyhow!("{} requires 1 argument", name.to_lowercase()));
2287 }
2288 match &args[0] {
2289 Value::Temporal(TemporalValue::Btic { lo, hi, meta }) => Ok(Some((*lo, *hi, *meta))),
2290 Value::Null => Ok(None),
2291 _ => Err(anyhow!("{}: expected BTIC value", name.to_lowercase())),
2292 }
2293}
2294
2295fn require_btic_1arg_parsed(name: &str, args: &[Value]) -> Result<Option<uni_btic::Btic>> {
2298 let Some((lo, hi, meta)) = require_btic_1arg(name, args)? else {
2299 return Ok(None);
2300 };
2301 uni_btic::Btic::new(lo, hi, meta)
2302 .map(Some)
2303 .map_err(|e| anyhow!("invalid BTIC: {}", e))
2304}
2305
2306fn ms_to_utc_datetime(ms: i64) -> Value {
2308 Value::Temporal(TemporalValue::DateTime {
2309 nanos_since_epoch: ms * 1_000_000,
2310 offset_seconds: 0,
2311 timezone_name: Some("UTC".to_string()),
2312 })
2313}
2314
2315fn eval_btic_binary_predicate(name: &str, args: &[Value]) -> Result<Value> {
2317 if args.len() != 2 {
2318 return Err(anyhow!("{} requires 2 arguments", name.to_lowercase()));
2319 }
2320 if args[0].is_null() || args[1].is_null() {
2321 return Ok(Value::Null);
2322 }
2323 match (&args[0], &args[1]) {
2324 (
2325 Value::Temporal(TemporalValue::Btic {
2326 lo: a_lo, hi: a_hi, ..
2327 }),
2328 Value::Temporal(TemporalValue::Btic {
2329 lo: b_lo, hi: b_hi, ..
2330 }),
2331 ) => {
2332 let result = match name {
2333 "BTIC_OVERLAPS" => *a_lo < *b_hi && *b_lo < *a_hi,
2334 "BTIC_CONTAINS" => *a_lo <= *b_lo && *b_hi <= *a_hi,
2335 "BTIC_BEFORE" => *a_hi <= *b_lo,
2336 "BTIC_AFTER" => *b_hi <= *a_lo,
2337 "BTIC_MEETS" => *a_hi == *b_lo,
2338 "BTIC_ADJACENT" => *a_hi == *b_lo || *b_hi == *a_lo,
2339 "BTIC_DISJOINT" => *a_hi <= *b_lo || *b_hi <= *a_lo,
2340 "BTIC_EQUALS" => *a_lo == *b_lo && *a_hi == *b_hi,
2341 "BTIC_STARTS" => *a_lo == *b_lo && *a_hi < *b_hi,
2342 "BTIC_DURING" => *b_lo < *a_lo && *a_hi < *b_hi,
2343 "BTIC_FINISHES" => *a_hi == *b_hi && *b_lo < *a_lo,
2344 _ => unreachable!(),
2345 };
2346 Ok(Value::Bool(result))
2347 }
2348 _ => Err(anyhow!(
2349 "{}: both args must be BTIC values",
2350 name.to_lowercase()
2351 )),
2352 }
2353}
2354
2355fn eval_btic_set_op<F>(name: &str, args: &[Value], op: F) -> Result<Value>
2360where
2361 F: FnOnce(&uni_btic::Btic, &uni_btic::Btic) -> Option<uni_btic::Btic>,
2362{
2363 if args.len() != 2 {
2364 return Err(anyhow!("{} requires 2 arguments", name.to_lowercase()));
2365 }
2366 if args[0].is_null() || args[1].is_null() {
2367 return Ok(Value::Null);
2368 }
2369 match (&args[0], &args[1]) {
2370 (
2371 Value::Temporal(TemporalValue::Btic {
2372 lo: a_lo,
2373 hi: a_hi,
2374 meta: a_meta,
2375 }),
2376 Value::Temporal(TemporalValue::Btic {
2377 lo: b_lo,
2378 hi: b_hi,
2379 meta: b_meta,
2380 }),
2381 ) => {
2382 let a = uni_btic::Btic::new(*a_lo, *a_hi, *a_meta)
2383 .map_err(|e| anyhow!("invalid BTIC: {}", e))?;
2384 let b = uni_btic::Btic::new(*b_lo, *b_hi, *b_meta)
2385 .map_err(|e| anyhow!("invalid BTIC: {}", e))?;
2386 match op(&a, &b) {
2387 Some(r) => Ok(Value::Temporal(TemporalValue::Btic {
2388 lo: r.lo(),
2389 hi: r.hi(),
2390 meta: r.meta(),
2391 })),
2392 None => Ok(Value::Null),
2393 }
2394 }
2395 _ => Err(anyhow!(
2396 "{}: both args must be BTIC values",
2397 name.to_lowercase()
2398 )),
2399 }
2400}
2401
2402fn extract_point_ms(val: &Value) -> Result<i64> {
2404 match val {
2405 Value::Int(ms) => Ok(*ms),
2406 Value::Temporal(TemporalValue::DateTime {
2407 nanos_since_epoch, ..
2408 }) => Ok(*nanos_since_epoch / 1_000_000),
2409 Value::Temporal(TemporalValue::LocalDateTime {
2410 nanos_since_epoch, ..
2411 }) => Ok(*nanos_since_epoch / 1_000_000),
2412 Value::Temporal(TemporalValue::Date { days_since_epoch }) => {
2413 Ok(*days_since_epoch as i64 * 86_400_000)
2414 }
2415 _ => Err(anyhow!(
2416 "expected a temporal point value (datetime, integer ms), got {:?}",
2417 val
2418 )),
2419 }
2420}
2421
2422fn eval_bitwise_function(name: &str, args: &[Value]) -> Result<Value> {
2424 let require_int = |v: &Value, fname: &str| -> Result<i64> {
2425 v.as_i64()
2426 .ok_or_else(|| anyhow!("{} requires integer arguments", fname))
2427 };
2428
2429 let bitwise_binary = |fname: &str, op: fn(i64, i64) -> i64| -> Result<Value> {
2430 if args.len() != 2 {
2431 return Err(anyhow!("{} requires exactly 2 arguments", fname));
2432 }
2433 let l = require_int(&args[0], fname)?;
2434 let r = require_int(&args[1], fname)?;
2435 Ok(Value::Int(op(l, r)))
2436 };
2437
2438 match name {
2439 "UNI_BITWISE_OR" => bitwise_binary("uni_bitwise_or", |l, r| l | r),
2440 "UNI_BITWISE_AND" => bitwise_binary("uni_bitwise_and", |l, r| l & r),
2441 "UNI_BITWISE_XOR" => bitwise_binary("uni_bitwise_xor", |l, r| l ^ r),
2442 "UNI_BITWISE_SHIFTLEFT" => bitwise_binary("uni_bitwise_shiftLeft", |l, r| l << r),
2443 "UNI_BITWISE_SHIFTRIGHT" => bitwise_binary("uni_bitwise_shiftRight", |l, r| l >> r),
2444 "UNI_BITWISE_NOT" => {
2445 if args.len() != 1 {
2446 return Err(anyhow!("uni_bitwise_not requires exactly 1 argument"));
2447 }
2448 Ok(Value::Int(!require_int(&args[0], "uni_bitwise_not")?))
2449 }
2450 _ => Err(anyhow!("Unknown bitwise function: {}", name)),
2451 }
2452}
2453
2454#[cfg(test)]
2455mod tests {
2456 use super::*;
2457 fn s(v: &str) -> Value {
2459 Value::String(v.into())
2460 }
2461 fn i(v: i64) -> Value {
2463 Value::Int(v)
2464 }
2465
2466 #[test]
2467 fn test_binary_op_eq() {
2468 assert_eq!(
2469 eval_binary_op(&i(1), &BinaryOp::Eq, &i(1)).unwrap(),
2470 Value::Bool(true)
2471 );
2472 assert_eq!(
2473 eval_binary_op(&i(1), &BinaryOp::Eq, &i(2)).unwrap(),
2474 Value::Bool(false)
2475 );
2476 }
2477
2478 #[test]
2479 fn test_binary_op_comparison() {
2480 assert_eq!(
2481 eval_binary_op(&i(5), &BinaryOp::Gt, &i(3)).unwrap(),
2482 Value::Bool(true)
2483 );
2484 assert_eq!(
2485 eval_binary_op(&i(5), &BinaryOp::Lt, &i(3)).unwrap(),
2486 Value::Bool(false)
2487 );
2488 }
2489
2490 #[test]
2491 fn test_binary_op_xor() {
2492 assert_eq!(
2494 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Bool(true)).unwrap(),
2495 Value::Bool(false)
2496 );
2497 assert_eq!(
2499 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Bool(false)).unwrap(),
2500 Value::Bool(true)
2501 );
2502 assert_eq!(
2504 eval_binary_op(&Value::Bool(false), &BinaryOp::Xor, &Value::Bool(true)).unwrap(),
2505 Value::Bool(true)
2506 );
2507 assert_eq!(
2509 eval_binary_op(&Value::Bool(false), &BinaryOp::Xor, &Value::Bool(false)).unwrap(),
2510 Value::Bool(false)
2511 );
2512 }
2513
2514 #[test]
2515 fn test_binary_op_contains() {
2516 assert_eq!(
2517 eval_binary_op(&s("hello world"), &BinaryOp::Contains, &s("world")).unwrap(),
2518 Value::Bool(true)
2519 );
2520 }
2521
2522 #[test]
2523 fn test_scalar_function_size() {
2524 assert_eq!(
2525 eval_scalar_function("SIZE", &[Value::List(vec![i(1), i(2), i(3)])], None).unwrap(),
2526 Value::Int(3)
2527 );
2528 }
2529
2530 #[test]
2531 fn test_scalar_function_head() {
2532 assert_eq!(
2533 eval_scalar_function("HEAD", &[Value::List(vec![i(1), i(2), i(3)])], None).unwrap(),
2534 Value::Int(1)
2535 );
2536 }
2537
2538 #[test]
2539 fn test_scalar_function_coalesce() {
2540 assert_eq!(
2541 eval_scalar_function(
2542 "COALESCE",
2543 &[Value::Null, Value::Int(1), Value::Int(2)],
2544 None
2545 )
2546 .unwrap(),
2547 Value::Int(1)
2548 );
2549 }
2550
2551 #[test]
2552 fn test_vector_similarity() {
2553 let v1 = Value::List(vec![Value::Float(1.0), Value::Float(0.0)]);
2554 let v2 = Value::List(vec![Value::Float(1.0), Value::Float(0.0)]);
2555 let result = eval_vector_similarity(&v1, &v2).unwrap();
2556 assert_eq!(result.as_f64().unwrap(), 1.0);
2557 }
2558
2559 #[test]
2560 fn test_regex_match() {
2561 assert_eq!(
2563 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("hello.*")).unwrap(),
2564 Value::Bool(true)
2565 );
2566
2567 assert_eq!(
2569 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("^world")).unwrap(),
2570 Value::Bool(false)
2571 );
2572
2573 assert_eq!(
2575 eval_binary_op(&s("Hello"), &BinaryOp::Regex, &s("hello")).unwrap(),
2576 Value::Bool(false)
2577 );
2578
2579 assert_eq!(
2581 eval_binary_op(&s("Hello"), &BinaryOp::Regex, &s("(?i)hello")).unwrap(),
2582 Value::Bool(true)
2583 );
2584 }
2585
2586 #[test]
2587 fn test_regex_null_handling() {
2588 assert_eq!(
2590 eval_binary_op(&Value::Null, &BinaryOp::Regex, &s(".*")).unwrap(),
2591 Value::Null
2592 );
2593
2594 assert_eq!(
2596 eval_binary_op(&s("hello"), &BinaryOp::Regex, &Value::Null).unwrap(),
2597 Value::Null
2598 );
2599 }
2600
2601 #[test]
2602 fn test_regex_invalid_pattern() {
2603 let result = eval_binary_op(&s("hello"), &BinaryOp::Regex, &s("[invalid"));
2605 assert!(result.is_err());
2606 assert!(result.unwrap_err().to_string().contains("Invalid regex"));
2607 }
2608
2609 #[test]
2610 fn test_regex_special_characters() {
2611 assert_eq!(
2613 eval_binary_op(
2614 &s("test@example.com"),
2615 &BinaryOp::Regex,
2616 &s(r"^[\w.-]+@[\w.-]+\.\w+$")
2617 )
2618 .unwrap(),
2619 Value::Bool(true)
2620 );
2621
2622 assert_eq!(
2624 eval_binary_op(
2625 &s("123-456-7890"),
2626 &BinaryOp::Regex,
2627 &s(r"^\d{3}-\d{3}-\d{4}$")
2628 )
2629 .unwrap(),
2630 Value::Bool(true)
2631 );
2632
2633 assert_eq!(
2635 eval_binary_op(
2636 &s("1234567890"),
2637 &BinaryOp::Regex,
2638 &s(r"^\d{3}-\d{3}-\d{4}$")
2639 )
2640 .unwrap(),
2641 Value::Bool(false)
2642 );
2643 }
2644
2645 #[test]
2646 fn test_regex_anchors() {
2647 assert_eq!(
2649 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("^hello")).unwrap(),
2650 Value::Bool(true)
2651 );
2652 assert_eq!(
2653 eval_binary_op(&s("say hello"), &BinaryOp::Regex, &s("^hello")).unwrap(),
2654 Value::Bool(false)
2655 );
2656
2657 assert_eq!(
2659 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("world$")).unwrap(),
2660 Value::Bool(true)
2661 );
2662 assert_eq!(
2663 eval_binary_op(&s("world hello"), &BinaryOp::Regex, &s("world$")).unwrap(),
2664 Value::Bool(false)
2665 );
2666
2667 assert_eq!(
2669 eval_binary_op(&s("hello"), &BinaryOp::Regex, &s("^hello$")).unwrap(),
2670 Value::Bool(true)
2671 );
2672 assert_eq!(
2673 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("^hello$")).unwrap(),
2674 Value::Bool(false)
2675 );
2676 }
2677
2678 #[test]
2679 fn test_temporal_arithmetic() {
2680 let dt = s("2024-01-15T10:00:00Z");
2682 let dur = Value::Int(3_600_000_000_i64);
2683 let result = eval_binary_op(&dt, &BinaryOp::Add, &dur).unwrap();
2684 assert!(result.to_string().contains("11:00"));
2685
2686 let d = s("2024-01-01");
2688 let dur_day = Value::Int(86_400_000_000_i64);
2689 let result = eval_binary_op(&d, &BinaryOp::Add, &dur_day).unwrap();
2690 assert_eq!(result.to_string(), "2024-01-02");
2691
2692 let dt1 = s("2024-01-02T00:00:00Z");
2694 let dt2 = s("2024-01-01T00:00:00Z");
2695 let result = eval_binary_op(&dt1, &BinaryOp::Sub, &dt2).unwrap();
2696 let dur_str = result.to_string();
2698 assert!(dur_str.starts_with('P'));
2699 assert!(dur_str.contains("24H")); }
2701
2702 #[test]
2706 fn test_temporal_arithmetic_edge_cases() {
2707 let dt = s("2024-01-15T10:00:00Z");
2709 let neg_dur = Value::Int(-3_600_000_000_i64); let result = eval_binary_op(&dt, &BinaryOp::Add, &neg_dur).unwrap();
2711 assert!(result.to_string().contains("09:00"));
2712
2713 let dur1 = s("PT1H"); let dur2 = s("PT2H"); let result = eval_binary_op(&dur1, &BinaryOp::Sub, &dur2).unwrap();
2717 let dur_str = result.to_string();
2719 assert!(dur_str.starts_with('P') || dur_str.starts_with("-P"));
2720
2721 let dt = s("2024-01-15T10:00:00Z");
2723 let zero_dur = Value::Int(0_i64);
2724 let result = eval_binary_op(&dt, &BinaryOp::Add, &zero_dur).unwrap();
2725 assert!(result.to_string().contains("10:00"));
2726
2727 let d = s("2023-12-31");
2729 let one_day = Value::Int(86_400_000_000_i64);
2730 let result = eval_binary_op(&d, &BinaryOp::Add, &one_day).unwrap();
2731 assert_eq!(result.to_string(), "2024-01-01");
2732
2733 let dt1 = s("2024-01-15T10:00:00Z");
2735 let dt2 = s("2024-01-15T10:00:00Z");
2736 let result = eval_binary_op(&dt1, &BinaryOp::Sub, &dt2).unwrap();
2737 let dur_str = result.to_string();
2739 assert!(dur_str.starts_with('P'));
2740
2741 let leap_day = s("2024-02-28");
2743 let one_day = Value::Int(86_400_000_000_i64);
2744 let result = eval_binary_op(&leap_day, &BinaryOp::Add, &one_day).unwrap();
2745 assert_eq!(result.to_string(), "2024-02-29");
2746 }
2747
2748 #[test]
2749 fn test_regex_empty_string() {
2750 assert_eq!(
2752 eval_binary_op(&s(""), &BinaryOp::Regex, &s("^$")).unwrap(),
2753 Value::Bool(true)
2754 );
2755
2756 assert_eq!(
2758 eval_binary_op(&s(""), &BinaryOp::Regex, &s(".+")).unwrap(),
2759 Value::Bool(false)
2760 );
2761
2762 assert_eq!(
2764 eval_binary_op(&s("hello"), &BinaryOp::Regex, &s(".*")).unwrap(),
2765 Value::Bool(true)
2766 );
2767 }
2768
2769 #[test]
2770 fn test_regex_type_errors() {
2771 let result = eval_binary_op(&Value::Int(123), &BinaryOp::Regex, &s("\\d+"));
2773 assert!(result.is_err());
2774 assert!(result.unwrap_err().to_string().contains("must be a string"));
2775
2776 let result = eval_binary_op(&s("hello"), &BinaryOp::Regex, &Value::Int(123));
2778 assert!(result.is_err());
2779 assert!(result.unwrap_err().to_string().contains("pattern string"));
2780 }
2781
2782 #[test]
2783 fn test_and_null_handling() {
2784 assert_eq!(
2788 eval_binary_op(&Value::Bool(false), &BinaryOp::And, &Value::Null).unwrap(),
2789 Value::Bool(false)
2790 );
2791 assert_eq!(
2792 eval_binary_op(&Value::Null, &BinaryOp::And, &Value::Bool(false)).unwrap(),
2793 Value::Bool(false)
2794 );
2795
2796 assert_eq!(
2798 eval_binary_op(&Value::Bool(true), &BinaryOp::And, &Value::Null).unwrap(),
2799 Value::Null
2800 );
2801 assert_eq!(
2802 eval_binary_op(&Value::Null, &BinaryOp::And, &Value::Bool(true)).unwrap(),
2803 Value::Null
2804 );
2805
2806 assert_eq!(
2808 eval_binary_op(&Value::Null, &BinaryOp::And, &Value::Null).unwrap(),
2809 Value::Null
2810 );
2811
2812 assert_eq!(
2814 eval_binary_op(&Value::Bool(true), &BinaryOp::And, &Value::Bool(true)).unwrap(),
2815 Value::Bool(true)
2816 );
2817 assert_eq!(
2818 eval_binary_op(&Value::Bool(true), &BinaryOp::And, &Value::Bool(false)).unwrap(),
2819 Value::Bool(false)
2820 );
2821 }
2822
2823 #[test]
2824 fn test_or_null_handling() {
2825 assert_eq!(
2829 eval_binary_op(&Value::Bool(true), &BinaryOp::Or, &Value::Null).unwrap(),
2830 Value::Bool(true)
2831 );
2832 assert_eq!(
2833 eval_binary_op(&Value::Null, &BinaryOp::Or, &Value::Bool(true)).unwrap(),
2834 Value::Bool(true)
2835 );
2836
2837 assert_eq!(
2839 eval_binary_op(&Value::Bool(false), &BinaryOp::Or, &Value::Null).unwrap(),
2840 Value::Null
2841 );
2842 assert_eq!(
2843 eval_binary_op(&Value::Null, &BinaryOp::Or, &Value::Bool(false)).unwrap(),
2844 Value::Null
2845 );
2846
2847 assert_eq!(
2849 eval_binary_op(&Value::Null, &BinaryOp::Or, &Value::Null).unwrap(),
2850 Value::Null
2851 );
2852
2853 assert_eq!(
2855 eval_binary_op(&Value::Bool(false), &BinaryOp::Or, &Value::Bool(false)).unwrap(),
2856 Value::Bool(false)
2857 );
2858 assert_eq!(
2859 eval_binary_op(&Value::Bool(true), &BinaryOp::Or, &Value::Bool(false)).unwrap(),
2860 Value::Bool(true)
2861 );
2862 }
2863
2864 #[test]
2865 fn test_nan_comparison_with_non_numeric() {
2866 let nan = Value::Float(f64::NAN);
2867
2868 assert_eq!(
2870 eval_binary_op(&nan, &BinaryOp::Gt, &i(1)).unwrap(),
2871 Value::Bool(false)
2872 );
2873
2874 assert_eq!(
2876 eval_binary_op(&nan, &BinaryOp::Gt, &nan).unwrap(),
2877 Value::Bool(false)
2878 );
2879
2880 assert_eq!(
2882 eval_binary_op(&nan, &BinaryOp::Gt, &s("a")).unwrap(),
2883 Value::Null
2884 );
2885
2886 assert_eq!(
2888 eval_binary_op(&s("a"), &BinaryOp::Lt, &nan).unwrap(),
2889 Value::Null
2890 );
2891 }
2892
2893 #[test]
2894 fn test_nan_equality_with_non_numeric() {
2895 let nan = Value::Float(f64::NAN);
2896
2897 assert_eq!(
2899 eval_binary_op(&nan, &BinaryOp::Eq, &nan).unwrap(),
2900 Value::Bool(false)
2901 );
2902
2903 assert_eq!(
2905 eval_binary_op(&nan, &BinaryOp::NotEq, &nan).unwrap(),
2906 Value::Bool(true)
2907 );
2908
2909 assert_eq!(
2911 eval_binary_op(&nan, &BinaryOp::Eq, &s("a")).unwrap(),
2912 Value::Bool(false)
2913 );
2914
2915 assert_eq!(
2917 eval_binary_op(&nan, &BinaryOp::NotEq, &s("a")).unwrap(),
2918 Value::Bool(true)
2919 );
2920 }
2921
2922 #[test]
2923 fn test_large_integer_equality() {
2924 let a = Value::Int(4611686018427387905_i64);
2926 let b = Value::Int(4611686018427387900_i64);
2927
2928 assert_eq!(
2929 eval_binary_op(&a, &BinaryOp::Eq, &b).unwrap(),
2930 Value::Bool(false)
2931 );
2932 assert_eq!(
2933 eval_binary_op(&a, &BinaryOp::Eq, &a).unwrap(),
2934 Value::Bool(true)
2935 );
2936 }
2937
2938 #[test]
2939 fn test_large_integer_ordering() {
2940 let a = Value::Int(4611686018427387905_i64);
2941 let b = Value::Int(4611686018427387900_i64);
2942
2943 assert_eq!(
2944 eval_binary_op(&a, &BinaryOp::Gt, &b).unwrap(),
2945 Value::Bool(true)
2946 );
2947 assert_eq!(
2948 eval_binary_op(&b, &BinaryOp::Lt, &a).unwrap(),
2949 Value::Bool(true)
2950 );
2951 }
2952
2953 #[test]
2954 fn test_int_float_equality_still_works() {
2955 assert_eq!(
2957 eval_binary_op(&i(1), &BinaryOp::Eq, &Value::Float(1.0)).unwrap(),
2958 Value::Bool(true)
2959 );
2960 assert_eq!(
2961 eval_binary_op(&i(1), &BinaryOp::NotEq, &Value::Float(1.0)).unwrap(),
2962 Value::Bool(false)
2963 );
2964 }
2965
2966 #[test]
2967 fn test_xor_null_handling() {
2968 assert_eq!(
2971 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Null).unwrap(),
2972 Value::Null
2973 );
2974 assert_eq!(
2975 eval_binary_op(&Value::Bool(false), &BinaryOp::Xor, &Value::Null).unwrap(),
2976 Value::Null
2977 );
2978 assert_eq!(
2979 eval_binary_op(&Value::Null, &BinaryOp::Xor, &Value::Bool(true)).unwrap(),
2980 Value::Null
2981 );
2982 assert_eq!(
2983 eval_binary_op(&Value::Null, &BinaryOp::Xor, &Value::Null).unwrap(),
2984 Value::Null
2985 );
2986
2987 assert_eq!(
2989 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Bool(false)).unwrap(),
2990 Value::Bool(true)
2991 );
2992 assert_eq!(
2993 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Bool(true)).unwrap(),
2994 Value::Bool(false)
2995 );
2996 }
2997}