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 "SPARSE_SIMILAR_TO" => {
1929 if args.len() != 2 {
1930 return Err(anyhow!("sparse_similar_to takes 2 arguments"));
1931 }
1932 crate::similar_to::eval_sparse_similar_to_pure(&args[0], &args[1])
1933 }
1934
1935 _ if is_btic_function(&name_upper) => eval_btic_function(&name_upper, args),
1937
1938 _ => {
1939 if let Some(func) = custom_fns.and_then(|r| r.get(name)) {
1941 return func(args).map_err(|e| anyhow!("{}", e));
1942 }
1943 Err(anyhow!("Function {} not implemented or is aggregate", name))
1944 }
1945 }
1946}
1947
1948fn eval_valid_at(args: &[Value]) -> Result<Value> {
1956 if args.len() != 4 {
1957 return Err(anyhow!(
1958 "validAt requires 4 arguments: node, start_prop, end_prop, time"
1959 ));
1960 }
1961
1962 let node_map = match &args[0] {
1963 Value::Map(map) => map,
1964 Value::Null => return Ok(Value::Bool(false)),
1965 _ => {
1966 return Err(anyhow!(
1967 "validAt expects a Node or Edge (Object) as first argument"
1968 ));
1969 }
1970 };
1971
1972 let start_prop = args[1]
1973 .as_str()
1974 .ok_or_else(|| anyhow!("start_prop must be a string"))?;
1975 let end_prop = args[2]
1976 .as_str()
1977 .ok_or_else(|| anyhow!("end_prop must be a string"))?;
1978
1979 let time_str = match &args[3] {
1980 Value::String(s) => s,
1981 _ => return Err(anyhow!("time argument must be a datetime string")),
1982 };
1983
1984 let query_time = parse_datetime_utc(time_str)
1985 .map_err(|_| anyhow!("Invalid query time format: {}", time_str))?;
1986
1987 let valid_from_val = node_map.get(start_prop);
1988 let valid_from = match valid_from_val {
1989 Some(Value::String(s)) => parse_datetime_utc(s)
1990 .map_err(|_| anyhow!("Invalid datetime in property {}: {}", start_prop, s))?,
1991 Some(Value::Null) | None => return Ok(Value::Bool(false)),
1992 _ => return Err(anyhow!("Property {} must be a datetime string", start_prop)),
1993 };
1994
1995 let valid_to_val = node_map.get(end_prop);
1996 let valid_to = match valid_to_val {
1997 Some(Value::String(s)) => Some(
1998 parse_datetime_utc(s)
1999 .map_err(|_| anyhow!("Invalid datetime in property {}: {}", end_prop, s))?,
2000 ),
2001 Some(Value::Null) | None => None,
2002 _ => {
2003 return Err(anyhow!(
2004 "Property {} must be a datetime string or null",
2005 end_prop
2006 ));
2007 }
2008 };
2009
2010 let is_valid = valid_from <= query_time && valid_to.map(|vt| query_time < vt).unwrap_or(true);
2012
2013 Ok(Value::Bool(is_valid))
2014}
2015
2016fn vector_arg_to_f64(v: &Value) -> Result<Vec<f64>> {
2026 match v {
2027 Value::Vector(a) => Ok(a.iter().map(|&f| f as f64).collect()),
2028 Value::List(a) => a
2029 .iter()
2030 .map(|e| {
2031 e.as_f64()
2032 .ok_or_else(|| anyhow!("Vector element not a number"))
2033 })
2034 .collect(),
2035 _ => Err(anyhow!("vector argument must be an array")),
2036 }
2037}
2038
2039pub fn eval_vector_similarity(v1: &Value, v2: &Value) -> Result<Value> {
2041 let arr1 = vector_arg_to_f64(v1)?;
2042 let arr2 = vector_arg_to_f64(v2)?;
2043
2044 if arr1.len() != arr2.len() {
2045 return Err(anyhow!(
2046 "Vector dimensions mismatch: {} vs {}",
2047 arr1.len(),
2048 arr2.len()
2049 ));
2050 }
2051
2052 let mut dot = 0.0;
2053 let mut norm1_sq = 0.0;
2054 let mut norm2_sq = 0.0;
2055
2056 for (&f1, &f2) in arr1.iter().zip(arr2.iter()) {
2057 dot += f1 * f2;
2058 norm1_sq += f1 * f1;
2059 norm2_sq += f2 * f2;
2060 }
2061
2062 let mag1 = norm1_sq.sqrt();
2063 let mag2 = norm2_sq.sqrt();
2064
2065 let sim = if mag1 == 0.0 || mag2 == 0.0 {
2066 0.0
2067 } else {
2068 dot / (mag1 * mag2)
2069 };
2070
2071 Ok(Value::Float(sim))
2072}
2073
2074pub fn eval_vector_distance(v1: &Value, v2: &Value, metric: &str) -> Result<Value> {
2076 let arr1 = vector_arg_to_f64(v1)?;
2077 let arr2 = vector_arg_to_f64(v2)?;
2078
2079 if arr1.len() != arr2.len() {
2080 return Err(anyhow!(
2081 "Vector dimensions mismatch: {} vs {}",
2082 arr1.len(),
2083 arr2.len()
2084 ));
2085 }
2086
2087 let pairs = || arr1.iter().copied().zip(arr2.iter().copied());
2088
2089 match metric.to_lowercase().as_str() {
2090 "cosine" => {
2091 let mut dot = 0.0;
2093 let mut norm1_sq = 0.0;
2094 let mut norm2_sq = 0.0;
2095
2096 for (f1, f2) in pairs() {
2097 dot += f1 * f2;
2098 norm1_sq += f1 * f1;
2099 norm2_sq += f2 * f2;
2100 }
2101
2102 let mag1 = norm1_sq.sqrt();
2103 let mag2 = norm2_sq.sqrt();
2104
2105 if mag1 == 0.0 || mag2 == 0.0 {
2106 Ok(Value::Float(1.0))
2107 } else {
2108 let sim = dot / (mag1 * mag2);
2109 let sim = sim.clamp(-1.0, 1.0);
2111 Ok(Value::Float(1.0 - sim))
2112 }
2113 }
2114 "euclidean" | "l2" => {
2115 let mut sum_sq_diff = 0.0;
2116 for (f1, f2) in pairs() {
2117 let diff = f1 - f2;
2118 sum_sq_diff += diff * diff;
2119 }
2120 Ok(Value::Float(sum_sq_diff.sqrt()))
2121 }
2122 "dot" | "inner_product" => {
2123 let mut dot = 0.0;
2124 for (f1, f2) in pairs() {
2125 dot += f1 * f2;
2126 }
2127 Ok(Value::Float(1.0 - dot))
2128 }
2129 _ => Err(anyhow!("Unknown metric: {}", metric)),
2130 }
2131}
2132
2133pub fn is_scalar_function(name: &str) -> bool {
2135 let name_upper = name.to_uppercase();
2136 matches!(
2137 name_upper.as_str(),
2138 "COALESCE"
2139 | "NULLIF"
2140 | "SIZE"
2141 | "KEYS"
2142 | "HEAD"
2143 | "TAIL"
2144 | "LAST"
2145 | "LENGTH"
2146 | "NODES"
2147 | "RELATIONSHIPS"
2148 | "TOINTEGER"
2149 | "TOINT"
2150 | "TOFLOAT"
2151 | "TOSTRING"
2152 | "TOBOOLEAN"
2153 | "TOBOOL"
2154 | "ABS"
2155 | "CEIL"
2156 | "FLOOR"
2157 | "ROUND"
2158 | "SQRT"
2159 | "SIGN"
2160 | "LOG"
2161 | "LOG10"
2162 | "EXP"
2163 | "POWER"
2164 | "POW"
2165 | "SIN"
2166 | "COS"
2167 | "TAN"
2168 | "ASIN"
2169 | "ACOS"
2170 | "ATAN"
2171 | "ATAN2"
2172 | "DEGREES"
2173 | "RADIANS"
2174 | "HAVERSIN"
2175 | "PI"
2176 | "E"
2177 | "RAND"
2178 | "TOUPPER"
2179 | "UPPER"
2180 | "TOLOWER"
2181 | "LOWER"
2182 | "TRIM"
2183 | "LTRIM"
2184 | "RTRIM"
2185 | "REVERSE"
2186 | "REPLACE"
2187 | "SPLIT"
2188 | "SUBSTRING"
2189 | "LEFT"
2190 | "RIGHT"
2191 | "LPAD"
2192 | "RPAD"
2193 | "RANGE"
2194 | "UNI.VALIDAT"
2195 | "VALIDAT"
2196 | "SIMILAR_TO"
2197 | "VECTOR_SIMILARITY"
2198 | "SPARSE_SIMILAR_TO"
2199 | "VECTOR_DISTANCE"
2200 | "DATE"
2201 | "TIME"
2202 | "DATETIME"
2203 | "DURATION"
2204 | "YEAR"
2205 | "MONTH"
2206 | "DAY"
2207 | "HOUR"
2208 | "MINUTE"
2209 | "SECOND"
2210 | "ID"
2211 | "ELEMENTID"
2212 | "TYPE"
2213 | "LABELS"
2214 | "PROPERTIES"
2215 | "STARTNODE"
2216 | "ENDNODE"
2217 | "ANY"
2218 | "ALL"
2219 | "NONE"
2220 | "SINGLE"
2221 ) || is_btic_function(&name_upper)
2222}
2223
2224pub fn is_btic_function(name_upper: &str) -> bool {
2226 matches!(
2227 name_upper,
2228 "BTIC_LO"
2229 | "BTIC_HI"
2230 | "BTIC_DURATION"
2231 | "BTIC_CONTAINS_POINT"
2232 | "BTIC_OVERLAPS"
2233 | "BTIC_IS_INSTANT"
2234 | "BTIC_IS_UNBOUNDED"
2235 | "BTIC_IS_FINITE"
2236 | "BTIC_GRANULARITY"
2237 | "BTIC_LO_GRANULARITY"
2238 | "BTIC_HI_GRANULARITY"
2239 | "BTIC_CERTAINTY"
2240 | "BTIC_LO_CERTAINTY"
2241 | "BTIC_HI_CERTAINTY"
2242 | "BTIC_CONTAINS"
2243 | "BTIC_BEFORE"
2244 | "BTIC_AFTER"
2245 | "BTIC_MEETS"
2246 | "BTIC_ADJACENT"
2247 | "BTIC_DISJOINT"
2248 | "BTIC_EQUALS"
2249 | "BTIC_STARTS"
2250 | "BTIC_DURING"
2251 | "BTIC_FINISHES"
2252 | "BTIC_INTERSECTION"
2253 | "BTIC_SPAN"
2254 | "BTIC_GAP"
2255 )
2256}
2257
2258pub fn eval_btic_function(name: &str, args: &[Value]) -> Result<Value> {
2260 match name {
2261 "BTIC_LO" => {
2262 let Some((lo, _, _)) = require_btic_1arg(name, args)? else {
2263 return Ok(Value::Null);
2264 };
2265 if lo == i64::MIN {
2266 return Ok(Value::Null);
2267 }
2268 Ok(ms_to_utc_datetime(lo))
2269 }
2270 "BTIC_HI" => {
2271 let Some((_, hi, _)) = require_btic_1arg(name, args)? else {
2272 return Ok(Value::Null);
2273 };
2274 if hi == i64::MAX {
2275 return Ok(Value::Null);
2276 }
2277 Ok(ms_to_utc_datetime(hi))
2278 }
2279 "BTIC_DURATION" => {
2280 let Some((lo, hi, _)) = require_btic_1arg(name, args)? else {
2281 return Ok(Value::Null);
2282 };
2283 if lo == i64::MIN || hi == i64::MAX {
2284 Ok(Value::Null)
2285 } else {
2286 Ok(Value::Int(hi - lo))
2287 }
2288 }
2289 "BTIC_IS_INSTANT" => {
2290 let Some((lo, hi, _)) = require_btic_1arg(name, args)? else {
2291 return Ok(Value::Null);
2292 };
2293 Ok(Value::Bool(hi == lo + 1))
2294 }
2295 "BTIC_IS_UNBOUNDED" => {
2296 let Some((lo, hi, _)) = require_btic_1arg(name, args)? else {
2297 return Ok(Value::Null);
2298 };
2299 Ok(Value::Bool(lo == i64::MIN || hi == i64::MAX))
2300 }
2301 "BTIC_IS_FINITE" => {
2302 let Some((lo, hi, _)) = require_btic_1arg(name, args)? else {
2303 return Ok(Value::Null);
2304 };
2305 Ok(Value::Bool(lo != i64::MIN && hi != i64::MAX))
2306 }
2307 "BTIC_GRANULARITY" | "BTIC_LO_GRANULARITY" => {
2308 let Some(btic) = require_btic_1arg_parsed(name, args)? else {
2309 return Ok(Value::Null);
2310 };
2311 Ok(Value::String(btic.lo_granularity().name().to_string()))
2312 }
2313 "BTIC_HI_GRANULARITY" => {
2314 let Some(btic) = require_btic_1arg_parsed(name, args)? else {
2315 return Ok(Value::Null);
2316 };
2317 Ok(Value::String(btic.hi_granularity().name().to_string()))
2318 }
2319 "BTIC_CERTAINTY" | "BTIC_LO_CERTAINTY" => {
2320 let Some(btic) = require_btic_1arg_parsed(name, args)? else {
2321 return Ok(Value::Null);
2322 };
2323 if name == "BTIC_CERTAINTY" {
2324 let lc = btic.lo_certainty();
2325 let hc = btic.hi_certainty();
2326 Ok(Value::String(lc.least_certain(hc).name().to_string()))
2327 } else {
2328 Ok(Value::String(btic.lo_certainty().name().to_string()))
2329 }
2330 }
2331 "BTIC_HI_CERTAINTY" => {
2332 let Some(btic) = require_btic_1arg_parsed(name, args)? else {
2333 return Ok(Value::Null);
2334 };
2335 Ok(Value::String(btic.hi_certainty().name().to_string()))
2336 }
2337 "BTIC_CONTAINS_POINT" => {
2338 if args.len() != 2 {
2339 return Err(anyhow!("btic_contains_point requires 2 arguments"));
2340 }
2341 if args[0].is_null() || args[1].is_null() {
2342 return Ok(Value::Null);
2343 }
2344 match &args[0] {
2345 Value::Temporal(TemporalValue::Btic { lo, hi, .. }) => {
2346 let point_ms = extract_point_ms(&args[1])?;
2347 Ok(Value::Bool(*lo <= point_ms && point_ms < *hi))
2348 }
2349 _ => Err(anyhow!("btic_contains_point: first arg must be BTIC")),
2350 }
2351 }
2352 "BTIC_OVERLAPS" | "BTIC_CONTAINS" | "BTIC_BEFORE" | "BTIC_AFTER" | "BTIC_MEETS"
2354 | "BTIC_ADJACENT" | "BTIC_DISJOINT" | "BTIC_EQUALS" | "BTIC_STARTS" | "BTIC_DURING"
2355 | "BTIC_FINISHES" => eval_btic_binary_predicate(name, args),
2356 "BTIC_INTERSECTION" => eval_btic_set_op(name, args, uni_btic::set_ops::intersection),
2358 "BTIC_SPAN" => eval_btic_set_op(name, args, |a, b| Some(uni_btic::set_ops::span(a, b))),
2359 "BTIC_GAP" => eval_btic_set_op(name, args, uni_btic::set_ops::gap),
2360 _ => Err(anyhow!("unknown BTIC function: {}", name)),
2361 }
2362}
2363
2364fn require_btic_1arg(name: &str, args: &[Value]) -> Result<Option<(i64, i64, u64)>> {
2367 if args.len() != 1 {
2368 return Err(anyhow!("{} requires 1 argument", name.to_lowercase()));
2369 }
2370 match &args[0] {
2371 Value::Temporal(TemporalValue::Btic { lo, hi, meta }) => Ok(Some((*lo, *hi, *meta))),
2372 Value::Null => Ok(None),
2373 _ => Err(anyhow!("{}: expected BTIC value", name.to_lowercase())),
2374 }
2375}
2376
2377fn require_btic_1arg_parsed(name: &str, args: &[Value]) -> Result<Option<uni_btic::Btic>> {
2380 let Some((lo, hi, meta)) = require_btic_1arg(name, args)? else {
2381 return Ok(None);
2382 };
2383 uni_btic::Btic::new(lo, hi, meta)
2384 .map(Some)
2385 .map_err(|e| anyhow!("invalid BTIC: {}", e))
2386}
2387
2388fn ms_to_utc_datetime(ms: i64) -> Value {
2390 Value::Temporal(TemporalValue::DateTime {
2391 nanos_since_epoch: ms * 1_000_000,
2392 offset_seconds: 0,
2393 timezone_name: Some("UTC".to_string()),
2394 })
2395}
2396
2397fn eval_btic_binary_predicate(name: &str, args: &[Value]) -> Result<Value> {
2399 if args.len() != 2 {
2400 return Err(anyhow!("{} requires 2 arguments", name.to_lowercase()));
2401 }
2402 if args[0].is_null() || args[1].is_null() {
2403 return Ok(Value::Null);
2404 }
2405 match (&args[0], &args[1]) {
2406 (
2407 Value::Temporal(TemporalValue::Btic {
2408 lo: a_lo, hi: a_hi, ..
2409 }),
2410 Value::Temporal(TemporalValue::Btic {
2411 lo: b_lo, hi: b_hi, ..
2412 }),
2413 ) => {
2414 let result = match name {
2415 "BTIC_OVERLAPS" => *a_lo < *b_hi && *b_lo < *a_hi,
2416 "BTIC_CONTAINS" => *a_lo <= *b_lo && *b_hi <= *a_hi,
2417 "BTIC_BEFORE" => *a_hi <= *b_lo,
2418 "BTIC_AFTER" => *b_hi <= *a_lo,
2419 "BTIC_MEETS" => *a_hi == *b_lo,
2420 "BTIC_ADJACENT" => *a_hi == *b_lo || *b_hi == *a_lo,
2421 "BTIC_DISJOINT" => *a_hi <= *b_lo || *b_hi <= *a_lo,
2422 "BTIC_EQUALS" => *a_lo == *b_lo && *a_hi == *b_hi,
2423 "BTIC_STARTS" => *a_lo == *b_lo && *a_hi < *b_hi,
2424 "BTIC_DURING" => *b_lo < *a_lo && *a_hi < *b_hi,
2425 "BTIC_FINISHES" => *a_hi == *b_hi && *b_lo < *a_lo,
2426 _ => unreachable!(),
2427 };
2428 Ok(Value::Bool(result))
2429 }
2430 _ => Err(anyhow!(
2431 "{}: both args must be BTIC values",
2432 name.to_lowercase()
2433 )),
2434 }
2435}
2436
2437fn eval_btic_set_op<F>(name: &str, args: &[Value], op: F) -> Result<Value>
2442where
2443 F: FnOnce(&uni_btic::Btic, &uni_btic::Btic) -> Option<uni_btic::Btic>,
2444{
2445 if args.len() != 2 {
2446 return Err(anyhow!("{} requires 2 arguments", name.to_lowercase()));
2447 }
2448 if args[0].is_null() || args[1].is_null() {
2449 return Ok(Value::Null);
2450 }
2451 match (&args[0], &args[1]) {
2452 (
2453 Value::Temporal(TemporalValue::Btic {
2454 lo: a_lo,
2455 hi: a_hi,
2456 meta: a_meta,
2457 }),
2458 Value::Temporal(TemporalValue::Btic {
2459 lo: b_lo,
2460 hi: b_hi,
2461 meta: b_meta,
2462 }),
2463 ) => {
2464 let a = uni_btic::Btic::new(*a_lo, *a_hi, *a_meta)
2465 .map_err(|e| anyhow!("invalid BTIC: {}", e))?;
2466 let b = uni_btic::Btic::new(*b_lo, *b_hi, *b_meta)
2467 .map_err(|e| anyhow!("invalid BTIC: {}", e))?;
2468 match op(&a, &b) {
2469 Some(r) => Ok(Value::Temporal(TemporalValue::Btic {
2470 lo: r.lo(),
2471 hi: r.hi(),
2472 meta: r.meta(),
2473 })),
2474 None => Ok(Value::Null),
2475 }
2476 }
2477 _ => Err(anyhow!(
2478 "{}: both args must be BTIC values",
2479 name.to_lowercase()
2480 )),
2481 }
2482}
2483
2484fn extract_point_ms(val: &Value) -> Result<i64> {
2486 match val {
2487 Value::Int(ms) => Ok(*ms),
2488 Value::Temporal(TemporalValue::DateTime {
2489 nanos_since_epoch, ..
2490 }) => Ok(*nanos_since_epoch / 1_000_000),
2491 Value::Temporal(TemporalValue::LocalDateTime {
2492 nanos_since_epoch, ..
2493 }) => Ok(*nanos_since_epoch / 1_000_000),
2494 Value::Temporal(TemporalValue::Date { days_since_epoch }) => {
2495 Ok(*days_since_epoch as i64 * 86_400_000)
2496 }
2497 _ => Err(anyhow!(
2498 "expected a temporal point value (datetime, integer ms), got {:?}",
2499 val
2500 )),
2501 }
2502}
2503
2504fn eval_bitwise_function(name: &str, args: &[Value]) -> Result<Value> {
2506 let require_int = |v: &Value, fname: &str| -> Result<i64> {
2507 v.as_i64()
2508 .ok_or_else(|| anyhow!("{} requires integer arguments", fname))
2509 };
2510
2511 let bitwise_binary = |fname: &str, op: fn(i64, i64) -> i64| -> Result<Value> {
2512 if args.len() != 2 {
2513 return Err(anyhow!("{} requires exactly 2 arguments", fname));
2514 }
2515 let l = require_int(&args[0], fname)?;
2516 let r = require_int(&args[1], fname)?;
2517 Ok(Value::Int(op(l, r)))
2518 };
2519
2520 match name {
2521 "UNI_BITWISE_OR" => bitwise_binary("uni_bitwise_or", |l, r| l | r),
2522 "UNI_BITWISE_AND" => bitwise_binary("uni_bitwise_and", |l, r| l & r),
2523 "UNI_BITWISE_XOR" => bitwise_binary("uni_bitwise_xor", |l, r| l ^ r),
2524 "UNI_BITWISE_SHIFTLEFT" => bitwise_binary("uni_bitwise_shiftLeft", |l, r| l << r),
2525 "UNI_BITWISE_SHIFTRIGHT" => bitwise_binary("uni_bitwise_shiftRight", |l, r| l >> r),
2526 "UNI_BITWISE_NOT" => {
2527 if args.len() != 1 {
2528 return Err(anyhow!("uni_bitwise_not requires exactly 1 argument"));
2529 }
2530 Ok(Value::Int(!require_int(&args[0], "uni_bitwise_not")?))
2531 }
2532 _ => Err(anyhow!("Unknown bitwise function: {}", name)),
2533 }
2534}
2535
2536#[cfg(test)]
2537mod tests {
2538 use super::*;
2539 fn s(v: &str) -> Value {
2541 Value::String(v.into())
2542 }
2543 fn i(v: i64) -> Value {
2545 Value::Int(v)
2546 }
2547
2548 #[test]
2549 fn test_binary_op_eq() {
2550 assert_eq!(
2551 eval_binary_op(&i(1), &BinaryOp::Eq, &i(1)).unwrap(),
2552 Value::Bool(true)
2553 );
2554 assert_eq!(
2555 eval_binary_op(&i(1), &BinaryOp::Eq, &i(2)).unwrap(),
2556 Value::Bool(false)
2557 );
2558 }
2559
2560 #[test]
2561 fn test_binary_op_comparison() {
2562 assert_eq!(
2563 eval_binary_op(&i(5), &BinaryOp::Gt, &i(3)).unwrap(),
2564 Value::Bool(true)
2565 );
2566 assert_eq!(
2567 eval_binary_op(&i(5), &BinaryOp::Lt, &i(3)).unwrap(),
2568 Value::Bool(false)
2569 );
2570 }
2571
2572 #[test]
2573 fn test_binary_op_xor() {
2574 assert_eq!(
2576 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Bool(true)).unwrap(),
2577 Value::Bool(false)
2578 );
2579 assert_eq!(
2581 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Bool(false)).unwrap(),
2582 Value::Bool(true)
2583 );
2584 assert_eq!(
2586 eval_binary_op(&Value::Bool(false), &BinaryOp::Xor, &Value::Bool(true)).unwrap(),
2587 Value::Bool(true)
2588 );
2589 assert_eq!(
2591 eval_binary_op(&Value::Bool(false), &BinaryOp::Xor, &Value::Bool(false)).unwrap(),
2592 Value::Bool(false)
2593 );
2594 }
2595
2596 #[test]
2597 fn test_binary_op_contains() {
2598 assert_eq!(
2599 eval_binary_op(&s("hello world"), &BinaryOp::Contains, &s("world")).unwrap(),
2600 Value::Bool(true)
2601 );
2602 }
2603
2604 #[test]
2605 fn test_scalar_function_size() {
2606 assert_eq!(
2607 eval_scalar_function("SIZE", &[Value::List(vec![i(1), i(2), i(3)])], None).unwrap(),
2608 Value::Int(3)
2609 );
2610 }
2611
2612 #[test]
2613 fn test_scalar_function_head() {
2614 assert_eq!(
2615 eval_scalar_function("HEAD", &[Value::List(vec![i(1), i(2), i(3)])], None).unwrap(),
2616 Value::Int(1)
2617 );
2618 }
2619
2620 #[test]
2621 fn test_scalar_function_coalesce() {
2622 assert_eq!(
2623 eval_scalar_function(
2624 "COALESCE",
2625 &[Value::Null, Value::Int(1), Value::Int(2)],
2626 None
2627 )
2628 .unwrap(),
2629 Value::Int(1)
2630 );
2631 }
2632
2633 #[test]
2634 fn test_vector_similarity() {
2635 let v1 = Value::List(vec![Value::Float(1.0), Value::Float(0.0)]);
2636 let v2 = Value::List(vec![Value::Float(1.0), Value::Float(0.0)]);
2637 let result = eval_vector_similarity(&v1, &v2).unwrap();
2638 assert_eq!(result.as_f64().unwrap(), 1.0);
2639 }
2640
2641 #[test]
2642 fn test_regex_match() {
2643 assert_eq!(
2645 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("hello.*")).unwrap(),
2646 Value::Bool(true)
2647 );
2648
2649 assert_eq!(
2651 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("^world")).unwrap(),
2652 Value::Bool(false)
2653 );
2654
2655 assert_eq!(
2657 eval_binary_op(&s("Hello"), &BinaryOp::Regex, &s("hello")).unwrap(),
2658 Value::Bool(false)
2659 );
2660
2661 assert_eq!(
2663 eval_binary_op(&s("Hello"), &BinaryOp::Regex, &s("(?i)hello")).unwrap(),
2664 Value::Bool(true)
2665 );
2666 }
2667
2668 #[test]
2669 fn test_regex_null_handling() {
2670 assert_eq!(
2672 eval_binary_op(&Value::Null, &BinaryOp::Regex, &s(".*")).unwrap(),
2673 Value::Null
2674 );
2675
2676 assert_eq!(
2678 eval_binary_op(&s("hello"), &BinaryOp::Regex, &Value::Null).unwrap(),
2679 Value::Null
2680 );
2681 }
2682
2683 #[test]
2684 fn test_regex_invalid_pattern() {
2685 let result = eval_binary_op(&s("hello"), &BinaryOp::Regex, &s("[invalid"));
2687 assert!(result.is_err());
2688 assert!(result.unwrap_err().to_string().contains("Invalid regex"));
2689 }
2690
2691 #[test]
2692 fn test_regex_special_characters() {
2693 assert_eq!(
2695 eval_binary_op(
2696 &s("test@example.com"),
2697 &BinaryOp::Regex,
2698 &s(r"^[\w.-]+@[\w.-]+\.\w+$")
2699 )
2700 .unwrap(),
2701 Value::Bool(true)
2702 );
2703
2704 assert_eq!(
2706 eval_binary_op(
2707 &s("123-456-7890"),
2708 &BinaryOp::Regex,
2709 &s(r"^\d{3}-\d{3}-\d{4}$")
2710 )
2711 .unwrap(),
2712 Value::Bool(true)
2713 );
2714
2715 assert_eq!(
2717 eval_binary_op(
2718 &s("1234567890"),
2719 &BinaryOp::Regex,
2720 &s(r"^\d{3}-\d{3}-\d{4}$")
2721 )
2722 .unwrap(),
2723 Value::Bool(false)
2724 );
2725 }
2726
2727 #[test]
2728 fn test_regex_anchors() {
2729 assert_eq!(
2731 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("^hello")).unwrap(),
2732 Value::Bool(true)
2733 );
2734 assert_eq!(
2735 eval_binary_op(&s("say hello"), &BinaryOp::Regex, &s("^hello")).unwrap(),
2736 Value::Bool(false)
2737 );
2738
2739 assert_eq!(
2741 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("world$")).unwrap(),
2742 Value::Bool(true)
2743 );
2744 assert_eq!(
2745 eval_binary_op(&s("world hello"), &BinaryOp::Regex, &s("world$")).unwrap(),
2746 Value::Bool(false)
2747 );
2748
2749 assert_eq!(
2751 eval_binary_op(&s("hello"), &BinaryOp::Regex, &s("^hello$")).unwrap(),
2752 Value::Bool(true)
2753 );
2754 assert_eq!(
2755 eval_binary_op(&s("hello world"), &BinaryOp::Regex, &s("^hello$")).unwrap(),
2756 Value::Bool(false)
2757 );
2758 }
2759
2760 #[test]
2761 fn test_temporal_arithmetic() {
2762 let dt = s("2024-01-15T10:00:00Z");
2764 let dur = Value::Int(3_600_000_000_i64);
2765 let result = eval_binary_op(&dt, &BinaryOp::Add, &dur).unwrap();
2766 assert!(result.to_string().contains("11:00"));
2767
2768 let d = s("2024-01-01");
2770 let dur_day = Value::Int(86_400_000_000_i64);
2771 let result = eval_binary_op(&d, &BinaryOp::Add, &dur_day).unwrap();
2772 assert_eq!(result.to_string(), "2024-01-02");
2773
2774 let dt1 = s("2024-01-02T00:00:00Z");
2776 let dt2 = s("2024-01-01T00:00:00Z");
2777 let result = eval_binary_op(&dt1, &BinaryOp::Sub, &dt2).unwrap();
2778 let dur_str = result.to_string();
2780 assert!(dur_str.starts_with('P'));
2781 assert!(dur_str.contains("24H")); }
2783
2784 #[test]
2788 fn test_temporal_arithmetic_edge_cases() {
2789 let dt = s("2024-01-15T10:00:00Z");
2791 let neg_dur = Value::Int(-3_600_000_000_i64); let result = eval_binary_op(&dt, &BinaryOp::Add, &neg_dur).unwrap();
2793 assert!(result.to_string().contains("09:00"));
2794
2795 let dur1 = s("PT1H"); let dur2 = s("PT2H"); let result = eval_binary_op(&dur1, &BinaryOp::Sub, &dur2).unwrap();
2799 let dur_str = result.to_string();
2801 assert!(dur_str.starts_with('P') || dur_str.starts_with("-P"));
2802
2803 let dt = s("2024-01-15T10:00:00Z");
2805 let zero_dur = Value::Int(0_i64);
2806 let result = eval_binary_op(&dt, &BinaryOp::Add, &zero_dur).unwrap();
2807 assert!(result.to_string().contains("10:00"));
2808
2809 let d = s("2023-12-31");
2811 let one_day = Value::Int(86_400_000_000_i64);
2812 let result = eval_binary_op(&d, &BinaryOp::Add, &one_day).unwrap();
2813 assert_eq!(result.to_string(), "2024-01-01");
2814
2815 let dt1 = s("2024-01-15T10:00:00Z");
2817 let dt2 = s("2024-01-15T10:00:00Z");
2818 let result = eval_binary_op(&dt1, &BinaryOp::Sub, &dt2).unwrap();
2819 let dur_str = result.to_string();
2821 assert!(dur_str.starts_with('P'));
2822
2823 let leap_day = s("2024-02-28");
2825 let one_day = Value::Int(86_400_000_000_i64);
2826 let result = eval_binary_op(&leap_day, &BinaryOp::Add, &one_day).unwrap();
2827 assert_eq!(result.to_string(), "2024-02-29");
2828 }
2829
2830 #[test]
2831 fn test_regex_empty_string() {
2832 assert_eq!(
2834 eval_binary_op(&s(""), &BinaryOp::Regex, &s("^$")).unwrap(),
2835 Value::Bool(true)
2836 );
2837
2838 assert_eq!(
2840 eval_binary_op(&s(""), &BinaryOp::Regex, &s(".+")).unwrap(),
2841 Value::Bool(false)
2842 );
2843
2844 assert_eq!(
2846 eval_binary_op(&s("hello"), &BinaryOp::Regex, &s(".*")).unwrap(),
2847 Value::Bool(true)
2848 );
2849 }
2850
2851 #[test]
2852 fn test_regex_type_errors() {
2853 let result = eval_binary_op(&Value::Int(123), &BinaryOp::Regex, &s("\\d+"));
2855 assert!(result.is_err());
2856 assert!(result.unwrap_err().to_string().contains("must be a string"));
2857
2858 let result = eval_binary_op(&s("hello"), &BinaryOp::Regex, &Value::Int(123));
2860 assert!(result.is_err());
2861 assert!(result.unwrap_err().to_string().contains("pattern string"));
2862 }
2863
2864 #[test]
2865 fn test_and_null_handling() {
2866 assert_eq!(
2870 eval_binary_op(&Value::Bool(false), &BinaryOp::And, &Value::Null).unwrap(),
2871 Value::Bool(false)
2872 );
2873 assert_eq!(
2874 eval_binary_op(&Value::Null, &BinaryOp::And, &Value::Bool(false)).unwrap(),
2875 Value::Bool(false)
2876 );
2877
2878 assert_eq!(
2880 eval_binary_op(&Value::Bool(true), &BinaryOp::And, &Value::Null).unwrap(),
2881 Value::Null
2882 );
2883 assert_eq!(
2884 eval_binary_op(&Value::Null, &BinaryOp::And, &Value::Bool(true)).unwrap(),
2885 Value::Null
2886 );
2887
2888 assert_eq!(
2890 eval_binary_op(&Value::Null, &BinaryOp::And, &Value::Null).unwrap(),
2891 Value::Null
2892 );
2893
2894 assert_eq!(
2896 eval_binary_op(&Value::Bool(true), &BinaryOp::And, &Value::Bool(true)).unwrap(),
2897 Value::Bool(true)
2898 );
2899 assert_eq!(
2900 eval_binary_op(&Value::Bool(true), &BinaryOp::And, &Value::Bool(false)).unwrap(),
2901 Value::Bool(false)
2902 );
2903 }
2904
2905 #[test]
2906 fn test_or_null_handling() {
2907 assert_eq!(
2911 eval_binary_op(&Value::Bool(true), &BinaryOp::Or, &Value::Null).unwrap(),
2912 Value::Bool(true)
2913 );
2914 assert_eq!(
2915 eval_binary_op(&Value::Null, &BinaryOp::Or, &Value::Bool(true)).unwrap(),
2916 Value::Bool(true)
2917 );
2918
2919 assert_eq!(
2921 eval_binary_op(&Value::Bool(false), &BinaryOp::Or, &Value::Null).unwrap(),
2922 Value::Null
2923 );
2924 assert_eq!(
2925 eval_binary_op(&Value::Null, &BinaryOp::Or, &Value::Bool(false)).unwrap(),
2926 Value::Null
2927 );
2928
2929 assert_eq!(
2931 eval_binary_op(&Value::Null, &BinaryOp::Or, &Value::Null).unwrap(),
2932 Value::Null
2933 );
2934
2935 assert_eq!(
2937 eval_binary_op(&Value::Bool(false), &BinaryOp::Or, &Value::Bool(false)).unwrap(),
2938 Value::Bool(false)
2939 );
2940 assert_eq!(
2941 eval_binary_op(&Value::Bool(true), &BinaryOp::Or, &Value::Bool(false)).unwrap(),
2942 Value::Bool(true)
2943 );
2944 }
2945
2946 #[test]
2947 fn test_nan_comparison_with_non_numeric() {
2948 let nan = Value::Float(f64::NAN);
2949
2950 assert_eq!(
2952 eval_binary_op(&nan, &BinaryOp::Gt, &i(1)).unwrap(),
2953 Value::Bool(false)
2954 );
2955
2956 assert_eq!(
2958 eval_binary_op(&nan, &BinaryOp::Gt, &nan).unwrap(),
2959 Value::Bool(false)
2960 );
2961
2962 assert_eq!(
2964 eval_binary_op(&nan, &BinaryOp::Gt, &s("a")).unwrap(),
2965 Value::Null
2966 );
2967
2968 assert_eq!(
2970 eval_binary_op(&s("a"), &BinaryOp::Lt, &nan).unwrap(),
2971 Value::Null
2972 );
2973 }
2974
2975 #[test]
2976 fn test_nan_equality_with_non_numeric() {
2977 let nan = Value::Float(f64::NAN);
2978
2979 assert_eq!(
2981 eval_binary_op(&nan, &BinaryOp::Eq, &nan).unwrap(),
2982 Value::Bool(false)
2983 );
2984
2985 assert_eq!(
2987 eval_binary_op(&nan, &BinaryOp::NotEq, &nan).unwrap(),
2988 Value::Bool(true)
2989 );
2990
2991 assert_eq!(
2993 eval_binary_op(&nan, &BinaryOp::Eq, &s("a")).unwrap(),
2994 Value::Bool(false)
2995 );
2996
2997 assert_eq!(
2999 eval_binary_op(&nan, &BinaryOp::NotEq, &s("a")).unwrap(),
3000 Value::Bool(true)
3001 );
3002 }
3003
3004 #[test]
3005 fn test_large_integer_equality() {
3006 let a = Value::Int(4611686018427387905_i64);
3008 let b = Value::Int(4611686018427387900_i64);
3009
3010 assert_eq!(
3011 eval_binary_op(&a, &BinaryOp::Eq, &b).unwrap(),
3012 Value::Bool(false)
3013 );
3014 assert_eq!(
3015 eval_binary_op(&a, &BinaryOp::Eq, &a).unwrap(),
3016 Value::Bool(true)
3017 );
3018 }
3019
3020 #[test]
3021 fn test_large_integer_ordering() {
3022 let a = Value::Int(4611686018427387905_i64);
3023 let b = Value::Int(4611686018427387900_i64);
3024
3025 assert_eq!(
3026 eval_binary_op(&a, &BinaryOp::Gt, &b).unwrap(),
3027 Value::Bool(true)
3028 );
3029 assert_eq!(
3030 eval_binary_op(&b, &BinaryOp::Lt, &a).unwrap(),
3031 Value::Bool(true)
3032 );
3033 }
3034
3035 #[test]
3036 fn test_int_float_equality_still_works() {
3037 assert_eq!(
3039 eval_binary_op(&i(1), &BinaryOp::Eq, &Value::Float(1.0)).unwrap(),
3040 Value::Bool(true)
3041 );
3042 assert_eq!(
3043 eval_binary_op(&i(1), &BinaryOp::NotEq, &Value::Float(1.0)).unwrap(),
3044 Value::Bool(false)
3045 );
3046 }
3047
3048 #[test]
3049 fn test_xor_null_handling() {
3050 assert_eq!(
3053 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Null).unwrap(),
3054 Value::Null
3055 );
3056 assert_eq!(
3057 eval_binary_op(&Value::Bool(false), &BinaryOp::Xor, &Value::Null).unwrap(),
3058 Value::Null
3059 );
3060 assert_eq!(
3061 eval_binary_op(&Value::Null, &BinaryOp::Xor, &Value::Bool(true)).unwrap(),
3062 Value::Null
3063 );
3064 assert_eq!(
3065 eval_binary_op(&Value::Null, &BinaryOp::Xor, &Value::Null).unwrap(),
3066 Value::Null
3067 );
3068
3069 assert_eq!(
3071 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Bool(false)).unwrap(),
3072 Value::Bool(true)
3073 );
3074 assert_eq!(
3075 eval_binary_op(&Value::Bool(true), &BinaryOp::Xor, &Value::Bool(true)).unwrap(),
3076 Value::Bool(false)
3077 );
3078 }
3079
3080 #[test]
3094 fn test_int_add_overflow_is_error() {
3095 let result = eval_binary_op(&i(i64::MAX), &BinaryOp::Add, &i(1));
3096 assert!(
3097 result.is_err(),
3098 "expected i64::MAX + 1 to be an arithmetic error, got {result:?}"
3099 );
3100 }
3101
3102 #[test]
3108 fn test_int_mul_precision_above_2pow53() {
3109 let big = (1i64 << 53) + 1;
3110 let result = eval_binary_op(&i(big), &BinaryOp::Mul, &i(1)).unwrap();
3111 assert_eq!(
3112 result,
3113 Value::Int(big),
3114 "expected exact integer multiplication, got {result:?}"
3115 );
3116 }
3117
3118 #[test]
3123 fn test_int_mod_zero_is_error() {
3124 let result = eval_binary_op(&i(1), &BinaryOp::Mod, &i(0));
3125 assert!(
3126 result.is_err(),
3127 "expected 1 % 0 to be a division-by-zero error, got {result:?}"
3128 );
3129 }
3130
3131 #[test]
3137 fn test_int_mod_precision_above_2pow53() {
3138 let big = (1i64 << 53) + 3;
3139 let result = eval_binary_op(&i(big), &BinaryOp::Mod, &i(10)).unwrap();
3140 assert_eq!(
3141 result,
3142 Value::Int(big % 10),
3143 "expected exact integer remainder, got {result:?}"
3144 );
3145 }
3146
3147 #[test]
3153 fn test_substring_negative_start_no_panic() {
3154 let result = eval_scalar_function("substring", &[s("hello"), i(-1), i(5)], None);
3155 let _ = result.is_ok();
3157 }
3158
3159 #[test]
3165 fn test_range_near_i64_max_no_panic() {
3166 let result = eval_scalar_function("range", &[i(i64::MAX - 1), i(i64::MAX)], None);
3167 let _ = result.is_ok();
3169 }
3170}