1use alloc::boxed::Box;
19use alloc::format;
20use alloc::string::{String, ToString};
21use alloc::vec::Vec;
22
23use spg_sql::ast::{BinOp, CastTarget, ColumnName, Expr, Literal, UnOp};
24use spg_storage::{ColumnSchema, DataType, Row, TsLexeme, TsQueryAst, Value};
25
26#[derive(Debug, Clone)]
30pub struct EvalContext<'a> {
31 pub columns: &'a [ColumnSchema],
32 pub table_alias: Option<&'a str>,
33 pub params: &'a [Value],
38 pub default_text_search_config: Option<&'a str>,
45}
46
47impl<'a> EvalContext<'a> {
48 pub const fn new(columns: &'a [ColumnSchema], table_alias: Option<&'a str>) -> Self {
49 Self {
50 columns,
51 table_alias,
52 params: &[],
53 default_text_search_config: None,
54 }
55 }
56
57 #[must_use]
61 pub const fn with_params(mut self, params: &'a [Value]) -> Self {
62 self.params = params;
63 self
64 }
65
66 #[must_use]
70 pub const fn with_default_text_search_config(mut self, cfg: Option<&'a str>) -> Self {
71 self.default_text_search_config = cfg;
72 self
73 }
74}
75
76#[derive(Debug, Clone, PartialEq)]
77pub enum EvalError {
78 ColumnNotFound {
79 name: String,
80 },
81 UnknownQualifier {
82 qualifier: String,
83 },
84 DivisionByZero,
85 TypeMismatch {
86 detail: String,
87 },
88 PlaceholderOutOfRange {
92 n: u16,
93 bound: u16,
94 },
95}
96
97impl core::fmt::Display for EvalError {
98 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
99 match self {
100 Self::ColumnNotFound { name } => write!(f, "column not found: {name}"),
101 Self::UnknownQualifier { qualifier } => {
102 write!(f, "unknown table qualifier: {qualifier}")
103 }
104 Self::DivisionByZero => f.write_str("division by zero"),
105 Self::TypeMismatch { detail } => write!(f, "type mismatch: {detail}"),
106 Self::PlaceholderOutOfRange { n, bound } => write!(
107 f,
108 "parameter ${n} referenced but only {bound} bound by client"
109 ),
110 }
111 }
112}
113
114pub fn eval_expr(expr: &Expr, row: &Row, ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
115 match expr {
116 Expr::Literal(l) => Ok(literal_to_value(l)),
117 Expr::Column(c) => resolve_column(c, row, ctx),
118 Expr::Placeholder(n) => {
119 let idx = usize::from(*n).saturating_sub(1);
120 ctx.params
121 .get(idx)
122 .cloned()
123 .ok_or_else(|| EvalError::PlaceholderOutOfRange {
124 n: *n,
125 bound: u16::try_from(ctx.params.len()).unwrap_or(u16::MAX),
126 })
127 }
128 Expr::Unary { op, expr } => {
129 let v = eval_expr(expr, row, ctx)?;
130 apply_unary(*op, v)
131 }
132 Expr::Binary { lhs, op, rhs } => {
133 let l = eval_expr(lhs, row, ctx)?;
134 let r = eval_expr(rhs, row, ctx)?;
135 apply_binary(*op, l, r)
136 }
137 Expr::Cast { expr, target } => {
138 let v = eval_expr(expr, row, ctx)?;
139 cast_value(v, *target)
140 }
141 Expr::IsNull { expr, negated } => {
142 let v = eval_expr(expr, row, ctx)?;
143 let is_null = matches!(v, Value::Null);
144 Ok(Value::Bool(if *negated { !is_null } else { is_null }))
145 }
146 Expr::FunctionCall { name, args } => {
147 let evaluated: Result<Vec<Value>, _> =
148 args.iter().map(|a| eval_expr(a, row, ctx)).collect();
149 apply_function(name, &evaluated?, ctx)
150 }
151 Expr::Like {
152 expr,
153 pattern,
154 negated,
155 } => {
156 let v = eval_expr(expr, row, ctx)?;
157 let p = eval_expr(pattern, row, ctx)?;
158 let (text, pat) = match (v, p) {
160 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
161 (Value::Text(a), Value::Text(b)) => (a, b),
162 (Value::Text(_), other) | (other, _) => {
163 return Err(EvalError::TypeMismatch {
164 detail: format!("LIKE requires text operands, got {:?}", other.data_type()),
165 });
166 }
167 };
168 let m = like_match(&text, &pat);
169 Ok(Value::Bool(if *negated { !m } else { m }))
170 }
171 Expr::Extract { field, source } => {
172 let v = eval_expr(source, row, ctx)?;
173 extract_field(*field, &v)
174 }
175 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
179 Err(EvalError::TypeMismatch {
180 detail: "subquery reached row eval — engine resolver bug".into(),
181 })
182 }
183 Expr::WindowFunction { .. } => Err(EvalError::TypeMismatch {
188 detail: "window function reached row eval — engine rewrite bug".into(),
189 }),
190 Expr::Array(items) => {
196 let mut materialised: Vec<Value> = Vec::with_capacity(items.len());
197 for elem in items {
198 materialised.push(eval_expr(elem, row, ctx)?);
199 }
200 let mut has_text = false;
201 let mut has_bigint = false;
202 let mut has_int = false;
203 for v in &materialised {
204 match v {
205 Value::Null => {}
206 Value::Int(_) | Value::SmallInt(_) => has_int = true,
207 Value::BigInt(_) => has_bigint = true,
208 Value::Text(_) | Value::Json(_) => has_text = true,
209 _ => has_text = true,
210 }
211 }
212 if has_text || (!has_int && !has_bigint) {
213 let out: Vec<Option<String>> = materialised
214 .into_iter()
215 .map(|v| match v {
216 Value::Null => None,
217 Value::Text(s) | Value::Json(s) => Some(s),
218 other => Some(value_to_text_for_array(&other)),
219 })
220 .collect();
221 return Ok(Value::TextArray(out));
222 }
223 if has_bigint {
224 let out: Vec<Option<i64>> = materialised
225 .into_iter()
226 .map(|v| match v {
227 Value::Null => None,
228 Value::Int(n) => Some(i64::from(n)),
229 Value::SmallInt(n) => Some(i64::from(n)),
230 Value::BigInt(n) => Some(n),
231 _ => unreachable!(),
232 })
233 .collect();
234 return Ok(Value::BigIntArray(out));
235 }
236 let out: Vec<Option<i32>> = materialised
237 .into_iter()
238 .map(|v| match v {
239 Value::Null => None,
240 Value::Int(n) => Some(n),
241 Value::SmallInt(n) => Some(i32::from(n)),
242 _ => unreachable!(),
243 })
244 .collect();
245 Ok(Value::IntArray(out))
246 }
247 Expr::ArraySubscript { target, index } => {
250 let target_v = eval_expr(target, row, ctx)?;
251 let idx_v = eval_expr(index, row, ctx)?;
252 if matches!(target_v, Value::Null) || matches!(idx_v, Value::Null) {
253 return Ok(Value::Null);
254 }
255 let i: i64 = match idx_v {
256 Value::Int(n) => i64::from(n),
257 Value::BigInt(n) => n,
258 Value::SmallInt(n) => i64::from(n),
259 other => {
260 return Err(EvalError::TypeMismatch {
261 detail: format!(
262 "array subscript must be integer, got {:?}",
263 other.data_type()
264 ),
265 });
266 }
267 };
268 if i < 1 {
269 return Ok(Value::Null);
270 }
271 let pos = (i - 1) as usize;
272 match target_v {
273 Value::TextArray(items) => match items.get(pos) {
274 Some(Some(s)) => Ok(Value::Text(s.clone())),
275 Some(None) | None => Ok(Value::Null),
276 },
277 Value::IntArray(items) => match items.get(pos) {
278 Some(Some(n)) => Ok(Value::Int(*n)),
279 Some(None) | None => Ok(Value::Null),
280 },
281 Value::BigIntArray(items) => match items.get(pos) {
282 Some(Some(n)) => Ok(Value::BigInt(*n)),
283 Some(None) | None => Ok(Value::Null),
284 },
285 other => Err(EvalError::TypeMismatch {
286 detail: format!(
287 "subscript target must be an array, got {:?}",
288 other.data_type()
289 ),
290 }),
291 }
292 }
293 Expr::AnyAll {
299 expr,
300 op,
301 array,
302 is_any,
303 } => {
304 let lhs = eval_expr(expr, row, ctx)?;
305 let arr = eval_expr(array, row, ctx)?;
306 if matches!(arr, Value::Null) {
307 return Ok(Value::Null);
308 }
309 let elems: Vec<Option<Value>> = match arr {
310 Value::TextArray(items) => items.into_iter().map(|o| o.map(Value::Text)).collect(),
311 Value::IntArray(items) => items.into_iter().map(|o| o.map(Value::Int)).collect(),
312 Value::BigIntArray(items) => {
313 items.into_iter().map(|o| o.map(Value::BigInt)).collect()
314 }
315 other => {
316 return Err(EvalError::TypeMismatch {
317 detail: format!(
318 "ANY/ALL right-hand side must be an array, got {:?}",
319 other.data_type()
320 ),
321 });
322 }
323 };
324 let mut saw_null = matches!(lhs, Value::Null);
325 let mut saw_match = false;
326 let mut saw_mismatch = false;
327 for elem in elems {
328 let elem_v = match elem {
329 Some(v) => v,
330 None => {
331 saw_null = true;
332 continue;
333 }
334 };
335 if matches!(lhs, Value::Null) {
336 saw_null = true;
337 continue;
338 }
339 match apply_binary(*op, lhs.clone(), elem_v) {
340 Ok(Value::Bool(true)) => saw_match = true,
341 Ok(Value::Bool(false)) => saw_mismatch = true,
342 Ok(Value::Null) => saw_null = true,
343 Ok(other) => {
344 return Err(EvalError::TypeMismatch {
345 detail: format!(
346 "ANY/ALL comparison didn't return Bool: {:?}",
347 other.data_type()
348 ),
349 });
350 }
351 Err(e) => return Err(e),
352 }
353 }
354 let result = if *is_any {
355 if saw_match {
356 Value::Bool(true)
357 } else if saw_null {
358 Value::Null
359 } else {
360 Value::Bool(false)
361 }
362 } else if saw_mismatch {
363 Value::Bool(false)
364 } else if saw_null {
365 Value::Null
366 } else {
367 Value::Bool(true)
368 };
369 Ok(result)
370 }
371 Expr::Case {
377 operand,
378 branches,
379 else_branch,
380 } => {
381 let operand_value = match operand {
382 Some(o) => Some(eval_expr(o, row, ctx)?),
383 None => None,
384 };
385 for (when_expr, then_expr) in branches {
386 let when_value = eval_expr(when_expr, row, ctx)?;
387 let matched = match &operand_value {
388 None => matches!(when_value, Value::Bool(true)),
389 Some(op_v) => matches!(
390 apply_binary(spg_sql::ast::BinOp::Eq, op_v.clone(), when_value)?,
391 Value::Bool(true)
392 ),
393 };
394 if matched {
395 return eval_expr(then_expr, row, ctx);
396 }
397 }
398 match else_branch {
399 Some(e) => eval_expr(e, row, ctx),
400 None => Ok(Value::Null),
401 }
402 }
403 }
404}
405
406fn value_to_text_for_array(v: &Value) -> String {
413 match v {
414 Value::Text(s) | Value::Json(s) => s.clone(),
415 Value::Int(n) => n.to_string(),
416 Value::BigInt(n) => n.to_string(),
417 Value::SmallInt(n) => n.to_string(),
418 Value::Bool(b) => {
419 if *b {
420 "true".into()
421 } else {
422 "false".into()
423 }
424 }
425 Value::Float(x) => format!("{x}"),
426 Value::Date(d) => format_date(*d),
427 Value::Timestamp(t) => format_timestamp(*t),
428 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
429 _ => format!("{v:?}"),
430 }
431}
432
433fn extract_field(field: spg_sql::ast::ExtractField, v: &Value) -> Result<Value, EvalError> {
437 use spg_sql::ast::ExtractField as F;
438 if matches!(v, Value::Null) {
439 return Ok(Value::Null);
440 }
441 if let Value::Interval { months, micros } = *v {
445 let years = months / 12;
446 let mons = months % 12;
447 let secs_total = micros / 1_000_000;
448 let frac = micros % 1_000_000;
449 let result = match field {
450 F::Year => i64::from(years),
451 F::Month => i64::from(mons),
452 F::Day => micros / 86_400_000_000,
453 F::Hour => (secs_total / 3600) % 24,
454 F::Minute => (secs_total / 60) % 60,
455 F::Second => secs_total % 60,
456 F::Microsecond => (secs_total % 60) * 1_000_000 + frac,
457 };
458 return Ok(Value::BigInt(result));
459 }
460 let (days, day_micros) = match *v {
461 Value::Date(d) => (d, 0_i64),
462 Value::Timestamp(t) => {
463 let days = t.div_euclid(86_400_000_000);
464 let day_micros = t.rem_euclid(86_400_000_000);
465 (i32::try_from(days).unwrap_or(i32::MAX), day_micros)
466 }
467 _ => {
468 return Err(EvalError::TypeMismatch {
469 detail: format!(
470 "EXTRACT requires DATE / TIMESTAMP / INTERVAL, got {:?}",
471 v.data_type()
472 ),
473 });
474 }
475 };
476 let (y, m, d) = civil_components(days);
477 let secs = day_micros / 1_000_000;
478 let hh = secs / 3600;
479 let mm = (secs / 60) % 60;
480 let ss = secs % 60;
481 let frac = day_micros % 1_000_000;
482 let result = match field {
483 F::Year => i64::from(y),
484 F::Month => i64::from(m),
485 F::Day => i64::from(d),
486 F::Hour => hh,
487 F::Minute => mm,
488 F::Second => ss,
489 F::Microsecond => ss * 1_000_000 + frac,
490 };
491 Ok(Value::BigInt(result))
492}
493
494fn civil_components(days: i32) -> (i32, u32, u32) {
497 civil_from_days(days)
498}
499
500fn like_match(text: &str, pattern: &str) -> bool {
505 let text: Vec<char> = text.chars().collect();
506 let pat: Vec<char> = pattern.chars().collect();
507 like_match_inner(&text, 0, &pat, 0)
508}
509
510fn like_match_inner(text: &[char], mut ti: usize, pat: &[char], mut pi: usize) -> bool {
511 while pi < pat.len() {
512 match pat[pi] {
513 '%' => {
514 while pi < pat.len() && pat[pi] == '%' {
516 pi += 1;
517 }
518 if pi == pat.len() {
519 return true;
520 }
521 for k in ti..=text.len() {
522 if like_match_inner(text, k, pat, pi) {
523 return true;
524 }
525 }
526 return false;
527 }
528 '_' => {
529 if ti >= text.len() {
530 return false;
531 }
532 ti += 1;
533 pi += 1;
534 }
535 '\\' if pi + 1 < pat.len() => {
536 let want = pat[pi + 1];
537 if ti >= text.len() || text[ti] != want {
538 return false;
539 }
540 ti += 1;
541 pi += 2;
542 }
543 c => {
544 if ti >= text.len() || text[ti] != c {
545 return false;
546 }
547 ti += 1;
548 pi += 1;
549 }
550 }
551 }
552 ti == text.len()
553}
554
555fn apply_function(name: &str, args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
558 match name.to_ascii_lowercase().as_str() {
559 "length" => {
560 if args.len() != 1 {
561 return Err(EvalError::TypeMismatch {
562 detail: format!("length() takes 1 arg, got {}", args.len()),
563 });
564 }
565 match &args[0] {
566 Value::Null => Ok(Value::Null),
567 Value::Text(s) => {
568 let n = i32::try_from(s.chars().count()).unwrap_or(i32::MAX);
569 Ok(Value::Int(n))
570 }
571 Value::Bytes(b) => {
576 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
577 Ok(Value::Int(n))
578 }
579 other => Err(EvalError::TypeMismatch {
580 detail: format!("length() needs text or bytea, got {:?}", other.data_type()),
581 }),
582 }
583 }
584 "octet_length" => {
588 if args.len() != 1 {
589 return Err(EvalError::TypeMismatch {
590 detail: format!("octet_length() takes 1 arg, got {}", args.len()),
591 });
592 }
593 match &args[0] {
594 Value::Null => Ok(Value::Null),
595 Value::Text(s) => {
596 let n = i32::try_from(s.len()).unwrap_or(i32::MAX);
597 Ok(Value::Int(n))
598 }
599 Value::Bytes(b) => {
600 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
601 Ok(Value::Int(n))
602 }
603 other => Err(EvalError::TypeMismatch {
604 detail: format!(
605 "octet_length() needs text or bytea, got {:?}",
606 other.data_type()
607 ),
608 }),
609 }
610 }
611 "array_length" => {
618 if args.len() != 2 {
619 return Err(EvalError::TypeMismatch {
620 detail: format!("array_length() takes 2 args, got {}", args.len()),
621 });
622 }
623 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
624 return Ok(Value::Null);
625 }
626 let len = match &args[0] {
627 Value::TextArray(items) => items.len(),
628 Value::IntArray(items) => items.len(),
629 Value::BigIntArray(items) => items.len(),
630 _ => {
631 return Err(EvalError::TypeMismatch {
632 detail: format!(
633 "array_length() first arg must be an array, got {:?}",
634 args[0].data_type()
635 ),
636 });
637 }
638 };
639 let dim: i64 = match args[1] {
640 Value::Int(n) => i64::from(n),
641 Value::BigInt(n) => n,
642 Value::SmallInt(n) => i64::from(n),
643 _ => {
644 return Err(EvalError::TypeMismatch {
645 detail: format!(
646 "array_length() second arg must be integer, got {:?}",
647 args[1].data_type()
648 ),
649 });
650 }
651 };
652 if dim != 1 {
653 return Ok(Value::Null);
654 }
655 let n = i32::try_from(len).unwrap_or(i32::MAX);
656 Ok(Value::Int(n))
657 }
658 "array_position" => {
663 if args.len() != 2 {
664 return Err(EvalError::TypeMismatch {
665 detail: format!("array_position() takes 2 args, got {}", args.len()),
666 });
667 }
668 if matches!(args[0], Value::Null) {
669 return Ok(Value::Null);
670 }
671 if matches!(args[1], Value::Null) {
672 return Ok(Value::Null);
673 }
674 match (&args[0], &args[1]) {
675 (Value::TextArray(items), Value::Text(needle)) => {
676 for (idx, item) in items.iter().enumerate() {
677 if let Some(s) = item
678 && s == needle
679 {
680 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
681 }
682 }
683 Ok(Value::Null)
684 }
685 (Value::IntArray(items), needle_v)
686 if matches!(
687 needle_v,
688 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
689 ) =>
690 {
691 let needle: i64 = match *needle_v {
692 Value::Int(n) => i64::from(n),
693 Value::SmallInt(n) => i64::from(n),
694 Value::BigInt(n) => n,
695 _ => unreachable!(),
696 };
697 for (idx, item) in items.iter().enumerate() {
698 if let Some(n) = item
699 && i64::from(*n) == needle
700 {
701 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
702 }
703 }
704 Ok(Value::Null)
705 }
706 (Value::BigIntArray(items), needle_v)
707 if matches!(
708 needle_v,
709 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
710 ) =>
711 {
712 let needle: i64 = match *needle_v {
713 Value::Int(n) => i64::from(n),
714 Value::SmallInt(n) => i64::from(n),
715 Value::BigInt(n) => n,
716 _ => unreachable!(),
717 };
718 for (idx, item) in items.iter().enumerate() {
719 if let Some(n) = item
720 && *n == needle
721 {
722 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
723 }
724 }
725 Ok(Value::Null)
726 }
727 (
728 arr @ (Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_)),
729 other,
730 ) => Err(EvalError::TypeMismatch {
731 detail: format!(
732 "array_position() needle type {:?} doesn't match array {:?}",
733 other.data_type(),
734 arr.data_type()
735 ),
736 }),
737 (other, _) => Err(EvalError::TypeMismatch {
738 detail: format!(
739 "array_position() first arg must be an array, got {:?}",
740 other.data_type()
741 ),
742 }),
743 }
744 }
745 "substring" => {
753 if !matches!(args.len(), 2 | 3) {
754 return Err(EvalError::TypeMismatch {
755 detail: format!("substring() takes 2 or 3 args, got {}", args.len()),
756 });
757 }
758 if args.iter().any(|a| matches!(a, Value::Null)) {
759 return Ok(Value::Null);
760 }
761 let start: i64 = match args[1] {
762 Value::Int(n) => i64::from(n),
763 Value::BigInt(n) => n,
764 Value::SmallInt(n) => i64::from(n),
765 _ => {
766 return Err(EvalError::TypeMismatch {
767 detail: format!(
768 "substring() start must be integer, got {:?}",
769 args[1].data_type()
770 ),
771 });
772 }
773 };
774 let length: Option<i64> = if args.len() == 3 {
775 match args[2] {
776 Value::Int(n) => Some(i64::from(n)),
777 Value::BigInt(n) => Some(n),
778 Value::SmallInt(n) => Some(i64::from(n)),
779 _ => {
780 return Err(EvalError::TypeMismatch {
781 detail: format!(
782 "substring() length must be integer, got {:?}",
783 args[2].data_type()
784 ),
785 });
786 }
787 }
788 } else {
789 None
790 };
791 let (effective_start, effective_length): (i64, Option<i64>) = match length {
794 Some(len) => {
795 let end = start.saturating_add(len);
796 if end <= 1 || len < 0 {
797 return Ok(match &args[0] {
798 Value::Text(_) => Value::Text(String::new()),
799 Value::Bytes(_) => Value::Bytes(Vec::new()),
800 other => {
801 return Err(EvalError::TypeMismatch {
802 detail: format!(
803 "substring() needs text or bytea, got {:?}",
804 other.data_type()
805 ),
806 });
807 }
808 });
809 }
810 let eff_start = start.max(1);
811 let eff_len = end - eff_start;
812 (eff_start, Some(eff_len.max(0)))
813 }
814 None => (start.max(1), None),
815 };
816 match &args[0] {
817 Value::Text(s) => {
818 let chars: Vec<char> = s.chars().collect();
820 let skip = (effective_start - 1) as usize;
821 if skip >= chars.len() {
822 return Ok(Value::Text(String::new()));
823 }
824 let take = match effective_length {
825 Some(n) => (n as usize).min(chars.len() - skip),
826 None => chars.len() - skip,
827 };
828 Ok(Value::Text(chars[skip..skip + take].iter().collect()))
829 }
830 Value::Bytes(b) => {
831 let skip = (effective_start - 1) as usize;
832 if skip >= b.len() {
833 return Ok(Value::Bytes(Vec::new()));
834 }
835 let take = match effective_length {
836 Some(n) => (n as usize).min(b.len() - skip),
837 None => b.len() - skip,
838 };
839 Ok(Value::Bytes(b[skip..skip + take].to_vec()))
840 }
841 other => Err(EvalError::TypeMismatch {
842 detail: format!(
843 "substring() needs text or bytea, got {:?}",
844 other.data_type()
845 ),
846 }),
847 }
848 }
849 "position" => {
857 if args.len() != 2 {
858 return Err(EvalError::TypeMismatch {
859 detail: format!("position() takes 2 args, got {}", args.len()),
860 });
861 }
862 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
863 return Ok(Value::Null);
864 }
865 match (&args[0], &args[1]) {
866 (Value::Text(needle), Value::Text(haystack)) => {
867 if needle.is_empty() {
868 return Ok(Value::Int(1));
869 }
870 let h_chars: Vec<char> = haystack.chars().collect();
872 let n_chars: Vec<char> = needle.chars().collect();
873 if n_chars.len() > h_chars.len() {
874 return Ok(Value::Int(0));
875 }
876 for i in 0..=h_chars.len() - n_chars.len() {
877 if h_chars[i..i + n_chars.len()] == n_chars[..] {
878 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
879 }
880 }
881 Ok(Value::Int(0))
882 }
883 (Value::Bytes(needle), Value::Bytes(haystack)) => {
884 if needle.is_empty() {
885 return Ok(Value::Int(1));
886 }
887 if needle.len() > haystack.len() {
888 return Ok(Value::Int(0));
889 }
890 for i in 0..=haystack.len() - needle.len() {
891 if &haystack[i..i + needle.len()] == needle.as_slice() {
892 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
893 }
894 }
895 Ok(Value::Int(0))
896 }
897 (a, b) => Err(EvalError::TypeMismatch {
898 detail: format!(
899 "position() operands must both be text or both bytea, got {:?} and {:?}",
900 a.data_type(),
901 b.data_type()
902 ),
903 }),
904 }
905 }
906 "upper" => {
907 if args.len() != 1 {
908 return Err(EvalError::TypeMismatch {
909 detail: format!("upper() takes 1 arg, got {}", args.len()),
910 });
911 }
912 match &args[0] {
913 Value::Null => Ok(Value::Null),
914 Value::Text(s) => Ok(Value::Text(s.to_uppercase())),
915 other => Err(EvalError::TypeMismatch {
916 detail: format!("upper() needs text, got {:?}", other.data_type()),
917 }),
918 }
919 }
920 "lower" => {
921 if args.len() != 1 {
922 return Err(EvalError::TypeMismatch {
923 detail: format!("lower() takes 1 arg, got {}", args.len()),
924 });
925 }
926 match &args[0] {
927 Value::Null => Ok(Value::Null),
928 Value::Text(s) => Ok(Value::Text(s.to_lowercase())),
929 other => Err(EvalError::TypeMismatch {
930 detail: format!("lower() needs text, got {:?}", other.data_type()),
931 }),
932 }
933 }
934 "abs" => {
935 if args.len() != 1 {
936 return Err(EvalError::TypeMismatch {
937 detail: format!("abs() takes 1 arg, got {}", args.len()),
938 });
939 }
940 match &args[0] {
941 Value::Null => Ok(Value::Null),
942 Value::Int(n) => Ok(Value::Int(n.wrapping_abs())),
943 Value::BigInt(n) => Ok(Value::BigInt(n.wrapping_abs())),
944 Value::Float(x) => Ok(Value::Float(x.abs())),
945 other => Err(EvalError::TypeMismatch {
946 detail: format!("abs() needs numeric, got {:?}", other.data_type()),
947 }),
948 }
949 }
950 "coalesce" => {
951 for a in args {
952 if !matches!(a, Value::Null) {
953 return Ok(a.clone());
954 }
955 }
956 Ok(Value::Null)
957 }
958 "date_trunc" => date_trunc(args),
959 "date_part" => date_part(args),
960 "age" => age(args),
961 "to_char" => to_char(args),
962 "encode" => encode_text(args),
964 "decode" => decode_text(args),
965 "error_on_null" => error_on_null(args),
966 "to_tsvector" => fts_to_tsvector(args, ctx),
971 "plainto_tsquery" => fts_plainto_tsquery(args, ctx),
972 "phraseto_tsquery" => fts_phraseto_tsquery(args, ctx),
973 "websearch_to_tsquery" => fts_websearch_to_tsquery(args, ctx),
974 "to_tsquery" => fts_to_tsquery(args, ctx),
975 "ts_rank" => fts_ts_rank(args),
978 "ts_rank_cd" => fts_ts_rank_cd(args),
979 "set_config" => Ok(args.get(1).cloned().unwrap_or(Value::Null)),
984 "current_setting" => Ok(Value::Text(String::new())),
985 "pg_get_serial_sequence" | "pg_get_constraintdef" | "pg_get_indexdef" => Ok(Value::Null),
991 "version" => Ok(Value::Text("PostgreSQL 16 (SPG-compat)".into())),
992 "nextval" | "currval" | "lastval" => Ok(Value::Null),
999 "setval" => Ok(args.first().cloned().unwrap_or(Value::Null)),
1000 other => Err(EvalError::TypeMismatch {
1001 detail: format!("unknown function `{other}`"),
1002 }),
1003 }
1004}
1005
1006fn fts_ts_rank(args: &[Value]) -> Result<Value, EvalError> {
1011 let (vec, query) = parse_rank_args("ts_rank", args)?;
1012 match (vec, query) {
1013 (None, _) | (_, None) => Ok(Value::Null),
1014 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank(&v, &q)))),
1015 }
1016}
1017
1018fn fts_ts_rank_cd(args: &[Value]) -> Result<Value, EvalError> {
1019 let (vec, query) = parse_rank_args("ts_rank_cd", args)?;
1020 match (vec, query) {
1021 (None, _) | (_, None) => Ok(Value::Null),
1022 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank_cd(&v, &q)))),
1023 }
1024}
1025
1026fn parse_rank_args(
1027 name: &str,
1028 args: &[Value],
1029) -> Result<
1030 (
1031 Option<Vec<spg_storage::TsLexeme>>,
1032 Option<spg_storage::TsQueryAst>,
1033 ),
1034 EvalError,
1035> {
1036 if args.len() != 2 {
1037 return Err(EvalError::TypeMismatch {
1038 detail: format!(
1039 "{name}() takes 2 args in v7.12.2 (weights array + normalisation flag are v7.12.x carve-out), got {}",
1040 args.len()
1041 ),
1042 });
1043 }
1044 let vec = match &args[0] {
1045 Value::Null => None,
1046 Value::TsVector(v) => Some(v.clone()),
1047 other => {
1048 return Err(EvalError::TypeMismatch {
1049 detail: format!(
1050 "{name}() first arg must be tsvector, got {:?}",
1051 other.data_type()
1052 ),
1053 });
1054 }
1055 };
1056 let query = match &args[1] {
1057 Value::Null => None,
1058 Value::TsQuery(q) => Some(q.clone()),
1059 other => {
1060 return Err(EvalError::TypeMismatch {
1061 detail: format!(
1062 "{name}() second arg must be tsquery, got {:?}",
1063 other.data_type()
1064 ),
1065 });
1066 }
1067 };
1068 Ok((vec, query))
1069}
1070
1071fn ts_match(l: Value, r: Value) -> Result<Value, EvalError> {
1076 let (vec, query) = match (l, r) {
1077 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
1078 (Value::TsVector(v), Value::TsQuery(q)) => (v, q),
1079 (Value::TsQuery(q), Value::TsVector(v)) => (v, q),
1080 (l, r) => {
1081 return Err(EvalError::TypeMismatch {
1082 detail: format!(
1083 "@@ requires (tsvector, tsquery), got ({:?}, {:?})",
1084 l.data_type(),
1085 r.data_type()
1086 ),
1087 });
1088 }
1089 };
1090 Ok(Value::Bool(crate::fts::ts_query_matches(&vec, &query)))
1091}
1092
1093fn fts_to_tsvector(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
1098 let (config, text) = parse_fts_args("to_tsvector", args, ctx)?;
1099 match text {
1100 None => Ok(Value::Null),
1101 Some(t) => Ok(Value::TsVector(crate::fts::to_tsvector(config, &t))),
1102 }
1103}
1104
1105fn fts_plainto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
1106 let (config, text) = parse_fts_args("plainto_tsquery", args, ctx)?;
1107 match text {
1108 None => Ok(Value::Null),
1109 Some(t) => Ok(Value::TsQuery(crate::fts::plainto_tsquery(config, &t))),
1110 }
1111}
1112
1113fn fts_phraseto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
1114 let (config, text) = parse_fts_args("phraseto_tsquery", args, ctx)?;
1115 match text {
1116 None => Ok(Value::Null),
1117 Some(t) => Ok(Value::TsQuery(crate::fts::phraseto_tsquery(config, &t))),
1118 }
1119}
1120
1121fn fts_websearch_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
1122 let (config, text) = parse_fts_args("websearch_to_tsquery", args, ctx)?;
1123 match text {
1124 None => Ok(Value::Null),
1125 Some(t) => Ok(Value::TsQuery(crate::fts::websearch_to_tsquery(config, &t))),
1126 }
1127}
1128
1129fn fts_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
1130 let (config, text) = parse_fts_args("to_tsquery", args, ctx)?;
1131 match text {
1132 None => Ok(Value::Null),
1133 Some(t) => Ok(Value::TsQuery(crate::fts::to_tsquery(config, &t)?)),
1134 }
1135}
1136
1137fn parse_fts_args(
1142 name: &str,
1143 args: &[Value],
1144 ctx: &EvalContext<'_>,
1145) -> Result<(crate::fts::TsConfig, Option<String>), EvalError> {
1146 let (config_arg, text_arg) = match args {
1147 [t] => (None, t),
1148 [c, t] => (Some(c), t),
1149 _ => {
1150 return Err(EvalError::TypeMismatch {
1151 detail: format!("{name}() takes 1 or 2 args, got {}", args.len()),
1152 });
1153 }
1154 };
1155 let config = match config_arg {
1156 None => match ctx.default_text_search_config {
1157 Some(name_str) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
1158 EvalError::TypeMismatch {
1159 detail: format!(
1160 "text search config not implemented: {name_str:?} (supported: simple, english)"
1161 ),
1162 }
1163 })?,
1164 None => crate::fts::TsConfig::Simple,
1165 },
1166 Some(Value::Null) => return Ok((crate::fts::TsConfig::Simple, None)),
1167 Some(Value::Text(name_str)) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
1168 EvalError::TypeMismatch {
1169 detail: format!(
1170 "text search config not implemented: {name_str:?} (supported: simple, english)"
1171 ),
1172 }
1173 })?,
1174 Some(other) => {
1175 return Err(EvalError::TypeMismatch {
1176 detail: format!(
1177 "{name}() config arg must be text, got {:?}",
1178 other.data_type()
1179 ),
1180 });
1181 }
1182 };
1183 let text = match text_arg {
1184 Value::Null => None,
1185 Value::Text(s) => Some(s.clone()),
1186 other => {
1187 return Err(EvalError::TypeMismatch {
1188 detail: format!(
1189 "{name}() text arg must be text, got {:?}",
1190 other.data_type()
1191 ),
1192 });
1193 }
1194 };
1195 Ok((config, text))
1196}
1197
1198fn encode_text(args: &[Value]) -> Result<Value, EvalError> {
1204 if args.len() != 2 {
1205 return Err(EvalError::TypeMismatch {
1206 detail: format!("encode() takes 2 args, got {}", args.len()),
1207 });
1208 }
1209 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
1210 return Ok(Value::Null);
1211 }
1212 let bytes: &[u8] = match &args[0] {
1213 Value::Text(s) => s.as_bytes(),
1214 other => {
1215 return Err(EvalError::TypeMismatch {
1216 detail: format!("encode() expects text bytes, got {:?}", other.data_type()),
1217 });
1218 }
1219 };
1220 let fmt = match &args[1] {
1221 Value::Text(s) => s.to_ascii_lowercase(),
1222 other => {
1223 return Err(EvalError::TypeMismatch {
1224 detail: format!("encode() format must be text, got {:?}", other.data_type()),
1225 });
1226 }
1227 };
1228 let out = match fmt.as_str() {
1229 "base64" => b64_encode(bytes, B64_STD),
1230 "base64url" => b64_encode(bytes, B64_URL),
1231 "base32hex" => b32hex_encode(bytes),
1232 "hex" => hex_encode(bytes),
1233 other => {
1234 return Err(EvalError::TypeMismatch {
1235 detail: format!("encode(): unknown format `{other}`"),
1236 });
1237 }
1238 };
1239 Ok(Value::Text(out))
1240}
1241
1242fn decode_text(args: &[Value]) -> Result<Value, EvalError> {
1246 if args.len() != 2 {
1247 return Err(EvalError::TypeMismatch {
1248 detail: format!("decode() takes 2 args, got {}", args.len()),
1249 });
1250 }
1251 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
1252 return Ok(Value::Null);
1253 }
1254 let text = match &args[0] {
1255 Value::Text(s) => s.as_str(),
1256 other => {
1257 return Err(EvalError::TypeMismatch {
1258 detail: format!("decode() expects text, got {:?}", other.data_type()),
1259 });
1260 }
1261 };
1262 let fmt = match &args[1] {
1263 Value::Text(s) => s.to_ascii_lowercase(),
1264 other => {
1265 return Err(EvalError::TypeMismatch {
1266 detail: format!("decode() format must be text, got {:?}", other.data_type()),
1267 });
1268 }
1269 };
1270 let bytes = match fmt.as_str() {
1271 "base64" => b64_decode(text, B64_STD)?,
1272 "base64url" => b64_decode(text, B64_URL)?,
1273 "base32hex" => b32hex_decode(text)?,
1274 "hex" => hex_decode(text)?,
1275 other => {
1276 return Err(EvalError::TypeMismatch {
1277 detail: format!("decode(): unknown format `{other}`"),
1278 });
1279 }
1280 };
1281 let s = String::from_utf8(bytes).map_err(|_| EvalError::TypeMismatch {
1282 detail: "decode(): result bytes are not valid UTF-8 (SPG stores raw bytes as Text)".into(),
1283 })?;
1284 Ok(Value::Text(s))
1285}
1286
1287fn error_on_null(args: &[Value]) -> Result<Value, EvalError> {
1291 if args.len() != 1 {
1292 return Err(EvalError::TypeMismatch {
1293 detail: format!("error_on_null() takes 1 arg, got {}", args.len()),
1294 });
1295 }
1296 if matches!(args[0], Value::Null) {
1297 return Err(EvalError::TypeMismatch {
1298 detail: "error_on_null(): argument is NULL".into(),
1299 });
1300 }
1301 Ok(args[0].clone())
1302}
1303
1304const B64_STD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1307const B64_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1308const B32HEX_ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHIJKLMNOPQRSTUV";
1309
1310fn b64_encode(bytes: &[u8], alpha: &[u8; 64]) -> String {
1311 let mut out = String::with_capacity((bytes.len() + 2) / 3 * 4);
1312 let mut i = 0;
1313 while i + 3 <= bytes.len() {
1314 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8) | (bytes[i + 2] as u32);
1315 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
1316 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
1317 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
1318 out.push(alpha[(n & 0x3f) as usize] as char);
1319 i += 3;
1320 }
1321 let rem = bytes.len() - i;
1322 if rem == 1 {
1323 let n = (bytes[i] as u32) << 16;
1324 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
1325 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
1326 out.push('=');
1327 out.push('=');
1328 } else if rem == 2 {
1329 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8);
1330 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
1331 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
1332 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
1333 out.push('=');
1334 }
1335 out
1336}
1337
1338fn b64_decode(text: &str, alpha: &[u8; 64]) -> Result<Vec<u8>, EvalError> {
1339 let mut lookup = [255u8; 256];
1340 for (i, &c) in alpha.iter().enumerate() {
1341 lookup[c as usize] = i as u8;
1342 }
1343 let mut out = Vec::with_capacity(text.len() * 3 / 4);
1344 let mut buf: u32 = 0;
1345 let mut bits: u32 = 0;
1346 for c in text.bytes() {
1347 if c == b'=' {
1348 break;
1349 }
1350 if c == b'\n' || c == b'\r' || c == b' ' {
1351 continue;
1352 }
1353 let v = lookup[c as usize];
1354 if v == 255 {
1355 return Err(EvalError::TypeMismatch {
1356 detail: format!("decode(base64): invalid char {:?}", c as char),
1357 });
1358 }
1359 buf = (buf << 6) | v as u32;
1360 bits += 6;
1361 if bits >= 8 {
1362 bits -= 8;
1363 out.push(((buf >> bits) & 0xff) as u8);
1364 }
1365 }
1366 Ok(out)
1367}
1368
1369fn b32hex_encode(bytes: &[u8]) -> String {
1370 let mut out = String::with_capacity((bytes.len() * 8 + 4) / 5);
1371 let mut buf: u64 = 0;
1372 let mut bits: u32 = 0;
1373 for &b in bytes {
1374 buf = (buf << 8) | b as u64;
1375 bits += 8;
1376 while bits >= 5 {
1377 bits -= 5;
1378 out.push(B32HEX_ALPHABET[((buf >> bits) & 0x1f) as usize] as char);
1379 }
1380 }
1381 if bits > 0 {
1382 out.push(B32HEX_ALPHABET[((buf << (5 - bits)) & 0x1f) as usize] as char);
1383 }
1384 while out.len() % 8 != 0 {
1386 out.push('=');
1387 }
1388 out
1389}
1390
1391fn b32hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
1392 let mut lookup = [255u8; 256];
1393 for (i, &c) in B32HEX_ALPHABET.iter().enumerate() {
1394 lookup[c as usize] = i as u8;
1395 let lower = (c as char).to_ascii_lowercase() as u8;
1397 lookup[lower as usize] = i as u8;
1398 }
1399 let mut out = Vec::with_capacity(text.len() * 5 / 8);
1400 let mut buf: u64 = 0;
1401 let mut bits: u32 = 0;
1402 for c in text.bytes() {
1403 if c == b'=' {
1404 break;
1405 }
1406 if c == b'\n' || c == b'\r' || c == b' ' {
1407 continue;
1408 }
1409 let v = lookup[c as usize];
1410 if v == 255 {
1411 return Err(EvalError::TypeMismatch {
1412 detail: format!("decode(base32hex): invalid char {:?}", c as char),
1413 });
1414 }
1415 buf = (buf << 5) | v as u64;
1416 bits += 5;
1417 if bits >= 8 {
1418 bits -= 8;
1419 out.push(((buf >> bits) & 0xff) as u8);
1420 }
1421 }
1422 Ok(out)
1423}
1424
1425fn hex_encode(bytes: &[u8]) -> String {
1426 const HEX: &[u8; 16] = b"0123456789abcdef";
1427 let mut out = String::with_capacity(bytes.len() * 2);
1428 for &b in bytes {
1429 out.push(HEX[(b >> 4) as usize] as char);
1430 out.push(HEX[(b & 0xf) as usize] as char);
1431 }
1432 out
1433}
1434
1435fn hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
1436 let trimmed = text.trim();
1437 if trimmed.len() % 2 != 0 {
1438 return Err(EvalError::TypeMismatch {
1439 detail: "decode(hex): input length must be even".into(),
1440 });
1441 }
1442 let mut out = Vec::with_capacity(trimmed.len() / 2);
1443 let mut hi: u8 = 0;
1444 for (i, c) in trimmed.bytes().enumerate() {
1445 let v = match c {
1446 b'0'..=b'9' => c - b'0',
1447 b'a'..=b'f' => c - b'a' + 10,
1448 b'A'..=b'F' => c - b'A' + 10,
1449 _ => {
1450 return Err(EvalError::TypeMismatch {
1451 detail: format!("decode(hex): invalid char {:?}", c as char),
1452 });
1453 }
1454 };
1455 if i % 2 == 0 {
1456 hi = v;
1457 } else {
1458 out.push((hi << 4) | v);
1459 }
1460 }
1461 Ok(out)
1462}
1463
1464fn date_part(args: &[Value]) -> Result<Value, EvalError> {
1469 use spg_sql::ast::ExtractField as F;
1470 if args.len() != 2 {
1471 return Err(EvalError::TypeMismatch {
1472 detail: format!("date_part() takes 2 args, got {}", args.len()),
1473 });
1474 }
1475 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
1476 return Ok(Value::Null);
1477 }
1478 let Value::Text(field_name) = &args[0] else {
1479 return Err(EvalError::TypeMismatch {
1480 detail: format!(
1481 "date_part() needs a text field, got {:?}",
1482 args[0].data_type()
1483 ),
1484 });
1485 };
1486 let field = match field_name.to_ascii_lowercase().as_str() {
1487 "year" => F::Year,
1488 "month" => F::Month,
1489 "day" => F::Day,
1490 "hour" => F::Hour,
1491 "minute" => F::Minute,
1492 "second" => F::Second,
1493 "microsecond" | "microseconds" => F::Microsecond,
1494 other => {
1495 return Err(EvalError::TypeMismatch {
1496 detail: format!(
1497 "unknown date_part field {other:?}; \
1498 supported: year, month, day, hour, minute, second, microsecond"
1499 ),
1500 });
1501 }
1502 };
1503 extract_field(field, &args[1])
1504}
1505
1506fn age(args: &[Value]) -> Result<Value, EvalError> {
1516 if args.is_empty() || args.len() > 2 {
1517 return Err(EvalError::TypeMismatch {
1518 detail: format!("age() takes 1 or 2 args, got {}", args.len()),
1519 });
1520 }
1521 if args.iter().any(|v| matches!(v, Value::Null)) {
1522 return Ok(Value::Null);
1523 }
1524 let to_micros = |v: &Value| -> Result<i64, EvalError> {
1527 match v {
1528 Value::Timestamp(t) => Ok(*t),
1529 Value::Date(d) => Ok(i64::from(*d) * 86_400_000_000),
1530 other => Err(EvalError::TypeMismatch {
1531 detail: format!("age() needs DATE or TIMESTAMP, got {:?}", other.data_type()),
1532 }),
1533 }
1534 };
1535 if args.len() == 1 {
1536 return Err(EvalError::TypeMismatch {
1537 detail: "single-arg age() is unsupported in v2.12 \
1538 (use age(CURRENT_DATE, t) explicitly)"
1539 .into(),
1540 });
1541 }
1542 let a = to_micros(&args[0])?;
1543 let b = to_micros(&args[1])?;
1544 let delta = a.checked_sub(b).ok_or(EvalError::TypeMismatch {
1545 detail: "age() subtraction overflows i64 microseconds".into(),
1546 })?;
1547 Ok(Value::Interval {
1548 months: 0,
1549 micros: delta,
1550 })
1551}
1552
1553fn to_char(args: &[Value]) -> Result<Value, EvalError> {
1559 use core::fmt::Write as _;
1560 if args.len() != 2 {
1561 return Err(EvalError::TypeMismatch {
1562 detail: format!("to_char() takes 2 args, got {}", args.len()),
1563 });
1564 }
1565 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
1566 return Ok(Value::Null);
1567 }
1568 let Value::Text(fmt) = &args[1] else {
1569 return Err(EvalError::TypeMismatch {
1570 detail: format!(
1571 "to_char() needs a text format, got {:?}",
1572 args[1].data_type()
1573 ),
1574 });
1575 };
1576 let (days, day_micros) = match &args[0] {
1577 Value::Date(d) => (*d, 0_i64),
1578 Value::Timestamp(t) => {
1579 let days = t.div_euclid(86_400_000_000);
1580 (
1581 i32::try_from(days).unwrap_or(i32::MAX),
1582 t.rem_euclid(86_400_000_000),
1583 )
1584 }
1585 other => {
1586 return Err(EvalError::TypeMismatch {
1587 detail: format!(
1588 "to_char() needs DATE or TIMESTAMP, got {:?}",
1589 other.data_type()
1590 ),
1591 });
1592 }
1593 };
1594 let (y, mo, d) = civil_from_days(days);
1595 let secs = day_micros / 1_000_000;
1596 let frac = day_micros % 1_000_000;
1597 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
1601 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
1602 let ss = u32::try_from(secs % 60).unwrap_or(0);
1603 let hh12 = match hh24 % 12 {
1604 0 => 12,
1605 x => x,
1606 };
1607 let ampm = if hh24 < 12 { "AM" } else { "PM" };
1608 let ms = u32::try_from(frac / 1_000).unwrap_or(0); let us = u32::try_from(frac).unwrap_or(0); let mut out = String::with_capacity(fmt.len() + 8);
1612 let bytes = fmt.as_bytes();
1613 let mut i = 0;
1614 while i < bytes.len() {
1616 let rest = &bytes[i..];
1618 if rest.starts_with(b"YYYY") {
1619 let _ = write!(out, "{y:04}");
1620 i += 4;
1621 } else if rest.starts_with(b"YY") {
1622 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
1623 let yy = (y.rem_euclid(100)) as u32;
1624 let _ = write!(out, "{yy:02}");
1625 i += 2;
1626 } else if rest.starts_with(b"Month") {
1627 out.push_str(MONTH_FULL[(mo - 1) as usize]);
1628 i += 5;
1629 } else if rest.starts_with(b"Mon") {
1630 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
1631 i += 3;
1632 } else if rest.starts_with(b"MM") {
1633 let _ = write!(out, "{mo:02}");
1634 i += 2;
1635 } else if rest.starts_with(b"DD") {
1636 let _ = write!(out, "{d:02}");
1637 i += 2;
1638 } else if rest.starts_with(b"HH24") {
1639 let _ = write!(out, "{hh24:02}");
1640 i += 4;
1641 } else if rest.starts_with(b"HH12") {
1642 let _ = write!(out, "{hh12:02}");
1643 i += 4;
1644 } else if rest.starts_with(b"MI") {
1645 let _ = write!(out, "{mi:02}");
1646 i += 2;
1647 } else if rest.starts_with(b"SS") {
1648 let _ = write!(out, "{ss:02}");
1649 i += 2;
1650 } else if rest.starts_with(b"MS") {
1651 let _ = write!(out, "{ms:03}");
1652 i += 2;
1653 } else if rest.starts_with(b"US") {
1654 let _ = write!(out, "{us:06}");
1655 i += 2;
1656 } else if rest.starts_with(b"AM") || rest.starts_with(b"PM") {
1657 out.push_str(ampm);
1658 i += 2;
1659 } else {
1660 out.push(bytes[i] as char);
1662 i += 1;
1663 }
1664 }
1665 Ok(Value::Text(out))
1666}
1667
1668const MONTH_FULL: [&str; 12] = [
1669 "January",
1670 "February",
1671 "March",
1672 "April",
1673 "May",
1674 "June",
1675 "July",
1676 "August",
1677 "September",
1678 "October",
1679 "November",
1680 "December",
1681];
1682const MONTH_ABBR: [&str; 12] = [
1683 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
1684];
1685
1686fn date_trunc(args: &[Value]) -> Result<Value, EvalError> {
1691 if args.len() != 2 {
1692 return Err(EvalError::TypeMismatch {
1693 detail: format!("date_trunc() takes 2 args, got {}", args.len()),
1694 });
1695 }
1696 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
1697 return Ok(Value::Null);
1698 }
1699 let Value::Text(unit) = &args[0] else {
1700 return Err(EvalError::TypeMismatch {
1701 detail: format!(
1702 "date_trunc() needs a text unit, got {:?}",
1703 args[0].data_type()
1704 ),
1705 });
1706 };
1707 let micros = match &args[1] {
1710 Value::Timestamp(t) => *t,
1711 Value::Date(d) => i64::from(*d) * 86_400_000_000,
1712 other => {
1713 return Err(EvalError::TypeMismatch {
1714 detail: format!(
1715 "date_trunc() needs DATE or TIMESTAMP, got {:?}",
1716 other.data_type()
1717 ),
1718 });
1719 }
1720 };
1721 let unit_lc = unit.to_ascii_lowercase();
1722 let days = micros.div_euclid(86_400_000_000);
1723 let day_micros = micros.rem_euclid(86_400_000_000);
1724 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
1725 let (y, m, _) = civil_from_days(day_i32);
1726 let truncated = match unit_lc.as_str() {
1727 "year" => i64::from(days_from_civil(y, 1, 1)) * 86_400_000_000,
1728 "month" => i64::from(days_from_civil(y, m, 1)) * 86_400_000_000,
1729 "day" => days * 86_400_000_000,
1730 "hour" => days * 86_400_000_000 + (day_micros / 3_600_000_000) * 3_600_000_000,
1731 "minute" => days * 86_400_000_000 + (day_micros / 60_000_000) * 60_000_000,
1732 "second" => days * 86_400_000_000 + (day_micros / 1_000_000) * 1_000_000,
1733 other => {
1734 return Err(EvalError::TypeMismatch {
1735 detail: format!(
1736 "unknown date_trunc unit {other:?}; \
1737 supported: year, month, day, hour, minute, second"
1738 ),
1739 });
1740 }
1741 };
1742 Ok(Value::Timestamp(truncated))
1743}
1744
1745pub fn cast_value(v: Value, target: CastTarget) -> Result<Value, EvalError> {
1747 if matches!(v, Value::Null) {
1748 return Ok(Value::Null);
1749 }
1750 match target {
1751 CastTarget::Vector => cast_to_vector(v),
1752 CastTarget::Text => Ok(Value::Text(value_to_text(&v))),
1753 CastTarget::Int => cast_numeric_to_int(v),
1754 CastTarget::BigInt => cast_numeric_to_bigint(v),
1755 CastTarget::Float => cast_numeric_to_float(v),
1756 CastTarget::Bool => cast_to_bool(v),
1757 CastTarget::Date => cast_to_date(v),
1758 CastTarget::Timestamp | CastTarget::Timestamptz => cast_to_timestamp(v),
1761 CastTarget::Interval => cast_to_interval(v),
1765 CastTarget::Json | CastTarget::Jsonb => match v {
1769 Value::Json(s) => Ok(Value::Json(s)),
1770 Value::Text(s) => Ok(Value::Json(s)),
1771 other => Err(EvalError::TypeMismatch {
1772 detail: alloc::format!(
1773 "::json / ::jsonb only accepts TEXT-shape inputs, got {:?}",
1774 other.data_type()
1775 ),
1776 }),
1777 },
1778 CastTarget::RegType | CastTarget::RegClass => Err(EvalError::TypeMismatch {
1781 detail: "::regtype / ::regclass not supported on SPG \
1782 (no pg_catalog); use SHOW TABLES / spg_table_ddl instead"
1783 .into(),
1784 }),
1785 CastTarget::TextArray => match v {
1789 Value::TextArray(items) => Ok(Value::TextArray(items)),
1790 Value::Text(s) => decode_text_array_external(&s).map(Value::TextArray),
1791 other => Err(EvalError::TypeMismatch {
1792 detail: alloc::format!(
1793 "::TEXT[] only accepts TEXT / TEXT[] inputs, got {:?}",
1794 other.data_type()
1795 ),
1796 }),
1797 },
1798 CastTarget::IntArray => cast_to_int_array(v),
1802 CastTarget::BigIntArray => cast_to_bigint_array(v),
1803 CastTarget::TsVector => match v {
1810 Value::TsVector(items) => Ok(Value::TsVector(items)),
1811 Value::Text(s) => decode_tsvector_external(&s).map(Value::TsVector),
1812 other => Err(EvalError::TypeMismatch {
1813 detail: alloc::format!(
1814 "::tsvector only accepts TEXT / tsvector inputs, got {:?}",
1815 other.data_type()
1816 ),
1817 }),
1818 },
1819 CastTarget::TsQuery => match v {
1820 Value::TsQuery(ast) => Ok(Value::TsQuery(ast)),
1821 Value::Text(s) => decode_tsquery_external(&s).map(Value::TsQuery),
1822 other => Err(EvalError::TypeMismatch {
1823 detail: alloc::format!(
1824 "::tsquery only accepts TEXT / tsquery inputs, got {:?}",
1825 other.data_type()
1826 ),
1827 }),
1828 },
1829 }
1830}
1831
1832fn cast_to_int_array(v: Value) -> Result<Value, EvalError> {
1833 match v {
1834 Value::IntArray(items) => Ok(Value::IntArray(items)),
1835 Value::BigIntArray(items) => {
1836 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
1837 for item in items {
1838 match item {
1839 None => out.push(None),
1840 Some(n) => match i32::try_from(n) {
1841 Ok(x) => out.push(Some(x)),
1842 Err(_) => {
1843 return Err(EvalError::TypeMismatch {
1844 detail: alloc::format!("::INT[] element {n} overflows i32"),
1845 });
1846 }
1847 },
1848 }
1849 }
1850 Ok(Value::IntArray(out))
1851 }
1852 Value::Text(s) => decode_int_array_external(&s).map(Value::IntArray),
1853 Value::TextArray(items) => {
1854 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
1855 for item in items {
1856 match item {
1857 None => out.push(None),
1858 Some(s) => match s.parse::<i32>() {
1859 Ok(n) => out.push(Some(n)),
1860 Err(_) => {
1861 return Err(EvalError::TypeMismatch {
1862 detail: alloc::format!("::INT[] cannot parse {s:?}"),
1863 });
1864 }
1865 },
1866 }
1867 }
1868 Ok(Value::IntArray(out))
1869 }
1870 other => Err(EvalError::TypeMismatch {
1871 detail: alloc::format!("::INT[] does not accept {:?}", other.data_type()),
1872 }),
1873 }
1874}
1875
1876fn cast_to_bigint_array(v: Value) -> Result<Value, EvalError> {
1877 match v {
1878 Value::BigIntArray(items) => Ok(Value::BigIntArray(items)),
1879 Value::IntArray(items) => Ok(Value::BigIntArray(
1880 items.into_iter().map(|x| x.map(i64::from)).collect(),
1881 )),
1882 Value::Text(s) => decode_bigint_array_external(&s).map(Value::BigIntArray),
1883 Value::TextArray(items) => {
1884 let mut out: Vec<Option<i64>> = Vec::with_capacity(items.len());
1885 for item in items {
1886 match item {
1887 None => out.push(None),
1888 Some(s) => match s.parse::<i64>() {
1889 Ok(n) => out.push(Some(n)),
1890 Err(_) => {
1891 return Err(EvalError::TypeMismatch {
1892 detail: alloc::format!("::BIGINT[] cannot parse {s:?}"),
1893 });
1894 }
1895 },
1896 }
1897 }
1898 Ok(Value::BigIntArray(out))
1899 }
1900 other => Err(EvalError::TypeMismatch {
1901 detail: alloc::format!("::BIGINT[] does not accept {:?}", other.data_type()),
1902 }),
1903 }
1904}
1905
1906fn decode_int_array_external(s: &str) -> Result<Vec<Option<i32>>, EvalError> {
1907 let trimmed = s.trim();
1908 let inner = trimmed
1909 .strip_prefix('{')
1910 .and_then(|x| x.strip_suffix('}'))
1911 .ok_or_else(|| EvalError::TypeMismatch {
1912 detail: alloc::format!("INT[] literal {s:?} must be enclosed in '{{...}}'"),
1913 })?;
1914 if inner.trim().is_empty() {
1915 return Ok(Vec::new());
1916 }
1917 inner
1918 .split(',')
1919 .map(|part| {
1920 let p = part.trim();
1921 if p.eq_ignore_ascii_case("NULL") {
1922 Ok(None)
1923 } else {
1924 p.parse::<i32>()
1925 .map(Some)
1926 .map_err(|_| EvalError::TypeMismatch {
1927 detail: alloc::format!("INT[] element {p:?} is not an i32"),
1928 })
1929 }
1930 })
1931 .collect()
1932}
1933
1934fn decode_bigint_array_external(s: &str) -> Result<Vec<Option<i64>>, EvalError> {
1935 let trimmed = s.trim();
1936 let inner = trimmed
1937 .strip_prefix('{')
1938 .and_then(|x| x.strip_suffix('}'))
1939 .ok_or_else(|| EvalError::TypeMismatch {
1940 detail: alloc::format!("BIGINT[] literal {s:?} must be enclosed in '{{...}}'"),
1941 })?;
1942 if inner.trim().is_empty() {
1943 return Ok(Vec::new());
1944 }
1945 inner
1946 .split(',')
1947 .map(|part| {
1948 let p = part.trim();
1949 if p.eq_ignore_ascii_case("NULL") {
1950 Ok(None)
1951 } else {
1952 p.parse::<i64>()
1953 .map(Some)
1954 .map_err(|_| EvalError::TypeMismatch {
1955 detail: alloc::format!("BIGINT[] element {p:?} is not an i64"),
1956 })
1957 }
1958 })
1959 .collect()
1960}
1961
1962fn decode_text_array_external(s: &str) -> Result<Vec<Option<String>>, EvalError> {
1967 let trimmed = s.trim();
1968 let inner = trimmed
1969 .strip_prefix('{')
1970 .and_then(|x| x.strip_suffix('}'))
1971 .ok_or_else(|| EvalError::TypeMismatch {
1972 detail: alloc::format!("TEXT[] literal {s:?} must be enclosed in '{{...}}'"),
1973 })?;
1974 let mut out: Vec<Option<String>> = Vec::new();
1975 if inner.trim().is_empty() {
1976 return Ok(out);
1977 }
1978 let bytes = inner.as_bytes();
1979 let mut i = 0;
1980 while i <= bytes.len() {
1981 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
1982 i += 1;
1983 }
1984 if i < bytes.len() && bytes[i] == b'"' {
1985 i += 1;
1986 let mut buf = String::new();
1987 while i < bytes.len() && bytes[i] != b'"' {
1988 if bytes[i] == b'\\' && i + 1 < bytes.len() {
1989 buf.push(bytes[i + 1] as char);
1990 i += 2;
1991 } else {
1992 buf.push(bytes[i] as char);
1993 i += 1;
1994 }
1995 }
1996 if i >= bytes.len() {
1997 return Err(EvalError::TypeMismatch {
1998 detail: "unterminated quoted element in TEXT[] literal".into(),
1999 });
2000 }
2001 i += 1;
2002 out.push(Some(buf));
2003 } else {
2004 let start = i;
2005 while i < bytes.len() && bytes[i] != b',' {
2006 i += 1;
2007 }
2008 let raw = inner[start..i].trim();
2009 if raw.eq_ignore_ascii_case("NULL") {
2010 out.push(None);
2011 } else {
2012 out.push(Some(raw.to_string()));
2013 }
2014 }
2015 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
2016 i += 1;
2017 }
2018 if i >= bytes.len() {
2019 break;
2020 }
2021 if bytes[i] != b',' {
2022 return Err(EvalError::TypeMismatch {
2023 detail: "expected ',' between TEXT[] elements".into(),
2024 });
2025 }
2026 i += 1;
2027 }
2028 Ok(out)
2029}
2030
2031fn cast_to_interval(v: Value) -> Result<Value, EvalError> {
2032 match v {
2033 Value::Interval { months, micros } => Ok(Value::Interval { months, micros }),
2034 Value::Text(s) => {
2035 let (months, micros) = spg_sql::parser::parse_interval_text(&s).ok_or_else(|| {
2036 EvalError::TypeMismatch {
2037 detail: alloc::format!("cannot parse {s:?} as INTERVAL"),
2038 }
2039 })?;
2040 Ok(Value::Interval { months, micros })
2041 }
2042 other => Err(EvalError::TypeMismatch {
2043 detail: alloc::format!(
2044 "::INTERVAL only accepts TEXT-shape inputs, got {:?}",
2045 other.data_type()
2046 ),
2047 }),
2048 }
2049}
2050
2051fn cast_to_date(v: Value) -> Result<Value, EvalError> {
2052 match v {
2053 Value::Date(d) => Ok(Value::Date(d)),
2054 Value::Int(n) => Ok(Value::Date(n)),
2057 Value::BigInt(n) => {
2058 i32::try_from(n)
2059 .map(Value::Date)
2060 .map_err(|_| EvalError::TypeMismatch {
2061 detail: "bigint days-since-epoch out of DATE range".into(),
2062 })
2063 }
2064 Value::Timestamp(t) => {
2066 let days = t.div_euclid(86_400_000_000);
2067 i32::try_from(days)
2068 .map(Value::Date)
2069 .map_err(|_| EvalError::TypeMismatch {
2070 detail: "timestamp out of DATE range".into(),
2071 })
2072 }
2073 Value::Text(s) => parse_date_literal(&s)
2074 .map(Value::Date)
2075 .ok_or(EvalError::TypeMismatch {
2076 detail: format!("cannot parse {s:?} as DATE (expected YYYY-MM-DD)"),
2077 }),
2078 other => Err(EvalError::TypeMismatch {
2079 detail: format!("cannot cast {:?} to DATE", other.data_type()),
2080 }),
2081 }
2082}
2083
2084fn cast_to_timestamp(v: Value) -> Result<Value, EvalError> {
2085 match v {
2086 Value::Timestamp(t) => Ok(Value::Timestamp(t)),
2087 Value::Int(n) => Ok(Value::Timestamp(i64::from(n))),
2091 Value::BigInt(n) => Ok(Value::Timestamp(n)),
2092 Value::Date(d) => Ok(Value::Timestamp(i64::from(d) * 86_400_000_000)),
2094 Value::Text(s) => {
2095 parse_timestamp_literal(&s)
2096 .map(Value::Timestamp)
2097 .ok_or(EvalError::TypeMismatch {
2098 detail: format!(
2099 "cannot parse {s:?} as TIMESTAMP \
2100 (expected YYYY-MM-DD[ HH:MM:SS[.ffffff]])"
2101 ),
2102 })
2103 }
2104 other => Err(EvalError::TypeMismatch {
2105 detail: format!("cannot cast {:?} to TIMESTAMP", other.data_type()),
2106 }),
2107 }
2108}
2109
2110fn value_to_text(v: &Value) -> String {
2111 match v {
2112 Value::SmallInt(n) => format!("{n}"),
2116 Value::Int(n) => format!("{n}"),
2117 Value::BigInt(n) => format!("{n}"),
2118 Value::Float(x) => format!("{x}"),
2119 Value::Text(s) | Value::Json(s) => s.clone(),
2121 Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
2122 Value::Vector(v) => {
2123 let cells: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
2124 format!("[{}]", cells.join(", "))
2125 }
2126 Value::Sq8Vector(q) => {
2131 let cells: Vec<String> = spg_storage::quantize::dequantize(q)
2132 .iter()
2133 .map(|x| format!("{x}"))
2134 .collect();
2135 format!("[{}]", cells.join(", "))
2136 }
2137 Value::HalfVector(h) => {
2140 let cells: Vec<String> = h.to_f32_vec().iter().map(|x| format!("{x}")).collect();
2141 format!("[{}]", cells.join(", "))
2142 }
2143 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
2144 Value::Date(d) => format_date(*d),
2145 Value::Timestamp(t) => format_timestamp(*t),
2146 Value::Interval { months, micros } => format_interval(*months, *micros),
2147 Value::Null => "NULL".into(),
2148 Value::Bytes(b) => format_bytea_hex(b),
2150 Value::TextArray(items) => format_text_array(items),
2152 Value::IntArray(items) => format_int_array(items),
2153 Value::BigIntArray(items) => format_bigint_array(items),
2154 Value::TsVector(lexs) => format_tsvector(lexs),
2156 Value::TsQuery(ast) => format_tsquery(ast),
2157 _ => format!("{v:?}"),
2159 }
2160}
2161
2162pub fn format_date(days: i32) -> String {
2165 let (y, m, d) = civil_from_days(days);
2166 format!("{y:04}-{m:02}-{d:02}")
2167}
2168
2169pub fn format_timestamp(micros: i64) -> String {
2173 const MICROS_PER_DAY: i64 = 86_400_000_000;
2174 let days = micros.div_euclid(MICROS_PER_DAY);
2177 let day_micros = micros.rem_euclid(MICROS_PER_DAY);
2178 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
2179 let (y, m, d) = civil_from_days(day_i32);
2180 let secs = day_micros / 1_000_000;
2181 let frac = day_micros % 1_000_000;
2182 let hh = secs / 3600;
2183 let mm = (secs / 60) % 60;
2184 let ss = secs % 60;
2185 if frac == 0 {
2186 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}")
2187 } else {
2188 let raw = format!("{frac:06}");
2190 let trimmed = raw.trim_end_matches('0');
2191 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}.{trimmed}")
2192 }
2193}
2194
2195#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
2200fn civil_from_days(days: i32) -> (i32, u32, u32) {
2201 let z = i64::from(days) + 719_468;
2202 let era = z.div_euclid(146_097);
2203 let doe = (z - era * 146_097) as u32;
2207 let yoe = (doe.saturating_sub(doe / 1460) + doe / 36524 - doe / 146_096) / 365;
2208 let y_base = i64::from(yoe) + era * 400;
2209 let doy = doe.saturating_sub(365 * yoe + yoe / 4 - yoe / 100);
2210 let mp = (5 * doy + 2) / 153;
2211 let d = doy.saturating_sub((153 * mp + 2) / 5) + 1;
2212 let m = if mp < 10 { mp + 3 } else { mp - 9 };
2213 let y = if m <= 2 { y_base + 1 } else { y_base };
2214 (y as i32, m, d)
2215}
2216
2217#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
2220pub fn days_from_civil(y: i32, m: u32, d: u32) -> i32 {
2221 let y_adj = if m <= 2 {
2222 i64::from(y) - 1
2223 } else {
2224 i64::from(y)
2225 };
2226 let era = y_adj.div_euclid(400);
2227 let yoe = (y_adj - era * 400) as u32;
2228 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d.saturating_sub(1);
2229 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
2230 let total = era * 146_097 + i64::from(doe) - 719_468;
2231 i32::try_from(total).unwrap_or(i32::MAX)
2232}
2233
2234pub fn parse_date_literal(s: &str) -> Option<i32> {
2238 let bytes = s.as_bytes();
2239 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
2240 return None;
2241 }
2242 let y: i32 = s[0..4].parse().ok()?;
2243 let m: u32 = s[5..7].parse().ok()?;
2244 let d: u32 = s[8..10].parse().ok()?;
2245 if !(1..=12).contains(&m) || !(1..=31).contains(&d) {
2246 return None;
2247 }
2248 Some(days_from_civil(y, m, d))
2249}
2250
2251pub fn parse_timestamp_literal(s: &str) -> Option<i64> {
2256 let trimmed = s.trim();
2257 let (date_part, time_part) = match trimmed.find([' ', 'T']) {
2258 Some(i) => (&trimmed[..i], Some(&trimmed[i + 1..])),
2259 None => (trimmed, None),
2260 };
2261 let days = parse_date_literal(date_part)?;
2262 let day_micros = match time_part {
2263 None => 0,
2264 Some(t) => parse_time_of_day_micros(t)?,
2265 };
2266 Some(i64::from(days) * 86_400_000_000 + day_micros)
2267}
2268
2269fn parse_time_of_day_micros(t: &str) -> Option<i64> {
2270 let (time, frac_str) = match t.split_once('.') {
2271 Some((a, b)) => (a, Some(b)),
2272 None => (t, None),
2273 };
2274 let bytes = time.as_bytes();
2275 if bytes.len() != 8 || bytes[2] != b':' || bytes[5] != b':' {
2276 return None;
2277 }
2278 let hh: i64 = time[0..2].parse().ok()?;
2279 let mm: i64 = time[3..5].parse().ok()?;
2280 let ss: i64 = time[6..8].parse().ok()?;
2281 if !(0..24).contains(&hh) || !(0..60).contains(&mm) || !(0..60).contains(&ss) {
2282 return None;
2283 }
2284 let frac_micros: i64 = match frac_str {
2285 None => 0,
2286 Some(f) => {
2287 if f.is_empty() || f.len() > 9 {
2289 return None;
2290 }
2291 let mut padded = String::with_capacity(6);
2292 padded.push_str(&f[..f.len().min(6)]);
2293 while padded.len() < 6 {
2294 padded.push('0');
2295 }
2296 padded.parse().ok()?
2297 }
2298 };
2299 Some(((hh * 3600 + mm * 60 + ss) * 1_000_000) + frac_micros)
2300}
2301
2302pub fn format_interval(months: i32, micros: i64) -> String {
2307 const MICROS_PER_DAY: i64 = 86_400_000_000;
2308 let mut parts: Vec<String> = Vec::new();
2309 let years = months / 12;
2310 let mons = months % 12;
2311 let unit = |n: i64, singular: &'static str, plural: &'static str| -> &'static str {
2314 if n == 1 { singular } else { plural }
2315 };
2316 if years != 0 {
2317 parts.push(format!(
2318 "{years} {}",
2319 unit(i64::from(years), "year", "years")
2320 ));
2321 }
2322 if mons != 0 {
2323 parts.push(format!("{mons} {}", unit(i64::from(mons), "mon", "mons")));
2324 }
2325 let days = micros / MICROS_PER_DAY;
2326 let mut rem = micros % MICROS_PER_DAY;
2327 if days != 0 {
2328 parts.push(format!("{days} {}", unit(days, "day", "days")));
2329 }
2330 if rem != 0 {
2331 let neg = rem < 0;
2332 if neg {
2333 rem = -rem;
2334 }
2335 let secs = rem / 1_000_000;
2336 let frac = rem % 1_000_000;
2337 let hh = secs / 3600;
2338 let mm = (secs / 60) % 60;
2339 let ss = secs % 60;
2340 let sign = if neg { "-" } else { "" };
2341 if frac == 0 {
2342 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}"));
2343 } else {
2344 let raw = format!("{frac:06}");
2345 let trimmed = raw.trim_end_matches('0');
2346 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}.{trimmed}"));
2347 }
2348 }
2349 if parts.is_empty() {
2350 "0".into()
2351 } else {
2352 parts.join(" ")
2353 }
2354}
2355
2356fn add_months_to_civil(y: i32, m: u32, d: u32, months: i32) -> (i32, u32, u32) {
2359 let total_months = i64::from(y) * 12 + i64::from(m) - 1 + i64::from(months);
2360 let new_year = i32::try_from(total_months.div_euclid(12)).unwrap_or(i32::MAX);
2361 let new_month_zero = total_months.rem_euclid(12);
2362 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
2363 let new_month = (new_month_zero as u32) + 1;
2364 let max_day = days_in_month(new_year, new_month);
2365 (new_year, new_month, d.min(max_day))
2366}
2367
2368const fn days_in_month(y: i32, m: u32) -> u32 {
2369 match m {
2370 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
2371 2 => {
2372 if y.rem_euclid(4) == 0 && (y.rem_euclid(100) != 0 || y.rem_euclid(400) == 0) {
2374 29
2375 } else {
2376 28
2377 }
2378 }
2379 _ => 30,
2382 }
2383}
2384
2385pub fn format_text_array(items: &[Option<String>]) -> String {
2391 let mut out = String::with_capacity(2 + items.len() * 8);
2392 out.push('{');
2393 for (i, item) in items.iter().enumerate() {
2394 if i > 0 {
2395 out.push(',');
2396 }
2397 match item {
2398 None => out.push_str("NULL"),
2399 Some(s) => {
2400 let needs_quote = s.is_empty()
2401 || s.eq_ignore_ascii_case("NULL")
2402 || s.chars()
2403 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
2404 if needs_quote {
2405 out.push('"');
2406 for c in s.chars() {
2407 if c == '"' || c == '\\' {
2408 out.push('\\');
2409 }
2410 out.push(c);
2411 }
2412 out.push('"');
2413 } else {
2414 out.push_str(s);
2415 }
2416 }
2417 }
2418 }
2419 out.push('}');
2420 out
2421}
2422
2423pub fn format_int_array(items: &[Option<i32>]) -> String {
2427 let mut out = String::with_capacity(2 + items.len() * 4);
2428 out.push('{');
2429 for (i, item) in items.iter().enumerate() {
2430 if i > 0 {
2431 out.push(',');
2432 }
2433 match item {
2434 None => out.push_str("NULL"),
2435 Some(n) => out.push_str(&n.to_string()),
2436 }
2437 }
2438 out.push('}');
2439 out
2440}
2441
2442pub fn format_bigint_array(items: &[Option<i64>]) -> String {
2445 let mut out = String::with_capacity(2 + items.len() * 6);
2446 out.push('{');
2447 for (i, item) in items.iter().enumerate() {
2448 if i > 0 {
2449 out.push(',');
2450 }
2451 match item {
2452 None => out.push_str("NULL"),
2453 Some(n) => out.push_str(&n.to_string()),
2454 }
2455 }
2456 out.push('}');
2457 out
2458}
2459
2460pub fn format_tsvector(lexs: &[TsLexeme]) -> String {
2466 let mut out = String::with_capacity(lexs.len() * 12);
2467 for (i, l) in lexs.iter().enumerate() {
2468 if i > 0 {
2469 out.push(' ');
2470 }
2471 out.push('\'');
2472 for c in l.word.chars() {
2473 if c == '\'' {
2474 out.push('\'');
2475 }
2476 out.push(c);
2477 }
2478 out.push('\'');
2479 if !l.positions.is_empty() {
2480 for (pi, p) in l.positions.iter().enumerate() {
2481 out.push(if pi == 0 { ':' } else { ',' });
2482 out.push_str(&p.to_string());
2483 }
2484 match l.weight {
2489 3 => out.push('A'),
2490 2 => out.push('B'),
2491 1 => out.push('C'),
2492 _ => {}
2493 }
2494 }
2495 }
2496 out
2497}
2498
2499pub fn format_tsquery(ast: &TsQueryAst) -> String {
2502 fn go(ast: &TsQueryAst, parent_prec: u8, out: &mut String) {
2503 let (own_prec, write_self): (u8, &dyn Fn(&mut String)) = match ast {
2505 TsQueryAst::Or(_, _) => (1, &|_| {}),
2506 TsQueryAst::And(_, _) | TsQueryAst::Phrase { .. } => (2, &|_| {}),
2507 TsQueryAst::Not(_) => (3, &|_| {}),
2508 TsQueryAst::Term { .. } => (4, &|_| {}),
2509 };
2510 let need_parens = own_prec < parent_prec;
2511 if need_parens {
2512 out.push('(');
2513 }
2514 match ast {
2515 TsQueryAst::Term { word, .. } => {
2516 out.push('\'');
2517 for c in word.chars() {
2518 if c == '\'' {
2519 out.push('\'');
2520 }
2521 out.push(c);
2522 }
2523 out.push('\'');
2524 }
2525 TsQueryAst::And(a, b) => {
2526 go(a, own_prec, out);
2527 out.push_str(" & ");
2528 go(b, own_prec, out);
2529 }
2530 TsQueryAst::Or(a, b) => {
2531 go(a, own_prec, out);
2532 out.push_str(" | ");
2533 go(b, own_prec, out);
2534 }
2535 TsQueryAst::Not(x) => {
2536 out.push('!');
2537 go(x, own_prec, out);
2538 }
2539 TsQueryAst::Phrase {
2540 left,
2541 right,
2542 distance,
2543 } => {
2544 go(left, own_prec, out);
2545 out.push_str(&alloc::format!(" <{distance}> "));
2546 go(right, own_prec, out);
2547 }
2548 }
2549 write_self(out);
2550 if need_parens {
2551 out.push(')');
2552 }
2553 }
2554 let mut out = String::new();
2555 go(ast, 0, &mut out);
2556 out
2557}
2558
2559pub fn decode_tsvector_external(s: &str) -> Result<Vec<TsLexeme>, EvalError> {
2568 let mut out: Vec<TsLexeme> = Vec::new();
2569 let mut i = 0;
2570 let bytes = s.as_bytes();
2571 while i < bytes.len() {
2572 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
2573 i += 1;
2574 }
2575 if i >= bytes.len() {
2576 break;
2577 }
2578 let word = if bytes[i] == b'\'' {
2581 i += 1;
2582 let mut w = String::new();
2583 loop {
2584 if i >= bytes.len() {
2585 return Err(EvalError::TypeMismatch {
2586 detail: "tsvector literal: unterminated quoted lexeme".into(),
2587 });
2588 }
2589 let b = bytes[i];
2590 if b == b'\'' {
2591 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
2592 w.push('\'');
2593 i += 2;
2594 } else {
2595 i += 1;
2596 break;
2597 }
2598 } else {
2599 w.push(b as char);
2600 i += 1;
2601 }
2602 }
2603 w
2604 } else {
2605 let start = i;
2607 while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b':' {
2608 i += 1;
2609 }
2610 core::str::from_utf8(&bytes[start..i])
2611 .map_err(|_| EvalError::TypeMismatch {
2612 detail: "tsvector literal: non-UTF-8 lexeme".into(),
2613 })?
2614 .to_string()
2615 };
2616 if word.is_empty() {
2617 return Err(EvalError::TypeMismatch {
2618 detail: "tsvector literal: empty lexeme".into(),
2619 });
2620 }
2621 let mut positions: Vec<u16> = Vec::new();
2624 let mut weight: u8 = 0;
2625 if i < bytes.len() && bytes[i] == b':' {
2626 i += 1;
2627 loop {
2628 let start = i;
2629 while i < bytes.len() && bytes[i].is_ascii_digit() {
2630 i += 1;
2631 }
2632 if start == i {
2633 return Err(EvalError::TypeMismatch {
2634 detail: "tsvector literal: expected digit after ':'".into(),
2635 });
2636 }
2637 let num: u16 = core::str::from_utf8(&bytes[start..i])
2638 .expect("ascii digits")
2639 .parse()
2640 .map_err(|_| EvalError::TypeMismatch {
2641 detail: alloc::format!(
2642 "tsvector literal: position {} overflows u16",
2643 core::str::from_utf8(&bytes[start..i]).unwrap_or("?")
2644 ),
2645 })?;
2646 positions.push(num);
2647 if i < bytes.len() {
2648 let w = bytes[i];
2649 if matches!(w, b'A' | b'B' | b'C' | b'D') {
2650 weight = match w {
2651 b'A' => 3,
2652 b'B' => 2,
2653 b'C' => 1,
2654 _ => 0,
2655 };
2656 i += 1;
2657 }
2658 }
2659 if i < bytes.len() && bytes[i] == b',' {
2660 i += 1;
2661 continue;
2662 }
2663 break;
2664 }
2665 }
2666 positions.sort_unstable();
2667 positions.dedup();
2668 match out.binary_search_by(|l| l.word.as_str().cmp(word.as_str())) {
2671 Ok(idx) => {
2672 for p in positions {
2673 if !out[idx].positions.contains(&p) {
2674 out[idx].positions.push(p);
2675 }
2676 }
2677 out[idx].positions.sort_unstable();
2678 if weight != 0 {
2679 out[idx].weight = weight;
2680 }
2681 }
2682 Err(idx) => {
2683 out.insert(
2684 idx,
2685 TsLexeme {
2686 word,
2687 positions,
2688 weight,
2689 },
2690 );
2691 }
2692 }
2693 }
2694 Ok(out)
2695}
2696
2697pub fn decode_tsquery_external(s: &str) -> Result<TsQueryAst, EvalError> {
2703 let mut p = TsQueryParser {
2704 bytes: s.as_bytes(),
2705 pos: 0,
2706 };
2707 p.skip_ws();
2708 if p.pos >= p.bytes.len() {
2709 return Err(EvalError::TypeMismatch {
2710 detail: "tsquery literal: empty".into(),
2711 });
2712 }
2713 let ast = p.parse_or()?;
2714 p.skip_ws();
2715 if p.pos < p.bytes.len() {
2716 return Err(EvalError::TypeMismatch {
2717 detail: alloc::format!("tsquery literal: trailing garbage at offset {}", p.pos),
2718 });
2719 }
2720 Ok(ast)
2721}
2722
2723struct TsQueryParser<'a> {
2724 bytes: &'a [u8],
2725 pos: usize,
2726}
2727
2728impl<'a> TsQueryParser<'a> {
2729 fn skip_ws(&mut self) {
2730 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_whitespace() {
2731 self.pos += 1;
2732 }
2733 }
2734 fn peek(&self) -> Option<u8> {
2735 self.bytes.get(self.pos).copied()
2736 }
2737 fn parse_or(&mut self) -> Result<TsQueryAst, EvalError> {
2738 let mut lhs = self.parse_and()?;
2739 loop {
2740 self.skip_ws();
2741 if self.peek() != Some(b'|') {
2742 return Ok(lhs);
2743 }
2744 self.pos += 1;
2745 let rhs = self.parse_and()?;
2746 lhs = TsQueryAst::Or(Box::new(lhs), Box::new(rhs));
2747 }
2748 }
2749 fn parse_and(&mut self) -> Result<TsQueryAst, EvalError> {
2750 let mut lhs = self.parse_unary()?;
2751 loop {
2752 self.skip_ws();
2753 match self.peek() {
2754 Some(b'&') => {
2755 self.pos += 1;
2756 let rhs = self.parse_unary()?;
2757 lhs = TsQueryAst::And(Box::new(lhs), Box::new(rhs));
2758 }
2759 Some(b'<') => {
2760 self.pos += 1;
2762 let start = self.pos;
2763 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
2764 self.pos += 1;
2765 }
2766 if start == self.pos || self.peek() != Some(b'>') {
2767 return Err(EvalError::TypeMismatch {
2768 detail: "tsquery literal: malformed <N> phrase operator".into(),
2769 });
2770 }
2771 let n: u16 = core::str::from_utf8(&self.bytes[start..self.pos])
2772 .expect("ascii digits")
2773 .parse()
2774 .map_err(|_| EvalError::TypeMismatch {
2775 detail: "tsquery literal: phrase distance overflows u16".into(),
2776 })?;
2777 self.pos += 1; let rhs = self.parse_unary()?;
2779 lhs = TsQueryAst::Phrase {
2780 left: Box::new(lhs),
2781 right: Box::new(rhs),
2782 distance: n,
2783 };
2784 }
2785 _ => return Ok(lhs),
2786 }
2787 }
2788 }
2789 fn parse_unary(&mut self) -> Result<TsQueryAst, EvalError> {
2790 self.skip_ws();
2791 if self.peek() == Some(b'!') {
2792 self.pos += 1;
2793 let inner = self.parse_unary()?;
2794 return Ok(TsQueryAst::Not(Box::new(inner)));
2795 }
2796 self.parse_atom()
2797 }
2798 fn parse_atom(&mut self) -> Result<TsQueryAst, EvalError> {
2799 self.skip_ws();
2800 match self.peek() {
2801 Some(b'(') => {
2802 self.pos += 1;
2803 let inner = self.parse_or()?;
2804 self.skip_ws();
2805 if self.peek() != Some(b')') {
2806 return Err(EvalError::TypeMismatch {
2807 detail: "tsquery literal: missing ')'".into(),
2808 });
2809 }
2810 self.pos += 1;
2811 Ok(inner)
2812 }
2813 Some(b'\'') => {
2814 self.pos += 1;
2815 let mut w = String::new();
2816 loop {
2817 match self.peek() {
2818 None => {
2819 return Err(EvalError::TypeMismatch {
2820 detail: "tsquery literal: unterminated quoted lexeme".into(),
2821 });
2822 }
2823 Some(b'\'') => {
2824 if self.bytes.get(self.pos + 1) == Some(&b'\'') {
2825 w.push('\'');
2826 self.pos += 2;
2827 } else {
2828 self.pos += 1;
2829 break;
2830 }
2831 }
2832 Some(b) => {
2833 w.push(b as char);
2834 self.pos += 1;
2835 }
2836 }
2837 }
2838 self.skip_weight_suffix();
2841 Ok(TsQueryAst::Term {
2842 word: w,
2843 weight_mask: 0,
2844 })
2845 }
2846 Some(b) if b.is_ascii_alphanumeric() || b == b'_' => {
2847 let start = self.pos;
2848 while self.pos < self.bytes.len() {
2849 let c = self.bytes[self.pos];
2850 if c.is_ascii_alphanumeric() || c == b'_' {
2851 self.pos += 1;
2852 } else {
2853 break;
2854 }
2855 }
2856 let w = core::str::from_utf8(&self.bytes[start..self.pos])
2857 .map_err(|_| EvalError::TypeMismatch {
2858 detail: "tsquery literal: non-UTF-8 lexeme".into(),
2859 })?
2860 .to_string();
2861 self.skip_weight_suffix();
2862 Ok(TsQueryAst::Term {
2863 word: w,
2864 weight_mask: 0,
2865 })
2866 }
2867 Some(b) => Err(EvalError::TypeMismatch {
2868 detail: alloc::format!(
2869 "tsquery literal: unexpected byte {:?} at offset {}",
2870 b as char,
2871 self.pos
2872 ),
2873 }),
2874 None => Err(EvalError::TypeMismatch {
2875 detail: "tsquery literal: expected term".into(),
2876 }),
2877 }
2878 }
2879 fn skip_weight_suffix(&mut self) {
2880 if self.peek() != Some(b':') {
2881 return;
2882 }
2883 self.pos += 1;
2884 while let Some(b) = self.peek() {
2885 if matches!(
2886 b,
2887 b'A' | b'B' | b'C' | b'D' | b'a' | b'b' | b'c' | b'd' | b'*'
2888 ) || b.is_ascii_digit()
2889 {
2890 self.pos += 1;
2891 } else {
2892 break;
2893 }
2894 }
2895 }
2896}
2897
2898pub fn format_bytea_hex(b: &[u8]) -> String {
2902 let mut out = String::with_capacity(2 + 2 * b.len());
2903 out.push_str("\\x");
2904 const HEX: &[u8; 16] = b"0123456789abcdef";
2905 for byte in b {
2906 out.push(HEX[(byte >> 4) as usize] as char);
2907 out.push(HEX[(byte & 0x0F) as usize] as char);
2908 }
2909 out
2910}
2911
2912pub fn format_numeric(scaled: i128, scale: u8) -> String {
2917 if scale == 0 {
2918 return format!("{scaled}");
2919 }
2920 let negative = scaled < 0;
2921 let mag_str = scaled.unsigned_abs().to_string();
2922 let mag_bytes = mag_str.as_bytes();
2923 let scale_u = scale as usize;
2924 let mut out = String::with_capacity(mag_str.len() + 3);
2925 if negative {
2926 out.push('-');
2927 }
2928 if mag_bytes.len() <= scale_u {
2929 out.push('0');
2930 out.push('.');
2931 for _ in mag_bytes.len()..scale_u {
2932 out.push('0');
2933 }
2934 out.push_str(&mag_str);
2935 } else {
2936 let split = mag_bytes.len() - scale_u;
2937 out.push_str(&mag_str[..split]);
2938 out.push('.');
2939 out.push_str(&mag_str[split..]);
2940 }
2941 out
2942}
2943
2944fn cast_numeric_to_int(v: Value) -> Result<Value, EvalError> {
2945 match v {
2946 Value::Int(n) => Ok(Value::Int(n)),
2947 Value::BigInt(n) => i32::try_from(n)
2948 .map(Value::Int)
2949 .map_err(|_| EvalError::TypeMismatch {
2950 detail: format!("bigint {n} does not fit in int"),
2951 }),
2952 #[allow(clippy::cast_possible_truncation)]
2953 Value::Float(x) => Ok(Value::Int(x as i32)),
2954 Value::Text(s) => {
2955 s.trim()
2956 .parse::<i32>()
2957 .map(Value::Int)
2958 .map_err(|_| EvalError::TypeMismatch {
2959 detail: format!("cannot parse {s:?} as int"),
2960 })
2961 }
2962 Value::Bool(b) => Ok(Value::Int(i32::from(b))),
2963 other => Err(EvalError::TypeMismatch {
2964 detail: format!("cannot cast {:?} to int", other.data_type()),
2965 }),
2966 }
2967}
2968
2969fn cast_numeric_to_bigint(v: Value) -> Result<Value, EvalError> {
2970 match v {
2971 Value::Int(n) => Ok(Value::BigInt(i64::from(n))),
2972 Value::BigInt(n) => Ok(Value::BigInt(n)),
2973 #[allow(clippy::cast_possible_truncation)]
2974 Value::Float(x) => Ok(Value::BigInt(x as i64)),
2975 Value::Text(s) => {
2976 s.trim()
2977 .parse::<i64>()
2978 .map(Value::BigInt)
2979 .map_err(|_| EvalError::TypeMismatch {
2980 detail: format!("cannot parse {s:?} as bigint"),
2981 })
2982 }
2983 Value::Bool(b) => Ok(Value::BigInt(i64::from(b))),
2984 other => Err(EvalError::TypeMismatch {
2985 detail: format!("cannot cast {:?} to bigint", other.data_type()),
2986 }),
2987 }
2988}
2989
2990fn cast_numeric_to_float(v: Value) -> Result<Value, EvalError> {
2991 match v {
2992 Value::Int(n) => Ok(Value::Float(f64::from(n))),
2993 #[allow(clippy::cast_precision_loss)]
2994 Value::BigInt(n) => Ok(Value::Float(n as f64)),
2995 Value::Float(x) => Ok(Value::Float(x)),
2996 Value::Text(s) => {
2997 s.trim()
2998 .parse::<f64>()
2999 .map(Value::Float)
3000 .map_err(|_| EvalError::TypeMismatch {
3001 detail: format!("cannot parse {s:?} as float"),
3002 })
3003 }
3004 other => Err(EvalError::TypeMismatch {
3005 detail: format!("cannot cast {:?} to float", other.data_type()),
3006 }),
3007 }
3008}
3009
3010fn cast_to_bool(v: Value) -> Result<Value, EvalError> {
3011 match v {
3012 Value::Bool(b) => Ok(Value::Bool(b)),
3013 Value::Int(n) => Ok(Value::Bool(n != 0)),
3014 Value::BigInt(n) => Ok(Value::Bool(n != 0)),
3015 Value::Text(s) => {
3016 let lo = s.trim().to_ascii_lowercase();
3017 match lo.as_str() {
3018 "true" | "t" | "yes" | "y" | "1" | "on" => Ok(Value::Bool(true)),
3019 "false" | "f" | "no" | "n" | "0" | "off" => Ok(Value::Bool(false)),
3020 _ => Err(EvalError::TypeMismatch {
3021 detail: format!("cannot parse {s:?} as bool"),
3022 }),
3023 }
3024 }
3025 other => Err(EvalError::TypeMismatch {
3026 detail: format!("cannot cast {:?} to bool", other.data_type()),
3027 }),
3028 }
3029}
3030
3031pub fn cast_to_vector(v: Value) -> Result<Value, EvalError> {
3034 match v {
3035 Value::Null => Ok(Value::Null),
3036 Value::Vector(v) => Ok(Value::Vector(v)),
3037 Value::Text(s) => parse_vector_text(&s)
3038 .map(Value::Vector)
3039 .ok_or(EvalError::TypeMismatch {
3040 detail: format!("cannot parse {s:?} as a vector literal"),
3041 }),
3042 other => Err(EvalError::TypeMismatch {
3043 detail: format!("::vector requires text input, got {:?}", other.data_type()),
3044 }),
3045 }
3046}
3047
3048fn parse_vector_text(s: &str) -> Option<Vec<f32>> {
3050 let trimmed = s.trim();
3051 let inner = trimmed.strip_prefix('[')?.strip_suffix(']')?;
3052 let trimmed_inner = inner.trim();
3053 if trimmed_inner.is_empty() {
3054 return Some(Vec::new());
3055 }
3056 let mut out = Vec::new();
3057 for part in trimmed_inner.split(',') {
3058 let f: f32 = part.trim().parse().ok()?;
3059 out.push(f);
3060 }
3061 Some(out)
3062}
3063
3064fn literal_to_value(l: &Literal) -> Value {
3065 match l {
3066 Literal::Integer(n) => {
3067 if let Ok(small) = i32::try_from(*n) {
3068 Value::Int(small)
3069 } else {
3070 Value::BigInt(*n)
3071 }
3072 }
3073 Literal::Float(x) => Value::Float(*x),
3074 Literal::String(s) => Value::Text(s.clone()),
3075 Literal::Vector(v) => Value::Vector(v.clone()),
3076 Literal::Bool(b) => Value::Bool(*b),
3077 Literal::Null => Value::Null,
3078 Literal::Interval { months, micros, .. } => Value::Interval {
3079 months: *months,
3080 micros: *micros,
3081 },
3082 }
3083}
3084
3085fn resolve_column(c: &ColumnName, row: &Row, ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
3086 if let Some(q) = &c.qualifier {
3087 let composite = alloc::format!("{q}.{name}", name = c.name);
3092 if let Some(pos) = ctx.columns.iter().position(|s| s.name == composite) {
3093 return Ok(row.values[pos].clone());
3094 }
3095 let expected = ctx.table_alias.ok_or_else(|| EvalError::UnknownQualifier {
3096 qualifier: q.clone(),
3097 })?;
3098 if q != expected {
3099 return Err(EvalError::UnknownQualifier {
3100 qualifier: q.clone(),
3101 });
3102 }
3103 }
3104 if let Some(pos) = ctx.columns.iter().position(|s| s.name == c.name) {
3105 return Ok(row.values[pos].clone());
3106 }
3107 let suffix = alloc::format!(".{name}", name = c.name);
3110 let mut matches = ctx
3111 .columns
3112 .iter()
3113 .enumerate()
3114 .filter(|(_, s)| s.name.ends_with(&suffix));
3115 let first = matches.next();
3116 let extra = matches.next();
3117 match (first, extra) {
3118 (Some((pos, _)), None) => Ok(row.values[pos].clone()),
3119 (Some(_), Some(_)) => Err(EvalError::TypeMismatch {
3120 detail: alloc::format!("ambiguous column reference: {}", c.name),
3121 }),
3122 _ => Err(EvalError::ColumnNotFound {
3123 name: c.name.clone(),
3124 }),
3125 }
3126}
3127
3128fn apply_unary(op: UnOp, v: Value) -> Result<Value, EvalError> {
3129 match (op, v) {
3130 (_, Value::Null) => Ok(Value::Null),
3131 (UnOp::Neg, Value::Int(n)) => {
3132 n.checked_neg()
3133 .map(Value::Int)
3134 .ok_or(EvalError::TypeMismatch {
3135 detail: "integer overflow on unary -".into(),
3136 })
3137 }
3138 (UnOp::Neg, Value::BigInt(n)) => {
3139 n.checked_neg()
3140 .map(Value::BigInt)
3141 .ok_or(EvalError::TypeMismatch {
3142 detail: "bigint overflow on unary -".into(),
3143 })
3144 }
3145 (UnOp::Neg, Value::Float(x)) => Ok(Value::Float(-x)),
3146 (UnOp::Neg, other) => Err(EvalError::TypeMismatch {
3147 detail: format!("unary - applied to {:?}", other.data_type()),
3148 }),
3149 (UnOp::Not, Value::Bool(b)) => Ok(Value::Bool(!b)),
3150 (UnOp::Not, other) => Err(EvalError::TypeMismatch {
3151 detail: format!("NOT applied to {:?}", other.data_type()),
3152 }),
3153 }
3154}
3155
3156fn values_not_distinct(l: &Value, r: &Value) -> bool {
3159 match (l, r) {
3160 (Value::Null, Value::Null) => true,
3161 (Value::Null, _) | (_, Value::Null) => false,
3162 _ => l == r,
3163 }
3164}
3165
3166fn apply_binary(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
3167 if let BinOp::And = op {
3170 return and_3vl(l, r);
3171 }
3172 if let BinOp::Or = op {
3173 return or_3vl(l, r);
3174 }
3175 if let BinOp::IsNotDistinctFrom = op {
3178 return Ok(Value::Bool(values_not_distinct(&l, &r)));
3179 }
3180 if let BinOp::IsDistinctFrom = op {
3181 return Ok(Value::Bool(!values_not_distinct(&l, &r)));
3182 }
3183 if l.is_null() || r.is_null() {
3185 return Ok(Value::Null);
3186 }
3187 if matches!(l, Value::Numeric { .. }) || matches!(r, Value::Numeric { .. }) {
3190 return apply_binary_numeric(op, l, r);
3191 }
3192 if let Some(result) = apply_binary_calendar(op, &l, &r)? {
3200 return Ok(result);
3201 }
3202 match op {
3203 BinOp::Add => arith(l, r, i64::checked_add, |a, b| a + b, "+"),
3204 BinOp::Sub => arith(l, r, i64::checked_sub, |a, b| a - b, "-"),
3205 BinOp::Mul => arith(l, r, i64::checked_mul, |a, b| a * b, "*"),
3206 BinOp::Div => div_op(l, r),
3207 BinOp::L2Distance => l2_distance(l, r),
3208 BinOp::InnerProduct => inner_product(l, r),
3209 BinOp::CosineDistance => cosine_distance(l, r),
3210 BinOp::Concat => Ok(text_concat(&l, &r)),
3211 BinOp::JsonGet => crate::json::path_get(&l, &r, false),
3212 BinOp::JsonGetText => crate::json::path_get(&l, &r, true),
3213 BinOp::JsonGetPath => crate::json::path_walk(&l, &r, false),
3214 BinOp::JsonGetPathText => crate::json::path_walk(&l, &r, true),
3215 BinOp::JsonContains => crate::json::contains(&l, &r),
3216 BinOp::TsMatch => ts_match(l, r),
3219 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
3220 compare(op, &l, &r)
3221 }
3222 BinOp::And | BinOp::Or | BinOp::IsDistinctFrom | BinOp::IsNotDistinctFrom => {
3223 unreachable!("handled above")
3224 }
3225 }
3226}
3227
3228fn apply_binary_calendar(op: BinOp, l: &Value, r: &Value) -> Result<Option<Value>, EvalError> {
3232 let int_value = |v: &Value| -> Option<i64> {
3233 match v {
3234 Value::SmallInt(n) => Some(i64::from(*n)),
3235 Value::Int(n) => Some(i64::from(*n)),
3236 Value::BigInt(n) => Some(*n),
3237 _ => None,
3238 }
3239 };
3240 match (l, r) {
3244 (Value::Date(a), Value::Date(b)) if op == BinOp::Sub => {
3245 return Ok(Some(Value::BigInt(i64::from(*a) - i64::from(*b))));
3246 }
3247 (Value::Timestamp(a), Value::Timestamp(b)) if op == BinOp::Sub => {
3248 let delta = a.checked_sub(*b).ok_or(EvalError::TypeMismatch {
3249 detail: "TIMESTAMP - TIMESTAMP overflows i64 microseconds".into(),
3250 })?;
3251 return Ok(Some(Value::BigInt(delta)));
3252 }
3253 _ => {}
3254 }
3255 if let Some(out) = apply_binary_interval(op, l, r)? {
3259 return Ok(Some(out));
3260 }
3261 match (l, r) {
3262 (Value::Date(d), other) if op == BinOp::Add => {
3263 if let Some(n) = int_value(other) {
3264 let days = i64::from(*d).saturating_add(n);
3265 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
3266 detail: "DATE + integer overflows DATE range".into(),
3267 })?;
3268 return Ok(Some(Value::Date(days32)));
3269 }
3270 }
3271 (other, Value::Date(d)) if op == BinOp::Add => {
3272 if let Some(n) = int_value(other) {
3273 let days = i64::from(*d).saturating_add(n);
3274 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
3275 detail: "integer + DATE overflows DATE range".into(),
3276 })?;
3277 return Ok(Some(Value::Date(days32)));
3278 }
3279 }
3280 (Value::Date(d), other) if op == BinOp::Sub => {
3281 if let Some(n) = int_value(other) {
3282 let days = i64::from(*d).saturating_sub(n);
3283 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
3284 detail: "DATE - integer overflows DATE range".into(),
3285 })?;
3286 return Ok(Some(Value::Date(days32)));
3287 }
3288 }
3289 _ => {}
3290 }
3291 Ok(None)
3292}
3293
3294fn apply_binary_interval(op: BinOp, l: &Value, r: &Value) -> Result<Option<Value>, EvalError> {
3302 let (lhs, rhs, sign): (&Value, &Value, i64) = match (l, r, op) {
3305 (Value::Interval { .. }, _, BinOp::Add) => (r, l, 1),
3306 (_, Value::Interval { .. }, BinOp::Add) => (l, r, 1),
3307 (_, Value::Interval { .. }, BinOp::Sub) => (l, r, -1),
3308 _ => return Ok(None),
3309 };
3310 let Value::Interval {
3311 months: rhs_months,
3312 micros: rhs_us,
3313 } = rhs
3314 else {
3315 unreachable!("rhs guaranteed to be Interval by the match above");
3316 };
3317 let signed_months = i64::from(*rhs_months) * sign;
3318 let signed_micros = rhs_us.checked_mul(sign).ok_or(EvalError::TypeMismatch {
3319 detail: "INTERVAL micros overflows on negation".into(),
3320 })?;
3321 match lhs {
3322 Value::Timestamp(t) => Ok(Some(Value::Timestamp(add_interval_to_micros(
3323 *t,
3324 signed_months,
3325 signed_micros,
3326 )?))),
3327 Value::Date(d) => {
3328 let day_aligned = signed_micros.rem_euclid(86_400_000_000) == 0;
3332 if day_aligned {
3333 let micros_per_day = 86_400_000_000_i64;
3334 let days_delta = signed_micros / micros_per_day;
3335 let shifted = shift_date_by_months(*d, signed_months)?;
3336 let new_days =
3337 i64::from(shifted)
3338 .checked_add(days_delta)
3339 .ok_or(EvalError::TypeMismatch {
3340 detail: "DATE ± INTERVAL overflows DATE range".into(),
3341 })?;
3342 let days32 = i32::try_from(new_days).map_err(|_| EvalError::TypeMismatch {
3343 detail: "DATE ± INTERVAL overflows DATE range".into(),
3344 })?;
3345 Ok(Some(Value::Date(days32)))
3346 } else {
3347 let base =
3348 i64::from(*d)
3349 .checked_mul(86_400_000_000)
3350 .ok_or(EvalError::TypeMismatch {
3351 detail: "DATE → TIMESTAMP lift overflows for INTERVAL math".into(),
3352 })?;
3353 Ok(Some(Value::Timestamp(add_interval_to_micros(
3354 base,
3355 signed_months,
3356 signed_micros,
3357 )?)))
3358 }
3359 }
3360 Value::Interval {
3361 months: lhs_months,
3362 micros: lhs_us,
3363 } => {
3364 let new_months = i64::from(*lhs_months)
3365 .checked_add(signed_months)
3366 .and_then(|n| i32::try_from(n).ok())
3367 .ok_or(EvalError::TypeMismatch {
3368 detail: "INTERVAL ± INTERVAL months overflows i32".into(),
3369 })?;
3370 let new_micros = lhs_us
3371 .checked_add(signed_micros)
3372 .ok_or(EvalError::TypeMismatch {
3373 detail: "INTERVAL ± INTERVAL micros overflows i64".into(),
3374 })?;
3375 Ok(Some(Value::Interval {
3376 months: new_months,
3377 micros: new_micros,
3378 }))
3379 }
3380 _ => Err(EvalError::TypeMismatch {
3381 detail: format!(
3382 "operator {op:?} not defined for {:?} and INTERVAL",
3383 lhs.data_type()
3384 ),
3385 }),
3386 }
3387}
3388
3389fn shift_date_by_months(d: i32, months: i64) -> Result<i32, EvalError> {
3391 let (y, m, day) = civil_from_days(d);
3392 let months_i32 = i32::try_from(months).map_err(|_| EvalError::TypeMismatch {
3393 detail: "INTERVAL months delta out of i32 range".into(),
3394 })?;
3395 let (ny, nm, nd) = add_months_to_civil(y, m, day, months_i32);
3396 Ok(days_from_civil(ny, nm, nd))
3397}
3398
3399fn add_interval_to_micros(t: i64, months: i64, micros: i64) -> Result<i64, EvalError> {
3403 let mut out = t;
3404 if months != 0 {
3405 const MICROS_PER_DAY: i64 = 86_400_000_000;
3406 let days = out.div_euclid(MICROS_PER_DAY);
3407 let day_micros = out.rem_euclid(MICROS_PER_DAY);
3408 let day_i32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
3409 detail: "TIMESTAMP day component out of i32 range for INTERVAL months math".into(),
3410 })?;
3411 let shifted_days = shift_date_by_months(day_i32, months)?;
3412 out = i64::from(shifted_days)
3413 .checked_mul(MICROS_PER_DAY)
3414 .and_then(|n| n.checked_add(day_micros))
3415 .ok_or(EvalError::TypeMismatch {
3416 detail: "TIMESTAMP ± INTERVAL months overflows i64 microseconds".into(),
3417 })?;
3418 }
3419 out.checked_add(micros).ok_or(EvalError::TypeMismatch {
3420 detail: "TIMESTAMP ± INTERVAL micros overflows i64".into(),
3421 })
3422}
3423
3424#[allow(clippy::needless_pass_by_value)] fn apply_binary_numeric(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
3429 let float_path = matches!(l, Value::Float(_)) || matches!(r, Value::Float(_));
3433 if float_path {
3434 let af = as_f64(&l)?;
3435 let bf = as_f64(&r)?;
3436 return match op {
3437 BinOp::Add => Ok(Value::Float(af + bf)),
3438 BinOp::Sub => Ok(Value::Float(af - bf)),
3439 BinOp::Mul => Ok(Value::Float(af * bf)),
3440 BinOp::Div => {
3441 if bf == 0.0 {
3442 Err(EvalError::DivisionByZero)
3443 } else {
3444 Ok(Value::Float(af / bf))
3445 }
3446 }
3447 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
3448 let ord = af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
3449 detail: "NaN in NUMERIC/Float comparison".into(),
3450 })?;
3451 Ok(Value::Bool(cmp_to_bool(op, ord)))
3452 }
3453 BinOp::Concat => Ok(text_concat(&l, &r)),
3454 other => Err(EvalError::TypeMismatch {
3455 detail: format!("operator {other:?} not defined for NUMERIC and Float"),
3456 }),
3457 };
3458 }
3459 let (a, sa) = numeric_or_widen(&l).ok_or_else(|| EvalError::TypeMismatch {
3461 detail: format!("NUMERIC op against non-numeric {:?}", l.data_type()),
3462 })?;
3463 let (b, sb) = numeric_or_widen(&r).ok_or_else(|| EvalError::TypeMismatch {
3464 detail: format!("NUMERIC op against non-numeric {:?}", r.data_type()),
3465 })?;
3466 match op {
3467 BinOp::Add | BinOp::Sub => {
3468 let target_scale = sa.max(sb);
3469 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
3470 detail: "NUMERIC overflow on rescale".into(),
3471 })?;
3472 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
3473 detail: "NUMERIC overflow on rescale".into(),
3474 })?;
3475 let r = match op {
3476 BinOp::Add => lhs.checked_add(rhs),
3477 BinOp::Sub => lhs.checked_sub(rhs),
3478 _ => unreachable!(),
3479 }
3480 .ok_or(EvalError::TypeMismatch {
3481 detail: "NUMERIC overflow on +/-".into(),
3482 })?;
3483 Ok(Value::Numeric {
3484 scaled: r,
3485 scale: target_scale,
3486 })
3487 }
3488 BinOp::Mul => {
3489 let scaled = a.checked_mul(b).ok_or(EvalError::TypeMismatch {
3490 detail: "NUMERIC overflow on *".into(),
3491 })?;
3492 Ok(Value::Numeric {
3493 scaled,
3494 scale: sa.saturating_add(sb),
3495 })
3496 }
3497 BinOp::Div => {
3498 if b == 0 {
3499 return Err(EvalError::DivisionByZero);
3500 }
3501 let target_scale = sa.max(sb);
3505 let bump = pow10_i128(target_scale.saturating_add(sb).saturating_sub(sa));
3509 let num = a.checked_mul(bump).ok_or(EvalError::TypeMismatch {
3510 detail: "NUMERIC overflow on / scaling".into(),
3511 })?;
3512 let half = if b >= 0 { b / 2 } else { -(b / 2) };
3513 let adj = if (num >= 0) == (b >= 0) {
3514 num + half
3515 } else {
3516 num - half
3517 };
3518 Ok(Value::Numeric {
3519 scaled: adj / b,
3520 scale: target_scale,
3521 })
3522 }
3523 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
3524 let target_scale = sa.max(sb);
3525 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
3526 detail: "NUMERIC overflow on rescale".into(),
3527 })?;
3528 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
3529 detail: "NUMERIC overflow on rescale".into(),
3530 })?;
3531 Ok(Value::Bool(cmp_to_bool(op, lhs.cmp(&rhs))))
3532 }
3533 BinOp::Concat => Ok(text_concat(&l, &r)),
3534 other => Err(EvalError::TypeMismatch {
3535 detail: format!("operator {other:?} not defined for NUMERIC"),
3536 }),
3537 }
3538}
3539
3540fn numeric_or_widen(v: &Value) -> Option<(i128, u8)> {
3544 match v {
3545 Value::Numeric { scaled, scale } => Some((*scaled, *scale)),
3546 Value::Int(n) => Some((i128::from(*n), 0)),
3547 Value::SmallInt(n) => Some((i128::from(*n), 0)),
3548 Value::BigInt(n) => Some((i128::from(*n), 0)),
3549 _ => None,
3550 }
3551}
3552
3553fn rescale(scaled: i128, src: u8, dst: u8) -> Option<i128> {
3554 if src == dst {
3555 return Some(scaled);
3556 }
3557 if dst > src {
3558 scaled.checked_mul(pow10_i128(dst - src))
3559 } else {
3560 let drop = pow10_i128(src - dst);
3561 let half = drop / 2;
3562 let r = if scaled >= 0 {
3563 scaled + half
3564 } else {
3565 scaled - half
3566 };
3567 Some(r / drop)
3568 }
3569}
3570
3571const fn pow10_i128(p: u8) -> i128 {
3572 let mut acc: i128 = 1;
3573 let mut i = 0;
3574 while i < p {
3575 acc *= 10;
3576 i += 1;
3577 }
3578 acc
3579}
3580
3581const fn cmp_to_bool(op: BinOp, ord: core::cmp::Ordering) -> bool {
3582 use core::cmp::Ordering::{Equal, Greater, Less};
3583 match op {
3584 BinOp::Eq => matches!(ord, Equal),
3585 BinOp::NotEq => !matches!(ord, Equal),
3586 BinOp::Lt => matches!(ord, Less),
3587 BinOp::LtEq => matches!(ord, Less | Equal),
3588 BinOp::Gt => matches!(ord, Greater),
3589 BinOp::GtEq => matches!(ord, Greater | Equal),
3590 _ => false,
3591 }
3592}
3593
3594fn text_concat(l: &Value, r: &Value) -> Value {
3598 match (l, r) {
3603 (Value::Null, _) | (_, Value::Null) => {
3604 if matches!(
3608 l,
3609 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
3610 ) || matches!(
3611 r,
3612 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
3613 ) {
3614 return Value::Null;
3615 }
3616 }
3617 (Value::TextArray(a), Value::TextArray(b)) => {
3618 let mut out = a.clone();
3619 out.extend(b.iter().cloned());
3620 return Value::TextArray(out);
3621 }
3622 (Value::TextArray(a), Value::Text(s)) => {
3623 let mut out = a.clone();
3624 out.push(Some(s.clone()));
3625 return Value::TextArray(out);
3626 }
3627 (Value::Text(s), Value::TextArray(b)) => {
3628 let mut out: alloc::vec::Vec<Option<alloc::string::String>> =
3629 alloc::vec::Vec::with_capacity(1 + b.len());
3630 out.push(Some(s.clone()));
3631 out.extend(b.iter().cloned());
3632 return Value::TextArray(out);
3633 }
3634 (Value::IntArray(a), Value::IntArray(b)) => {
3639 let mut out = a.clone();
3640 out.extend(b.iter().copied());
3641 return Value::IntArray(out);
3642 }
3643 (Value::IntArray(a), Value::Int(n)) => {
3644 let mut out = a.clone();
3645 out.push(Some(*n));
3646 return Value::IntArray(out);
3647 }
3648 (Value::IntArray(a), Value::SmallInt(n)) => {
3649 let mut out = a.clone();
3650 out.push(Some(i32::from(*n)));
3651 return Value::IntArray(out);
3652 }
3653 (Value::Int(n), Value::IntArray(b)) => {
3654 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
3655 out.push(Some(*n));
3656 out.extend(b.iter().copied());
3657 return Value::IntArray(out);
3658 }
3659 (Value::SmallInt(n), Value::IntArray(b)) => {
3660 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
3661 out.push(Some(i32::from(*n)));
3662 out.extend(b.iter().copied());
3663 return Value::IntArray(out);
3664 }
3665 (Value::BigIntArray(a), Value::BigIntArray(b)) => {
3666 let mut out = a.clone();
3667 out.extend(b.iter().copied());
3668 return Value::BigIntArray(out);
3669 }
3670 (Value::BigIntArray(a), Value::IntArray(b)) => {
3671 let mut out = a.clone();
3672 out.extend(b.iter().map(|o| o.map(i64::from)));
3673 return Value::BigIntArray(out);
3674 }
3675 (Value::IntArray(a), Value::BigIntArray(b)) => {
3676 let mut out: alloc::vec::Vec<Option<i64>> =
3677 a.iter().map(|o| o.map(i64::from)).collect();
3678 out.extend(b.iter().copied());
3679 return Value::BigIntArray(out);
3680 }
3681 (Value::BigIntArray(a), Value::BigInt(n)) => {
3682 let mut out = a.clone();
3683 out.push(Some(*n));
3684 return Value::BigIntArray(out);
3685 }
3686 (Value::BigIntArray(a), Value::Int(n)) => {
3687 let mut out = a.clone();
3688 out.push(Some(i64::from(*n)));
3689 return Value::BigIntArray(out);
3690 }
3691 (Value::BigIntArray(a), Value::SmallInt(n)) => {
3692 let mut out = a.clone();
3693 out.push(Some(i64::from(*n)));
3694 return Value::BigIntArray(out);
3695 }
3696 (Value::BigInt(n), Value::BigIntArray(b)) => {
3697 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
3698 out.push(Some(*n));
3699 out.extend(b.iter().copied());
3700 return Value::BigIntArray(out);
3701 }
3702 (Value::Int(n), Value::BigIntArray(b)) => {
3703 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
3704 out.push(Some(i64::from(*n)));
3705 out.extend(b.iter().copied());
3706 return Value::BigIntArray(out);
3707 }
3708 (Value::SmallInt(n), Value::BigIntArray(b)) => {
3709 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
3710 out.push(Some(i64::from(*n)));
3711 out.extend(b.iter().copied());
3712 return Value::BigIntArray(out);
3713 }
3714 (Value::Bytes(a), Value::Bytes(b)) => {
3716 let mut out = a.clone();
3717 out.extend_from_slice(b);
3718 return Value::Bytes(out);
3719 }
3720 _ => {}
3721 }
3722 let a = value_to_text(l);
3723 let b = value_to_text(r);
3724 Value::Text(a + &b)
3725}
3726
3727fn inner_product(l: Value, r: Value) -> Result<Value, EvalError> {
3730 let (a, b) = unwrap_vec_pair(l, r, "<#>")?;
3731 let mut dot: f64 = 0.0;
3732 for (x, y) in a.iter().zip(b.iter()) {
3733 dot += f64::from(*x) * f64::from(*y);
3734 }
3735 Ok(Value::Float(-dot))
3736}
3737
3738fn cosine_distance(l: Value, r: Value) -> Result<Value, EvalError> {
3741 let (a, b) = unwrap_vec_pair(l, r, "<=>")?;
3742 let mut dot: f64 = 0.0;
3743 let mut na: f64 = 0.0;
3744 let mut nb: f64 = 0.0;
3745 for (x, y) in a.iter().zip(b.iter()) {
3746 let xf = f64::from(*x);
3747 let yf = f64::from(*y);
3748 dot += xf * yf;
3749 na += xf * xf;
3750 nb += yf * yf;
3751 }
3752 let denom = sqrt_newton(na) * sqrt_newton(nb);
3753 if denom == 0.0 {
3754 return Ok(Value::Float(f64::NAN));
3755 }
3756 Ok(Value::Float(1.0 - dot / denom))
3757}
3758
3759fn unwrap_vec_pair(l: Value, r: Value, op: &str) -> Result<(Vec<f32>, Vec<f32>), EvalError> {
3760 let to_f32 = |v: Value| -> Option<Vec<f32>> {
3768 match v {
3769 Value::Vector(a) => Some(a),
3770 Value::Sq8Vector(q) => Some(spg_storage::quantize::dequantize(&q)),
3771 Value::HalfVector(h) => Some(h.to_f32_vec()),
3773 _ => None,
3774 }
3775 };
3776 let l_ty = l.data_type();
3777 let r_ty = r.data_type();
3778 match (to_f32(l), to_f32(r)) {
3779 (Some(a), Some(b)) => {
3780 if a.len() != b.len() {
3781 return Err(EvalError::TypeMismatch {
3782 detail: format!("vector dim mismatch in {op}: {} vs {}", a.len(), b.len()),
3783 });
3784 }
3785 Ok((a, b))
3786 }
3787 _ => Err(EvalError::TypeMismatch {
3788 detail: format!("{op} requires two vectors, got {l_ty:?} and {r_ty:?}"),
3789 }),
3790 }
3791}
3792
3793fn arith(
3798 l: Value,
3799 r: Value,
3800 int_op: impl Fn(i64, i64) -> Option<i64>,
3801 float_op: impl Fn(f64, f64) -> f64,
3802 op_name: &str,
3803) -> Result<Value, EvalError> {
3804 let widen = |v: Value| -> Value {
3807 match v {
3808 Value::SmallInt(n) => Value::Int(i32::from(n)),
3809 other => other,
3810 }
3811 };
3812 let l = widen(l);
3813 let r = widen(r);
3814 match (l, r) {
3815 (Value::Int(a), Value::Int(b)) => {
3816 let result = int_op(i64::from(a), i64::from(b)).ok_or(EvalError::TypeMismatch {
3817 detail: format!("integer overflow on {op_name}"),
3818 })?;
3819 if let Ok(small) = i32::try_from(result) {
3820 Ok(Value::Int(small))
3821 } else {
3822 Ok(Value::BigInt(result))
3823 }
3824 }
3825 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
3826 let result = int_op(i64::from(a), b).ok_or(EvalError::TypeMismatch {
3827 detail: format!("bigint overflow on {op_name}"),
3828 })?;
3829 Ok(Value::BigInt(result))
3830 }
3831 (Value::BigInt(a), Value::BigInt(b)) => {
3832 let result = int_op(a, b).ok_or(EvalError::TypeMismatch {
3833 detail: format!("bigint overflow on {op_name}"),
3834 })?;
3835 Ok(Value::BigInt(result))
3836 }
3837 (a, b)
3838 if a.data_type() == Some(DataType::Float) || b.data_type() == Some(DataType::Float) =>
3839 {
3840 let af = as_f64(&a)?;
3841 let bf = as_f64(&b)?;
3842 Ok(Value::Float(float_op(af, bf)))
3843 }
3844 (a, b) => Err(EvalError::TypeMismatch {
3845 detail: format!(
3846 "{op_name} applied to non-numeric: {:?} vs {:?}",
3847 a.data_type(),
3848 b.data_type()
3849 ),
3850 }),
3851 }
3852}
3853
3854#[allow(clippy::many_single_char_names)] fn l2_distance(l: Value, r: Value) -> Result<Value, EvalError> {
3860 let (a, b) = unwrap_vec_pair(l, r, "<->")?;
3865 let mut sum: f64 = 0.0;
3866 for (x, y) in a.iter().zip(b.iter()) {
3867 let d = f64::from(*x) - f64::from(*y);
3868 sum += d * d;
3869 }
3870 Ok(Value::Float(sqrt_newton(sum)))
3871}
3872
3873fn sqrt_newton(x: f64) -> f64 {
3878 if x <= 0.0 {
3879 return 0.0;
3880 }
3881 let mut g = x;
3882 for _ in 0..10 {
3885 g = 0.5 * (g + x / g);
3886 }
3887 g
3888}
3889
3890fn div_op(l: Value, r: Value) -> Result<Value, EvalError> {
3891 let any_float = matches!(l.data_type(), Some(DataType::Float))
3892 || matches!(r.data_type(), Some(DataType::Float));
3893 if any_float {
3894 let a = as_f64(&l)?;
3895 let b = as_f64(&r)?;
3896 if b == 0.0 {
3897 return Err(EvalError::DivisionByZero);
3898 }
3899 return Ok(Value::Float(a / b));
3900 }
3901 arith(
3902 l,
3903 r,
3904 |a, b| {
3905 if b == 0 { None } else { Some(a / b) }
3906 },
3907 |a, b| a / b,
3908 "/",
3909 )
3910 .map_err(|e| match e {
3911 EvalError::TypeMismatch { detail } if detail.contains('/') => EvalError::DivisionByZero,
3914 other => other,
3915 })
3916}
3917
3918fn as_f64(v: &Value) -> Result<f64, EvalError> {
3919 match v {
3920 Value::SmallInt(n) => Ok(f64::from(*n)),
3921 Value::Int(n) => Ok(f64::from(*n)),
3922 #[allow(clippy::cast_precision_loss)]
3923 Value::BigInt(n) => Ok(*n as f64),
3924 Value::Float(x) => Ok(*x),
3925 #[allow(clippy::cast_precision_loss)]
3926 Value::Numeric { scaled, scale } => {
3927 let mut div = 1.0_f64;
3928 for _ in 0..*scale {
3929 div *= 10.0;
3930 }
3931 Ok((*scaled as f64) / div)
3932 }
3933 other => Err(EvalError::TypeMismatch {
3934 detail: format!("cannot convert {:?} to FLOAT", other.data_type()),
3935 }),
3936 }
3937}
3938
3939fn compare(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
3940 let ord = match (l, r) {
3941 (Value::Int(a), Value::Int(b)) => i64::from(*a).cmp(&i64::from(*b)),
3942 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
3943 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
3944 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
3945 (a, b)
3946 if matches!(a.data_type(), Some(DataType::Float))
3947 || matches!(b.data_type(), Some(DataType::Float)) =>
3948 {
3949 let af = as_f64(a)?;
3950 let bf = as_f64(b)?;
3951 af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
3952 detail: "NaN in comparison".into(),
3953 })?
3954 }
3955 (Value::Text(a), Value::Text(b)) => a.cmp(b),
3956 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
3957 (Value::Date(a), Value::Date(b)) => a.cmp(b),
3961 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
3962 (Value::Date(a), Value::Timestamp(b)) => (i64::from(*a) * 86_400_000_000).cmp(b),
3963 (Value::Timestamp(a), Value::Date(b)) => a.cmp(&(i64::from(*b) * 86_400_000_000)),
3964 (Value::Date(a), Value::Text(b)) => {
3968 let bd = parse_date_literal(b).ok_or_else(|| EvalError::TypeMismatch {
3969 detail: format!("cannot parse {b:?} as DATE for comparison"),
3970 })?;
3971 a.cmp(&bd)
3972 }
3973 (Value::Text(a), Value::Date(b)) => {
3974 let ad = parse_date_literal(a).ok_or_else(|| EvalError::TypeMismatch {
3975 detail: format!("cannot parse {a:?} as DATE for comparison"),
3976 })?;
3977 ad.cmp(b)
3978 }
3979 (Value::Timestamp(a), Value::Text(b)) => {
3980 let bt = parse_timestamp_literal(b).ok_or_else(|| EvalError::TypeMismatch {
3981 detail: format!("cannot parse {b:?} as TIMESTAMP for comparison"),
3982 })?;
3983 a.cmp(&bt)
3984 }
3985 (Value::Text(a), Value::Timestamp(b)) => {
3986 let at = parse_timestamp_literal(a).ok_or_else(|| EvalError::TypeMismatch {
3987 detail: format!("cannot parse {a:?} as TIMESTAMP for comparison"),
3988 })?;
3989 at.cmp(b)
3990 }
3991 (a, b) => {
3992 return Err(EvalError::TypeMismatch {
3993 detail: format!(
3994 "comparison between {:?} and {:?}",
3995 a.data_type(),
3996 b.data_type()
3997 ),
3998 });
3999 }
4000 };
4001 let result = match op {
4002 BinOp::Eq => ord.is_eq(),
4003 BinOp::NotEq => !ord.is_eq(),
4004 BinOp::Lt => ord.is_lt(),
4005 BinOp::LtEq => ord.is_le(),
4006 BinOp::Gt => ord.is_gt(),
4007 BinOp::GtEq => ord.is_ge(),
4008 BinOp::And
4009 | BinOp::Or
4010 | BinOp::Add
4011 | BinOp::Sub
4012 | BinOp::Mul
4013 | BinOp::Div
4014 | BinOp::L2Distance
4015 | BinOp::InnerProduct
4016 | BinOp::CosineDistance
4017 | BinOp::Concat
4018 | BinOp::JsonGet
4019 | BinOp::JsonGetText
4020 | BinOp::JsonGetPath
4021 | BinOp::JsonGetPathText
4022 | BinOp::JsonContains
4023 | BinOp::TsMatch
4024 | BinOp::IsDistinctFrom
4025 | BinOp::IsNotDistinctFrom => {
4026 unreachable!("compare() only called with comparison ops")
4027 }
4028 };
4029 Ok(Value::Bool(result))
4030}
4031
4032fn and_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
4034 match (l, r) {
4035 (Value::Bool(false), _) | (_, Value::Bool(false)) => Ok(Value::Bool(false)),
4036 (Value::Bool(true), Value::Bool(true)) => Ok(Value::Bool(true)),
4037 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
4038 (a, b) => Err(EvalError::TypeMismatch {
4039 detail: format!(
4040 "AND on non-boolean: {:?} and {:?}",
4041 a.data_type(),
4042 b.data_type()
4043 ),
4044 }),
4045 }
4046}
4047
4048fn or_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
4049 match (l, r) {
4050 (Value::Bool(true), _) | (_, Value::Bool(true)) => Ok(Value::Bool(true)),
4051 (Value::Bool(false), Value::Bool(false)) => Ok(Value::Bool(false)),
4052 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
4053 (a, b) => Err(EvalError::TypeMismatch {
4054 detail: format!(
4055 "OR on non-boolean: {:?} and {:?}",
4056 a.data_type(),
4057 b.data_type()
4058 ),
4059 }),
4060 }
4061}
4062
4063#[cfg(test)]
4064mod tests {
4065 use super::*;
4066 use alloc::vec;
4067 use spg_storage::{ColumnSchema, Row};
4068
4069 fn col(name: &str, ty: DataType) -> ColumnSchema {
4070 ColumnSchema::new(name, ty, true)
4071 }
4072
4073 fn ctx<'a>(cols: &'a [ColumnSchema], alias: Option<&'a str>) -> EvalContext<'a> {
4074 EvalContext::new(cols, alias)
4075 }
4076
4077 fn lit(n: i64) -> Expr {
4078 Expr::Literal(Literal::Integer(n))
4079 }
4080
4081 fn null() -> Expr {
4082 Expr::Literal(Literal::Null)
4083 }
4084
4085 fn col_ref(name: &str) -> Expr {
4086 Expr::Column(ColumnName {
4087 qualifier: None,
4088 name: name.into(),
4089 })
4090 }
4091
4092 #[test]
4093 fn literal_evaluates_to_value() {
4094 let r = Row::new(vec![]);
4095 let cs: [ColumnSchema; 0] = [];
4096 let c = ctx(&cs, None);
4097 assert_eq!(eval_expr(&lit(42), &r, &c).unwrap(), Value::Int(42));
4098 assert_eq!(
4099 eval_expr(&Expr::Literal(Literal::Float(1.5)), &r, &c).unwrap(),
4100 Value::Float(1.5)
4101 );
4102 assert_eq!(eval_expr(&null(), &r, &c).unwrap(), Value::Null);
4103 }
4104
4105 #[test]
4106 fn column_lookup_unqualified() {
4107 let cs = vec![col("a", DataType::Int), col("b", DataType::Text)];
4108 let r = Row::new(vec![Value::Int(7), Value::Text("hi".into())]);
4109 let c = ctx(&cs, None);
4110 assert_eq!(eval_expr(&col_ref("a"), &r, &c).unwrap(), Value::Int(7));
4111 assert_eq!(
4112 eval_expr(&col_ref("b"), &r, &c).unwrap(),
4113 Value::Text("hi".into())
4114 );
4115 }
4116
4117 #[test]
4118 fn column_not_found_errors() {
4119 let cs = vec![col("a", DataType::Int)];
4120 let r = Row::new(vec![Value::Int(0)]);
4121 let c = ctx(&cs, None);
4122 let err = eval_expr(&col_ref("ghost"), &r, &c).unwrap_err();
4123 assert!(matches!(err, EvalError::ColumnNotFound { ref name } if name == "ghost"));
4124 }
4125
4126 #[test]
4127 fn qualified_column_matches_alias() {
4128 let cs = vec![col("a", DataType::Int)];
4129 let r = Row::new(vec![Value::Int(5)]);
4130 let c = ctx(&cs, Some("u"));
4131 let qualified = Expr::Column(ColumnName {
4132 qualifier: Some("u".into()),
4133 name: "a".into(),
4134 });
4135 assert_eq!(eval_expr(&qualified, &r, &c).unwrap(), Value::Int(5));
4136 }
4137
4138 #[test]
4139 fn qualified_column_unknown_alias_errors() {
4140 let cs = vec![col("a", DataType::Int)];
4141 let r = Row::new(vec![Value::Int(5)]);
4142 let c = ctx(&cs, Some("u"));
4143 let wrong = Expr::Column(ColumnName {
4144 qualifier: Some("x".into()),
4145 name: "a".into(),
4146 });
4147 assert!(matches!(
4148 eval_expr(&wrong, &r, &c).unwrap_err(),
4149 EvalError::UnknownQualifier { .. }
4150 ));
4151 }
4152
4153 #[test]
4154 fn arithmetic_with_widening() {
4155 let r = Row::new(vec![]);
4156 let cs: [ColumnSchema; 0] = [];
4157 let c = ctx(&cs, None);
4158 let e = Expr::Binary {
4159 lhs: alloc::boxed::Box::new(lit(2)),
4160 op: BinOp::Add,
4161 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::Float(0.5))),
4162 };
4163 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Float(2.5));
4164 }
4165
4166 #[test]
4167 fn division_by_zero_errors() {
4168 let r = Row::new(vec![]);
4169 let cs: [ColumnSchema; 0] = [];
4170 let c = ctx(&cs, None);
4171 let e = Expr::Binary {
4172 lhs: alloc::boxed::Box::new(lit(1)),
4173 op: BinOp::Div,
4174 rhs: alloc::boxed::Box::new(lit(0)),
4175 };
4176 assert_eq!(
4177 eval_expr(&e, &r, &c).unwrap_err(),
4178 EvalError::DivisionByZero
4179 );
4180 }
4181
4182 #[test]
4183 fn comparison_returns_bool() {
4184 let r = Row::new(vec![]);
4185 let cs: [ColumnSchema; 0] = [];
4186 let c = ctx(&cs, None);
4187 let e = Expr::Binary {
4188 lhs: alloc::boxed::Box::new(lit(1)),
4189 op: BinOp::Lt,
4190 rhs: alloc::boxed::Box::new(lit(2)),
4191 };
4192 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
4193 }
4194
4195 #[test]
4196 fn null_propagates_through_arithmetic() {
4197 let r = Row::new(vec![]);
4198 let cs: [ColumnSchema; 0] = [];
4199 let c = ctx(&cs, None);
4200 let e = Expr::Binary {
4201 lhs: alloc::boxed::Box::new(lit(1)),
4202 op: BinOp::Add,
4203 rhs: alloc::boxed::Box::new(null()),
4204 };
4205 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
4206 }
4207
4208 #[test]
4209 fn and_three_valued_logic() {
4210 let r = Row::new(vec![]);
4211 let cs: [ColumnSchema; 0] = [];
4212 let c = ctx(&cs, None);
4213 let tt = |a: bool, b_null: bool| Expr::Binary {
4214 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
4215 op: BinOp::And,
4216 rhs: alloc::boxed::Box::new(if b_null {
4217 null()
4218 } else {
4219 Expr::Literal(Literal::Bool(true))
4220 }),
4221 };
4222 assert_eq!(
4224 eval_expr(&tt(false, true), &r, &c).unwrap(),
4225 Value::Bool(false)
4226 );
4227 assert_eq!(eval_expr(&tt(true, true), &r, &c).unwrap(), Value::Null);
4229 assert_eq!(
4231 eval_expr(&tt(true, false), &r, &c).unwrap(),
4232 Value::Bool(true)
4233 );
4234 }
4235
4236 #[test]
4237 fn or_three_valued_logic() {
4238 let r = Row::new(vec![]);
4239 let cs: [ColumnSchema; 0] = [];
4240 let c = ctx(&cs, None);
4241 let or_with_null = |a: bool| Expr::Binary {
4242 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
4243 op: BinOp::Or,
4244 rhs: alloc::boxed::Box::new(null()),
4245 };
4246 assert_eq!(
4248 eval_expr(&or_with_null(true), &r, &c).unwrap(),
4249 Value::Bool(true)
4250 );
4251 assert_eq!(
4253 eval_expr(&or_with_null(false), &r, &c).unwrap(),
4254 Value::Null
4255 );
4256 }
4257
4258 #[test]
4259 fn not_on_null_is_null() {
4260 let r = Row::new(vec![]);
4261 let cs: [ColumnSchema; 0] = [];
4262 let c = ctx(&cs, None);
4263 let e = Expr::Unary {
4264 op: UnOp::Not,
4265 expr: alloc::boxed::Box::new(null()),
4266 };
4267 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
4268 }
4269
4270 #[test]
4271 fn text_comparison_lexicographic() {
4272 let r = Row::new(vec![]);
4273 let cs: [ColumnSchema; 0] = [];
4274 let c = ctx(&cs, None);
4275 let e = Expr::Binary {
4276 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("apple".into()))),
4277 op: BinOp::Lt,
4278 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("banana".into()))),
4279 };
4280 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
4281 }
4282
4283 #[test]
4284 fn interval_format_basics() {
4285 assert_eq!(format_interval(0, 0), "0");
4286 assert_eq!(format_interval(0, 86_400_000_000), "1 day");
4287 assert_eq!(format_interval(0, -86_400_000_000), "-1 days");
4288 assert_eq!(format_interval(0, 3_600_000_000), "01:00:00");
4289 assert_eq!(
4290 format_interval(0, 86_400_000_000 + 9_000_000),
4291 "1 day 00:00:09"
4292 );
4293 assert_eq!(format_interval(14, 0), "1 year 2 mons");
4294 assert_eq!(format_interval(-1, 0), "-1 mons");
4295 }
4296
4297 #[test]
4298 fn interval_add_to_timestamp_micros_part() {
4299 let ts = i64::from(days_from_civil(2024, 1, 1)) * 86_400_000_000;
4301 let r = add_interval_to_micros(ts, 0, 3_600_000_000).unwrap();
4302 let expected = ts + 3_600_000_000;
4303 assert_eq!(r, expected);
4304 }
4305
4306 #[test]
4307 fn interval_clamp_month_end() {
4308 let d = days_from_civil(2024, 1, 31);
4310 let shifted = shift_date_by_months(d, 1).unwrap();
4311 let (y, m, day) = civil_from_days(shifted);
4312 assert_eq!((y, m, day), (2024, 2, 29));
4313 let d = days_from_civil(2023, 1, 31);
4315 let shifted = shift_date_by_months(d, 1).unwrap();
4316 let (y, m, day) = civil_from_days(shifted);
4317 assert_eq!((y, m, day), (2023, 2, 28));
4318 let d = days_from_civil(2024, 3, 31);
4320 let shifted = shift_date_by_months(d, -1).unwrap();
4321 let (y, m, day) = civil_from_days(shifted);
4322 assert_eq!((y, m, day), (2024, 2, 29));
4323 }
4324
4325 #[test]
4326 fn interval_date_plus_pure_days_stays_date() {
4327 let d = days_from_civil(2024, 6, 1);
4329 let lhs = Value::Date(d);
4330 let rhs = Value::Interval {
4331 months: 0,
4332 micros: 7 * 86_400_000_000,
4333 };
4334 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
4335 .unwrap()
4336 .unwrap();
4337 let expected = days_from_civil(2024, 6, 8);
4338 assert_eq!(v, Value::Date(expected));
4339 }
4340
4341 #[test]
4342 fn interval_date_plus_sub_day_lifts_to_timestamp() {
4343 let d = days_from_civil(2024, 6, 1);
4345 let lhs = Value::Date(d);
4346 let rhs = Value::Interval {
4347 months: 0,
4348 micros: 3_600_000_000,
4349 };
4350 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
4351 .unwrap()
4352 .unwrap();
4353 let expected = i64::from(d) * 86_400_000_000 + 3_600_000_000;
4354 assert_eq!(v, Value::Timestamp(expected));
4355 }
4356}