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