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(Clone)]
30#[allow(missing_debug_implementations)] pub struct EvalContext<'a> {
32 pub columns: &'a [ColumnSchema],
33 pub table_alias: Option<&'a str>,
34 pub params: &'a [Value],
39 pub default_text_search_config: Option<&'a str>,
46 pub sequence_resolver: Option<&'a SequenceResolver<'a>>,
52}
53
54pub type SequenceResolver<'a> = dyn Fn(SequenceOp) -> Result<i64, EvalError> + 'a;
59
60#[derive(Debug, Clone)]
62pub enum SequenceOp {
63 Next(String),
64 Curr(String),
65 Set {
66 name: String,
67 value: i64,
68 is_called: bool,
69 },
70}
71
72impl<'a> EvalContext<'a> {
73 pub const fn new(columns: &'a [ColumnSchema], table_alias: Option<&'a str>) -> Self {
74 Self {
75 columns,
76 table_alias,
77 params: &[],
78 default_text_search_config: None,
79 sequence_resolver: None,
80 }
81 }
82
83 #[must_use]
87 pub const fn with_sequence_resolver(mut self, resolver: &'a SequenceResolver<'a>) -> Self {
88 self.sequence_resolver = Some(resolver);
89 self
90 }
91
92 #[must_use]
96 pub const fn with_params(mut self, params: &'a [Value]) -> Self {
97 self.params = params;
98 self
99 }
100
101 #[must_use]
105 pub const fn with_default_text_search_config(mut self, cfg: Option<&'a str>) -> Self {
106 self.default_text_search_config = cfg;
107 self
108 }
109}
110
111#[derive(Debug, Clone, PartialEq)]
112pub enum EvalError {
113 ColumnNotFound {
114 name: String,
115 },
116 UnknownQualifier {
117 qualifier: String,
118 },
119 DivisionByZero,
120 TypeMismatch {
121 detail: String,
122 },
123 PlaceholderOutOfRange {
127 n: u16,
128 bound: u16,
129 },
130}
131
132impl core::fmt::Display for EvalError {
133 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
134 match self {
135 Self::ColumnNotFound { name } => write!(f, "column not found: {name}"),
136 Self::UnknownQualifier { qualifier } => {
137 write!(f, "unknown table qualifier: {qualifier}")
138 }
139 Self::DivisionByZero => f.write_str("division by zero"),
140 Self::TypeMismatch { detail } => write!(f, "type mismatch: {detail}"),
141 Self::PlaceholderOutOfRange { n, bound } => write!(
142 f,
143 "parameter ${n} referenced but only {bound} bound by client"
144 ),
145 }
146 }
147}
148
149pub fn eval_expr(expr: &Expr, row: &Row, ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
150 match expr {
151 Expr::Literal(l) => Ok(literal_to_value(l)),
152 Expr::Column(c) => resolve_column(c, row, ctx),
153 Expr::Placeholder(n) => {
154 let idx = usize::from(*n).saturating_sub(1);
155 ctx.params
156 .get(idx)
157 .cloned()
158 .ok_or_else(|| EvalError::PlaceholderOutOfRange {
159 n: *n,
160 bound: u16::try_from(ctx.params.len()).unwrap_or(u16::MAX),
161 })
162 }
163 Expr::Unary { op, expr } => {
164 let v = eval_expr(expr, row, ctx)?;
165 apply_unary(*op, v)
166 }
167 Expr::Binary { lhs, op, rhs } => {
168 let l = eval_expr(lhs, row, ctx)?;
169 let r = eval_expr(rhs, row, ctx)?;
170 let (l, r) = collation_fold_for_compare(*op, lhs, rhs, l, r, ctx);
179 apply_binary(*op, l, r)
180 }
181 Expr::Cast { expr, target } => {
182 let v = eval_expr(expr, row, ctx)?;
183 cast_value(v, *target)
184 }
185 Expr::IsNull { expr, negated } => {
186 let v = eval_expr(expr, row, ctx)?;
187 let is_null = matches!(v, Value::Null);
188 Ok(Value::Bool(if *negated { !is_null } else { is_null }))
189 }
190 Expr::FunctionCall { name, args } => {
191 let evaluated: Result<Vec<Value>, _> =
192 args.iter().map(|a| eval_expr(a, row, ctx)).collect();
193 apply_function(name, &evaluated?, ctx)
194 }
195 Expr::Like {
196 expr,
197 pattern,
198 negated,
199 } => {
200 let v = eval_expr(expr, row, ctx)?;
201 let p = eval_expr(pattern, row, ctx)?;
202 let (text, pat) = match (v, p) {
204 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
205 (Value::Text(a), Value::Text(b)) => (a, b),
206 (Value::Text(_), other) | (other, _) => {
207 return Err(EvalError::TypeMismatch {
208 detail: format!("LIKE requires text operands, got {:?}", other.data_type()),
209 });
210 }
211 };
212 let m = like_match(&text, &pat);
213 Ok(Value::Bool(if *negated { !m } else { m }))
214 }
215 Expr::Extract { field, source } => {
216 let v = eval_expr(source, row, ctx)?;
217 extract_field(*field, &v)
218 }
219 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
223 Err(EvalError::TypeMismatch {
224 detail: "subquery reached row eval — engine resolver bug".into(),
225 })
226 }
227 Expr::WindowFunction { .. } => Err(EvalError::TypeMismatch {
232 detail: "window function reached row eval — engine rewrite bug".into(),
233 }),
234 Expr::Array(items) => {
240 let mut materialised: Vec<Value> = Vec::with_capacity(items.len());
241 for elem in items {
242 materialised.push(eval_expr(elem, row, ctx)?);
243 }
244 let mut has_text = false;
245 let mut has_bigint = false;
246 let mut has_int = false;
247 for v in &materialised {
248 match v {
249 Value::Null => {}
250 Value::Int(_) | Value::SmallInt(_) => has_int = true,
251 Value::BigInt(_) => has_bigint = true,
252 Value::Text(_) | Value::Json(_) => has_text = true,
253 _ => has_text = true,
254 }
255 }
256 if has_text || (!has_int && !has_bigint) {
257 let out: Vec<Option<String>> = materialised
258 .into_iter()
259 .map(|v| match v {
260 Value::Null => None,
261 Value::Text(s) | Value::Json(s) => Some(s),
262 other => Some(value_to_text_for_array(&other)),
263 })
264 .collect();
265 return Ok(Value::TextArray(out));
266 }
267 if has_bigint {
268 let out: Vec<Option<i64>> = materialised
269 .into_iter()
270 .map(|v| match v {
271 Value::Null => None,
272 Value::Int(n) => Some(i64::from(n)),
273 Value::SmallInt(n) => Some(i64::from(n)),
274 Value::BigInt(n) => Some(n),
275 _ => unreachable!(),
276 })
277 .collect();
278 return Ok(Value::BigIntArray(out));
279 }
280 let out: Vec<Option<i32>> = materialised
281 .into_iter()
282 .map(|v| match v {
283 Value::Null => None,
284 Value::Int(n) => Some(n),
285 Value::SmallInt(n) => Some(i32::from(n)),
286 _ => unreachable!(),
287 })
288 .collect();
289 Ok(Value::IntArray(out))
290 }
291 Expr::ArraySubscript { target, index } => {
294 let target_v = eval_expr(target, row, ctx)?;
295 let idx_v = eval_expr(index, row, ctx)?;
296 if matches!(target_v, Value::Null) || matches!(idx_v, Value::Null) {
297 return Ok(Value::Null);
298 }
299 let i: i64 = match idx_v {
300 Value::Int(n) => i64::from(n),
301 Value::BigInt(n) => n,
302 Value::SmallInt(n) => i64::from(n),
303 other => {
304 return Err(EvalError::TypeMismatch {
305 detail: format!(
306 "array subscript must be integer, got {:?}",
307 other.data_type()
308 ),
309 });
310 }
311 };
312 if i < 1 {
313 return Ok(Value::Null);
314 }
315 let pos = (i - 1) as usize;
316 match target_v {
317 Value::TextArray(items) => match items.get(pos) {
318 Some(Some(s)) => Ok(Value::Text(s.clone())),
319 Some(None) | None => Ok(Value::Null),
320 },
321 Value::IntArray(items) => match items.get(pos) {
322 Some(Some(n)) => Ok(Value::Int(*n)),
323 Some(None) | None => Ok(Value::Null),
324 },
325 Value::BigIntArray(items) => match items.get(pos) {
326 Some(Some(n)) => Ok(Value::BigInt(*n)),
327 Some(None) | None => Ok(Value::Null),
328 },
329 other => Err(EvalError::TypeMismatch {
330 detail: format!(
331 "subscript target must be an array, got {:?}",
332 other.data_type()
333 ),
334 }),
335 }
336 }
337 Expr::AnyAll {
343 expr,
344 op,
345 array,
346 is_any,
347 } => {
348 let lhs = eval_expr(expr, row, ctx)?;
349 let arr = eval_expr(array, row, ctx)?;
350 if matches!(arr, Value::Null) {
351 return Ok(Value::Null);
352 }
353 let elems: Vec<Option<Value>> = match arr {
354 Value::TextArray(items) => items.into_iter().map(|o| o.map(Value::Text)).collect(),
355 Value::IntArray(items) => items.into_iter().map(|o| o.map(Value::Int)).collect(),
356 Value::BigIntArray(items) => {
357 items.into_iter().map(|o| o.map(Value::BigInt)).collect()
358 }
359 other => {
360 return Err(EvalError::TypeMismatch {
361 detail: format!(
362 "ANY/ALL right-hand side must be an array, got {:?}",
363 other.data_type()
364 ),
365 });
366 }
367 };
368 let mut saw_null = matches!(lhs, Value::Null);
369 let mut saw_match = false;
370 let mut saw_mismatch = false;
371 for elem in elems {
372 let elem_v = match elem {
373 Some(v) => v,
374 None => {
375 saw_null = true;
376 continue;
377 }
378 };
379 if matches!(lhs, Value::Null) {
380 saw_null = true;
381 continue;
382 }
383 match apply_binary(*op, lhs.clone(), elem_v) {
384 Ok(Value::Bool(true)) => saw_match = true,
385 Ok(Value::Bool(false)) => saw_mismatch = true,
386 Ok(Value::Null) => saw_null = true,
387 Ok(other) => {
388 return Err(EvalError::TypeMismatch {
389 detail: format!(
390 "ANY/ALL comparison didn't return Bool: {:?}",
391 other.data_type()
392 ),
393 });
394 }
395 Err(e) => return Err(e),
396 }
397 }
398 let result = if *is_any {
399 if saw_match {
400 Value::Bool(true)
401 } else if saw_null {
402 Value::Null
403 } else {
404 Value::Bool(false)
405 }
406 } else if saw_mismatch {
407 Value::Bool(false)
408 } else if saw_null {
409 Value::Null
410 } else {
411 Value::Bool(true)
412 };
413 Ok(result)
414 }
415 Expr::Case {
421 operand,
422 branches,
423 else_branch,
424 } => {
425 let operand_value = match operand {
426 Some(o) => Some(eval_expr(o, row, ctx)?),
427 None => None,
428 };
429 for (when_expr, then_expr) in branches {
430 let when_value = eval_expr(when_expr, row, ctx)?;
431 let matched = match &operand_value {
432 None => matches!(when_value, Value::Bool(true)),
433 Some(op_v) => matches!(
434 apply_binary(spg_sql::ast::BinOp::Eq, op_v.clone(), when_value)?,
435 Value::Bool(true)
436 ),
437 };
438 if matched {
439 return eval_expr(then_expr, row, ctx);
440 }
441 }
442 match else_branch {
443 Some(e) => eval_expr(e, row, ctx),
444 None => Ok(Value::Null),
445 }
446 }
447 }
448}
449
450fn value_to_text_for_array(v: &Value) -> String {
457 match v {
458 Value::Text(s) | Value::Json(s) => s.clone(),
459 Value::Int(n) => n.to_string(),
460 Value::BigInt(n) => n.to_string(),
461 Value::SmallInt(n) => n.to_string(),
462 Value::Bool(b) => {
463 if *b {
464 "true".into()
465 } else {
466 "false".into()
467 }
468 }
469 Value::Float(x) => format!("{x}"),
470 Value::Date(d) => format_date(*d),
471 Value::Timestamp(t) => format_timestamp(*t),
472 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
473 _ => format!("{v:?}"),
474 }
475}
476
477fn extract_field(field: spg_sql::ast::ExtractField, v: &Value) -> Result<Value, EvalError> {
481 use spg_sql::ast::ExtractField as F;
482 if matches!(v, Value::Null) {
483 return Ok(Value::Null);
484 }
485 if let Value::Interval { months, micros } = *v {
489 let years = months / 12;
490 let mons = months % 12;
491 let secs_total = micros / 1_000_000;
492 let frac = micros % 1_000_000;
493 let result = match field {
494 F::Year => i64::from(years),
495 F::Month => i64::from(mons),
496 F::Day => micros / 86_400_000_000,
497 F::Hour => (secs_total / 3600) % 24,
498 F::Minute => (secs_total / 60) % 60,
499 F::Second => secs_total % 60,
500 F::Microsecond => (secs_total % 60) * 1_000_000 + frac,
501 };
502 return Ok(Value::BigInt(result));
503 }
504 let (days, day_micros) = match *v {
505 Value::Date(d) => (d, 0_i64),
506 Value::Timestamp(t) => {
507 let days = t.div_euclid(86_400_000_000);
508 let day_micros = t.rem_euclid(86_400_000_000);
509 (i32::try_from(days).unwrap_or(i32::MAX), day_micros)
510 }
511 _ => {
512 return Err(EvalError::TypeMismatch {
513 detail: format!(
514 "EXTRACT requires DATE / TIMESTAMP / INTERVAL, got {:?}",
515 v.data_type()
516 ),
517 });
518 }
519 };
520 let (y, m, d) = civil_components(days);
521 let secs = day_micros / 1_000_000;
522 let hh = secs / 3600;
523 let mm = (secs / 60) % 60;
524 let ss = secs % 60;
525 let frac = day_micros % 1_000_000;
526 let result = match field {
527 F::Year => i64::from(y),
528 F::Month => i64::from(m),
529 F::Day => i64::from(d),
530 F::Hour => hh,
531 F::Minute => mm,
532 F::Second => ss,
533 F::Microsecond => ss * 1_000_000 + frac,
534 };
535 Ok(Value::BigInt(result))
536}
537
538fn civil_components(days: i32) -> (i32, u32, u32) {
541 civil_from_days(days)
542}
543
544fn like_match(text: &str, pattern: &str) -> bool {
549 let text: Vec<char> = text.chars().collect();
550 let pat: Vec<char> = pattern.chars().collect();
551 like_match_inner(&text, 0, &pat, 0)
552}
553
554fn like_match_inner(text: &[char], mut ti: usize, pat: &[char], mut pi: usize) -> bool {
555 while pi < pat.len() {
556 match pat[pi] {
557 '%' => {
558 while pi < pat.len() && pat[pi] == '%' {
560 pi += 1;
561 }
562 if pi == pat.len() {
563 return true;
564 }
565 for k in ti..=text.len() {
566 if like_match_inner(text, k, pat, pi) {
567 return true;
568 }
569 }
570 return false;
571 }
572 '_' => {
573 if ti >= text.len() {
574 return false;
575 }
576 ti += 1;
577 pi += 1;
578 }
579 '\\' if pi + 1 < pat.len() => {
580 let want = pat[pi + 1];
581 if ti >= text.len() || text[ti] != want {
582 return false;
583 }
584 ti += 1;
585 pi += 2;
586 }
587 c => {
588 if ti >= text.len() || text[ti] != c {
589 return false;
590 }
591 ti += 1;
592 pi += 1;
593 }
594 }
595 }
596 ti == text.len()
597}
598
599fn apply_function(name: &str, args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
602 match name.to_ascii_lowercase().as_str() {
603 "nextval" => {
605 if args.len() != 1 {
606 return Err(EvalError::TypeMismatch {
607 detail: format!("nextval() takes 1 arg, got {}", args.len()),
608 });
609 }
610 let seq_name = match &args[0] {
611 Value::Text(s) => s.clone(),
612 Value::Null => return Ok(Value::Null),
613 other => {
614 return Err(EvalError::TypeMismatch {
615 detail: format!(
616 "nextval() argument must be TEXT, got {:?}",
617 other.data_type()
618 ),
619 });
620 }
621 };
622 let resolver = ctx
623 .sequence_resolver
624 .ok_or_else(|| EvalError::TypeMismatch {
625 detail: "nextval() requires a sequence resolver (read-only context)".into(),
626 })?;
627 let v = resolver(SequenceOp::Next(seq_name))?;
628 Ok(Value::BigInt(v))
629 }
630 "currval" => {
631 if args.len() != 1 {
632 return Err(EvalError::TypeMismatch {
633 detail: format!("currval() takes 1 arg, got {}", args.len()),
634 });
635 }
636 let seq_name = match &args[0] {
637 Value::Text(s) => s.clone(),
638 Value::Null => return Ok(Value::Null),
639 other => {
640 return Err(EvalError::TypeMismatch {
641 detail: format!(
642 "currval() argument must be TEXT, got {:?}",
643 other.data_type()
644 ),
645 });
646 }
647 };
648 let resolver = ctx
649 .sequence_resolver
650 .ok_or_else(|| EvalError::TypeMismatch {
651 detail: "currval() requires a sequence resolver (read-only context)".into(),
652 })?;
653 let v = resolver(SequenceOp::Curr(seq_name))?;
654 Ok(Value::BigInt(v))
655 }
656 "setval" => {
657 if args.len() != 2 && args.len() != 3 {
658 return Err(EvalError::TypeMismatch {
659 detail: format!("setval() takes 2 or 3 args, got {}", args.len()),
660 });
661 }
662 let seq_name = match &args[0] {
663 Value::Text(s) => s.clone(),
664 Value::Null => return Ok(Value::Null),
665 other => {
666 return Err(EvalError::TypeMismatch {
667 detail: format!(
668 "setval() name argument must be TEXT, got {:?}",
669 other.data_type()
670 ),
671 });
672 }
673 };
674 let value = match &args[1] {
675 Value::SmallInt(n) => i64::from(*n),
676 Value::Int(n) => i64::from(*n),
677 Value::BigInt(n) => *n,
678 Value::Null => return Ok(Value::Null),
679 other => {
680 return Err(EvalError::TypeMismatch {
681 detail: format!(
682 "setval() value argument must be integer, got {:?}",
683 other.data_type()
684 ),
685 });
686 }
687 };
688 let is_called = if args.len() == 3 {
689 match &args[2] {
690 Value::Bool(b) => *b,
691 Value::Null => return Ok(Value::Null),
692 other => {
693 return Err(EvalError::TypeMismatch {
694 detail: format!(
695 "setval() is_called argument must be BOOL, got {:?}",
696 other.data_type()
697 ),
698 });
699 }
700 }
701 } else {
702 true
703 };
704 let resolver = ctx
705 .sequence_resolver
706 .ok_or_else(|| EvalError::TypeMismatch {
707 detail: "setval() requires a sequence resolver (read-only context)".into(),
708 })?;
709 let v = resolver(SequenceOp::Set {
710 name: seq_name,
711 value,
712 is_called,
713 })?;
714 Ok(Value::BigInt(v))
715 }
716 "length" => {
717 if args.len() != 1 {
718 return Err(EvalError::TypeMismatch {
719 detail: format!("length() takes 1 arg, got {}", args.len()),
720 });
721 }
722 match &args[0] {
723 Value::Null => Ok(Value::Null),
724 Value::Text(s) => {
725 let n = i32::try_from(s.chars().count()).unwrap_or(i32::MAX);
726 Ok(Value::Int(n))
727 }
728 Value::Bytes(b) => {
733 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
734 Ok(Value::Int(n))
735 }
736 other => Err(EvalError::TypeMismatch {
737 detail: format!("length() needs text or bytea, got {:?}", other.data_type()),
738 }),
739 }
740 }
741 "octet_length" => {
745 if args.len() != 1 {
746 return Err(EvalError::TypeMismatch {
747 detail: format!("octet_length() takes 1 arg, got {}", args.len()),
748 });
749 }
750 match &args[0] {
751 Value::Null => Ok(Value::Null),
752 Value::Text(s) => {
753 let n = i32::try_from(s.len()).unwrap_or(i32::MAX);
754 Ok(Value::Int(n))
755 }
756 Value::Bytes(b) => {
757 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
758 Ok(Value::Int(n))
759 }
760 other => Err(EvalError::TypeMismatch {
761 detail: format!(
762 "octet_length() needs text or bytea, got {:?}",
763 other.data_type()
764 ),
765 }),
766 }
767 }
768 "array_length" => {
775 if args.len() != 2 {
776 return Err(EvalError::TypeMismatch {
777 detail: format!("array_length() takes 2 args, got {}", args.len()),
778 });
779 }
780 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
781 return Ok(Value::Null);
782 }
783 let len = match &args[0] {
784 Value::TextArray(items) => items.len(),
785 Value::IntArray(items) => items.len(),
786 Value::BigIntArray(items) => items.len(),
787 _ => {
788 return Err(EvalError::TypeMismatch {
789 detail: format!(
790 "array_length() first arg must be an array, got {:?}",
791 args[0].data_type()
792 ),
793 });
794 }
795 };
796 let dim: i64 = match args[1] {
797 Value::Int(n) => i64::from(n),
798 Value::BigInt(n) => n,
799 Value::SmallInt(n) => i64::from(n),
800 _ => {
801 return Err(EvalError::TypeMismatch {
802 detail: format!(
803 "array_length() second arg must be integer, got {:?}",
804 args[1].data_type()
805 ),
806 });
807 }
808 };
809 if dim != 1 {
810 return Ok(Value::Null);
811 }
812 let n = i32::try_from(len).unwrap_or(i32::MAX);
813 Ok(Value::Int(n))
814 }
815 "array_position" => {
820 if args.len() != 2 {
821 return Err(EvalError::TypeMismatch {
822 detail: format!("array_position() takes 2 args, got {}", args.len()),
823 });
824 }
825 if matches!(args[0], Value::Null) {
826 return Ok(Value::Null);
827 }
828 if matches!(args[1], Value::Null) {
829 return Ok(Value::Null);
830 }
831 match (&args[0], &args[1]) {
832 (Value::TextArray(items), Value::Text(needle)) => {
833 for (idx, item) in items.iter().enumerate() {
834 if let Some(s) = item
835 && s == needle
836 {
837 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
838 }
839 }
840 Ok(Value::Null)
841 }
842 (Value::IntArray(items), needle_v)
843 if matches!(
844 needle_v,
845 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
846 ) =>
847 {
848 let needle: i64 = match *needle_v {
849 Value::Int(n) => i64::from(n),
850 Value::SmallInt(n) => i64::from(n),
851 Value::BigInt(n) => n,
852 _ => unreachable!(),
853 };
854 for (idx, item) in items.iter().enumerate() {
855 if let Some(n) = item
856 && i64::from(*n) == needle
857 {
858 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
859 }
860 }
861 Ok(Value::Null)
862 }
863 (Value::BigIntArray(items), needle_v)
864 if matches!(
865 needle_v,
866 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
867 ) =>
868 {
869 let needle: i64 = match *needle_v {
870 Value::Int(n) => i64::from(n),
871 Value::SmallInt(n) => i64::from(n),
872 Value::BigInt(n) => n,
873 _ => unreachable!(),
874 };
875 for (idx, item) in items.iter().enumerate() {
876 if let Some(n) = item
877 && *n == needle
878 {
879 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
880 }
881 }
882 Ok(Value::Null)
883 }
884 (
885 arr @ (Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_)),
886 other,
887 ) => Err(EvalError::TypeMismatch {
888 detail: format!(
889 "array_position() needle type {:?} doesn't match array {:?}",
890 other.data_type(),
891 arr.data_type()
892 ),
893 }),
894 (other, _) => Err(EvalError::TypeMismatch {
895 detail: format!(
896 "array_position() first arg must be an array, got {:?}",
897 other.data_type()
898 ),
899 }),
900 }
901 }
902 "substring" | "substr" => {
910 if !matches!(args.len(), 2 | 3) {
911 return Err(EvalError::TypeMismatch {
912 detail: format!("substring() takes 2 or 3 args, got {}", args.len()),
913 });
914 }
915 if args.iter().any(|a| matches!(a, Value::Null)) {
916 return Ok(Value::Null);
917 }
918 let start: i64 = match args[1] {
919 Value::Int(n) => i64::from(n),
920 Value::BigInt(n) => n,
921 Value::SmallInt(n) => i64::from(n),
922 _ => {
923 return Err(EvalError::TypeMismatch {
924 detail: format!(
925 "substring() start must be integer, got {:?}",
926 args[1].data_type()
927 ),
928 });
929 }
930 };
931 let length: Option<i64> = if args.len() == 3 {
932 match args[2] {
933 Value::Int(n) => Some(i64::from(n)),
934 Value::BigInt(n) => Some(n),
935 Value::SmallInt(n) => Some(i64::from(n)),
936 _ => {
937 return Err(EvalError::TypeMismatch {
938 detail: format!(
939 "substring() length must be integer, got {:?}",
940 args[2].data_type()
941 ),
942 });
943 }
944 }
945 } else {
946 None
947 };
948 let (effective_start, effective_length): (i64, Option<i64>) = match length {
951 Some(len) => {
952 let end = start.saturating_add(len);
953 if end <= 1 || len < 0 {
954 return Ok(match &args[0] {
955 Value::Text(_) => Value::Text(String::new()),
956 Value::Bytes(_) => Value::Bytes(Vec::new()),
957 other => {
958 return Err(EvalError::TypeMismatch {
959 detail: format!(
960 "substring() needs text or bytea, got {:?}",
961 other.data_type()
962 ),
963 });
964 }
965 });
966 }
967 let eff_start = start.max(1);
968 let eff_len = end - eff_start;
969 (eff_start, Some(eff_len.max(0)))
970 }
971 None => (start.max(1), None),
972 };
973 match &args[0] {
974 Value::Text(s) => {
975 let chars: Vec<char> = s.chars().collect();
977 let skip = (effective_start - 1) as usize;
978 if skip >= chars.len() {
979 return Ok(Value::Text(String::new()));
980 }
981 let take = match effective_length {
982 Some(n) => (n as usize).min(chars.len() - skip),
983 None => chars.len() - skip,
984 };
985 Ok(Value::Text(chars[skip..skip + take].iter().collect()))
986 }
987 Value::Bytes(b) => {
988 let skip = (effective_start - 1) as usize;
989 if skip >= b.len() {
990 return Ok(Value::Bytes(Vec::new()));
991 }
992 let take = match effective_length {
993 Some(n) => (n as usize).min(b.len() - skip),
994 None => b.len() - skip,
995 };
996 Ok(Value::Bytes(b[skip..skip + take].to_vec()))
997 }
998 other => Err(EvalError::TypeMismatch {
999 detail: format!(
1000 "substring() needs text or bytea, got {:?}",
1001 other.data_type()
1002 ),
1003 }),
1004 }
1005 }
1006 "position" => {
1014 if args.len() != 2 {
1015 return Err(EvalError::TypeMismatch {
1016 detail: format!("position() takes 2 args, got {}", args.len()),
1017 });
1018 }
1019 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
1020 return Ok(Value::Null);
1021 }
1022 match (&args[0], &args[1]) {
1023 (Value::Text(needle), Value::Text(haystack)) => {
1024 if needle.is_empty() {
1025 return Ok(Value::Int(1));
1026 }
1027 let h_chars: Vec<char> = haystack.chars().collect();
1029 let n_chars: Vec<char> = needle.chars().collect();
1030 if n_chars.len() > h_chars.len() {
1031 return Ok(Value::Int(0));
1032 }
1033 for i in 0..=h_chars.len() - n_chars.len() {
1034 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1035 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1036 }
1037 }
1038 Ok(Value::Int(0))
1039 }
1040 (Value::Bytes(needle), Value::Bytes(haystack)) => {
1041 if needle.is_empty() {
1042 return Ok(Value::Int(1));
1043 }
1044 if needle.len() > haystack.len() {
1045 return Ok(Value::Int(0));
1046 }
1047 for i in 0..=haystack.len() - needle.len() {
1048 if &haystack[i..i + needle.len()] == needle.as_slice() {
1049 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1050 }
1051 }
1052 Ok(Value::Int(0))
1053 }
1054 (a, b) => Err(EvalError::TypeMismatch {
1055 detail: format!(
1056 "position() operands must both be text or both bytea, got {:?} and {:?}",
1057 a.data_type(),
1058 b.data_type()
1059 ),
1060 }),
1061 }
1062 }
1063 "upper" => {
1064 if args.len() != 1 {
1065 return Err(EvalError::TypeMismatch {
1066 detail: format!("upper() takes 1 arg, got {}", args.len()),
1067 });
1068 }
1069 match &args[0] {
1070 Value::Null => Ok(Value::Null),
1071 Value::Text(s) => Ok(Value::Text(s.to_uppercase())),
1072 other => Err(EvalError::TypeMismatch {
1073 detail: format!("upper() needs text, got {:?}", other.data_type()),
1074 }),
1075 }
1076 }
1077 "lower" => {
1078 if args.len() != 1 {
1079 return Err(EvalError::TypeMismatch {
1080 detail: format!("lower() takes 1 arg, got {}", args.len()),
1081 });
1082 }
1083 match &args[0] {
1084 Value::Null => Ok(Value::Null),
1085 Value::Text(s) => Ok(Value::Text(s.to_lowercase())),
1086 other => Err(EvalError::TypeMismatch {
1087 detail: format!("lower() needs text, got {:?}", other.data_type()),
1088 }),
1089 }
1090 }
1091 "abs" => {
1092 if args.len() != 1 {
1093 return Err(EvalError::TypeMismatch {
1094 detail: format!("abs() takes 1 arg, got {}", args.len()),
1095 });
1096 }
1097 match &args[0] {
1098 Value::Null => Ok(Value::Null),
1099 Value::Int(n) => Ok(Value::Int(n.wrapping_abs())),
1100 Value::BigInt(n) => Ok(Value::BigInt(n.wrapping_abs())),
1101 Value::Float(x) => Ok(Value::Float(x.abs())),
1102 other => Err(EvalError::TypeMismatch {
1103 detail: format!("abs() needs numeric, got {:?}", other.data_type()),
1104 }),
1105 }
1106 }
1107 "coalesce" => {
1108 for a in args {
1109 if !matches!(a, Value::Null) {
1110 return Ok(a.clone());
1111 }
1112 }
1113 Ok(Value::Null)
1114 }
1115 "date_trunc" => date_trunc(args),
1116 "date_part" => date_part(args),
1117 "age" => age(args),
1118 "to_char" => to_char(args),
1119 "date_format" => date_format_mysql(args),
1125 "unix_timestamp" => unix_timestamp_of(args),
1126 "from_unixtime" => from_unixtime(args),
1127 "format" => format_string(args),
1135 "concat" => {
1156 let mut out = String::new();
1157 for v in args {
1158 if matches!(v, Value::Null) {
1159 continue;
1160 }
1161 out.push_str(&value_to_format_text(v));
1162 }
1163 Ok(Value::Text(out))
1164 }
1165 "random" => {
1288 if !args.is_empty() {
1289 return Err(EvalError::TypeMismatch {
1290 detail: alloc::format!("random() takes 0 args, got {}", args.len()),
1291 });
1292 }
1293 Ok(Value::Float(prng_next_f64()))
1294 }
1295 "gen_random_uuid" | "uuid_generate_v4" => {
1302 if !args.is_empty() {
1303 return Err(EvalError::TypeMismatch {
1304 detail: alloc::format!("{name}() takes 0 args, got {}", args.len()),
1305 });
1306 }
1307 Ok(Value::Uuid(gen_random_uuid_bytes()))
1308 }
1309 "sign" => {
1310 if args.len() != 1 {
1311 return Err(EvalError::TypeMismatch {
1312 detail: alloc::format!("sign() takes 1 arg, got {}", args.len()),
1313 });
1314 }
1315 match &args[0] {
1316 Value::Null => Ok(Value::Null),
1317 Value::SmallInt(n) => Ok(Value::SmallInt(n.signum())),
1318 Value::Int(n) => Ok(Value::Int(n.signum())),
1319 Value::BigInt(n) => Ok(Value::BigInt(n.signum())),
1320 Value::Float(x) => {
1321 let s = if *x > 0.0 {
1322 1.0
1323 } else if *x < 0.0 {
1324 -1.0
1325 } else {
1326 0.0
1327 };
1328 Ok(Value::Float(s))
1329 }
1330 Value::Numeric { scaled, scale } => {
1331 let s = scaled.signum();
1332 Ok(Value::Numeric {
1333 scaled: s * pow10_i128(*scale),
1334 scale: *scale,
1335 })
1336 }
1337 other => Err(EvalError::TypeMismatch {
1338 detail: alloc::format!("sign() needs numeric, got {:?}", other.data_type()),
1339 }),
1340 }
1341 }
1342 "sqrt" => {
1343 if args.len() != 1 {
1344 return Err(EvalError::TypeMismatch {
1345 detail: alloc::format!("sqrt() takes 1 arg, got {}", args.len()),
1346 });
1347 }
1348 match &args[0] {
1349 Value::Null => Ok(Value::Null),
1350 v => {
1351 let x = value_to_f64(v).ok_or_else(|| EvalError::TypeMismatch {
1352 detail: alloc::format!("sqrt() needs numeric, got {:?}", v.data_type()),
1353 })?;
1354 if x < 0.0 {
1355 return Err(EvalError::TypeMismatch {
1356 detail: "sqrt(): negative input outside real domain".into(),
1357 });
1358 }
1359 if x == 0.0 {
1360 return Ok(Value::Float(0.0));
1361 }
1362 Ok(Value::Float(f64_sqrt(x)))
1363 }
1364 }
1365 }
1366 "power" | "pow" => {
1367 if args.len() != 2 {
1368 return Err(EvalError::TypeMismatch {
1369 detail: alloc::format!("power() takes 2 args, got {}", args.len()),
1370 });
1371 }
1372 if args.iter().any(|v| matches!(v, Value::Null)) {
1373 return Ok(Value::Null);
1374 }
1375 let x = value_to_f64(&args[0]).ok_or_else(|| EvalError::TypeMismatch {
1376 detail: "power() needs numeric x".into(),
1377 })?;
1378 let y = value_to_f64(&args[1]).ok_or_else(|| EvalError::TypeMismatch {
1379 detail: "power() needs numeric y".into(),
1380 })?;
1381 let y_int = y as i32;
1383 if (y_int as f64) == y && y.abs() < 1024.0 {
1384 let result = f64_powi(x, y_int);
1385 return Ok(Value::Float(result));
1386 }
1387 if x < 0.0 {
1391 return Err(EvalError::TypeMismatch {
1392 detail: "power(): negative base with fractional exponent yields complex result"
1393 .into(),
1394 });
1395 }
1396 if x == 0.0 && y < 0.0 {
1397 return Err(EvalError::TypeMismatch {
1398 detail: "power(): 0 raised to negative power is undefined".into(),
1399 });
1400 }
1401 if x == 0.0 {
1402 return Ok(Value::Float(0.0));
1403 }
1404 Ok(Value::Float(f64_exp(y * f64_ln(x))))
1405 }
1406 "mod" => {
1407 if args.len() != 2 {
1408 return Err(EvalError::TypeMismatch {
1409 detail: alloc::format!("mod() takes 2 args, got {}", args.len()),
1410 });
1411 }
1412 if args.iter().any(|v| matches!(v, Value::Null)) {
1413 return Ok(Value::Null);
1414 }
1415 let to_i64 = |v: &Value| -> Result<i64, EvalError> {
1416 match v {
1417 Value::SmallInt(x) => Ok(i64::from(*x)),
1418 Value::Int(x) => Ok(i64::from(*x)),
1419 Value::BigInt(x) => Ok(*x),
1420 other => Err(EvalError::TypeMismatch {
1421 detail: alloc::format!("mod() needs integer, got {:?}", other.data_type()),
1422 }),
1423 }
1424 };
1425 let y = to_i64(&args[0])?;
1426 let x = to_i64(&args[1])?;
1427 if x == 0 {
1428 return Err(EvalError::TypeMismatch {
1429 detail: "mod(): division by zero".into(),
1430 });
1431 }
1432 let result = y % x;
1435 if let Ok(small) = i16::try_from(result) {
1437 if matches!(args[0], Value::SmallInt(_)) && matches!(args[1], Value::SmallInt(_)) {
1438 return Ok(Value::SmallInt(small));
1439 }
1440 }
1441 if let Ok(int_) = i32::try_from(result) {
1442 if !matches!(args[0], Value::BigInt(_)) && !matches!(args[1], Value::BigInt(_)) {
1443 return Ok(Value::Int(int_));
1444 }
1445 }
1446 Ok(Value::BigInt(result))
1447 }
1448 "greatest" | "least" => {
1449 if args.is_empty() {
1450 return Err(EvalError::TypeMismatch {
1451 detail: alloc::format!(
1452 "{lc}() takes at least 1 arg",
1453 lc = if name.eq_ignore_ascii_case("greatest") {
1454 "greatest"
1455 } else {
1456 "least"
1457 }
1458 ),
1459 });
1460 }
1461 let non_null: alloc::vec::Vec<&Value> =
1462 args.iter().filter(|v| !matches!(v, Value::Null)).collect();
1463 if non_null.is_empty() {
1464 return Ok(Value::Null);
1465 }
1466 let is_greatest = name.eq_ignore_ascii_case("greatest");
1467 let mut best = non_null[0].clone();
1468 for v in &non_null[1..] {
1469 let ord = value_cmp_for_min_max(&best, v);
1470 let take = if is_greatest {
1471 ord == core::cmp::Ordering::Less
1472 } else {
1473 ord == core::cmp::Ordering::Greater
1474 };
1475 if take {
1476 best = (*v).clone();
1477 }
1478 }
1479 Ok(best)
1480 }
1481 "ifnull" => {
1485 if args.len() != 2 {
1486 return Err(EvalError::TypeMismatch {
1487 detail: alloc::format!("ifnull() takes 2 args, got {}", args.len()),
1488 });
1489 }
1490 for v in args {
1491 if !matches!(v, Value::Null) {
1492 return Ok(v.clone());
1493 }
1494 }
1495 Ok(Value::Null)
1496 }
1497 "if" => {
1501 if args.len() != 3 {
1502 return Err(EvalError::TypeMismatch {
1503 detail: alloc::format!(
1504 "if() takes 3 args (cond, then, else), got {}",
1505 args.len()
1506 ),
1507 });
1508 }
1509 let truthy = match &args[0] {
1510 Value::Null => false,
1511 Value::Bool(b) => *b,
1512 Value::SmallInt(n) => *n != 0,
1513 Value::Int(n) => *n != 0,
1514 Value::BigInt(n) => *n != 0,
1515 Value::Float(x) => *x != 0.0,
1516 Value::Text(s) => !s.is_empty() && s != "0",
1517 _ => true,
1518 };
1519 if truthy {
1520 Ok(args[1].clone())
1521 } else {
1522 Ok(args[2].clone())
1523 }
1524 }
1525 "nullif" => {
1526 if args.len() != 2 {
1527 return Err(EvalError::TypeMismatch {
1528 detail: alloc::format!("nullif() takes 2 args, got {}", args.len()),
1529 });
1530 }
1531 match (&args[0], &args[1]) {
1532 (Value::Null, _) => Ok(Value::Null),
1533 (a, Value::Null) => Ok(a.clone()),
1534 (a, b) => {
1535 if values_equal_for_nullif(a, b) {
1539 Ok(Value::Null)
1540 } else {
1541 Ok(a.clone())
1542 }
1543 }
1544 }
1545 }
1546 "trunc" => {
1547 match args.len() {
1548 1 => match &args[0] {
1549 Value::Null => Ok(Value::Null),
1550 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1551 Value::Float(x) => Ok(Value::Float(f64_trunc(*x))),
1552 Value::Numeric { scaled, scale } => {
1553 let factor = pow10_i128(*scale);
1554 let q = scaled / factor;
1556 Ok(Value::Numeric {
1557 scaled: q * factor,
1558 scale: *scale,
1559 })
1560 }
1561 other => Err(EvalError::TypeMismatch {
1562 detail: alloc::format!(
1563 "trunc() needs numeric, got {:?}",
1564 other.data_type()
1565 ),
1566 }),
1567 },
1568 2 => {
1569 if args.iter().any(|v| matches!(v, Value::Null)) {
1570 return Ok(Value::Null);
1571 }
1572 let n = match &args[1] {
1573 Value::SmallInt(x) => i32::from(*x),
1574 Value::Int(x) => *x,
1575 Value::BigInt(x) => {
1576 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1577 detail: "trunc(): scale must fit in i32".into(),
1578 })?
1579 }
1580 other => {
1581 return Err(EvalError::TypeMismatch {
1582 detail: alloc::format!(
1583 "trunc(): scale must be integer, got {:?}",
1584 other.data_type()
1585 ),
1586 });
1587 }
1588 };
1589 let x = match &args[0] {
1590 Value::SmallInt(v) => f64::from(*v),
1591 Value::Int(v) => f64::from(*v),
1592 Value::BigInt(v) => *v as f64,
1593 Value::Float(v) => *v,
1594 Value::Numeric { scaled, scale } => {
1595 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1596 }
1597 other => {
1598 return Err(EvalError::TypeMismatch {
1599 detail: alloc::format!(
1600 "trunc() needs numeric x, got {:?}",
1601 other.data_type()
1602 ),
1603 });
1604 }
1605 };
1606 let result = if n >= 0 {
1607 let factor = f64_powi(10.0, n);
1608 f64_trunc(x * factor) / factor
1609 } else {
1610 let factor = f64_powi(10.0, -n);
1611 f64_trunc(x / factor) * factor
1612 };
1613 Ok(Value::Float(result))
1614 }
1615 _ => Err(EvalError::TypeMismatch {
1616 detail: alloc::format!("trunc() takes 1 or 2 args, got {}", args.len()),
1617 }),
1618 }
1619 }
1620 "round" => {
1621 match args.len() {
1622 1 => match &args[0] {
1623 Value::Null => Ok(Value::Null),
1624 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1625 Value::Float(x) => Ok(Value::Float(f64_round_half_away(*x))),
1626 Value::Numeric { scaled, scale } => {
1627 let factor = pow10_i128(*scale);
1628 let q = scaled.div_euclid(factor);
1629 let r = scaled.rem_euclid(factor);
1630 let result = if 2 * r >= factor { q + 1 } else { q };
1632 Ok(Value::Numeric {
1633 scaled: result * factor,
1634 scale: *scale,
1635 })
1636 }
1637 other => Err(EvalError::TypeMismatch {
1638 detail: alloc::format!(
1639 "round() needs numeric, got {:?}",
1640 other.data_type()
1641 ),
1642 }),
1643 },
1644 2 => {
1645 if args.iter().any(|v| matches!(v, Value::Null)) {
1646 return Ok(Value::Null);
1647 }
1648 let n = match &args[1] {
1649 Value::SmallInt(x) => i32::from(*x),
1650 Value::Int(x) => *x,
1651 Value::BigInt(x) => {
1652 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1653 detail: "round(): scale must fit in i32".into(),
1654 })?
1655 }
1656 other => {
1657 return Err(EvalError::TypeMismatch {
1658 detail: alloc::format!(
1659 "round(): scale must be integer, got {:?}",
1660 other.data_type()
1661 ),
1662 });
1663 }
1664 };
1665 let x = match &args[0] {
1671 Value::SmallInt(v) => f64::from(*v),
1672 Value::Int(v) => f64::from(*v),
1673 Value::BigInt(v) => *v as f64,
1674 Value::Float(v) => *v,
1675 Value::Numeric { scaled, scale } => {
1676 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1677 }
1678 other => {
1679 return Err(EvalError::TypeMismatch {
1680 detail: alloc::format!(
1681 "round() needs numeric x, got {:?}",
1682 other.data_type()
1683 ),
1684 });
1685 }
1686 };
1687 let result = if n >= 0 {
1692 let factor = f64_powi(10.0, n);
1693 f64_round_half_away(x * factor) / factor
1694 } else {
1695 let factor = f64_powi(10.0, -n);
1696 f64_round_half_away(x / factor) * factor
1697 };
1698 Ok(Value::Float(result))
1699 }
1700 _ => Err(EvalError::TypeMismatch {
1701 detail: alloc::format!("round() takes 1 or 2 args, got {}", args.len()),
1702 }),
1703 }
1704 }
1705 "ceil" | "ceiling" => {
1706 if args.len() != 1 {
1707 return Err(EvalError::TypeMismatch {
1708 detail: alloc::format!("ceil() takes 1 arg, got {}", args.len()),
1709 });
1710 }
1711 match &args[0] {
1712 Value::Null => Ok(Value::Null),
1713 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1714 Value::Float(x) => Ok(Value::Float(f64_ceil(*x))),
1715 Value::Numeric { scaled, scale } => {
1716 let factor = pow10_i128(*scale);
1717 let q = scaled.div_euclid(factor);
1718 let r = scaled.rem_euclid(factor);
1719 let result = if r == 0 { q } else { q + 1 };
1720 Ok(Value::Numeric {
1721 scaled: result * factor,
1722 scale: *scale,
1723 })
1724 }
1725 other => Err(EvalError::TypeMismatch {
1726 detail: alloc::format!("ceil() needs numeric, got {:?}", other.data_type()),
1727 }),
1728 }
1729 }
1730 "floor" => {
1731 if args.len() != 1 {
1732 return Err(EvalError::TypeMismatch {
1733 detail: alloc::format!("floor() takes 1 arg, got {}", args.len()),
1734 });
1735 }
1736 match &args[0] {
1737 Value::Null => Ok(Value::Null),
1738 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1739 Value::Float(x) => Ok(Value::Float(f64_floor(*x))),
1740 Value::Numeric { scaled, scale } => {
1741 let factor = pow10_i128(*scale);
1742 let q = scaled.div_euclid(factor);
1743 Ok(Value::Numeric {
1747 scaled: q * factor,
1748 scale: *scale,
1749 })
1750 }
1751 other => Err(EvalError::TypeMismatch {
1752 detail: alloc::format!("floor() needs numeric, got {:?}", other.data_type()),
1753 }),
1754 }
1755 }
1756 "left" => string_left_right(args, true, "left"),
1757 "right" => string_left_right(args, false, "right"),
1758 "strpos" => {
1759 if args.len() != 2 {
1760 return Err(EvalError::TypeMismatch {
1761 detail: alloc::format!(
1762 "strpos() takes 2 args (haystack, needle), got {}",
1763 args.len()
1764 ),
1765 });
1766 }
1767 if args.iter().any(|v| matches!(v, Value::Null)) {
1768 return Ok(Value::Null);
1769 }
1770 let haystack = value_to_format_text(&args[0]);
1771 let needle = value_to_format_text(&args[1]);
1772 if needle.is_empty() {
1773 return Ok(Value::Int(1));
1774 }
1775 let h_chars: Vec<char> = haystack.chars().collect();
1776 let n_chars: Vec<char> = needle.chars().collect();
1777 if n_chars.len() > h_chars.len() {
1778 return Ok(Value::Int(0));
1779 }
1780 for i in 0..=h_chars.len() - n_chars.len() {
1781 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1782 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1783 }
1784 }
1785 Ok(Value::Int(0))
1786 }
1787 "lpad" => string_pad(args, true, "lpad"),
1788 "rpad" => string_pad(args, false, "rpad"),
1789 "repeat" => {
1790 if args.len() != 2 {
1791 return Err(EvalError::TypeMismatch {
1792 detail: alloc::format!("repeat() takes 2 args, got {}", args.len()),
1793 });
1794 }
1795 if args.iter().any(|v| matches!(v, Value::Null)) {
1796 return Ok(Value::Null);
1797 }
1798 let s = value_to_format_text(&args[0]);
1799 let n = match &args[1] {
1800 Value::SmallInt(x) => i64::from(*x),
1801 Value::Int(x) => i64::from(*x),
1802 Value::BigInt(x) => *x,
1803 other => {
1804 return Err(EvalError::TypeMismatch {
1805 detail: alloc::format!(
1806 "repeat(): n must be integer, got {:?}",
1807 other.data_type()
1808 ),
1809 });
1810 }
1811 };
1812 if n <= 0 {
1813 return Ok(Value::Text(String::new()));
1814 }
1815 const MAX_REPEAT_BYTES: usize = 64 * 1024 * 1024;
1819 let needed =
1820 s.len()
1821 .checked_mul(n as usize)
1822 .ok_or_else(|| EvalError::TypeMismatch {
1823 detail: "repeat(): result size overflows usize".into(),
1824 })?;
1825 if needed > MAX_REPEAT_BYTES {
1826 return Err(EvalError::TypeMismatch {
1827 detail: alloc::format!(
1828 "repeat(): result would exceed {MAX_REPEAT_BYTES} bytes"
1829 ),
1830 });
1831 }
1832 Ok(Value::Text(s.repeat(n as usize)))
1833 }
1834 "split_part" => {
1835 if args.len() != 3 {
1836 return Err(EvalError::TypeMismatch {
1837 detail: alloc::format!(
1838 "split_part() takes 3 args (string, delim, n), got {}",
1839 args.len()
1840 ),
1841 });
1842 }
1843 if args.iter().any(|v| matches!(v, Value::Null)) {
1844 return Ok(Value::Null);
1845 }
1846 let s = value_to_format_text(&args[0]);
1847 let delim = value_to_format_text(&args[1]);
1848 if delim.is_empty() {
1849 return Err(EvalError::TypeMismatch {
1850 detail: "split_part(): delimiter cannot be empty".into(),
1851 });
1852 }
1853 let n = match &args[2] {
1854 Value::SmallInt(x) => i64::from(*x),
1855 Value::Int(x) => i64::from(*x),
1856 Value::BigInt(x) => *x,
1857 other => {
1858 return Err(EvalError::TypeMismatch {
1859 detail: alloc::format!(
1860 "split_part(): n must be integer, got {:?}",
1861 other.data_type()
1862 ),
1863 });
1864 }
1865 };
1866 if n == 0 {
1867 return Err(EvalError::TypeMismatch {
1868 detail: "split_part(): n must be nonzero (PG: 1-indexed)".into(),
1869 });
1870 }
1871 let parts: alloc::vec::Vec<&str> = s.split(&delim[..]).collect();
1872 let total = parts.len() as i64;
1873 let idx = if n > 0 {
1874 n - 1
1875 } else {
1876 total + n
1878 };
1879 if idx < 0 || idx >= total {
1880 return Ok(Value::Text(String::new()));
1881 }
1882 Ok(Value::Text(parts[idx as usize].to_string()))
1883 }
1884 "translate" => {
1891 if args.len() != 3 {
1892 return Err(EvalError::TypeMismatch {
1893 detail: alloc::format!("translate() takes 3 args, got {}", args.len()),
1894 });
1895 }
1896 if args.iter().any(|v| matches!(v, Value::Null)) {
1897 return Ok(Value::Null);
1898 }
1899 let s = value_to_format_text(&args[0]);
1900 let from = value_to_format_text(&args[1]);
1901 let to = value_to_format_text(&args[2]);
1902 let from_chars: Vec<char> = from.chars().collect();
1903 let to_chars: Vec<char> = to.chars().collect();
1904 let mut map: alloc::collections::BTreeMap<char, Option<char>> =
1906 alloc::collections::BTreeMap::new();
1907 for (i, &fc) in from_chars.iter().enumerate() {
1908 if map.contains_key(&fc) {
1909 continue;
1910 }
1911 let replacement = to_chars.get(i).copied();
1912 map.insert(fc, replacement);
1913 }
1914 let mut out = String::with_capacity(s.len());
1915 for c in s.chars() {
1916 match map.get(&c) {
1917 Some(Some(r)) => out.push(*r),
1918 Some(None) => {} None => out.push(c),
1920 }
1921 }
1922 Ok(Value::Text(out))
1923 }
1924 "replace" => {
1925 if args.len() != 3 {
1926 return Err(EvalError::TypeMismatch {
1927 detail: alloc::format!(
1928 "replace() takes 3 args (string, from, to), got {}",
1929 args.len()
1930 ),
1931 });
1932 }
1933 if args.iter().any(|v| matches!(v, Value::Null)) {
1934 return Ok(Value::Null);
1935 }
1936 let s = value_to_format_text(&args[0]);
1937 let from = value_to_format_text(&args[1]);
1938 let to = value_to_format_text(&args[2]);
1939 if from.is_empty() {
1940 return Ok(Value::Text(s));
1941 }
1942 Ok(Value::Text(s.replace(&from[..], &to)))
1947 }
1948 "trim" | "btrim" => string_trim(args, TrimSide::Both, "trim"),
1949 "ltrim" => string_trim(args, TrimSide::Left, "ltrim"),
1950 "rtrim" => string_trim(args, TrimSide::Right, "rtrim"),
1951 "concat_ws" => {
1952 if args.is_empty() {
1953 return Err(EvalError::TypeMismatch {
1954 detail: "concat_ws() requires at least 1 arg (the separator)".into(),
1955 });
1956 }
1957 let sep = match &args[0] {
1959 Value::Null => return Ok(Value::Null),
1960 v => value_to_format_text(v),
1961 };
1962 let mut out = String::new();
1963 let mut first = true;
1964 for v in &args[1..] {
1965 if matches!(v, Value::Null) {
1966 continue;
1967 }
1968 if first {
1969 first = false;
1970 } else {
1971 out.push_str(&sep);
1972 }
1973 out.push_str(&value_to_format_text(v));
1974 }
1975 Ok(Value::Text(out))
1976 }
1977 "regexp_matches" => regexp_matches(args),
1979 "regexp_replace" => regexp_replace(args),
1980 "regexp_split_to_array" => regexp_split_to_array(args),
1981 "to_json" | "to_jsonb" => {
1985 if args.len() != 1 {
1986 return Err(EvalError::TypeMismatch {
1987 detail: alloc::format!("to_json() takes 1 arg, got {}", args.len()),
1988 });
1989 }
1990 if let Value::Json(s) = &args[0] {
1992 return Ok(Value::Json(s.clone()));
1993 }
1994 Ok(Value::Json(crate::json::value_to_json_text(&args[0])))
1995 }
1996 "json_build_object" | "jsonb_build_object" => crate::json::build_object(args),
1997 "json_build_array" | "jsonb_build_array" => crate::json::build_array(args),
1998 "jsonb_set" | "json_set" => crate::json::set(args),
1999 "jsonb_insert" | "json_insert" => crate::json::insert(args),
2000 "jsonb_path_query" | "json_path_query" => {
2002 if args.len() != 2 {
2003 return Err(EvalError::TypeMismatch {
2004 detail: alloc::format!("jsonb_path_query() takes 2 args, got {}", args.len()),
2005 });
2006 }
2007 crate::json::path_query(&args[0], &args[1])
2008 }
2009 "jsonb_path_query_first" | "json_path_query_first" => {
2010 if args.len() != 2 {
2011 return Err(EvalError::TypeMismatch {
2012 detail: alloc::format!(
2013 "jsonb_path_query_first() takes 2 args, got {}",
2014 args.len()
2015 ),
2016 });
2017 }
2018 crate::json::path_query_first(&args[0], &args[1])
2019 }
2020 "jsonb_path_query_array" | "json_path_query_array" => {
2021 if args.len() != 2 {
2022 return Err(EvalError::TypeMismatch {
2023 detail: alloc::format!(
2024 "jsonb_path_query_array() takes 2 args, got {}",
2025 args.len()
2026 ),
2027 });
2028 }
2029 crate::json::path_query_array(&args[0], &args[1])
2030 }
2031 "host" => inet_host(args),
2033 "network" => inet_network(args),
2034 "masklen" => inet_masklen(args),
2035 "encode" => encode_text(args),
2037 "decode" => decode_text(args),
2038 "error_on_null" => error_on_null(args),
2039 "to_tsvector" => fts_to_tsvector(args, ctx),
2044 "plainto_tsquery" => fts_plainto_tsquery(args, ctx),
2045 "phraseto_tsquery" => fts_phraseto_tsquery(args, ctx),
2046 "websearch_to_tsquery" => fts_websearch_to_tsquery(args, ctx),
2047 "to_tsquery" => fts_to_tsquery(args, ctx),
2048 "ts_rank" => fts_ts_rank(args),
2051 "ts_rank_cd" => fts_ts_rank_cd(args),
2052 "set_config" => Ok(args.get(1).cloned().unwrap_or(Value::Null)),
2057 "current_setting" => Ok(Value::Text(String::new())),
2058 "pg_get_serial_sequence" | "pg_get_constraintdef" | "pg_get_indexdef" => Ok(Value::Null),
2064 "version" => Ok(Value::Text("PostgreSQL 16 (SPG-compat)".into())),
2065 "current_database" | "database" => Ok(Value::Text("spg".into())),
2073 "current_schema" => Ok(Value::Text("public".into())),
2074 "current_user" | "session_user" | "user" => Ok(Value::Text("admin".into())),
2075 "pg_typeof" => {
2082 if args.len() != 1 {
2083 return Err(EvalError::TypeMismatch {
2084 detail: format!("pg_typeof() takes 1 arg, got {}", args.len()),
2085 });
2086 }
2087 Ok(Value::Text(pg_typeof_name(&args[0]).into()))
2088 }
2089 "lastval" => Ok(Value::Null),
2094 "similarity" => {
2099 if args.len() != 2 {
2100 return Err(EvalError::TypeMismatch {
2101 detail: format!("similarity() takes 2 args, got {}", args.len()),
2102 });
2103 }
2104 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2105 return Ok(Value::Null);
2106 }
2107 let a = match &args[0] {
2108 Value::Text(s) => s.as_str(),
2109 other => {
2110 return Err(EvalError::TypeMismatch {
2111 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2112 });
2113 }
2114 };
2115 let b = match &args[1] {
2116 Value::Text(s) => s.as_str(),
2117 other => {
2118 return Err(EvalError::TypeMismatch {
2119 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2120 });
2121 }
2122 };
2123 Ok(Value::Float(spg_storage::trgm::similarity(a, b)))
2126 }
2127 "show_trgm" => {
2128 if args.len() != 1 {
2129 return Err(EvalError::TypeMismatch {
2130 detail: format!("show_trgm() takes 1 arg, got {}", args.len()),
2131 });
2132 }
2133 if matches!(args[0], Value::Null) {
2134 return Ok(Value::Null);
2135 }
2136 let s = match &args[0] {
2137 Value::Text(s) => s.as_str(),
2138 other => {
2139 return Err(EvalError::TypeMismatch {
2140 detail: format!("show_trgm() needs text, got {:?}", other.data_type()),
2141 });
2142 }
2143 };
2144 let trigrams: Vec<Option<String>> = spg_storage::trgm::extract_trigrams(s)
2148 .into_iter()
2149 .map(Some)
2150 .collect();
2151 Ok(Value::TextArray(trigrams))
2152 }
2153 other => Err(EvalError::TypeMismatch {
2154 detail: format!("unknown function `{other}`"),
2155 }),
2156 }
2157}
2158
2159fn fts_ts_rank(args: &[Value]) -> Result<Value, EvalError> {
2164 let (vec, query) = parse_rank_args("ts_rank", args)?;
2165 match (vec, query) {
2166 (None, _) | (_, None) => Ok(Value::Null),
2167 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank(&v, &q)))),
2168 }
2169}
2170
2171fn fts_ts_rank_cd(args: &[Value]) -> Result<Value, EvalError> {
2172 let (vec, query) = parse_rank_args("ts_rank_cd", args)?;
2173 match (vec, query) {
2174 (None, _) | (_, None) => Ok(Value::Null),
2175 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank_cd(&v, &q)))),
2176 }
2177}
2178
2179fn parse_rank_args(
2180 name: &str,
2181 args: &[Value],
2182) -> Result<
2183 (
2184 Option<Vec<spg_storage::TsLexeme>>,
2185 Option<spg_storage::TsQueryAst>,
2186 ),
2187 EvalError,
2188> {
2189 if args.len() != 2 {
2190 return Err(EvalError::TypeMismatch {
2191 detail: format!(
2192 "{name}() takes 2 args in v7.12.2 (weights array + normalisation flag are v7.12.x carve-out), got {}",
2193 args.len()
2194 ),
2195 });
2196 }
2197 let vec = match &args[0] {
2198 Value::Null => None,
2199 Value::TsVector(v) => Some(v.clone()),
2200 other => {
2201 return Err(EvalError::TypeMismatch {
2202 detail: format!(
2203 "{name}() first arg must be tsvector, got {:?}",
2204 other.data_type()
2205 ),
2206 });
2207 }
2208 };
2209 let query = match &args[1] {
2210 Value::Null => None,
2211 Value::TsQuery(q) => Some(q.clone()),
2212 other => {
2213 return Err(EvalError::TypeMismatch {
2214 detail: format!(
2215 "{name}() second arg must be tsquery, got {:?}",
2216 other.data_type()
2217 ),
2218 });
2219 }
2220 };
2221 Ok((vec, query))
2222}
2223
2224fn ts_match(l: Value, r: Value) -> Result<Value, EvalError> {
2229 let (vec, query) = match (l, r) {
2230 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
2231 (Value::TsVector(v), Value::TsQuery(q)) => (v, q),
2232 (Value::TsQuery(q), Value::TsVector(v)) => (v, q),
2233 (l, r) => {
2234 return Err(EvalError::TypeMismatch {
2235 detail: format!(
2236 "@@ requires (tsvector, tsquery), got ({:?}, {:?})",
2237 l.data_type(),
2238 r.data_type()
2239 ),
2240 });
2241 }
2242 };
2243 Ok(Value::Bool(crate::fts::ts_query_matches(&vec, &query)))
2244}
2245
2246fn fts_to_tsvector(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2251 let (config, text) = parse_fts_args("to_tsvector", args, ctx)?;
2252 match text {
2253 None => Ok(Value::Null),
2254 Some(t) => Ok(Value::TsVector(crate::fts::to_tsvector(config, &t))),
2255 }
2256}
2257
2258fn fts_plainto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2259 let (config, text) = parse_fts_args("plainto_tsquery", args, ctx)?;
2260 match text {
2261 None => Ok(Value::Null),
2262 Some(t) => Ok(Value::TsQuery(crate::fts::plainto_tsquery(config, &t))),
2263 }
2264}
2265
2266fn fts_phraseto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2267 let (config, text) = parse_fts_args("phraseto_tsquery", args, ctx)?;
2268 match text {
2269 None => Ok(Value::Null),
2270 Some(t) => Ok(Value::TsQuery(crate::fts::phraseto_tsquery(config, &t))),
2271 }
2272}
2273
2274fn fts_websearch_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2275 let (config, text) = parse_fts_args("websearch_to_tsquery", args, ctx)?;
2276 match text {
2277 None => Ok(Value::Null),
2278 Some(t) => Ok(Value::TsQuery(crate::fts::websearch_to_tsquery(config, &t))),
2279 }
2280}
2281
2282fn fts_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2283 let (config, text) = parse_fts_args("to_tsquery", args, ctx)?;
2284 match text {
2285 None => Ok(Value::Null),
2286 Some(t) => Ok(Value::TsQuery(crate::fts::to_tsquery(config, &t)?)),
2287 }
2288}
2289
2290fn parse_fts_args(
2295 name: &str,
2296 args: &[Value],
2297 ctx: &EvalContext<'_>,
2298) -> Result<(crate::fts::TsConfig, Option<String>), EvalError> {
2299 let (config_arg, text_arg) = match args {
2300 [t] => (None, t),
2301 [c, t] => (Some(c), t),
2302 _ => {
2303 return Err(EvalError::TypeMismatch {
2304 detail: format!("{name}() takes 1 or 2 args, got {}", args.len()),
2305 });
2306 }
2307 };
2308 let config = match config_arg {
2309 None => match ctx.default_text_search_config {
2310 Some(name_str) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2311 EvalError::TypeMismatch {
2312 detail: format!(
2313 "text search config not implemented: {name_str:?} (supported: simple, english)"
2314 ),
2315 }
2316 })?,
2317 None => crate::fts::TsConfig::Simple,
2318 },
2319 Some(Value::Null) => return Ok((crate::fts::TsConfig::Simple, None)),
2320 Some(Value::Text(name_str)) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2321 EvalError::TypeMismatch {
2322 detail: format!(
2323 "text search config not implemented: {name_str:?} (supported: simple, english)"
2324 ),
2325 }
2326 })?,
2327 Some(other) => {
2328 return Err(EvalError::TypeMismatch {
2329 detail: format!(
2330 "{name}() config arg must be text, got {:?}",
2331 other.data_type()
2332 ),
2333 });
2334 }
2335 };
2336 let text = match text_arg {
2337 Value::Null => None,
2338 Value::Text(s) => Some(s.clone()),
2339 other => {
2340 return Err(EvalError::TypeMismatch {
2341 detail: format!(
2342 "{name}() text arg must be text, got {:?}",
2343 other.data_type()
2344 ),
2345 });
2346 }
2347 };
2348 Ok((config, text))
2349}
2350
2351fn encode_text(args: &[Value]) -> Result<Value, EvalError> {
2357 if args.len() != 2 {
2358 return Err(EvalError::TypeMismatch {
2359 detail: format!("encode() takes 2 args, got {}", args.len()),
2360 });
2361 }
2362 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2363 return Ok(Value::Null);
2364 }
2365 let bytes: &[u8] = match &args[0] {
2366 Value::Text(s) => s.as_bytes(),
2367 other => {
2368 return Err(EvalError::TypeMismatch {
2369 detail: format!("encode() expects text bytes, got {:?}", other.data_type()),
2370 });
2371 }
2372 };
2373 let fmt = match &args[1] {
2374 Value::Text(s) => s.to_ascii_lowercase(),
2375 other => {
2376 return Err(EvalError::TypeMismatch {
2377 detail: format!("encode() format must be text, got {:?}", other.data_type()),
2378 });
2379 }
2380 };
2381 let out = match fmt.as_str() {
2382 "base64" => b64_encode(bytes, B64_STD),
2383 "base64url" => b64_encode(bytes, B64_URL),
2384 "base32hex" => b32hex_encode(bytes),
2385 "hex" => hex_encode(bytes),
2386 other => {
2387 return Err(EvalError::TypeMismatch {
2388 detail: format!("encode(): unknown format `{other}`"),
2389 });
2390 }
2391 };
2392 Ok(Value::Text(out))
2393}
2394
2395fn decode_text(args: &[Value]) -> Result<Value, EvalError> {
2399 if args.len() != 2 {
2400 return Err(EvalError::TypeMismatch {
2401 detail: format!("decode() takes 2 args, got {}", args.len()),
2402 });
2403 }
2404 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2405 return Ok(Value::Null);
2406 }
2407 let text = match &args[0] {
2408 Value::Text(s) => s.as_str(),
2409 other => {
2410 return Err(EvalError::TypeMismatch {
2411 detail: format!("decode() expects text, got {:?}", other.data_type()),
2412 });
2413 }
2414 };
2415 let fmt = match &args[1] {
2416 Value::Text(s) => s.to_ascii_lowercase(),
2417 other => {
2418 return Err(EvalError::TypeMismatch {
2419 detail: format!("decode() format must be text, got {:?}", other.data_type()),
2420 });
2421 }
2422 };
2423 let bytes = match fmt.as_str() {
2424 "base64" => b64_decode(text, B64_STD)?,
2425 "base64url" => b64_decode(text, B64_URL)?,
2426 "base32hex" => b32hex_decode(text)?,
2427 "hex" => hex_decode(text)?,
2428 other => {
2429 return Err(EvalError::TypeMismatch {
2430 detail: format!("decode(): unknown format `{other}`"),
2431 });
2432 }
2433 };
2434 let s = String::from_utf8(bytes).map_err(|_| EvalError::TypeMismatch {
2435 detail: "decode(): result bytes are not valid UTF-8 (SPG stores raw bytes as Text)".into(),
2436 })?;
2437 Ok(Value::Text(s))
2438}
2439
2440fn error_on_null(args: &[Value]) -> Result<Value, EvalError> {
2444 if args.len() != 1 {
2445 return Err(EvalError::TypeMismatch {
2446 detail: format!("error_on_null() takes 1 arg, got {}", args.len()),
2447 });
2448 }
2449 if matches!(args[0], Value::Null) {
2450 return Err(EvalError::TypeMismatch {
2451 detail: "error_on_null(): argument is NULL".into(),
2452 });
2453 }
2454 Ok(args[0].clone())
2455}
2456
2457const B64_STD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2460const B64_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2461const B32HEX_ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHIJKLMNOPQRSTUV";
2462
2463fn b64_encode(bytes: &[u8], alpha: &[u8; 64]) -> String {
2464 let mut out = String::with_capacity((bytes.len() + 2) / 3 * 4);
2465 let mut i = 0;
2466 while i + 3 <= bytes.len() {
2467 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8) | (bytes[i + 2] as u32);
2468 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2469 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2470 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2471 out.push(alpha[(n & 0x3f) as usize] as char);
2472 i += 3;
2473 }
2474 let rem = bytes.len() - i;
2475 if rem == 1 {
2476 let n = (bytes[i] as u32) << 16;
2477 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2478 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2479 out.push('=');
2480 out.push('=');
2481 } else if rem == 2 {
2482 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8);
2483 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2484 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2485 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2486 out.push('=');
2487 }
2488 out
2489}
2490
2491fn b64_decode(text: &str, alpha: &[u8; 64]) -> Result<Vec<u8>, EvalError> {
2492 let mut lookup = [255u8; 256];
2493 for (i, &c) in alpha.iter().enumerate() {
2494 lookup[c as usize] = i as u8;
2495 }
2496 let mut out = Vec::with_capacity(text.len() * 3 / 4);
2497 let mut buf: u32 = 0;
2498 let mut bits: u32 = 0;
2499 for c in text.bytes() {
2500 if c == b'=' {
2501 break;
2502 }
2503 if c == b'\n' || c == b'\r' || c == b' ' {
2504 continue;
2505 }
2506 let v = lookup[c as usize];
2507 if v == 255 {
2508 return Err(EvalError::TypeMismatch {
2509 detail: format!("decode(base64): invalid char {:?}", c as char),
2510 });
2511 }
2512 buf = (buf << 6) | v as u32;
2513 bits += 6;
2514 if bits >= 8 {
2515 bits -= 8;
2516 out.push(((buf >> bits) & 0xff) as u8);
2517 }
2518 }
2519 Ok(out)
2520}
2521
2522fn b32hex_encode(bytes: &[u8]) -> String {
2523 let mut out = String::with_capacity((bytes.len() * 8 + 4) / 5);
2524 let mut buf: u64 = 0;
2525 let mut bits: u32 = 0;
2526 for &b in bytes {
2527 buf = (buf << 8) | b as u64;
2528 bits += 8;
2529 while bits >= 5 {
2530 bits -= 5;
2531 out.push(B32HEX_ALPHABET[((buf >> bits) & 0x1f) as usize] as char);
2532 }
2533 }
2534 if bits > 0 {
2535 out.push(B32HEX_ALPHABET[((buf << (5 - bits)) & 0x1f) as usize] as char);
2536 }
2537 while out.len() % 8 != 0 {
2539 out.push('=');
2540 }
2541 out
2542}
2543
2544fn b32hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2545 let mut lookup = [255u8; 256];
2546 for (i, &c) in B32HEX_ALPHABET.iter().enumerate() {
2547 lookup[c as usize] = i as u8;
2548 let lower = (c as char).to_ascii_lowercase() as u8;
2550 lookup[lower as usize] = i as u8;
2551 }
2552 let mut out = Vec::with_capacity(text.len() * 5 / 8);
2553 let mut buf: u64 = 0;
2554 let mut bits: u32 = 0;
2555 for c in text.bytes() {
2556 if c == b'=' {
2557 break;
2558 }
2559 if c == b'\n' || c == b'\r' || c == b' ' {
2560 continue;
2561 }
2562 let v = lookup[c as usize];
2563 if v == 255 {
2564 return Err(EvalError::TypeMismatch {
2565 detail: format!("decode(base32hex): invalid char {:?}", c as char),
2566 });
2567 }
2568 buf = (buf << 5) | v as u64;
2569 bits += 5;
2570 if bits >= 8 {
2571 bits -= 8;
2572 out.push(((buf >> bits) & 0xff) as u8);
2573 }
2574 }
2575 Ok(out)
2576}
2577
2578fn hex_encode(bytes: &[u8]) -> String {
2579 const HEX: &[u8; 16] = b"0123456789abcdef";
2580 let mut out = String::with_capacity(bytes.len() * 2);
2581 for &b in bytes {
2582 out.push(HEX[(b >> 4) as usize] as char);
2583 out.push(HEX[(b & 0xf) as usize] as char);
2584 }
2585 out
2586}
2587
2588fn hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2589 let trimmed = text.trim();
2590 if trimmed.len() % 2 != 0 {
2591 return Err(EvalError::TypeMismatch {
2592 detail: "decode(hex): input length must be even".into(),
2593 });
2594 }
2595 let mut out = Vec::with_capacity(trimmed.len() / 2);
2596 let mut hi: u8 = 0;
2597 for (i, c) in trimmed.bytes().enumerate() {
2598 let v = match c {
2599 b'0'..=b'9' => c - b'0',
2600 b'a'..=b'f' => c - b'a' + 10,
2601 b'A'..=b'F' => c - b'A' + 10,
2602 _ => {
2603 return Err(EvalError::TypeMismatch {
2604 detail: format!("decode(hex): invalid char {:?}", c as char),
2605 });
2606 }
2607 };
2608 if i % 2 == 0 {
2609 hi = v;
2610 } else {
2611 out.push((hi << 4) | v);
2612 }
2613 }
2614 Ok(out)
2615}
2616
2617fn date_part(args: &[Value]) -> Result<Value, EvalError> {
2622 use spg_sql::ast::ExtractField as F;
2623 if args.len() != 2 {
2624 return Err(EvalError::TypeMismatch {
2625 detail: format!("date_part() takes 2 args, got {}", args.len()),
2626 });
2627 }
2628 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
2629 return Ok(Value::Null);
2630 }
2631 let Value::Text(field_name) = &args[0] else {
2632 return Err(EvalError::TypeMismatch {
2633 detail: format!(
2634 "date_part() needs a text field, got {:?}",
2635 args[0].data_type()
2636 ),
2637 });
2638 };
2639 let field = match field_name.to_ascii_lowercase().as_str() {
2640 "year" => F::Year,
2641 "month" => F::Month,
2642 "day" => F::Day,
2643 "hour" => F::Hour,
2644 "minute" => F::Minute,
2645 "second" => F::Second,
2646 "microsecond" | "microseconds" => F::Microsecond,
2647 other => {
2648 return Err(EvalError::TypeMismatch {
2649 detail: format!(
2650 "unknown date_part field {other:?}; \
2651 supported: year, month, day, hour, minute, second, microsecond"
2652 ),
2653 });
2654 }
2655 };
2656 extract_field(field, &args[1])
2657}
2658
2659fn age(args: &[Value]) -> Result<Value, EvalError> {
2669 if args.is_empty() || args.len() > 2 {
2670 return Err(EvalError::TypeMismatch {
2671 detail: format!("age() takes 1 or 2 args, got {}", args.len()),
2672 });
2673 }
2674 if args.iter().any(|v| matches!(v, Value::Null)) {
2675 return Ok(Value::Null);
2676 }
2677 let to_micros = |v: &Value| -> Result<i64, EvalError> {
2680 match v {
2681 Value::Timestamp(t) => Ok(*t),
2682 Value::Date(d) => Ok(i64::from(*d) * 86_400_000_000),
2683 other => Err(EvalError::TypeMismatch {
2684 detail: format!("age() needs DATE or TIMESTAMP, got {:?}", other.data_type()),
2685 }),
2686 }
2687 };
2688 if args.len() == 1 {
2689 return Err(EvalError::TypeMismatch {
2690 detail: "single-arg age() is unsupported in v2.12 \
2691 (use age(CURRENT_DATE, t) explicitly)"
2692 .into(),
2693 });
2694 }
2695 let a = to_micros(&args[0])?;
2696 let b = to_micros(&args[1])?;
2697 let delta = a.checked_sub(b).ok_or(EvalError::TypeMismatch {
2698 detail: "age() subtraction overflows i64 microseconds".into(),
2699 })?;
2700 Ok(Value::Interval {
2701 months: 0,
2702 micros: delta,
2703 })
2704}
2705
2706fn inet_host(args: &[Value]) -> Result<Value, EvalError> {
2720 let s = match args {
2721 [Value::Text(s)] => s.clone(),
2722 [Value::Null] => return Ok(Value::Null),
2723 _ => {
2724 return Err(EvalError::TypeMismatch {
2725 detail: alloc::format!("host() takes one TEXT arg, got {} args", args.len()),
2726 });
2727 }
2728 };
2729 let host = s.split('/').next().unwrap_or("").to_string();
2730 Ok(Value::Text(host))
2731}
2732
2733fn inet_network(args: &[Value]) -> Result<Value, EvalError> {
2734 let s = match args {
2735 [Value::Text(s)] => s.clone(),
2736 [Value::Null] => return Ok(Value::Null),
2737 _ => {
2738 return Err(EvalError::TypeMismatch {
2739 detail: alloc::format!("network() takes one TEXT arg, got {} args", args.len()),
2740 });
2741 }
2742 };
2743 let mut split = s.splitn(2, '/');
2747 let host = split.next().unwrap_or("").to_string();
2748 let mask: u32 = split.next().and_then(|m| m.parse().ok()).unwrap_or(32);
2749 if !host.contains('.') {
2750 return Ok(Value::Text(s));
2752 }
2753 let octets: Vec<&str> = host.split('.').collect();
2754 if octets.len() != 4 {
2755 return Ok(Value::Text(s));
2756 }
2757 let keep_bytes = ((mask + 7) / 8) as usize;
2758 let mut out = alloc::string::String::new();
2759 for (i, oct) in octets.iter().enumerate() {
2760 if i > 0 {
2761 out.push('.');
2762 }
2763 if i < keep_bytes {
2764 out.push_str(oct);
2765 } else {
2766 out.push('0');
2767 }
2768 }
2769 out.push('/');
2770 out.push_str(&mask.to_string());
2771 Ok(Value::Text(out))
2772}
2773
2774fn inet_masklen(args: &[Value]) -> Result<Value, EvalError> {
2775 let s = match args {
2776 [Value::Text(s)] => s.clone(),
2777 [Value::Null] => return Ok(Value::Null),
2778 _ => {
2779 return Err(EvalError::TypeMismatch {
2780 detail: alloc::format!("masklen() takes one TEXT arg, got {} args", args.len()),
2781 });
2782 }
2783 };
2784 let mask: i32 = s
2785 .split_once('/')
2786 .and_then(|(_, m)| m.parse().ok())
2787 .unwrap_or(32);
2788 Ok(Value::Int(mask))
2789}
2790
2791struct InetNet {
2810 bytes: [u8; 16],
2811 family_bytes: u8,
2813 prefix_bits: u8,
2815}
2816
2817fn parse_inet_text(s: &str) -> Option<InetNet> {
2818 let mut split = s.splitn(2, '/');
2819 let host = split.next()?;
2820 let mask_str = split.next();
2821 if host.contains(':') {
2822 let bytes = parse_ipv6(host)?;
2823 let prefix_bits = match mask_str {
2824 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 128)?,
2825 None => 128,
2826 };
2827 let mut out = [0u8; 16];
2828 out.copy_from_slice(&bytes);
2829 Some(InetNet {
2830 bytes: out,
2831 family_bytes: 16,
2832 prefix_bits,
2833 })
2834 } else {
2835 let bytes = parse_ipv4(host)?;
2836 let prefix_bits = match mask_str {
2837 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 32)?,
2838 None => 32,
2839 };
2840 let mut out = [0u8; 16];
2841 out[..4].copy_from_slice(&bytes);
2842 Some(InetNet {
2843 bytes: out,
2844 family_bytes: 4,
2845 prefix_bits,
2846 })
2847 }
2848}
2849
2850fn parse_ipv4(s: &str) -> Option<[u8; 4]> {
2851 let parts: Vec<&str> = s.split('.').collect();
2852 if parts.len() != 4 {
2853 return None;
2854 }
2855 let mut out = [0u8; 4];
2856 for (i, p) in parts.iter().enumerate() {
2857 out[i] = p.parse::<u8>().ok()?;
2858 }
2859 Some(out)
2860}
2861
2862fn parse_ipv6(s: &str) -> Option<[u8; 16]> {
2863 let (head, tail) = match s.find("::") {
2865 Some(idx) => (&s[..idx], Some(&s[idx + 2..])),
2866 None => (s, None),
2867 };
2868 let head_groups: Vec<&str> = if head.is_empty() {
2869 Vec::new()
2870 } else {
2871 head.split(':').collect()
2872 };
2873 let tail_groups: Vec<&str> = match tail {
2874 Some(t) if !t.is_empty() => t.split(':').collect(),
2875 _ => Vec::new(),
2876 };
2877 let head_len = head_groups.len();
2878 let tail_len = tail_groups.len();
2879 if tail.is_none() {
2881 if head_len != 8 {
2882 return None;
2883 }
2884 } else if head_len + tail_len > 7 {
2885 return None;
2886 }
2887 let mut words = [0u16; 8];
2888 for (i, g) in head_groups.iter().enumerate() {
2889 words[i] = u16::from_str_radix(g, 16).ok()?;
2890 }
2891 let tail_start = 8 - tail_len;
2892 for (i, g) in tail_groups.iter().enumerate() {
2893 words[tail_start + i] = u16::from_str_radix(g, 16).ok()?;
2894 }
2895 let mut out = [0u8; 16];
2896 for (i, w) in words.iter().enumerate() {
2897 out[i * 2] = (w >> 8) as u8;
2898 out[i * 2 + 1] = (w & 0xff) as u8;
2899 }
2900 Some(out)
2901}
2902
2903fn network_prefix_eq(a: &InetNet, b: &InetNet, prefix_bits: u8) -> bool {
2906 let full_bytes = (prefix_bits / 8) as usize;
2907 if a.bytes[..full_bytes] != b.bytes[..full_bytes] {
2908 return false;
2909 }
2910 let extra = prefix_bits % 8;
2911 if extra == 0 {
2912 return true;
2913 }
2914 let mask: u8 = 0xff << (8 - extra);
2915 (a.bytes[full_bytes] & mask) == (b.bytes[full_bytes] & mask)
2916}
2917
2918fn inet_contained_eq(a: &InetNet, b: &InetNet) -> bool {
2920 if a.family_bytes != b.family_bytes {
2921 return false;
2922 }
2923 if a.prefix_bits < b.prefix_bits {
2924 return false;
2925 }
2926 network_prefix_eq(a, b, b.prefix_bits)
2927}
2928
2929fn inet_networks_equal(a: &InetNet, b: &InetNet) -> bool {
2932 if a.family_bytes != b.family_bytes {
2933 return false;
2934 }
2935 if a.prefix_bits != b.prefix_bits {
2936 return false;
2937 }
2938 network_prefix_eq(a, b, a.prefix_bits)
2939}
2940
2941fn inet_op_bool_result(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
2942 if matches!(l, Value::Null) || matches!(r, Value::Null) {
2943 return Ok(Value::Null);
2944 }
2945 let (lt, rt) = match (l, r) {
2946 (Value::Text(a), Value::Text(b)) => (a, b),
2947 _ => {
2948 return Err(EvalError::TypeMismatch {
2949 detail: format!(
2950 "inet operator requires TEXT/INET operands, got {:?} and {:?}",
2951 l.data_type(),
2952 r.data_type()
2953 ),
2954 });
2955 }
2956 };
2957 let a = parse_inet_text(lt).ok_or_else(|| EvalError::TypeMismatch {
2958 detail: format!("invalid inet text: {:?}", lt),
2959 })?;
2960 let b = parse_inet_text(rt).ok_or_else(|| EvalError::TypeMismatch {
2961 detail: format!("invalid inet text: {:?}", rt),
2962 })?;
2963 let result = match op {
2964 BinOp::InetContainedByEq => inet_contained_eq(&a, &b),
2965 BinOp::InetContainedBy => inet_contained_eq(&a, &b) && !inet_networks_equal(&a, &b),
2966 BinOp::InetContainsEq => inet_contained_eq(&b, &a),
2967 BinOp::InetContains => inet_contained_eq(&b, &a) && !inet_networks_equal(&a, &b),
2968 BinOp::InetOverlap => inet_contained_eq(&a, &b) || inet_contained_eq(&b, &a),
2969 _ => unreachable!("inet_op_bool_result called with non-inet op"),
2970 };
2971 Ok(Value::Bool(result))
2972}
2973
2974#[derive(Debug, Clone)]
3005enum ReNode {
3006 Literal(char),
3009 AnyChar,
3011 Class {
3013 members: Vec<ClassMember>,
3014 negated: bool,
3015 },
3016 Start,
3018 End,
3020 Quant {
3022 inner: Box<ReNode>,
3023 min: usize,
3024 max: Option<usize>,
3025 },
3026 Concat(Vec<ReNode>),
3028 Alt(Vec<ReNode>),
3030}
3031
3032#[derive(Debug, Clone)]
3033enum ClassMember {
3034 Single(char),
3035 Range(char, char),
3036}
3037
3038fn re_compile(pat: &str) -> Result<ReNode, EvalError> {
3039 let chars: Vec<char> = pat.chars().collect();
3040 let mut p = 0;
3041 let n = re_parse_alt(&chars, &mut p)?;
3042 if p != chars.len() {
3043 return Err(EvalError::TypeMismatch {
3044 detail: alloc::format!("regex compile: trailing chars at pos {p} in {pat:?}"),
3045 });
3046 }
3047 Ok(n)
3048}
3049
3050fn re_parse_alt(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3051 let mut branches = alloc::vec![re_parse_concat(chars, p)?];
3052 while *p < chars.len() && chars[*p] == '|' {
3053 *p += 1;
3054 branches.push(re_parse_concat(chars, p)?);
3055 }
3056 if branches.len() == 1 {
3057 Ok(branches.pop().unwrap())
3058 } else {
3059 Ok(ReNode::Alt(branches))
3060 }
3061}
3062
3063fn re_parse_concat(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3064 let mut items: Vec<ReNode> = Vec::new();
3065 while *p < chars.len() {
3066 let c = chars[*p];
3067 if c == '|' || c == ')' {
3068 break;
3069 }
3070 let atom = re_parse_atom(chars, p)?;
3071 let quantified = if *p < chars.len() {
3073 match chars[*p] {
3074 '*' => {
3075 *p += 1;
3076 if *p < chars.len() && chars[*p] == '?' {
3080 *p += 1;
3081 }
3082 ReNode::Quant {
3083 inner: Box::new(atom),
3084 min: 0,
3085 max: None,
3086 }
3087 }
3088 '+' => {
3089 *p += 1;
3090 if *p < chars.len() && chars[*p] == '?' {
3091 *p += 1;
3092 }
3093 ReNode::Quant {
3094 inner: Box::new(atom),
3095 min: 1,
3096 max: None,
3097 }
3098 }
3099 '?' => {
3100 *p += 1;
3101 ReNode::Quant {
3102 inner: Box::new(atom),
3103 min: 0,
3104 max: Some(1),
3105 }
3106 }
3107 _ => atom,
3108 }
3109 } else {
3110 atom
3111 };
3112 items.push(quantified);
3113 }
3114 if items.len() == 1 {
3115 Ok(items.pop().unwrap())
3116 } else {
3117 Ok(ReNode::Concat(items))
3118 }
3119}
3120
3121fn re_parse_atom(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3122 let c = chars[*p];
3123 match c {
3124 '(' => {
3125 *p += 1;
3126 let inner = re_parse_alt(chars, p)?;
3127 if *p >= chars.len() || chars[*p] != ')' {
3128 return Err(EvalError::TypeMismatch {
3129 detail: "regex compile: unmatched '('".into(),
3130 });
3131 }
3132 *p += 1;
3133 Ok(inner)
3134 }
3135 '[' => {
3136 *p += 1;
3137 let mut negated = false;
3138 if *p < chars.len() && chars[*p] == '^' {
3139 negated = true;
3140 *p += 1;
3141 }
3142 let mut members: Vec<ClassMember> = Vec::new();
3143 while *p < chars.len() && chars[*p] != ']' {
3144 let start = chars[*p];
3145 *p += 1;
3146 if *p + 1 < chars.len() && chars[*p] == '-' && chars[*p + 1] != ']' {
3147 let end = chars[*p + 1];
3148 *p += 2;
3149 members.push(ClassMember::Range(start, end));
3150 } else {
3151 members.push(ClassMember::Single(start));
3152 }
3153 }
3154 if *p >= chars.len() {
3155 return Err(EvalError::TypeMismatch {
3156 detail: "regex compile: unmatched '['".into(),
3157 });
3158 }
3159 *p += 1; Ok(ReNode::Class { members, negated })
3161 }
3162 '.' => {
3163 *p += 1;
3164 Ok(ReNode::AnyChar)
3165 }
3166 '^' => {
3167 *p += 1;
3168 Ok(ReNode::Start)
3169 }
3170 '$' => {
3171 *p += 1;
3172 Ok(ReNode::End)
3173 }
3174 '\\' => {
3175 *p += 1;
3176 if *p >= chars.len() {
3177 return Err(EvalError::TypeMismatch {
3178 detail: "regex compile: dangling backslash".into(),
3179 });
3180 }
3181 let esc = chars[*p];
3182 *p += 1;
3183 match esc {
3184 'd' => Ok(ReNode::Class {
3185 members: alloc::vec![ClassMember::Range('0', '9')],
3186 negated: false,
3187 }),
3188 'D' => Ok(ReNode::Class {
3189 members: alloc::vec![ClassMember::Range('0', '9')],
3190 negated: true,
3191 }),
3192 'w' => Ok(ReNode::Class {
3193 members: alloc::vec![
3194 ClassMember::Range('a', 'z'),
3195 ClassMember::Range('A', 'Z'),
3196 ClassMember::Range('0', '9'),
3197 ClassMember::Single('_'),
3198 ],
3199 negated: false,
3200 }),
3201 'W' => Ok(ReNode::Class {
3202 members: alloc::vec![
3203 ClassMember::Range('a', 'z'),
3204 ClassMember::Range('A', 'Z'),
3205 ClassMember::Range('0', '9'),
3206 ClassMember::Single('_'),
3207 ],
3208 negated: true,
3209 }),
3210 's' => Ok(ReNode::Class {
3211 members: alloc::vec![
3212 ClassMember::Single(' '),
3213 ClassMember::Single('\t'),
3214 ClassMember::Single('\n'),
3215 ClassMember::Single('\r'),
3216 ],
3217 negated: false,
3218 }),
3219 'S' => Ok(ReNode::Class {
3220 members: alloc::vec![
3221 ClassMember::Single(' '),
3222 ClassMember::Single('\t'),
3223 ClassMember::Single('\n'),
3224 ClassMember::Single('\r'),
3225 ],
3226 negated: true,
3227 }),
3228 other => Ok(ReNode::Literal(other)),
3229 }
3230 }
3231 other => {
3232 *p += 1;
3233 Ok(ReNode::Literal(other))
3234 }
3235 }
3236}
3237
3238fn class_matches(member: &ClassMember, c: char) -> bool {
3239 match member {
3240 ClassMember::Single(s) => *s == c,
3241 ClassMember::Range(a, b) => c >= *a && c <= *b,
3242 }
3243}
3244
3245fn re_match_at(node: &ReNode, s: &[char], pos: usize) -> Option<usize> {
3250 match node {
3251 ReNode::Literal(c) => {
3252 if s.get(pos).copied() == Some(*c) {
3253 Some(pos + 1)
3254 } else {
3255 None
3256 }
3257 }
3258 ReNode::AnyChar => {
3259 if pos < s.len() && s[pos] != '\n' {
3260 Some(pos + 1)
3261 } else {
3262 None
3263 }
3264 }
3265 ReNode::Class { members, negated } => {
3266 let c = *s.get(pos)?;
3267 let hit = members.iter().any(|m| class_matches(m, c));
3268 if hit ^ negated { Some(pos + 1) } else { None }
3269 }
3270 ReNode::Start => {
3271 if pos == 0 {
3272 Some(pos)
3273 } else {
3274 None
3275 }
3276 }
3277 ReNode::End => {
3278 if pos == s.len() {
3279 Some(pos)
3280 } else {
3281 None
3282 }
3283 }
3284 ReNode::Concat(items) => {
3285 let mut p = pos;
3286 for it in items {
3287 p = re_match_at(it, s, p)?;
3288 }
3289 Some(p)
3290 }
3291 ReNode::Alt(branches) => {
3292 for b in branches {
3293 if let Some(p) = re_match_at(b, s, pos) {
3294 return Some(p);
3295 }
3296 }
3297 None
3298 }
3299 ReNode::Quant { inner, min, max } => {
3300 let mut count = 0usize;
3305 let mut p = pos;
3306 loop {
3307 if let Some(cap) = max {
3308 if count >= *cap {
3309 break;
3310 }
3311 }
3312 match re_match_at(inner, s, p) {
3313 Some(np) if np > p => {
3314 p = np;
3315 count += 1;
3316 }
3317 _ => break,
3318 }
3319 }
3320 if count < *min {
3321 return None;
3322 }
3323 Some(p)
3324 }
3325 }
3326}
3327
3328fn re_find(node: &ReNode, s: &[char], from: usize) -> Option<(usize, usize)> {
3331 let mut start = from;
3332 loop {
3333 if let Some(end) = re_match_at(node, s, start) {
3334 return Some((start, end));
3335 }
3336 if start >= s.len() {
3337 return None;
3338 }
3339 start += 1;
3340 }
3341}
3342
3343fn regexp_matches(args: &[Value]) -> Result<Value, EvalError> {
3349 let (text, pat, all_matches) = match args.len() {
3350 2 => (text_arg(&args[0])?, text_arg(&args[1])?, false),
3351 3 => {
3352 let flags = text_arg(&args[2])?.unwrap_or_default();
3353 (
3354 text_arg(&args[0])?,
3355 text_arg(&args[1])?,
3356 flags.contains('g'),
3357 )
3358 }
3359 n => {
3360 return Err(EvalError::TypeMismatch {
3361 detail: alloc::format!("regexp_matches() takes 2 or 3 args, got {n}"),
3362 });
3363 }
3364 };
3365 let Some(text) = text else {
3366 return Ok(Value::Null);
3367 };
3368 let Some(pat) = pat else {
3369 return Ok(Value::Null);
3370 };
3371 let node = re_compile(&pat)?;
3372 let chars: Vec<char> = text.chars().collect();
3373 let mut out: Vec<Option<String>> = Vec::new();
3374 let mut from = 0usize;
3375 while let Some((s_pos, e_pos)) = re_find(&node, &chars, from) {
3376 out.push(Some(chars[s_pos..e_pos].iter().collect()));
3377 if !all_matches {
3378 break;
3379 }
3380 from = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3382 if from > chars.len() {
3383 break;
3384 }
3385 }
3386 Ok(Value::TextArray(out))
3387}
3388
3389fn regexp_replace(args: &[Value]) -> Result<Value, EvalError> {
3393 let (text, pat, repl, flags) = match args.len() {
3394 3 => (
3395 text_arg(&args[0])?,
3396 text_arg(&args[1])?,
3397 text_arg(&args[2])?,
3398 String::new(),
3399 ),
3400 4 => (
3401 text_arg(&args[0])?,
3402 text_arg(&args[1])?,
3403 text_arg(&args[2])?,
3404 text_arg(&args[3])?.unwrap_or_default(),
3405 ),
3406 n => {
3407 return Err(EvalError::TypeMismatch {
3408 detail: alloc::format!("regexp_replace() takes 3 or 4 args, got {n}"),
3409 });
3410 }
3411 };
3412 let Some(text) = text else {
3413 return Ok(Value::Null);
3414 };
3415 let Some(pat) = pat else {
3416 return Ok(Value::Null);
3417 };
3418 let Some(repl) = repl else {
3419 return Ok(Value::Null);
3420 };
3421 let global = flags.contains('g');
3422 let node = re_compile(&pat)?;
3423 let chars: Vec<char> = text.chars().collect();
3424 let mut out = String::with_capacity(text.len());
3425 let mut from = 0usize;
3426 loop {
3427 match re_find(&node, &chars, from) {
3428 Some((s_pos, e_pos)) => {
3429 out.extend(chars[from..s_pos].iter());
3430 out.push_str(&repl);
3431 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3432 from = step;
3433 if !global {
3434 if from <= chars.len() {
3435 out.extend(chars[from..].iter());
3436 }
3437 return Ok(Value::Text(out));
3438 }
3439 if from > chars.len() {
3440 break;
3441 }
3442 }
3443 None => {
3444 out.extend(chars[from..].iter());
3445 break;
3446 }
3447 }
3448 }
3449 Ok(Value::Text(out))
3450}
3451
3452fn regexp_split_to_array(args: &[Value]) -> Result<Value, EvalError> {
3455 if args.len() != 2 {
3456 return Err(EvalError::TypeMismatch {
3457 detail: alloc::format!("regexp_split_to_array() takes 2 args, got {}", args.len()),
3458 });
3459 }
3460 let text = text_arg(&args[0])?;
3461 let pat = text_arg(&args[1])?;
3462 let Some(text) = text else {
3463 return Ok(Value::Null);
3464 };
3465 let Some(pat) = pat else {
3466 return Ok(Value::Null);
3467 };
3468 let node = re_compile(&pat)?;
3469 let chars: Vec<char> = text.chars().collect();
3470 let mut out: Vec<Option<String>> = Vec::new();
3471 let mut piece_start = 0usize;
3472 let mut from = 0usize;
3473 loop {
3474 match re_find(&node, &chars, from) {
3475 Some((s_pos, e_pos)) => {
3476 let piece: String = chars[piece_start..s_pos].iter().collect();
3477 out.push(Some(piece));
3478 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3479 from = step;
3480 piece_start = step;
3481 if from > chars.len() {
3482 break;
3483 }
3484 }
3485 None => {
3486 let tail: String = chars[piece_start..].iter().collect();
3487 out.push(Some(tail));
3488 break;
3489 }
3490 }
3491 }
3492 Ok(Value::TextArray(out))
3493}
3494
3495fn text_arg(v: &Value) -> Result<Option<String>, EvalError> {
3498 match v {
3499 Value::Text(s) => Ok(Some(s.clone())),
3500 Value::Null => Ok(None),
3501 other => Err(EvalError::TypeMismatch {
3502 detail: alloc::format!(
3503 "regex function expects TEXT arg, got {:?}",
3504 other.data_type()
3505 ),
3506 }),
3507 }
3508}
3509
3510#[derive(Debug, Clone, Copy)]
3512enum TrimSide {
3513 Left,
3514 Right,
3515 Both,
3516}
3517
3518fn string_left_right(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
3522 if args.len() != 2 {
3523 return Err(EvalError::TypeMismatch {
3524 detail: alloc::format!("{fn_name}() takes 2 args, got {}", args.len()),
3525 });
3526 }
3527 if args.iter().any(|v| matches!(v, Value::Null)) {
3528 return Ok(Value::Null);
3529 }
3530 let s = value_to_format_text(&args[0]);
3531 let n = match &args[1] {
3532 Value::SmallInt(x) => i64::from(*x),
3533 Value::Int(x) => i64::from(*x),
3534 Value::BigInt(x) => *x,
3535 other => {
3536 return Err(EvalError::TypeMismatch {
3537 detail: alloc::format!(
3538 "{fn_name}(): n must be integer, got {:?}",
3539 other.data_type()
3540 ),
3541 });
3542 }
3543 };
3544 let chars: Vec<char> = s.chars().collect();
3545 let len = chars.len() as i64;
3546 if n == 0 {
3547 return Ok(Value::Text(String::new()));
3548 }
3549 let (start, end) = if is_left {
3550 if n > 0 {
3551 (0usize, (n.min(len)) as usize)
3552 } else {
3553 let drop = (-n).min(len);
3555 (0usize, (len - drop) as usize)
3556 }
3557 } else if n > 0 {
3558 let start = (len - n).max(0);
3560 (start as usize, len as usize)
3561 } else {
3562 let drop = (-n).min(len);
3564 (drop as usize, len as usize)
3565 };
3566 if start >= end {
3567 return Ok(Value::Text(String::new()));
3568 }
3569 Ok(Value::Text(chars[start..end].iter().collect()))
3570}
3571
3572fn value_cmp_for_min_max(a: &Value, b: &Value) -> core::cmp::Ordering {
3576 use core::cmp::Ordering;
3577 let a_int = match a {
3579 Value::SmallInt(x) => Some(i64::from(*x)),
3580 Value::Int(x) => Some(i64::from(*x)),
3581 Value::BigInt(x) => Some(*x),
3582 _ => None,
3583 };
3584 let b_int = match b {
3585 Value::SmallInt(x) => Some(i64::from(*x)),
3586 Value::Int(x) => Some(i64::from(*x)),
3587 Value::BigInt(x) => Some(*x),
3588 _ => None,
3589 };
3590 if let (Some(av), Some(bv)) = (a_int, b_int) {
3591 return av.cmp(&bv);
3592 }
3593 let a_f = value_to_f64(a);
3595 let b_f = value_to_f64(b);
3596 if let (Some(av), Some(bv)) = (a_f, b_f) {
3597 return av.partial_cmp(&bv).unwrap_or(Ordering::Equal);
3598 }
3599 match (a, b) {
3601 (Value::Text(av), Value::Text(bv)) => av.cmp(bv),
3602 (Value::Bytes(av), Value::Bytes(bv)) => av.cmp(bv),
3603 _ => Ordering::Equal,
3604 }
3605}
3606
3607fn value_to_f64(v: &Value) -> Option<f64> {
3608 match v {
3609 Value::Float(x) => Some(*x),
3610 Value::SmallInt(x) => Some(f64::from(*x)),
3611 Value::Int(x) => Some(f64::from(*x)),
3612 Value::BigInt(x) => Some(*x as f64),
3613 Value::Numeric { scaled, scale } => {
3614 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3615 }
3616 _ => None,
3617 }
3618}
3619
3620fn values_equal_for_nullif(a: &Value, b: &Value) -> bool {
3625 if a == b {
3627 return true;
3628 }
3629 let a_int = match a {
3631 Value::SmallInt(x) => Some(i64::from(*x)),
3632 Value::Int(x) => Some(i64::from(*x)),
3633 Value::BigInt(x) => Some(*x),
3634 _ => None,
3635 };
3636 let b_int = match b {
3637 Value::SmallInt(x) => Some(i64::from(*x)),
3638 Value::Int(x) => Some(i64::from(*x)),
3639 Value::BigInt(x) => Some(*x),
3640 _ => None,
3641 };
3642 if let (Some(a), Some(b)) = (a_int, b_int) {
3643 return a == b;
3644 }
3645 let a_f = match a {
3647 Value::Float(x) => Some(*x),
3648 Value::SmallInt(x) => Some(f64::from(*x)),
3649 Value::Int(x) => Some(f64::from(*x)),
3650 Value::BigInt(x) => Some(*x as f64),
3651 Value::Numeric { scaled, scale } => {
3652 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3653 }
3654 _ => None,
3655 };
3656 let b_f = match b {
3657 Value::Float(x) => Some(*x),
3658 Value::SmallInt(x) => Some(f64::from(*x)),
3659 Value::Int(x) => Some(f64::from(*x)),
3660 Value::BigInt(x) => Some(*x as f64),
3661 Value::Numeric { scaled, scale } => {
3662 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3663 }
3664 _ => None,
3665 };
3666 if let (Some(a), Some(b)) = (a_f, b_f) {
3667 return a == b;
3668 }
3669 false
3670}
3671
3672fn f64_trunc(x: f64) -> f64 {
3677 if x.is_nan() || x.is_infinite() {
3678 return x;
3679 }
3680 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3681 return x;
3682 }
3683 (x as i64) as f64
3684}
3685
3686static PRNG_STATE: core::sync::atomic::AtomicU64 =
3691 core::sync::atomic::AtomicU64::new(0x2545_F491_4F6C_DD1D);
3692
3693fn prng_next_u64() -> u64 {
3699 use core::sync::atomic::Ordering;
3700 let mut x = PRNG_STATE.load(Ordering::Relaxed);
3701 loop {
3702 if x == 0 {
3703 x = 0x2545_F491_4F6C_DD1D;
3704 }
3705 let mut next = x;
3706 next ^= next << 13;
3707 next ^= next >> 7;
3708 next ^= next << 17;
3709 match PRNG_STATE.compare_exchange_weak(x, next, Ordering::Relaxed, Ordering::Relaxed) {
3710 Ok(_) => return next,
3711 Err(seen) => x = seen,
3712 }
3713 }
3714}
3715
3716fn prng_next_f64() -> f64 {
3718 let mantissa = prng_next_u64() >> 11;
3720 let denom = (1u64 << 53) as f64;
3721 mantissa as f64 / denom
3722}
3723
3724pub fn gen_random_uuid_bytes() -> [u8; 16] {
3731 let mut out = [0u8; 16];
3732 let hi = prng_next_u64().to_be_bytes();
3733 let lo = prng_next_u64().to_be_bytes();
3734 out[..8].copy_from_slice(&hi);
3735 out[8..].copy_from_slice(&lo);
3736 out[6] = (out[6] & 0x0f) | 0x40;
3738 out[8] = (out[8] & 0x3f) | 0x80;
3740 out
3741}
3742
3743fn f64_sqrt(x: f64) -> f64 {
3748 if x == 0.0 || x.is_nan() {
3749 return x;
3750 }
3751 if x.is_infinite() {
3752 return x;
3753 }
3754 let bits = x.to_bits();
3758 let exp = ((bits >> 52) & 0x7ff) as i64 - 1023;
3759 let new_exp = (exp / 2) + 1023;
3760 let mut guess = f64::from_bits(((new_exp as u64) & 0x7ff) << 52);
3761 for _ in 0..8 {
3763 guess = 0.5 * (guess + x / guess);
3764 }
3765 guess
3766}
3767
3768fn f64_exp(x: f64) -> f64 {
3773 if x.is_nan() {
3774 return x;
3775 }
3776 if x > 709.0 {
3777 return f64::INFINITY;
3778 }
3779 if x < -745.0 {
3780 return 0.0;
3781 }
3782 const LN2: f64 = 0.6931471805599453;
3784 let k = f64_round_half_away(x / LN2) as i32;
3785 let r = x - (k as f64) * LN2;
3786 let mut term = 1.0;
3788 let mut sum = 1.0;
3789 for n in 1..=20 {
3790 term *= r / (n as f64);
3791 sum += term;
3792 if term.abs() < 1e-18 {
3793 break;
3794 }
3795 }
3796 f64_powi(2.0, k) * sum
3798}
3799
3800fn f64_ln(x: f64) -> f64 {
3803 if x <= 0.0 {
3804 return f64::NAN;
3805 }
3806 if x == 1.0 {
3807 return 0.0;
3808 }
3809 const LN2: f64 = 0.6931471805599453;
3811 let mut k = 0i32;
3812 let mut m = x;
3813 while m >= 2.0 {
3814 m *= 0.5;
3815 k += 1;
3816 }
3817 while m < 1.0 {
3818 m *= 2.0;
3819 k -= 1;
3820 }
3821 let u = (m - 1.0) / (m + 1.0);
3824 let u2 = u * u;
3825 let mut term = u;
3826 let mut sum = u;
3827 for k_iter in 1..50 {
3828 term *= u2;
3829 let denom = (2 * k_iter + 1) as f64;
3830 sum += term / denom;
3831 if (term / denom).abs() < 1e-18 {
3832 break;
3833 }
3834 }
3835 2.0 * sum + (k as f64) * LN2
3836}
3837
3838fn f64_powi(base: f64, exp: i32) -> f64 {
3842 if exp == 0 {
3843 return 1.0;
3844 }
3845 let mut result = 1.0;
3846 let mut b = if exp > 0 { base } else { 1.0 / base };
3847 let mut e = exp.unsigned_abs();
3848 while e > 0 {
3849 if e & 1 == 1 {
3850 result *= b;
3851 }
3852 e >>= 1;
3853 if e > 0 {
3854 b *= b;
3855 }
3856 }
3857 result
3858}
3859
3860fn f64_round_half_away(x: f64) -> f64 {
3863 if x.is_nan() || x.is_infinite() {
3864 return x;
3865 }
3866 if x >= 0.0 {
3867 f64_floor(x + 0.5)
3868 } else {
3869 f64_ceil(x - 0.5)
3870 }
3871}
3872
3873fn f64_ceil(x: f64) -> f64 {
3878 if x.is_nan() || x.is_infinite() {
3879 return x;
3880 }
3881 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3882 return x;
3883 }
3884 let trunc = (x as i64) as f64;
3885 if x > 0.0 && x != trunc {
3886 trunc + 1.0
3887 } else {
3888 trunc
3889 }
3890}
3891
3892fn f64_floor(x: f64) -> f64 {
3900 if x.is_nan() || x.is_infinite() {
3901 return x;
3902 }
3903 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3906 return x;
3907 }
3908 let trunc = (x as i64) as f64;
3909 if x < 0.0 && x != trunc {
3910 trunc - 1.0
3911 } else {
3912 trunc
3913 }
3914}
3915
3916fn string_pad(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
3924 if args.len() != 2 && args.len() != 3 {
3925 return Err(EvalError::TypeMismatch {
3926 detail: alloc::format!("{fn_name}() takes 2 or 3 args, got {}", args.len()),
3927 });
3928 }
3929 if args.iter().any(|v| matches!(v, Value::Null)) {
3930 return Ok(Value::Null);
3931 }
3932 let s = value_to_format_text(&args[0]);
3933 let target = match &args[1] {
3934 Value::SmallInt(x) => i64::from(*x),
3935 Value::Int(x) => i64::from(*x),
3936 Value::BigInt(x) => *x,
3937 other => {
3938 return Err(EvalError::TypeMismatch {
3939 detail: alloc::format!(
3940 "{fn_name}(): length must be integer, got {:?}",
3941 other.data_type()
3942 ),
3943 });
3944 }
3945 };
3946 let fill = if args.len() == 3 {
3947 value_to_format_text(&args[2])
3948 } else {
3949 String::from(" ")
3950 };
3951 if target <= 0 {
3952 return Ok(Value::Text(String::new()));
3953 }
3954 let target = target as usize;
3955 let s_chars: Vec<char> = s.chars().collect();
3956 if s_chars.len() >= target {
3957 return Ok(Value::Text(s_chars[..target].iter().collect()));
3960 }
3961 if fill.is_empty() {
3962 return Ok(Value::Text(s));
3963 }
3964 let pad_needed = target - s_chars.len();
3965 let fill_chars: Vec<char> = fill.chars().collect();
3966 let mut padding = String::with_capacity(pad_needed * 4);
3967 for i in 0..pad_needed {
3968 padding.push(fill_chars[i % fill_chars.len()]);
3969 }
3970 if is_left {
3971 Ok(Value::Text(padding + &s))
3972 } else {
3973 Ok(Value::Text(s + &padding))
3974 }
3975}
3976
3977fn string_trim(args: &[Value], side: TrimSide, fn_name: &str) -> Result<Value, EvalError> {
3983 let (input, chars_str) = match args {
3984 [v] => (v.clone(), String::from(" ")),
3985 [v, c] => (v.clone(), {
3986 if matches!(c, Value::Null) {
3988 return Ok(Value::Null);
3989 }
3990 value_to_format_text(c)
3991 }),
3992 _ => {
3993 return Err(EvalError::TypeMismatch {
3994 detail: alloc::format!("{fn_name}() takes 1 or 2 args, got {}", args.len()),
3995 });
3996 }
3997 };
3998 if matches!(input, Value::Null) {
3999 return Ok(Value::Null);
4000 }
4001 let s = value_to_format_text(&input);
4002 let charset: alloc::collections::BTreeSet<char> = chars_str.chars().collect();
4003 let chars: Vec<char> = s.chars().collect();
4004 let mut start = 0usize;
4005 let mut end = chars.len();
4006 if matches!(side, TrimSide::Left | TrimSide::Both) {
4007 while start < end && charset.contains(&chars[start]) {
4008 start += 1;
4009 }
4010 }
4011 if matches!(side, TrimSide::Right | TrimSide::Both) {
4012 while end > start && charset.contains(&chars[end - 1]) {
4013 end -= 1;
4014 }
4015 }
4016 Ok(Value::Text(chars[start..end].iter().collect()))
4017}
4018
4019fn format_string(args: &[Value]) -> Result<Value, EvalError> {
4030 if args.is_empty() {
4031 return Err(EvalError::TypeMismatch {
4032 detail: "format() takes at least 1 arg (format string)".into(),
4033 });
4034 }
4035 let fmt = match &args[0] {
4036 Value::Text(s) => s.clone(),
4037 Value::Null => return Ok(Value::Null),
4038 other => {
4039 return Err(EvalError::TypeMismatch {
4040 detail: format!(
4041 "format(): first arg must be text, got {:?}",
4042 other.data_type()
4043 ),
4044 });
4045 }
4046 };
4047 let arg_values = &args[1..];
4048 let mut out = String::new();
4049 let mut chars = fmt.chars().peekable();
4050 let mut implicit_cursor: usize = 0;
4054 while let Some(c) = chars.next() {
4055 if c != '%' {
4056 out.push(c);
4057 continue;
4058 }
4059 let mut explicit_pos: Option<usize> = None;
4061 let mut digit_buf = String::new();
4063 while let Some(&d) = chars.peek() {
4064 if d.is_ascii_digit() {
4065 digit_buf.push(d);
4066 chars.next();
4067 } else {
4068 break;
4069 }
4070 }
4071 if !digit_buf.is_empty() && matches!(chars.peek(), Some(&'$')) {
4072 chars.next(); explicit_pos =
4074 Some(
4075 digit_buf
4076 .parse::<usize>()
4077 .map_err(|_| EvalError::TypeMismatch {
4078 detail: format!("format(): invalid arg position {digit_buf:?}"),
4079 })?,
4080 );
4081 digit_buf.clear();
4082 }
4083 let spec = match chars.next() {
4085 Some(c) => c,
4086 None => {
4087 return Err(EvalError::TypeMismatch {
4088 detail: "format(): trailing `%` with no specifier".into(),
4089 });
4090 }
4091 };
4092 let _ = digit_buf;
4099 if spec == '%' {
4100 out.push('%');
4101 continue;
4102 }
4103 let arg_index = match explicit_pos {
4104 Some(p) => p.saturating_sub(1),
4105 None => {
4106 let i = implicit_cursor;
4107 implicit_cursor += 1;
4108 i
4109 }
4110 };
4111 let arg = arg_values.get(arg_index).cloned().unwrap_or(Value::Null);
4112 match spec {
4113 's' => match arg {
4114 Value::Null => {} v => out.push_str(&value_to_format_text(&v)),
4116 },
4117 'I' => match arg {
4118 Value::Null => {
4119 return Err(EvalError::TypeMismatch {
4120 detail: "format(): NULL is not a valid identifier (%I)".into(),
4121 });
4122 }
4123 v => {
4124 let s = value_to_format_text(&v);
4125 out.push('"');
4126 for ch in s.chars() {
4127 if ch == '"' {
4128 out.push('"');
4129 out.push('"');
4130 } else {
4131 out.push(ch);
4132 }
4133 }
4134 out.push('"');
4135 }
4136 },
4137 'L' => match arg {
4138 Value::Null => out.push_str("NULL"),
4139 v => {
4140 let s = value_to_format_text(&v);
4141 out.push('\'');
4142 for ch in s.chars() {
4143 if ch == '\'' {
4144 out.push('\'');
4145 out.push('\'');
4146 } else {
4147 out.push(ch);
4148 }
4149 }
4150 out.push('\'');
4151 }
4152 },
4153 other => {
4154 return Err(EvalError::TypeMismatch {
4155 detail: format!(
4156 "format(): unknown specifier '%{other}' \
4157 (v7.17 supports %s %I %L %%)"
4158 ),
4159 });
4160 }
4161 }
4162 }
4163 Ok(Value::Text(out))
4164}
4165
4166fn pg_typeof_name(v: &Value) -> &'static str {
4172 match v {
4173 Value::SmallInt(_) => "smallint",
4174 Value::Int(_) => "integer",
4175 Value::BigInt(_) => "bigint",
4176 Value::Float(_) => "double precision",
4177 Value::Text(_) => "text",
4178 Value::Bool(_) => "boolean",
4179 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => "vector",
4180 Value::Numeric { .. } => "numeric",
4181 Value::Date(_) => "date",
4182 Value::Timestamp(_) => "timestamp without time zone",
4183 Value::Interval { .. } => "interval",
4184 Value::Json(_) => {
4185 "json"
4196 }
4197 Value::Bytes(_) => "bytea",
4198 Value::TextArray(_) => "text[]",
4199 Value::IntArray(_) => "integer[]",
4200 Value::BigIntArray(_) => "bigint[]",
4201 Value::TsVector(_) => "tsvector",
4202 Value::TsQuery(_) => "tsquery",
4203 Value::Uuid(_) => "uuid",
4204 Value::Null => "unknown",
4205 _ => "unknown",
4208 }
4209}
4210
4211fn value_to_format_text(v: &Value) -> String {
4212 match v {
4213 Value::Text(s) | Value::Json(s) => s.clone(),
4214 Value::SmallInt(n) => n.to_string(),
4215 Value::Int(n) => n.to_string(),
4216 Value::BigInt(n) => n.to_string(),
4217 Value::Float(x) => format!("{x}"),
4218 Value::Bool(b) => {
4219 if *b {
4220 "t".into()
4221 } else {
4222 "f".into()
4223 }
4224 }
4225 Value::Null => String::new(),
4226 other => format!("{other:?}"),
4227 }
4228}
4229
4230fn to_char(args: &[Value]) -> Result<Value, EvalError> {
4231 use core::fmt::Write as _;
4232 if args.len() != 2 {
4233 return Err(EvalError::TypeMismatch {
4234 detail: format!("to_char() takes 2 args, got {}", args.len()),
4235 });
4236 }
4237 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4238 return Ok(Value::Null);
4239 }
4240 let Value::Text(fmt) = &args[1] else {
4241 return Err(EvalError::TypeMismatch {
4242 detail: format!(
4243 "to_char() needs a text format, got {:?}",
4244 args[1].data_type()
4245 ),
4246 });
4247 };
4248 let (days, day_micros) = match &args[0] {
4249 Value::Date(d) => (*d, 0_i64),
4250 Value::Timestamp(t) => {
4251 let days = t.div_euclid(86_400_000_000);
4252 (
4253 i32::try_from(days).unwrap_or(i32::MAX),
4254 t.rem_euclid(86_400_000_000),
4255 )
4256 }
4257 other => {
4258 return Err(EvalError::TypeMismatch {
4259 detail: format!(
4260 "to_char() needs DATE or TIMESTAMP, got {:?}",
4261 other.data_type()
4262 ),
4263 });
4264 }
4265 };
4266 let (y, mo, d) = civil_from_days(days);
4267 let secs = day_micros / 1_000_000;
4268 let frac = day_micros % 1_000_000;
4269 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4273 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4274 let ss = u32::try_from(secs % 60).unwrap_or(0);
4275 let hh12 = match hh24 % 12 {
4276 0 => 12,
4277 x => x,
4278 };
4279 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4280 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);
4284 let bytes = fmt.as_bytes();
4285 let mut i = 0;
4286 while i < bytes.len() {
4288 let rest = &bytes[i..];
4290 if rest.starts_with(b"YYYY") {
4291 let _ = write!(out, "{y:04}");
4292 i += 4;
4293 } else if rest.starts_with(b"YY") {
4294 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4295 let yy = (y.rem_euclid(100)) as u32;
4296 let _ = write!(out, "{yy:02}");
4297 i += 2;
4298 } else if rest.starts_with(b"Month") {
4299 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4300 i += 5;
4301 } else if rest.starts_with(b"Mon") {
4302 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4303 i += 3;
4304 } else if rest.starts_with(b"MM") {
4305 let _ = write!(out, "{mo:02}");
4306 i += 2;
4307 } else if rest.starts_with(b"DD") {
4308 let _ = write!(out, "{d:02}");
4309 i += 2;
4310 } else if rest.starts_with(b"HH24") {
4311 let _ = write!(out, "{hh24:02}");
4312 i += 4;
4313 } else if rest.starts_with(b"HH12") {
4314 let _ = write!(out, "{hh12:02}");
4315 i += 4;
4316 } else if rest.starts_with(b"MI") {
4317 let _ = write!(out, "{mi:02}");
4318 i += 2;
4319 } else if rest.starts_with(b"SS") {
4320 let _ = write!(out, "{ss:02}");
4321 i += 2;
4322 } else if rest.starts_with(b"MS") {
4323 let _ = write!(out, "{ms:03}");
4324 i += 2;
4325 } else if rest.starts_with(b"US") {
4326 let _ = write!(out, "{us:06}");
4327 i += 2;
4328 } else if rest.starts_with(b"AM") || rest.starts_with(b"PM") {
4329 out.push_str(ampm);
4330 i += 2;
4331 } else {
4332 out.push(bytes[i] as char);
4334 i += 1;
4335 }
4336 }
4337 Ok(Value::Text(out))
4338}
4339
4340const MONTH_FULL: [&str; 12] = [
4341 "January",
4342 "February",
4343 "March",
4344 "April",
4345 "May",
4346 "June",
4347 "July",
4348 "August",
4349 "September",
4350 "October",
4351 "November",
4352 "December",
4353];
4354const MONTH_ABBR: [&str; 12] = [
4355 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
4356];
4357
4358fn date_format_mysql(args: &[Value]) -> Result<Value, EvalError> {
4377 use core::fmt::Write as _;
4378 if args.len() != 2 {
4379 return Err(EvalError::TypeMismatch {
4380 detail: format!("date_format() takes 2 args, got {}", args.len()),
4381 });
4382 }
4383 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4384 return Ok(Value::Null);
4385 }
4386 let Value::Text(fmt) = &args[1] else {
4387 return Err(EvalError::TypeMismatch {
4388 detail: format!(
4389 "date_format() needs a text format, got {:?}",
4390 args[1].data_type()
4391 ),
4392 });
4393 };
4394 let (days, day_micros) = match &args[0] {
4395 Value::Date(d) => (*d, 0_i64),
4396 Value::Timestamp(t) => {
4397 let days = t.div_euclid(86_400_000_000);
4398 (
4399 i32::try_from(days).unwrap_or(i32::MAX),
4400 t.rem_euclid(86_400_000_000),
4401 )
4402 }
4403 other => {
4404 return Err(EvalError::TypeMismatch {
4405 detail: format!(
4406 "date_format() needs DATE or TIMESTAMP, got {:?}",
4407 other.data_type()
4408 ),
4409 });
4410 }
4411 };
4412 let (y, mo, d) = civil_from_days(days);
4413 let secs = day_micros / 1_000_000;
4414 let frac = day_micros % 1_000_000;
4415 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4416 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4417 let ss = u32::try_from(secs % 60).unwrap_or(0);
4418 let hh12 = match hh24 % 12 {
4419 0 => 12,
4420 x => x,
4421 };
4422 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4423 let us = u32::try_from(frac).unwrap_or(0);
4424
4425 let mut out = String::with_capacity(fmt.len() + 8);
4426 let bytes = fmt.as_bytes();
4427 let mut i = 0;
4428 while i < bytes.len() {
4429 if bytes[i] != b'%' {
4430 out.push(bytes[i] as char);
4431 i += 1;
4432 continue;
4433 }
4434 if i + 1 >= bytes.len() {
4435 out.push('%');
4437 i += 1;
4438 continue;
4439 }
4440 let token = bytes[i + 1];
4441 match token {
4442 b'Y' => {
4443 let _ = write!(out, "{y:04}");
4444 }
4445 b'y' => {
4446 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4447 let yy = (y.rem_euclid(100)) as u32;
4448 let _ = write!(out, "{yy:02}");
4449 }
4450 b'm' => {
4451 let _ = write!(out, "{mo:02}");
4452 }
4453 b'c' => {
4454 let _ = write!(out, "{mo}");
4455 }
4456 b'd' => {
4457 let _ = write!(out, "{d:02}");
4458 }
4459 b'e' => {
4460 let _ = write!(out, "{d}");
4461 }
4462 b'H' => {
4463 let _ = write!(out, "{hh24:02}");
4464 }
4465 b'h' | b'I' => {
4466 let _ = write!(out, "{hh12:02}");
4467 }
4468 b'i' => {
4469 let _ = write!(out, "{mi:02}");
4472 }
4473 b's' | b'S' => {
4474 let _ = write!(out, "{ss:02}");
4475 }
4476 b'f' => {
4477 let _ = write!(out, "{us:06}");
4478 }
4479 b'p' => {
4480 out.push_str(ampm);
4481 }
4482 b'M' => {
4483 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4484 }
4485 b'b' => {
4486 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4487 }
4488 b'%' => {
4489 out.push('%');
4490 }
4491 other => {
4492 out.push(other as char);
4495 }
4496 }
4497 i += 2;
4498 }
4499 Ok(Value::Text(out))
4500}
4501
4502fn unix_timestamp_of(args: &[Value]) -> Result<Value, EvalError> {
4509 if args.len() != 1 {
4510 return Err(EvalError::TypeMismatch {
4511 detail: format!("unix_timestamp() takes 0 or 1 arg, got {}", args.len()),
4512 });
4513 }
4514 match &args[0] {
4515 Value::Null => Ok(Value::Null),
4516 Value::Timestamp(t) => Ok(Value::BigInt(t.div_euclid(1_000_000))),
4517 Value::Date(d) => Ok(Value::BigInt(i64::from(*d) * 86_400)),
4518 other => Err(EvalError::TypeMismatch {
4519 detail: format!(
4520 "unix_timestamp() needs DATE or TIMESTAMP, got {:?}",
4521 other.data_type()
4522 ),
4523 }),
4524 }
4525}
4526
4527fn from_unixtime(args: &[Value]) -> Result<Value, EvalError> {
4531 if !(1..=2).contains(&args.len()) {
4532 return Err(EvalError::TypeMismatch {
4533 detail: format!("from_unixtime() takes 1 or 2 args, got {}", args.len()),
4534 });
4535 }
4536 if args.iter().any(|v| matches!(v, Value::Null)) {
4537 return Ok(Value::Null);
4538 }
4539 let secs: i64 = match &args[0] {
4540 Value::SmallInt(n) => i64::from(*n),
4541 Value::Int(n) => i64::from(*n),
4542 Value::BigInt(n) => *n,
4543 Value::Float(x) => *x as i64,
4544 Value::Numeric { scaled, scale } => {
4545 let denom = 10_i128.pow(u32::from(*scale));
4546 i64::try_from(scaled.div_euclid(denom)).unwrap_or(i64::MAX)
4547 }
4548 other => {
4549 return Err(EvalError::TypeMismatch {
4550 detail: format!(
4551 "from_unixtime() needs a numeric epoch second count, got {:?}",
4552 other.data_type()
4553 ),
4554 });
4555 }
4556 };
4557 let ts = Value::Timestamp(secs.saturating_mul(1_000_000));
4558 if args.len() == 1 {
4559 Ok(ts)
4560 } else {
4561 date_format_mysql(&[ts, args[1].clone()])
4562 }
4563}
4564
4565fn date_trunc(args: &[Value]) -> Result<Value, EvalError> {
4570 if args.len() != 2 {
4571 return Err(EvalError::TypeMismatch {
4572 detail: format!("date_trunc() takes 2 args, got {}", args.len()),
4573 });
4574 }
4575 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4576 return Ok(Value::Null);
4577 }
4578 let Value::Text(unit) = &args[0] else {
4579 return Err(EvalError::TypeMismatch {
4580 detail: format!(
4581 "date_trunc() needs a text unit, got {:?}",
4582 args[0].data_type()
4583 ),
4584 });
4585 };
4586 let micros = match &args[1] {
4589 Value::Timestamp(t) => *t,
4590 Value::Date(d) => i64::from(*d) * 86_400_000_000,
4591 other => {
4592 return Err(EvalError::TypeMismatch {
4593 detail: format!(
4594 "date_trunc() needs DATE or TIMESTAMP, got {:?}",
4595 other.data_type()
4596 ),
4597 });
4598 }
4599 };
4600 let unit_lc = unit.to_ascii_lowercase();
4601 let days = micros.div_euclid(86_400_000_000);
4602 let day_micros = micros.rem_euclid(86_400_000_000);
4603 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
4604 let (y, m, _) = civil_from_days(day_i32);
4605 let truncated = match unit_lc.as_str() {
4606 "year" => i64::from(days_from_civil(y, 1, 1)) * 86_400_000_000,
4607 "month" => i64::from(days_from_civil(y, m, 1)) * 86_400_000_000,
4608 "day" => days * 86_400_000_000,
4609 "hour" => days * 86_400_000_000 + (day_micros / 3_600_000_000) * 3_600_000_000,
4610 "minute" => days * 86_400_000_000 + (day_micros / 60_000_000) * 60_000_000,
4611 "second" => days * 86_400_000_000 + (day_micros / 1_000_000) * 1_000_000,
4612 other => {
4613 return Err(EvalError::TypeMismatch {
4614 detail: format!(
4615 "unknown date_trunc unit {other:?}; \
4616 supported: year, month, day, hour, minute, second"
4617 ),
4618 });
4619 }
4620 };
4621 Ok(Value::Timestamp(truncated))
4622}
4623
4624pub fn cast_value(v: Value, target: CastTarget) -> Result<Value, EvalError> {
4626 if matches!(v, Value::Null) {
4627 return Ok(Value::Null);
4628 }
4629 match target {
4630 CastTarget::Vector => cast_to_vector(v),
4631 CastTarget::Text => Ok(Value::Text(value_to_text(&v))),
4632 CastTarget::Int => cast_numeric_to_int(v),
4633 CastTarget::BigInt => cast_numeric_to_bigint(v),
4634 CastTarget::Float => cast_numeric_to_float(v),
4635 CastTarget::Bool => cast_to_bool(v),
4636 CastTarget::Date => cast_to_date(v),
4637 CastTarget::Timestamp | CastTarget::Timestamptz => cast_to_timestamp(v),
4640 CastTarget::Interval => cast_to_interval(v),
4644 CastTarget::Json | CastTarget::Jsonb => match v {
4648 Value::Json(s) => Ok(Value::Json(s)),
4649 Value::Text(s) => Ok(Value::Json(s)),
4650 other => Err(EvalError::TypeMismatch {
4651 detail: alloc::format!(
4652 "::json / ::jsonb only accepts TEXT-shape inputs, got {:?}",
4653 other.data_type()
4654 ),
4655 }),
4656 },
4657 CastTarget::RegType | CastTarget::RegClass => match v {
4675 Value::Text(s) => {
4676 let bare = s.rsplit('.').next().unwrap_or(&s).to_string();
4681 Ok(Value::Text(bare))
4682 }
4683 Value::Int(n) => Ok(Value::Text(alloc::format!("{n}"))),
4684 Value::BigInt(n) => Ok(Value::Text(alloc::format!("{n}"))),
4685 other => Err(EvalError::TypeMismatch {
4686 detail: alloc::format!(
4687 "::regtype / ::regclass accepts TEXT (name) or integer (oid), got {:?}",
4688 other.data_type()
4689 ),
4690 }),
4691 },
4692 CastTarget::TextArray => match v {
4696 Value::TextArray(items) => Ok(Value::TextArray(items)),
4697 Value::Text(s) => decode_text_array_external(&s).map(Value::TextArray),
4698 other => Err(EvalError::TypeMismatch {
4699 detail: alloc::format!(
4700 "::TEXT[] only accepts TEXT / TEXT[] inputs, got {:?}",
4701 other.data_type()
4702 ),
4703 }),
4704 },
4705 CastTarget::IntArray => cast_to_int_array(v),
4709 CastTarget::BigIntArray => cast_to_bigint_array(v),
4710 CastTarget::TsVector => match v {
4717 Value::TsVector(items) => Ok(Value::TsVector(items)),
4718 Value::Text(s) => decode_tsvector_external(&s).map(Value::TsVector),
4719 other => Err(EvalError::TypeMismatch {
4720 detail: alloc::format!(
4721 "::tsvector only accepts TEXT / tsvector inputs, got {:?}",
4722 other.data_type()
4723 ),
4724 }),
4725 },
4726 CastTarget::TsQuery => match v {
4727 Value::TsQuery(ast) => Ok(Value::TsQuery(ast)),
4728 Value::Text(s) => decode_tsquery_external(&s).map(Value::TsQuery),
4729 other => Err(EvalError::TypeMismatch {
4730 detail: alloc::format!(
4731 "::tsquery only accepts TEXT / tsquery inputs, got {:?}",
4732 other.data_type()
4733 ),
4734 }),
4735 },
4736 CastTarget::Uuid => match v {
4741 Value::Uuid(b) => Ok(Value::Uuid(b)),
4742 Value::Text(s) => match spg_storage::parse_uuid_str(&s) {
4743 Some(b) => Ok(Value::Uuid(b)),
4744 None => Err(EvalError::TypeMismatch {
4745 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4746 }),
4747 },
4748 other => Err(EvalError::TypeMismatch {
4749 detail: alloc::format!(
4750 "::uuid only accepts TEXT / uuid inputs, got {:?}",
4751 other.data_type()
4752 ),
4753 }),
4754 },
4755 CastTarget::Bytea => match v {
4761 Value::Bytes(b) => Ok(Value::Bytes(b)),
4762 Value::Text(s) => match crate::decode_bytea_literal(&s) {
4763 Ok(b) => Ok(Value::Bytes(b)),
4764 Err(msg) => Err(EvalError::TypeMismatch {
4765 detail: alloc::format!("invalid input syntax for type bytea: {msg}"),
4766 }),
4767 },
4768 other => Err(EvalError::TypeMismatch {
4769 detail: alloc::format!(
4770 "::bytea only accepts TEXT / bytea inputs, got {:?}",
4771 other.data_type()
4772 ),
4773 }),
4774 },
4775 }
4776}
4777
4778fn cast_to_int_array(v: Value) -> Result<Value, EvalError> {
4779 match v {
4780 Value::IntArray(items) => Ok(Value::IntArray(items)),
4781 Value::BigIntArray(items) => {
4782 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4783 for item in items {
4784 match item {
4785 None => out.push(None),
4786 Some(n) => match i32::try_from(n) {
4787 Ok(x) => out.push(Some(x)),
4788 Err(_) => {
4789 return Err(EvalError::TypeMismatch {
4790 detail: alloc::format!("::INT[] element {n} overflows i32"),
4791 });
4792 }
4793 },
4794 }
4795 }
4796 Ok(Value::IntArray(out))
4797 }
4798 Value::Text(s) => decode_int_array_external(&s).map(Value::IntArray),
4799 Value::TextArray(items) => {
4800 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4801 for item in items {
4802 match item {
4803 None => out.push(None),
4804 Some(s) => match s.parse::<i32>() {
4805 Ok(n) => out.push(Some(n)),
4806 Err(_) => {
4807 return Err(EvalError::TypeMismatch {
4808 detail: alloc::format!("::INT[] cannot parse {s:?}"),
4809 });
4810 }
4811 },
4812 }
4813 }
4814 Ok(Value::IntArray(out))
4815 }
4816 other => Err(EvalError::TypeMismatch {
4817 detail: alloc::format!("::INT[] does not accept {:?}", other.data_type()),
4818 }),
4819 }
4820}
4821
4822fn cast_to_bigint_array(v: Value) -> Result<Value, EvalError> {
4823 match v {
4824 Value::BigIntArray(items) => Ok(Value::BigIntArray(items)),
4825 Value::IntArray(items) => Ok(Value::BigIntArray(
4826 items.into_iter().map(|x| x.map(i64::from)).collect(),
4827 )),
4828 Value::Text(s) => decode_bigint_array_external(&s).map(Value::BigIntArray),
4829 Value::TextArray(items) => {
4830 let mut out: Vec<Option<i64>> = Vec::with_capacity(items.len());
4831 for item in items {
4832 match item {
4833 None => out.push(None),
4834 Some(s) => match s.parse::<i64>() {
4835 Ok(n) => out.push(Some(n)),
4836 Err(_) => {
4837 return Err(EvalError::TypeMismatch {
4838 detail: alloc::format!("::BIGINT[] cannot parse {s:?}"),
4839 });
4840 }
4841 },
4842 }
4843 }
4844 Ok(Value::BigIntArray(out))
4845 }
4846 other => Err(EvalError::TypeMismatch {
4847 detail: alloc::format!("::BIGINT[] does not accept {:?}", other.data_type()),
4848 }),
4849 }
4850}
4851
4852fn decode_int_array_external(s: &str) -> Result<Vec<Option<i32>>, EvalError> {
4853 let trimmed = s.trim();
4854 let inner = trimmed
4855 .strip_prefix('{')
4856 .and_then(|x| x.strip_suffix('}'))
4857 .ok_or_else(|| EvalError::TypeMismatch {
4858 detail: alloc::format!("INT[] literal {s:?} must be enclosed in '{{...}}'"),
4859 })?;
4860 if inner.trim().is_empty() {
4861 return Ok(Vec::new());
4862 }
4863 inner
4864 .split(',')
4865 .map(|part| {
4866 let p = part.trim();
4867 if p.eq_ignore_ascii_case("NULL") {
4868 Ok(None)
4869 } else {
4870 p.parse::<i32>()
4871 .map(Some)
4872 .map_err(|_| EvalError::TypeMismatch {
4873 detail: alloc::format!("INT[] element {p:?} is not an i32"),
4874 })
4875 }
4876 })
4877 .collect()
4878}
4879
4880fn decode_bigint_array_external(s: &str) -> Result<Vec<Option<i64>>, EvalError> {
4881 let trimmed = s.trim();
4882 let inner = trimmed
4883 .strip_prefix('{')
4884 .and_then(|x| x.strip_suffix('}'))
4885 .ok_or_else(|| EvalError::TypeMismatch {
4886 detail: alloc::format!("BIGINT[] literal {s:?} must be enclosed in '{{...}}'"),
4887 })?;
4888 if inner.trim().is_empty() {
4889 return Ok(Vec::new());
4890 }
4891 inner
4892 .split(',')
4893 .map(|part| {
4894 let p = part.trim();
4895 if p.eq_ignore_ascii_case("NULL") {
4896 Ok(None)
4897 } else {
4898 p.parse::<i64>()
4899 .map(Some)
4900 .map_err(|_| EvalError::TypeMismatch {
4901 detail: alloc::format!("BIGINT[] element {p:?} is not an i64"),
4902 })
4903 }
4904 })
4905 .collect()
4906}
4907
4908fn decode_text_array_external(s: &str) -> Result<Vec<Option<String>>, EvalError> {
4913 let trimmed = s.trim();
4914 let inner = trimmed
4915 .strip_prefix('{')
4916 .and_then(|x| x.strip_suffix('}'))
4917 .ok_or_else(|| EvalError::TypeMismatch {
4918 detail: alloc::format!("TEXT[] literal {s:?} must be enclosed in '{{...}}'"),
4919 })?;
4920 let mut out: Vec<Option<String>> = Vec::new();
4921 if inner.trim().is_empty() {
4922 return Ok(out);
4923 }
4924 let bytes = inner.as_bytes();
4925 let mut i = 0;
4926 while i <= bytes.len() {
4927 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
4928 i += 1;
4929 }
4930 if i < bytes.len() && bytes[i] == b'"' {
4931 i += 1;
4932 let mut buf = String::new();
4933 while i < bytes.len() && bytes[i] != b'"' {
4934 if bytes[i] == b'\\' && i + 1 < bytes.len() {
4935 buf.push(bytes[i + 1] as char);
4936 i += 2;
4937 } else {
4938 buf.push(bytes[i] as char);
4939 i += 1;
4940 }
4941 }
4942 if i >= bytes.len() {
4943 return Err(EvalError::TypeMismatch {
4944 detail: "unterminated quoted element in TEXT[] literal".into(),
4945 });
4946 }
4947 i += 1;
4948 out.push(Some(buf));
4949 } else {
4950 let start = i;
4951 while i < bytes.len() && bytes[i] != b',' {
4952 i += 1;
4953 }
4954 let raw = inner[start..i].trim();
4955 if raw.eq_ignore_ascii_case("NULL") {
4956 out.push(None);
4957 } else {
4958 out.push(Some(raw.to_string()));
4959 }
4960 }
4961 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
4962 i += 1;
4963 }
4964 if i >= bytes.len() {
4965 break;
4966 }
4967 if bytes[i] != b',' {
4968 return Err(EvalError::TypeMismatch {
4969 detail: "expected ',' between TEXT[] elements".into(),
4970 });
4971 }
4972 i += 1;
4973 }
4974 Ok(out)
4975}
4976
4977fn cast_to_interval(v: Value) -> Result<Value, EvalError> {
4978 match v {
4979 Value::Interval { months, micros } => Ok(Value::Interval { months, micros }),
4980 Value::Text(s) => {
4981 let (months, micros) = spg_sql::parser::parse_interval_text(&s).ok_or_else(|| {
4982 EvalError::TypeMismatch {
4983 detail: alloc::format!("cannot parse {s:?} as INTERVAL"),
4984 }
4985 })?;
4986 Ok(Value::Interval { months, micros })
4987 }
4988 other => Err(EvalError::TypeMismatch {
4989 detail: alloc::format!(
4990 "::INTERVAL only accepts TEXT-shape inputs, got {:?}",
4991 other.data_type()
4992 ),
4993 }),
4994 }
4995}
4996
4997fn cast_to_date(v: Value) -> Result<Value, EvalError> {
4998 match v {
4999 Value::Date(d) => Ok(Value::Date(d)),
5000 Value::Int(n) => Ok(Value::Date(n)),
5003 Value::BigInt(n) => {
5004 i32::try_from(n)
5005 .map(Value::Date)
5006 .map_err(|_| EvalError::TypeMismatch {
5007 detail: "bigint days-since-epoch out of DATE range".into(),
5008 })
5009 }
5010 Value::Timestamp(t) => {
5012 let days = t.div_euclid(86_400_000_000);
5013 i32::try_from(days)
5014 .map(Value::Date)
5015 .map_err(|_| EvalError::TypeMismatch {
5016 detail: "timestamp out of DATE range".into(),
5017 })
5018 }
5019 Value::Text(s) => parse_date_literal(&s)
5020 .map(Value::Date)
5021 .ok_or(EvalError::TypeMismatch {
5022 detail: format!("cannot parse {s:?} as DATE (expected YYYY-MM-DD)"),
5023 }),
5024 other => Err(EvalError::TypeMismatch {
5025 detail: format!("cannot cast {:?} to DATE", other.data_type()),
5026 }),
5027 }
5028}
5029
5030fn cast_to_timestamp(v: Value) -> Result<Value, EvalError> {
5031 match v {
5032 Value::Timestamp(t) => Ok(Value::Timestamp(t)),
5033 Value::Int(n) => Ok(Value::Timestamp(i64::from(n))),
5037 Value::BigInt(n) => Ok(Value::Timestamp(n)),
5038 Value::Date(d) => Ok(Value::Timestamp(i64::from(d) * 86_400_000_000)),
5040 Value::Text(s) => {
5041 parse_timestamp_literal(&s)
5042 .map(Value::Timestamp)
5043 .ok_or(EvalError::TypeMismatch {
5044 detail: format!(
5045 "cannot parse {s:?} as TIMESTAMP \
5046 (expected YYYY-MM-DD[ HH:MM:SS[.ffffff]])"
5047 ),
5048 })
5049 }
5050 other => Err(EvalError::TypeMismatch {
5051 detail: format!("cannot cast {:?} to TIMESTAMP", other.data_type()),
5052 }),
5053 }
5054}
5055
5056fn value_to_text(v: &Value) -> String {
5057 match v {
5058 Value::SmallInt(n) => format!("{n}"),
5062 Value::Int(n) => format!("{n}"),
5063 Value::BigInt(n) => format!("{n}"),
5064 Value::Float(x) => format!("{x}"),
5065 Value::Text(s) | Value::Json(s) => s.clone(),
5067 Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
5068 Value::Vector(v) => {
5069 let cells: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
5070 format!("[{}]", cells.join(", "))
5071 }
5072 Value::Sq8Vector(q) => {
5077 let cells: Vec<String> = spg_storage::quantize::dequantize(q)
5078 .iter()
5079 .map(|x| format!("{x}"))
5080 .collect();
5081 format!("[{}]", cells.join(", "))
5082 }
5083 Value::HalfVector(h) => {
5086 let cells: Vec<String> = h.to_f32_vec().iter().map(|x| format!("{x}")).collect();
5087 format!("[{}]", cells.join(", "))
5088 }
5089 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
5090 Value::Date(d) => format_date(*d),
5091 Value::Timestamp(t) => format_timestamp(*t),
5092 Value::Interval { months, micros } => format_interval(*months, *micros),
5093 Value::Null => "NULL".into(),
5094 Value::Bytes(b) => format_bytea_hex(b),
5096 Value::TextArray(items) => format_text_array(items),
5098 Value::IntArray(items) => format_int_array(items),
5099 Value::BigIntArray(items) => format_bigint_array(items),
5100 Value::TsVector(lexs) => format_tsvector(lexs),
5102 Value::TsQuery(ast) => format_tsquery(ast),
5103 Value::Uuid(b) => spg_storage::format_uuid(b),
5106 Value::Time(us) => format_time(*us),
5108 Value::TimeTz { us, offset_secs } => format_timetz(*us, *offset_secs),
5110 Value::Year(y) => format!("{y:04}"),
5112 Value::Money(c) => format_money(*c),
5114 Value::Range { .. } => crate::format_range_text(v),
5118 Value::Hstore(pairs) => crate::format_hstore_text(pairs),
5120 Value::IntArray2D(rows) => crate::format_int_2d_text_pub(rows),
5122 Value::BigIntArray2D(rows) => crate::format_bigint_2d_text_pub(rows),
5123 Value::TextArray2D(rows) => crate::format_text_2d_text_pub(rows),
5124 _ => format!("{v:?}"),
5126 }
5127}
5128
5129pub fn format_date(days: i32) -> String {
5132 let (y, m, d) = civil_from_days(days);
5133 format!("{y:04}-{m:02}-{d:02}")
5134}
5135
5136pub fn format_timestamptz(micros: i64) -> String {
5147 let base = format_timestamp(micros);
5148 let mut s = String::with_capacity(base.len() + 3);
5149 s.push_str(&base);
5150 s.push_str("+00");
5151 s
5152}
5153
5154pub fn format_money(cents: i64) -> String {
5158 let neg = cents < 0;
5159 let abs = cents.unsigned_abs();
5160 let dollars = abs / 100;
5161 let cc = abs % 100;
5162 let dollar_str = dollars.to_string();
5164 let bytes = dollar_str.as_bytes();
5165 let mut int_part = String::with_capacity(dollar_str.len() + dollar_str.len() / 3);
5166 for (i, b) in bytes.iter().enumerate() {
5167 let from_right = bytes.len() - i;
5170 if i > 0 && from_right % 3 == 0 {
5171 int_part.push(',');
5172 }
5173 int_part.push(*b as char);
5174 }
5175 let sign = if neg { "-" } else { "" };
5176 format!("{sign}${int_part}.{cc:02}")
5177}
5178
5179pub fn format_timetz(us: i64, offset_secs: i32) -> String {
5184 let time = format_time(us);
5185 let sign = if offset_secs < 0 { '-' } else { '+' };
5186 let abs = offset_secs.unsigned_abs();
5187 let oh = abs / 3600;
5188 let om = (abs % 3600) / 60;
5189 if om == 0 {
5190 format!("{time}{sign}{oh:02}")
5191 } else {
5192 format!("{time}{sign}{oh:02}:{om:02}")
5193 }
5194}
5195
5196pub fn format_time(us: i64) -> String {
5201 let total_secs = us.div_euclid(1_000_000);
5202 let frac = us.rem_euclid(1_000_000);
5203 let hh = total_secs / 3600;
5204 let mm = (total_secs / 60) % 60;
5205 let ss = total_secs % 60;
5206 if frac == 0 {
5207 format!("{hh:02}:{mm:02}:{ss:02}")
5208 } else {
5209 let raw = format!("{frac:06}");
5210 let trimmed = raw.trim_end_matches('0');
5211 format!("{hh:02}:{mm:02}:{ss:02}.{trimmed}")
5212 }
5213}
5214
5215pub fn format_timestamp(micros: i64) -> String {
5216 const MICROS_PER_DAY: i64 = 86_400_000_000;
5217 let days = micros.div_euclid(MICROS_PER_DAY);
5220 let day_micros = micros.rem_euclid(MICROS_PER_DAY);
5221 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
5222 let (y, m, d) = civil_from_days(day_i32);
5223 let secs = day_micros / 1_000_000;
5224 let frac = day_micros % 1_000_000;
5225 let hh = secs / 3600;
5226 let mm = (secs / 60) % 60;
5227 let ss = secs % 60;
5228 if frac == 0 {
5229 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}")
5230 } else {
5231 let raw = format!("{frac:06}");
5233 let trimmed = raw.trim_end_matches('0');
5234 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}.{trimmed}")
5235 }
5236}
5237
5238#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5243fn civil_from_days(days: i32) -> (i32, u32, u32) {
5244 let z = i64::from(days) + 719_468;
5245 let era = z.div_euclid(146_097);
5246 let doe = (z - era * 146_097) as u32;
5250 let yoe = (doe.saturating_sub(doe / 1460) + doe / 36524 - doe / 146_096) / 365;
5251 let y_base = i64::from(yoe) + era * 400;
5252 let doy = doe.saturating_sub(365 * yoe + yoe / 4 - yoe / 100);
5253 let mp = (5 * doy + 2) / 153;
5254 let d = doy.saturating_sub((153 * mp + 2) / 5) + 1;
5255 let m = if mp < 10 { mp + 3 } else { mp - 9 };
5256 let y = if m <= 2 { y_base + 1 } else { y_base };
5257 (y as i32, m, d)
5258}
5259
5260#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5263pub fn days_from_civil(y: i32, m: u32, d: u32) -> i32 {
5264 let y_adj = if m <= 2 {
5265 i64::from(y) - 1
5266 } else {
5267 i64::from(y)
5268 };
5269 let era = y_adj.div_euclid(400);
5270 let yoe = (y_adj - era * 400) as u32;
5271 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d.saturating_sub(1);
5272 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
5273 let total = era * 146_097 + i64::from(doe) - 719_468;
5274 i32::try_from(total).unwrap_or(i32::MAX)
5275}
5276
5277pub fn parse_date_literal(s: &str) -> Option<i32> {
5281 let bytes = s.as_bytes();
5282 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
5283 return None;
5284 }
5285 let y: i32 = s[0..4].parse().ok()?;
5286 let m: u32 = s[5..7].parse().ok()?;
5287 let d: u32 = s[8..10].parse().ok()?;
5288 if !(1..=12).contains(&m) || !(1..=31).contains(&d) {
5289 return None;
5290 }
5291 Some(days_from_civil(y, m, d))
5292}
5293
5294pub fn parse_timestamp_literal(s: &str) -> Option<i64> {
5299 let trimmed = s.trim();
5300 let (date_part, time_part) = match trimmed.find([' ', 'T']) {
5301 Some(i) => (&trimmed[..i], Some(&trimmed[i + 1..])),
5302 None => (trimmed, None),
5303 };
5304 let days = parse_date_literal(date_part)?;
5305 let (day_micros, tz_offset_micros) = match time_part {
5306 None => (0, 0),
5307 Some(t) => parse_time_of_day_micros(t)?,
5308 };
5309 Some(i64::from(days) * 86_400_000_000 + day_micros - tz_offset_micros)
5319}
5320
5321fn parse_time_of_day_micros(t: &str) -> Option<(i64, i64)> {
5334 let t = t.trim();
5335 let (core, tz_micros) = if let Some(rest) = t.strip_suffix('Z') {
5341 (rest, 0i64)
5342 } else if let Some(rest) = t.strip_suffix(" UTC").or_else(|| t.strip_suffix("UTC")) {
5343 (rest, 0i64)
5344 } else if let Some((idx, sign_byte)) = find_offset_sign(t) {
5345 let suffix = &t[idx..];
5346 let micros = parse_tz_offset_suffix(suffix, sign_byte == b'+')?;
5347 (&t[..idx], micros)
5348 } else {
5349 (t, 0i64)
5350 };
5351 let (time, frac_str) = match core.split_once('.') {
5352 Some((a, b)) => (a, Some(b)),
5353 None => (core, None),
5354 };
5355 let bytes = time.as_bytes();
5356 if bytes.len() != 8 || bytes[2] != b':' || bytes[5] != b':' {
5357 return None;
5358 }
5359 let hh: i64 = time[0..2].parse().ok()?;
5360 let mm: i64 = time[3..5].parse().ok()?;
5361 let ss: i64 = time[6..8].parse().ok()?;
5362 if !(0..24).contains(&hh) || !(0..60).contains(&mm) || !(0..60).contains(&ss) {
5363 return None;
5364 }
5365 let frac_micros: i64 = match frac_str {
5366 None => 0,
5367 Some(f) => {
5368 if f.is_empty() || f.len() > 9 {
5370 return None;
5371 }
5372 let mut padded = String::with_capacity(6);
5373 padded.push_str(&f[..f.len().min(6)]);
5374 while padded.len() < 6 {
5375 padded.push('0');
5376 }
5377 padded.parse().ok()?
5378 }
5379 };
5380 Some((
5381 ((hh * 3600 + mm * 60 + ss) * 1_000_000) + frac_micros,
5382 tz_micros,
5383 ))
5384}
5385
5386fn find_offset_sign(t: &str) -> Option<(usize, u8)> {
5392 let bytes = t.as_bytes();
5393 if bytes.len() < 9 {
5395 return None;
5396 }
5397 for i in 8..bytes.len() {
5398 match bytes[i] {
5399 b'+' | b'-' => return Some((i, bytes[i])),
5400 _ => {}
5401 }
5402 }
5403 None
5404}
5405
5406fn parse_tz_offset_suffix(suffix: &str, is_positive: bool) -> Option<i64> {
5410 let body = &suffix[1..];
5412 let (hh, mm): (i64, i64) = if let Some((h, m)) = body.split_once(':') {
5413 (h.parse().ok()?, m.parse().ok()?)
5414 } else {
5415 match body.len() {
5416 2 => (body.parse().ok()?, 0),
5417 3 => {
5418 return None;
5422 }
5423 4 => {
5424 let h: i64 = body[0..2].parse().ok()?;
5425 let m: i64 = body[2..4].parse().ok()?;
5426 (h, m)
5427 }
5428 _ => return None,
5429 }
5430 };
5431 if !(0..=18).contains(&hh) || !(0..60).contains(&mm) {
5432 return None;
5433 }
5434 let abs = (hh * 3600 + mm * 60) * 1_000_000;
5435 Some(if is_positive { abs } else { -abs })
5436}
5437
5438pub fn format_interval(months: i32, micros: i64) -> String {
5443 const MICROS_PER_DAY: i64 = 86_400_000_000;
5444 let mut parts: Vec<String> = Vec::new();
5445 let years = months / 12;
5446 let mons = months % 12;
5447 let unit = |n: i64, singular: &'static str, plural: &'static str| -> &'static str {
5450 if n == 1 { singular } else { plural }
5451 };
5452 if years != 0 {
5453 parts.push(format!(
5454 "{years} {}",
5455 unit(i64::from(years), "year", "years")
5456 ));
5457 }
5458 if mons != 0 {
5459 parts.push(format!("{mons} {}", unit(i64::from(mons), "mon", "mons")));
5460 }
5461 let days = micros / MICROS_PER_DAY;
5462 let mut rem = micros % MICROS_PER_DAY;
5463 if days != 0 {
5464 parts.push(format!("{days} {}", unit(days, "day", "days")));
5465 }
5466 if rem != 0 {
5467 let neg = rem < 0;
5468 if neg {
5469 rem = -rem;
5470 }
5471 let secs = rem / 1_000_000;
5472 let frac = rem % 1_000_000;
5473 let hh = secs / 3600;
5474 let mm = (secs / 60) % 60;
5475 let ss = secs % 60;
5476 let sign = if neg { "-" } else { "" };
5477 if frac == 0 {
5478 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}"));
5479 } else {
5480 let raw = format!("{frac:06}");
5481 let trimmed = raw.trim_end_matches('0');
5482 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}.{trimmed}"));
5483 }
5484 }
5485 if parts.is_empty() {
5486 "0".into()
5487 } else {
5488 parts.join(" ")
5489 }
5490}
5491
5492fn add_months_to_civil(y: i32, m: u32, d: u32, months: i32) -> (i32, u32, u32) {
5495 let total_months = i64::from(y) * 12 + i64::from(m) - 1 + i64::from(months);
5496 let new_year = i32::try_from(total_months.div_euclid(12)).unwrap_or(i32::MAX);
5497 let new_month_zero = total_months.rem_euclid(12);
5498 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5499 let new_month = (new_month_zero as u32) + 1;
5500 let max_day = days_in_month(new_year, new_month);
5501 (new_year, new_month, d.min(max_day))
5502}
5503
5504const fn days_in_month(y: i32, m: u32) -> u32 {
5505 match m {
5506 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
5507 2 => {
5508 if y.rem_euclid(4) == 0 && (y.rem_euclid(100) != 0 || y.rem_euclid(400) == 0) {
5510 29
5511 } else {
5512 28
5513 }
5514 }
5515 _ => 30,
5518 }
5519}
5520
5521pub fn format_text_array(items: &[Option<String>]) -> String {
5527 let mut out = String::with_capacity(2 + items.len() * 8);
5528 out.push('{');
5529 for (i, item) in items.iter().enumerate() {
5530 if i > 0 {
5531 out.push(',');
5532 }
5533 match item {
5534 None => out.push_str("NULL"),
5535 Some(s) => {
5536 let needs_quote = s.is_empty()
5537 || s.eq_ignore_ascii_case("NULL")
5538 || s.chars()
5539 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
5540 if needs_quote {
5541 out.push('"');
5542 for c in s.chars() {
5543 if c == '"' || c == '\\' {
5544 out.push('\\');
5545 }
5546 out.push(c);
5547 }
5548 out.push('"');
5549 } else {
5550 out.push_str(s);
5551 }
5552 }
5553 }
5554 }
5555 out.push('}');
5556 out
5557}
5558
5559pub fn format_int_array(items: &[Option<i32>]) -> String {
5563 let mut out = String::with_capacity(2 + items.len() * 4);
5564 out.push('{');
5565 for (i, item) in items.iter().enumerate() {
5566 if i > 0 {
5567 out.push(',');
5568 }
5569 match item {
5570 None => out.push_str("NULL"),
5571 Some(n) => out.push_str(&n.to_string()),
5572 }
5573 }
5574 out.push('}');
5575 out
5576}
5577
5578pub fn format_bigint_array(items: &[Option<i64>]) -> String {
5581 let mut out = String::with_capacity(2 + items.len() * 6);
5582 out.push('{');
5583 for (i, item) in items.iter().enumerate() {
5584 if i > 0 {
5585 out.push(',');
5586 }
5587 match item {
5588 None => out.push_str("NULL"),
5589 Some(n) => out.push_str(&n.to_string()),
5590 }
5591 }
5592 out.push('}');
5593 out
5594}
5595
5596pub fn format_tsvector(lexs: &[TsLexeme]) -> String {
5602 let mut out = String::with_capacity(lexs.len() * 12);
5603 for (i, l) in lexs.iter().enumerate() {
5604 if i > 0 {
5605 out.push(' ');
5606 }
5607 out.push('\'');
5608 for c in l.word.chars() {
5609 if c == '\'' {
5610 out.push('\'');
5611 }
5612 out.push(c);
5613 }
5614 out.push('\'');
5615 if !l.positions.is_empty() {
5616 for (pi, p) in l.positions.iter().enumerate() {
5617 out.push(if pi == 0 { ':' } else { ',' });
5618 out.push_str(&p.to_string());
5619 }
5620 match l.weight {
5625 3 => out.push('A'),
5626 2 => out.push('B'),
5627 1 => out.push('C'),
5628 _ => {}
5629 }
5630 }
5631 }
5632 out
5633}
5634
5635pub fn format_tsquery(ast: &TsQueryAst) -> String {
5638 fn go(ast: &TsQueryAst, parent_prec: u8, out: &mut String) {
5639 let (own_prec, write_self): (u8, &dyn Fn(&mut String)) = match ast {
5641 TsQueryAst::Or(_, _) => (1, &|_| {}),
5642 TsQueryAst::And(_, _) | TsQueryAst::Phrase { .. } => (2, &|_| {}),
5643 TsQueryAst::Not(_) => (3, &|_| {}),
5644 TsQueryAst::Term { .. } => (4, &|_| {}),
5645 };
5646 let need_parens = own_prec < parent_prec;
5647 if need_parens {
5648 out.push('(');
5649 }
5650 match ast {
5651 TsQueryAst::Term { word, .. } => {
5652 out.push('\'');
5653 for c in word.chars() {
5654 if c == '\'' {
5655 out.push('\'');
5656 }
5657 out.push(c);
5658 }
5659 out.push('\'');
5660 }
5661 TsQueryAst::And(a, b) => {
5662 go(a, own_prec, out);
5663 out.push_str(" & ");
5664 go(b, own_prec, out);
5665 }
5666 TsQueryAst::Or(a, b) => {
5667 go(a, own_prec, out);
5668 out.push_str(" | ");
5669 go(b, own_prec, out);
5670 }
5671 TsQueryAst::Not(x) => {
5672 out.push('!');
5673 go(x, own_prec, out);
5674 }
5675 TsQueryAst::Phrase {
5676 left,
5677 right,
5678 distance,
5679 } => {
5680 go(left, own_prec, out);
5681 out.push_str(&alloc::format!(" <{distance}> "));
5682 go(right, own_prec, out);
5683 }
5684 }
5685 write_self(out);
5686 if need_parens {
5687 out.push(')');
5688 }
5689 }
5690 let mut out = String::new();
5691 go(ast, 0, &mut out);
5692 out
5693}
5694
5695pub fn decode_tsvector_external(s: &str) -> Result<Vec<TsLexeme>, EvalError> {
5704 let mut out: Vec<TsLexeme> = Vec::new();
5705 let mut i = 0;
5706 let bytes = s.as_bytes();
5707 while i < bytes.len() {
5708 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
5709 i += 1;
5710 }
5711 if i >= bytes.len() {
5712 break;
5713 }
5714 let word = if bytes[i] == b'\'' {
5717 i += 1;
5718 let mut w = String::new();
5719 loop {
5720 if i >= bytes.len() {
5721 return Err(EvalError::TypeMismatch {
5722 detail: "tsvector literal: unterminated quoted lexeme".into(),
5723 });
5724 }
5725 let b = bytes[i];
5726 if b == b'\'' {
5727 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
5728 w.push('\'');
5729 i += 2;
5730 } else {
5731 i += 1;
5732 break;
5733 }
5734 } else {
5735 w.push(b as char);
5736 i += 1;
5737 }
5738 }
5739 w
5740 } else {
5741 let start = i;
5743 while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b':' {
5744 i += 1;
5745 }
5746 core::str::from_utf8(&bytes[start..i])
5747 .map_err(|_| EvalError::TypeMismatch {
5748 detail: "tsvector literal: non-UTF-8 lexeme".into(),
5749 })?
5750 .to_string()
5751 };
5752 if word.is_empty() {
5753 return Err(EvalError::TypeMismatch {
5754 detail: "tsvector literal: empty lexeme".into(),
5755 });
5756 }
5757 let mut positions: Vec<u16> = Vec::new();
5760 let mut weight: u8 = 0;
5761 if i < bytes.len() && bytes[i] == b':' {
5762 i += 1;
5763 loop {
5764 let start = i;
5765 while i < bytes.len() && bytes[i].is_ascii_digit() {
5766 i += 1;
5767 }
5768 if start == i {
5769 return Err(EvalError::TypeMismatch {
5770 detail: "tsvector literal: expected digit after ':'".into(),
5771 });
5772 }
5773 let num: u16 = core::str::from_utf8(&bytes[start..i])
5774 .expect("ascii digits")
5775 .parse()
5776 .map_err(|_| EvalError::TypeMismatch {
5777 detail: alloc::format!(
5778 "tsvector literal: position {} overflows u16",
5779 core::str::from_utf8(&bytes[start..i]).unwrap_or("?")
5780 ),
5781 })?;
5782 positions.push(num);
5783 if i < bytes.len() {
5784 let w = bytes[i];
5785 if matches!(w, b'A' | b'B' | b'C' | b'D') {
5786 weight = match w {
5787 b'A' => 3,
5788 b'B' => 2,
5789 b'C' => 1,
5790 _ => 0,
5791 };
5792 i += 1;
5793 }
5794 }
5795 if i < bytes.len() && bytes[i] == b',' {
5796 i += 1;
5797 continue;
5798 }
5799 break;
5800 }
5801 }
5802 positions.sort_unstable();
5803 positions.dedup();
5804 match out.binary_search_by(|l| l.word.as_str().cmp(word.as_str())) {
5807 Ok(idx) => {
5808 for p in positions {
5809 if !out[idx].positions.contains(&p) {
5810 out[idx].positions.push(p);
5811 }
5812 }
5813 out[idx].positions.sort_unstable();
5814 if weight != 0 {
5815 out[idx].weight = weight;
5816 }
5817 }
5818 Err(idx) => {
5819 out.insert(
5820 idx,
5821 TsLexeme {
5822 word,
5823 positions,
5824 weight,
5825 },
5826 );
5827 }
5828 }
5829 }
5830 Ok(out)
5831}
5832
5833pub fn decode_tsquery_external(s: &str) -> Result<TsQueryAst, EvalError> {
5839 let mut p = TsQueryParser {
5840 bytes: s.as_bytes(),
5841 pos: 0,
5842 };
5843 p.skip_ws();
5844 if p.pos >= p.bytes.len() {
5845 return Err(EvalError::TypeMismatch {
5846 detail: "tsquery literal: empty".into(),
5847 });
5848 }
5849 let ast = p.parse_or()?;
5850 p.skip_ws();
5851 if p.pos < p.bytes.len() {
5852 return Err(EvalError::TypeMismatch {
5853 detail: alloc::format!("tsquery literal: trailing garbage at offset {}", p.pos),
5854 });
5855 }
5856 Ok(ast)
5857}
5858
5859struct TsQueryParser<'a> {
5860 bytes: &'a [u8],
5861 pos: usize,
5862}
5863
5864impl<'a> TsQueryParser<'a> {
5865 fn skip_ws(&mut self) {
5866 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_whitespace() {
5867 self.pos += 1;
5868 }
5869 }
5870 fn peek(&self) -> Option<u8> {
5871 self.bytes.get(self.pos).copied()
5872 }
5873 fn parse_or(&mut self) -> Result<TsQueryAst, EvalError> {
5874 let mut lhs = self.parse_and()?;
5875 loop {
5876 self.skip_ws();
5877 if self.peek() != Some(b'|') {
5878 return Ok(lhs);
5879 }
5880 self.pos += 1;
5881 let rhs = self.parse_and()?;
5882 lhs = TsQueryAst::Or(Box::new(lhs), Box::new(rhs));
5883 }
5884 }
5885 fn parse_and(&mut self) -> Result<TsQueryAst, EvalError> {
5886 let mut lhs = self.parse_unary()?;
5887 loop {
5888 self.skip_ws();
5889 match self.peek() {
5890 Some(b'&') => {
5891 self.pos += 1;
5892 let rhs = self.parse_unary()?;
5893 lhs = TsQueryAst::And(Box::new(lhs), Box::new(rhs));
5894 }
5895 Some(b'<') => {
5896 self.pos += 1;
5898 let start = self.pos;
5899 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
5900 self.pos += 1;
5901 }
5902 if start == self.pos || self.peek() != Some(b'>') {
5903 return Err(EvalError::TypeMismatch {
5904 detail: "tsquery literal: malformed <N> phrase operator".into(),
5905 });
5906 }
5907 let n: u16 = core::str::from_utf8(&self.bytes[start..self.pos])
5908 .expect("ascii digits")
5909 .parse()
5910 .map_err(|_| EvalError::TypeMismatch {
5911 detail: "tsquery literal: phrase distance overflows u16".into(),
5912 })?;
5913 self.pos += 1; let rhs = self.parse_unary()?;
5915 lhs = TsQueryAst::Phrase {
5916 left: Box::new(lhs),
5917 right: Box::new(rhs),
5918 distance: n,
5919 };
5920 }
5921 _ => return Ok(lhs),
5922 }
5923 }
5924 }
5925 fn parse_unary(&mut self) -> Result<TsQueryAst, EvalError> {
5926 self.skip_ws();
5927 if self.peek() == Some(b'!') {
5928 self.pos += 1;
5929 let inner = self.parse_unary()?;
5930 return Ok(TsQueryAst::Not(Box::new(inner)));
5931 }
5932 self.parse_atom()
5933 }
5934 fn parse_atom(&mut self) -> Result<TsQueryAst, EvalError> {
5935 self.skip_ws();
5936 match self.peek() {
5937 Some(b'(') => {
5938 self.pos += 1;
5939 let inner = self.parse_or()?;
5940 self.skip_ws();
5941 if self.peek() != Some(b')') {
5942 return Err(EvalError::TypeMismatch {
5943 detail: "tsquery literal: missing ')'".into(),
5944 });
5945 }
5946 self.pos += 1;
5947 Ok(inner)
5948 }
5949 Some(b'\'') => {
5950 self.pos += 1;
5951 let mut w = String::new();
5952 loop {
5953 match self.peek() {
5954 None => {
5955 return Err(EvalError::TypeMismatch {
5956 detail: "tsquery literal: unterminated quoted lexeme".into(),
5957 });
5958 }
5959 Some(b'\'') => {
5960 if self.bytes.get(self.pos + 1) == Some(&b'\'') {
5961 w.push('\'');
5962 self.pos += 2;
5963 } else {
5964 self.pos += 1;
5965 break;
5966 }
5967 }
5968 Some(b) => {
5969 w.push(b as char);
5970 self.pos += 1;
5971 }
5972 }
5973 }
5974 self.skip_weight_suffix();
5977 Ok(TsQueryAst::Term {
5978 word: w,
5979 weight_mask: 0,
5980 })
5981 }
5982 Some(b) if b.is_ascii_alphanumeric() || b == b'_' => {
5983 let start = self.pos;
5984 while self.pos < self.bytes.len() {
5985 let c = self.bytes[self.pos];
5986 if c.is_ascii_alphanumeric() || c == b'_' {
5987 self.pos += 1;
5988 } else {
5989 break;
5990 }
5991 }
5992 let w = core::str::from_utf8(&self.bytes[start..self.pos])
5993 .map_err(|_| EvalError::TypeMismatch {
5994 detail: "tsquery literal: non-UTF-8 lexeme".into(),
5995 })?
5996 .to_string();
5997 self.skip_weight_suffix();
5998 Ok(TsQueryAst::Term {
5999 word: w,
6000 weight_mask: 0,
6001 })
6002 }
6003 Some(b) => Err(EvalError::TypeMismatch {
6004 detail: alloc::format!(
6005 "tsquery literal: unexpected byte {:?} at offset {}",
6006 b as char,
6007 self.pos
6008 ),
6009 }),
6010 None => Err(EvalError::TypeMismatch {
6011 detail: "tsquery literal: expected term".into(),
6012 }),
6013 }
6014 }
6015 fn skip_weight_suffix(&mut self) {
6016 if self.peek() != Some(b':') {
6017 return;
6018 }
6019 self.pos += 1;
6020 while let Some(b) = self.peek() {
6021 if matches!(
6022 b,
6023 b'A' | b'B' | b'C' | b'D' | b'a' | b'b' | b'c' | b'd' | b'*'
6024 ) || b.is_ascii_digit()
6025 {
6026 self.pos += 1;
6027 } else {
6028 break;
6029 }
6030 }
6031 }
6032}
6033
6034pub fn format_bytea_hex(b: &[u8]) -> String {
6038 let mut out = String::with_capacity(2 + 2 * b.len());
6039 out.push_str("\\x");
6040 const HEX: &[u8; 16] = b"0123456789abcdef";
6041 for byte in b {
6042 out.push(HEX[(byte >> 4) as usize] as char);
6043 out.push(HEX[(byte & 0x0F) as usize] as char);
6044 }
6045 out
6046}
6047
6048pub fn format_numeric(scaled: i128, scale: u8) -> String {
6053 if scale == 0 {
6054 return format!("{scaled}");
6055 }
6056 let negative = scaled < 0;
6057 let mag_str = scaled.unsigned_abs().to_string();
6058 let mag_bytes = mag_str.as_bytes();
6059 let scale_u = scale as usize;
6060 let mut out = String::with_capacity(mag_str.len() + 3);
6061 if negative {
6062 out.push('-');
6063 }
6064 if mag_bytes.len() <= scale_u {
6065 out.push('0');
6066 out.push('.');
6067 for _ in mag_bytes.len()..scale_u {
6068 out.push('0');
6069 }
6070 out.push_str(&mag_str);
6071 } else {
6072 let split = mag_bytes.len() - scale_u;
6073 out.push_str(&mag_str[..split]);
6074 out.push('.');
6075 out.push_str(&mag_str[split..]);
6076 }
6077 out
6078}
6079
6080fn cast_numeric_to_int(v: Value) -> Result<Value, EvalError> {
6081 match v {
6082 Value::Int(n) => Ok(Value::Int(n)),
6083 Value::BigInt(n) => i32::try_from(n)
6084 .map(Value::Int)
6085 .map_err(|_| EvalError::TypeMismatch {
6086 detail: format!("bigint {n} does not fit in int"),
6087 }),
6088 #[allow(clippy::cast_possible_truncation)]
6089 Value::Float(x) => Ok(Value::Int(x as i32)),
6090 Value::Text(s) => {
6091 s.trim()
6092 .parse::<i32>()
6093 .map(Value::Int)
6094 .map_err(|_| EvalError::TypeMismatch {
6095 detail: format!("cannot parse {s:?} as int"),
6096 })
6097 }
6098 Value::Bool(b) => Ok(Value::Int(i32::from(b))),
6099 other => Err(EvalError::TypeMismatch {
6100 detail: format!("cannot cast {:?} to int", other.data_type()),
6101 }),
6102 }
6103}
6104
6105fn cast_numeric_to_bigint(v: Value) -> Result<Value, EvalError> {
6106 match v {
6107 Value::Int(n) => Ok(Value::BigInt(i64::from(n))),
6108 Value::BigInt(n) => Ok(Value::BigInt(n)),
6109 #[allow(clippy::cast_possible_truncation)]
6110 Value::Float(x) => Ok(Value::BigInt(x as i64)),
6111 Value::Text(s) => {
6112 s.trim()
6113 .parse::<i64>()
6114 .map(Value::BigInt)
6115 .map_err(|_| EvalError::TypeMismatch {
6116 detail: format!("cannot parse {s:?} as bigint"),
6117 })
6118 }
6119 Value::Bool(b) => Ok(Value::BigInt(i64::from(b))),
6120 other => Err(EvalError::TypeMismatch {
6121 detail: format!("cannot cast {:?} to bigint", other.data_type()),
6122 }),
6123 }
6124}
6125
6126fn cast_numeric_to_float(v: Value) -> Result<Value, EvalError> {
6127 match v {
6128 Value::Int(n) => Ok(Value::Float(f64::from(n))),
6129 #[allow(clippy::cast_precision_loss)]
6130 Value::BigInt(n) => Ok(Value::Float(n as f64)),
6131 Value::Float(x) => Ok(Value::Float(x)),
6132 Value::Text(s) => {
6133 s.trim()
6134 .parse::<f64>()
6135 .map(Value::Float)
6136 .map_err(|_| EvalError::TypeMismatch {
6137 detail: format!("cannot parse {s:?} as float"),
6138 })
6139 }
6140 other => Err(EvalError::TypeMismatch {
6141 detail: format!("cannot cast {:?} to float", other.data_type()),
6142 }),
6143 }
6144}
6145
6146fn cast_to_bool(v: Value) -> Result<Value, EvalError> {
6147 match v {
6148 Value::Bool(b) => Ok(Value::Bool(b)),
6149 Value::Int(n) => Ok(Value::Bool(n != 0)),
6150 Value::BigInt(n) => Ok(Value::Bool(n != 0)),
6151 Value::Text(s) => {
6152 let lo = s.trim().to_ascii_lowercase();
6153 match lo.as_str() {
6154 "true" | "t" | "yes" | "y" | "1" | "on" => Ok(Value::Bool(true)),
6155 "false" | "f" | "no" | "n" | "0" | "off" => Ok(Value::Bool(false)),
6156 _ => Err(EvalError::TypeMismatch {
6157 detail: format!("cannot parse {s:?} as bool"),
6158 }),
6159 }
6160 }
6161 other => Err(EvalError::TypeMismatch {
6162 detail: format!("cannot cast {:?} to bool", other.data_type()),
6163 }),
6164 }
6165}
6166
6167pub fn cast_to_vector(v: Value) -> Result<Value, EvalError> {
6170 match v {
6171 Value::Null => Ok(Value::Null),
6172 Value::Vector(v) => Ok(Value::Vector(v)),
6173 Value::Text(s) => parse_vector_text(&s)
6174 .map(Value::Vector)
6175 .ok_or(EvalError::TypeMismatch {
6176 detail: format!("cannot parse {s:?} as a vector literal"),
6177 }),
6178 other => Err(EvalError::TypeMismatch {
6179 detail: format!("::vector requires text input, got {:?}", other.data_type()),
6180 }),
6181 }
6182}
6183
6184pub fn parse_vector_text(s: &str) -> Option<Vec<f32>> {
6186 let trimmed = s.trim();
6187 let inner = trimmed.strip_prefix('[')?.strip_suffix(']')?;
6188 let trimmed_inner = inner.trim();
6189 if trimmed_inner.is_empty() {
6190 return Some(Vec::new());
6191 }
6192 let mut out = Vec::new();
6193 for part in trimmed_inner.split(',') {
6194 let f: f32 = part.trim().parse().ok()?;
6195 out.push(f);
6196 }
6197 Some(out)
6198}
6199
6200fn literal_to_value(l: &Literal) -> Value {
6201 match l {
6202 Literal::Integer(n) => {
6203 if let Ok(small) = i32::try_from(*n) {
6204 Value::Int(small)
6205 } else {
6206 Value::BigInt(*n)
6207 }
6208 }
6209 Literal::Float(x) => Value::Float(*x),
6210 Literal::String(s) => Value::Text(s.clone()),
6211 Literal::Vector(v) => Value::Vector(v.clone()),
6212 Literal::Bool(b) => Value::Bool(*b),
6213 Literal::Null => Value::Null,
6214 Literal::Interval { months, micros, .. } => Value::Interval {
6215 months: *months,
6216 micros: *micros,
6217 },
6218 }
6219}
6220
6221pub(crate) fn column_collation(e: &Expr, ctx: &EvalContext<'_>) -> Option<spg_storage::Collation> {
6227 let Expr::Column(c) = e else {
6228 return None;
6229 };
6230 if let Some(q) = &c.qualifier {
6231 let composite = alloc::format!("{q}.{name}", name = c.name);
6232 if let Some(s) = ctx.columns.iter().find(|s| s.name == composite) {
6233 return Some(s.collation);
6234 }
6235 }
6236 if let Some(s) = ctx.columns.iter().find(|s| s.name == c.name) {
6237 return Some(s.collation);
6238 }
6239 let suffix = alloc::format!(".{name}", name = c.name);
6243 let mut matches = ctx.columns.iter().filter(|s| s.name.ends_with(&suffix));
6244 let first = matches.next();
6245 let extra = matches.next();
6246 match (first, extra) {
6247 (Some(s), None) => Some(s.collation),
6248 _ => None,
6249 }
6250}
6251
6252fn collation_fold_for_compare(
6259 op: BinOp,
6260 lhs: &Expr,
6261 rhs: &Expr,
6262 l: Value,
6263 r: Value,
6264 ctx: &EvalContext<'_>,
6265) -> (Value, Value) {
6266 if !matches!(
6267 op,
6268 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq
6269 ) {
6270 return (l, r);
6271 }
6272 let lhs_col = column_collation(lhs, ctx);
6273 let rhs_col = column_collation(rhs, ctx);
6274 let ci = matches!(lhs_col, Some(spg_storage::Collation::CaseInsensitive))
6275 || matches!(rhs_col, Some(spg_storage::Collation::CaseInsensitive));
6276 if !ci {
6277 return (l, r);
6278 }
6279 let fold = |v: Value| match v {
6280 Value::Text(s) => Value::Text(s.to_ascii_lowercase()),
6281 other => other,
6282 };
6283 (fold(l), fold(r))
6284}
6285
6286fn resolve_column(c: &ColumnName, row: &Row, ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
6287 if let Some(q) = &c.qualifier {
6288 let composite = alloc::format!("{q}.{name}", name = c.name);
6293 if let Some(pos) = ctx.columns.iter().position(|s| s.name == composite) {
6294 return Ok(row.values[pos].clone());
6295 }
6296 let expected = ctx.table_alias.ok_or_else(|| EvalError::UnknownQualifier {
6297 qualifier: q.clone(),
6298 })?;
6299 if q != expected {
6300 return Err(EvalError::UnknownQualifier {
6301 qualifier: q.clone(),
6302 });
6303 }
6304 }
6305 if let Some(pos) = ctx.columns.iter().position(|s| s.name == c.name) {
6306 return Ok(row.values[pos].clone());
6307 }
6308 let suffix = alloc::format!(".{name}", name = c.name);
6311 let mut matches = ctx
6312 .columns
6313 .iter()
6314 .enumerate()
6315 .filter(|(_, s)| s.name.ends_with(&suffix));
6316 let first = matches.next();
6317 let extra = matches.next();
6318 match (first, extra) {
6319 (Some((pos, _)), None) => Ok(row.values[pos].clone()),
6320 (Some(_), Some(_)) => Err(EvalError::TypeMismatch {
6321 detail: alloc::format!("ambiguous column reference: {}", c.name),
6322 }),
6323 _ => Err(EvalError::ColumnNotFound {
6324 name: c.name.clone(),
6325 }),
6326 }
6327}
6328
6329fn apply_unary(op: UnOp, v: Value) -> Result<Value, EvalError> {
6330 match (op, v) {
6331 (_, Value::Null) => Ok(Value::Null),
6332 (UnOp::Neg, Value::Int(n)) => {
6333 n.checked_neg()
6334 .map(Value::Int)
6335 .ok_or(EvalError::TypeMismatch {
6336 detail: "integer overflow on unary -".into(),
6337 })
6338 }
6339 (UnOp::Neg, Value::BigInt(n)) => {
6340 n.checked_neg()
6341 .map(Value::BigInt)
6342 .ok_or(EvalError::TypeMismatch {
6343 detail: "bigint overflow on unary -".into(),
6344 })
6345 }
6346 (UnOp::Neg, Value::Float(x)) => Ok(Value::Float(-x)),
6347 (UnOp::Neg, other) => Err(EvalError::TypeMismatch {
6348 detail: format!("unary - applied to {:?}", other.data_type()),
6349 }),
6350 (UnOp::Not, Value::Bool(b)) => Ok(Value::Bool(!b)),
6351 (UnOp::Not, other) => Err(EvalError::TypeMismatch {
6352 detail: format!("NOT applied to {:?}", other.data_type()),
6353 }),
6354 }
6355}
6356
6357fn values_not_distinct(l: &Value, r: &Value) -> bool {
6360 match (l, r) {
6361 (Value::Null, Value::Null) => true,
6362 (Value::Null, _) | (_, Value::Null) => false,
6363 _ => l == r,
6364 }
6365}
6366
6367fn apply_binary(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6368 if let BinOp::And = op {
6371 return and_3vl(l, r);
6372 }
6373 if let BinOp::Or = op {
6374 return or_3vl(l, r);
6375 }
6376 if let BinOp::IsNotDistinctFrom = op {
6379 return Ok(Value::Bool(values_not_distinct(&l, &r)));
6380 }
6381 if let BinOp::IsDistinctFrom = op {
6382 return Ok(Value::Bool(!values_not_distinct(&l, &r)));
6383 }
6384 if l.is_null() || r.is_null() {
6386 return Ok(Value::Null);
6387 }
6388 if matches!(l, Value::Numeric { .. }) || matches!(r, Value::Numeric { .. }) {
6391 return apply_binary_numeric(op, l, r);
6392 }
6393 if let Some(result) = apply_binary_calendar(op, &l, &r)? {
6401 return Ok(result);
6402 }
6403 match op {
6404 BinOp::Add => arith(l, r, i64::checked_add, |a, b| a + b, "+"),
6405 BinOp::Sub => arith(l, r, i64::checked_sub, |a, b| a - b, "-"),
6406 BinOp::Mul => arith(l, r, i64::checked_mul, |a, b| a * b, "*"),
6407 BinOp::Div => div_op(l, r),
6408 BinOp::L2Distance => l2_distance(l, r),
6409 BinOp::InnerProduct => inner_product(l, r),
6410 BinOp::CosineDistance => cosine_distance(l, r),
6411 BinOp::Concat => Ok(text_concat(&l, &r)),
6412 BinOp::JsonGet => crate::json::path_get(&l, &r, false),
6413 BinOp::JsonGetText => crate::json::path_get(&l, &r, true),
6414 BinOp::JsonGetPath => crate::json::path_walk(&l, &r, false),
6415 BinOp::JsonGetPathText => crate::json::path_walk(&l, &r, true),
6416 BinOp::JsonContains => crate::json::contains(&l, &r),
6417 BinOp::TsMatch => ts_match(l, r),
6420 BinOp::InetContainedBy
6422 | BinOp::InetContainedByEq
6423 | BinOp::InetContains
6424 | BinOp::InetContainsEq
6425 | BinOp::InetOverlap => inet_op_bool_result(op, &l, &r),
6426 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6427 compare(op, &l, &r)
6428 }
6429 BinOp::And | BinOp::Or | BinOp::IsDistinctFrom | BinOp::IsNotDistinctFrom => {
6430 unreachable!("handled above")
6431 }
6432 }
6433}
6434
6435fn apply_binary_calendar(op: BinOp, l: &Value, r: &Value) -> Result<Option<Value>, EvalError> {
6439 let int_value = |v: &Value| -> Option<i64> {
6440 match v {
6441 Value::SmallInt(n) => Some(i64::from(*n)),
6442 Value::Int(n) => Some(i64::from(*n)),
6443 Value::BigInt(n) => Some(*n),
6444 _ => None,
6445 }
6446 };
6447 match (l, r) {
6451 (Value::Date(a), Value::Date(b)) if op == BinOp::Sub => {
6452 return Ok(Some(Value::BigInt(i64::from(*a) - i64::from(*b))));
6453 }
6454 (Value::Timestamp(a), Value::Timestamp(b)) if op == BinOp::Sub => {
6455 let delta = a.checked_sub(*b).ok_or(EvalError::TypeMismatch {
6456 detail: "TIMESTAMP - TIMESTAMP overflows i64 microseconds".into(),
6457 })?;
6458 return Ok(Some(Value::BigInt(delta)));
6459 }
6460 _ => {}
6461 }
6462 if let Some(out) = apply_binary_interval(op, l, r)? {
6466 return Ok(Some(out));
6467 }
6468 match (l, r) {
6469 (Value::Date(d), other) if op == BinOp::Add => {
6470 if let Some(n) = int_value(other) {
6471 let days = i64::from(*d).saturating_add(n);
6472 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6473 detail: "DATE + integer overflows DATE range".into(),
6474 })?;
6475 return Ok(Some(Value::Date(days32)));
6476 }
6477 }
6478 (other, Value::Date(d)) if op == BinOp::Add => {
6479 if let Some(n) = int_value(other) {
6480 let days = i64::from(*d).saturating_add(n);
6481 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6482 detail: "integer + DATE overflows DATE range".into(),
6483 })?;
6484 return Ok(Some(Value::Date(days32)));
6485 }
6486 }
6487 (Value::Date(d), other) if op == BinOp::Sub => {
6488 if let Some(n) = int_value(other) {
6489 let days = i64::from(*d).saturating_sub(n);
6490 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6491 detail: "DATE - integer overflows DATE range".into(),
6492 })?;
6493 return Ok(Some(Value::Date(days32)));
6494 }
6495 }
6496 _ => {}
6497 }
6498 Ok(None)
6499}
6500
6501pub(crate) fn apply_binary_interval(
6509 op: BinOp,
6510 l: &Value,
6511 r: &Value,
6512) -> Result<Option<Value>, EvalError> {
6513 let (lhs, rhs, sign): (&Value, &Value, i64) = match (l, r, op) {
6516 (Value::Interval { .. }, _, BinOp::Add) => (r, l, 1),
6517 (_, Value::Interval { .. }, BinOp::Add) => (l, r, 1),
6518 (_, Value::Interval { .. }, BinOp::Sub) => (l, r, -1),
6519 _ => return Ok(None),
6520 };
6521 let Value::Interval {
6522 months: rhs_months,
6523 micros: rhs_us,
6524 } = rhs
6525 else {
6526 unreachable!("rhs guaranteed to be Interval by the match above");
6527 };
6528 let signed_months = i64::from(*rhs_months) * sign;
6529 let signed_micros = rhs_us.checked_mul(sign).ok_or(EvalError::TypeMismatch {
6530 detail: "INTERVAL micros overflows on negation".into(),
6531 })?;
6532 match lhs {
6533 Value::Timestamp(t) => Ok(Some(Value::Timestamp(add_interval_to_micros(
6534 *t,
6535 signed_months,
6536 signed_micros,
6537 )?))),
6538 Value::Date(d) => {
6539 let day_aligned = signed_micros.rem_euclid(86_400_000_000) == 0;
6543 if day_aligned {
6544 let micros_per_day = 86_400_000_000_i64;
6545 let days_delta = signed_micros / micros_per_day;
6546 let shifted = shift_date_by_months(*d, signed_months)?;
6547 let new_days =
6548 i64::from(shifted)
6549 .checked_add(days_delta)
6550 .ok_or(EvalError::TypeMismatch {
6551 detail: "DATE ± INTERVAL overflows DATE range".into(),
6552 })?;
6553 let days32 = i32::try_from(new_days).map_err(|_| EvalError::TypeMismatch {
6554 detail: "DATE ± INTERVAL overflows DATE range".into(),
6555 })?;
6556 Ok(Some(Value::Date(days32)))
6557 } else {
6558 let base =
6559 i64::from(*d)
6560 .checked_mul(86_400_000_000)
6561 .ok_or(EvalError::TypeMismatch {
6562 detail: "DATE → TIMESTAMP lift overflows for INTERVAL math".into(),
6563 })?;
6564 Ok(Some(Value::Timestamp(add_interval_to_micros(
6565 base,
6566 signed_months,
6567 signed_micros,
6568 )?)))
6569 }
6570 }
6571 Value::Interval {
6572 months: lhs_months,
6573 micros: lhs_us,
6574 } => {
6575 let new_months = i64::from(*lhs_months)
6576 .checked_add(signed_months)
6577 .and_then(|n| i32::try_from(n).ok())
6578 .ok_or(EvalError::TypeMismatch {
6579 detail: "INTERVAL ± INTERVAL months overflows i32".into(),
6580 })?;
6581 let new_micros = lhs_us
6582 .checked_add(signed_micros)
6583 .ok_or(EvalError::TypeMismatch {
6584 detail: "INTERVAL ± INTERVAL micros overflows i64".into(),
6585 })?;
6586 Ok(Some(Value::Interval {
6587 months: new_months,
6588 micros: new_micros,
6589 }))
6590 }
6591 _ => Err(EvalError::TypeMismatch {
6592 detail: format!(
6593 "operator {op:?} not defined for {:?} and INTERVAL",
6594 lhs.data_type()
6595 ),
6596 }),
6597 }
6598}
6599
6600fn shift_date_by_months(d: i32, months: i64) -> Result<i32, EvalError> {
6602 let (y, m, day) = civil_from_days(d);
6603 let months_i32 = i32::try_from(months).map_err(|_| EvalError::TypeMismatch {
6604 detail: "INTERVAL months delta out of i32 range".into(),
6605 })?;
6606 let (ny, nm, nd) = add_months_to_civil(y, m, day, months_i32);
6607 Ok(days_from_civil(ny, nm, nd))
6608}
6609
6610fn add_interval_to_micros(t: i64, months: i64, micros: i64) -> Result<i64, EvalError> {
6614 let mut out = t;
6615 if months != 0 {
6616 const MICROS_PER_DAY: i64 = 86_400_000_000;
6617 let days = out.div_euclid(MICROS_PER_DAY);
6618 let day_micros = out.rem_euclid(MICROS_PER_DAY);
6619 let day_i32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6620 detail: "TIMESTAMP day component out of i32 range for INTERVAL months math".into(),
6621 })?;
6622 let shifted_days = shift_date_by_months(day_i32, months)?;
6623 out = i64::from(shifted_days)
6624 .checked_mul(MICROS_PER_DAY)
6625 .and_then(|n| n.checked_add(day_micros))
6626 .ok_or(EvalError::TypeMismatch {
6627 detail: "TIMESTAMP ± INTERVAL months overflows i64 microseconds".into(),
6628 })?;
6629 }
6630 out.checked_add(micros).ok_or(EvalError::TypeMismatch {
6631 detail: "TIMESTAMP ± INTERVAL micros overflows i64".into(),
6632 })
6633}
6634
6635#[allow(clippy::needless_pass_by_value)] fn apply_binary_numeric(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6640 let float_path = matches!(l, Value::Float(_)) || matches!(r, Value::Float(_));
6644 if float_path {
6645 let af = as_f64(&l)?;
6646 let bf = as_f64(&r)?;
6647 return match op {
6648 BinOp::Add => Ok(Value::Float(af + bf)),
6649 BinOp::Sub => Ok(Value::Float(af - bf)),
6650 BinOp::Mul => Ok(Value::Float(af * bf)),
6651 BinOp::Div => {
6652 if bf == 0.0 {
6653 Err(EvalError::DivisionByZero)
6654 } else {
6655 Ok(Value::Float(af / bf))
6656 }
6657 }
6658 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6659 let ord = af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
6660 detail: "NaN in NUMERIC/Float comparison".into(),
6661 })?;
6662 Ok(Value::Bool(cmp_to_bool(op, ord)))
6663 }
6664 BinOp::Concat => Ok(text_concat(&l, &r)),
6665 other => Err(EvalError::TypeMismatch {
6666 detail: format!("operator {other:?} not defined for NUMERIC and Float"),
6667 }),
6668 };
6669 }
6670 let (a, sa) = numeric_or_widen(&l).ok_or_else(|| EvalError::TypeMismatch {
6672 detail: format!("NUMERIC op against non-numeric {:?}", l.data_type()),
6673 })?;
6674 let (b, sb) = numeric_or_widen(&r).ok_or_else(|| EvalError::TypeMismatch {
6675 detail: format!("NUMERIC op against non-numeric {:?}", r.data_type()),
6676 })?;
6677 match op {
6678 BinOp::Add | BinOp::Sub => {
6679 let target_scale = sa.max(sb);
6680 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
6681 detail: "NUMERIC overflow on rescale".into(),
6682 })?;
6683 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
6684 detail: "NUMERIC overflow on rescale".into(),
6685 })?;
6686 let r = match op {
6687 BinOp::Add => lhs.checked_add(rhs),
6688 BinOp::Sub => lhs.checked_sub(rhs),
6689 _ => unreachable!(),
6690 }
6691 .ok_or(EvalError::TypeMismatch {
6692 detail: "NUMERIC overflow on +/-".into(),
6693 })?;
6694 Ok(Value::Numeric {
6695 scaled: r,
6696 scale: target_scale,
6697 })
6698 }
6699 BinOp::Mul => {
6700 let scaled = a.checked_mul(b).ok_or(EvalError::TypeMismatch {
6701 detail: "NUMERIC overflow on *".into(),
6702 })?;
6703 Ok(Value::Numeric {
6704 scaled,
6705 scale: sa.saturating_add(sb),
6706 })
6707 }
6708 BinOp::Div => {
6709 if b == 0 {
6710 return Err(EvalError::DivisionByZero);
6711 }
6712 let target_scale = sa.max(sb);
6716 let bump = pow10_i128(target_scale.saturating_add(sb).saturating_sub(sa));
6720 let num = a.checked_mul(bump).ok_or(EvalError::TypeMismatch {
6721 detail: "NUMERIC overflow on / scaling".into(),
6722 })?;
6723 let half = if b >= 0 { b / 2 } else { -(b / 2) };
6724 let adj = if (num >= 0) == (b >= 0) {
6725 num + half
6726 } else {
6727 num - half
6728 };
6729 Ok(Value::Numeric {
6730 scaled: adj / b,
6731 scale: target_scale,
6732 })
6733 }
6734 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6735 let target_scale = sa.max(sb);
6736 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
6737 detail: "NUMERIC overflow on rescale".into(),
6738 })?;
6739 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
6740 detail: "NUMERIC overflow on rescale".into(),
6741 })?;
6742 Ok(Value::Bool(cmp_to_bool(op, lhs.cmp(&rhs))))
6743 }
6744 BinOp::Concat => Ok(text_concat(&l, &r)),
6745 other => Err(EvalError::TypeMismatch {
6746 detail: format!("operator {other:?} not defined for NUMERIC"),
6747 }),
6748 }
6749}
6750
6751fn numeric_or_widen(v: &Value) -> Option<(i128, u8)> {
6755 match v {
6756 Value::Numeric { scaled, scale } => Some((*scaled, *scale)),
6757 Value::Int(n) => Some((i128::from(*n), 0)),
6758 Value::SmallInt(n) => Some((i128::from(*n), 0)),
6759 Value::BigInt(n) => Some((i128::from(*n), 0)),
6760 _ => None,
6761 }
6762}
6763
6764fn rescale(scaled: i128, src: u8, dst: u8) -> Option<i128> {
6765 if src == dst {
6766 return Some(scaled);
6767 }
6768 if dst > src {
6769 scaled.checked_mul(pow10_i128(dst - src))
6770 } else {
6771 let drop = pow10_i128(src - dst);
6772 let half = drop / 2;
6773 let r = if scaled >= 0 {
6774 scaled + half
6775 } else {
6776 scaled - half
6777 };
6778 Some(r / drop)
6779 }
6780}
6781
6782const fn pow10_i128(p: u8) -> i128 {
6783 let mut acc: i128 = 1;
6784 let mut i = 0;
6785 while i < p {
6786 acc *= 10;
6787 i += 1;
6788 }
6789 acc
6790}
6791
6792const fn cmp_to_bool(op: BinOp, ord: core::cmp::Ordering) -> bool {
6793 use core::cmp::Ordering::{Equal, Greater, Less};
6794 match op {
6795 BinOp::Eq => matches!(ord, Equal),
6796 BinOp::NotEq => !matches!(ord, Equal),
6797 BinOp::Lt => matches!(ord, Less),
6798 BinOp::LtEq => matches!(ord, Less | Equal),
6799 BinOp::Gt => matches!(ord, Greater),
6800 BinOp::GtEq => matches!(ord, Greater | Equal),
6801 _ => false,
6802 }
6803}
6804
6805fn text_concat(l: &Value, r: &Value) -> Value {
6809 match (l, r) {
6814 (Value::Null, _) | (_, Value::Null) => {
6815 if matches!(
6819 l,
6820 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
6821 ) || matches!(
6822 r,
6823 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
6824 ) {
6825 return Value::Null;
6826 }
6827 }
6828 (Value::TextArray(a), Value::TextArray(b)) => {
6829 let mut out = a.clone();
6830 out.extend(b.iter().cloned());
6831 return Value::TextArray(out);
6832 }
6833 (Value::TextArray(a), Value::Text(s)) => {
6834 let mut out = a.clone();
6835 out.push(Some(s.clone()));
6836 return Value::TextArray(out);
6837 }
6838 (Value::Text(s), Value::TextArray(b)) => {
6839 let mut out: alloc::vec::Vec<Option<alloc::string::String>> =
6840 alloc::vec::Vec::with_capacity(1 + b.len());
6841 out.push(Some(s.clone()));
6842 out.extend(b.iter().cloned());
6843 return Value::TextArray(out);
6844 }
6845 (Value::IntArray(a), Value::IntArray(b)) => {
6850 let mut out = a.clone();
6851 out.extend(b.iter().copied());
6852 return Value::IntArray(out);
6853 }
6854 (Value::IntArray(a), Value::Int(n)) => {
6855 let mut out = a.clone();
6856 out.push(Some(*n));
6857 return Value::IntArray(out);
6858 }
6859 (Value::IntArray(a), Value::SmallInt(n)) => {
6860 let mut out = a.clone();
6861 out.push(Some(i32::from(*n)));
6862 return Value::IntArray(out);
6863 }
6864 (Value::Int(n), Value::IntArray(b)) => {
6865 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
6866 out.push(Some(*n));
6867 out.extend(b.iter().copied());
6868 return Value::IntArray(out);
6869 }
6870 (Value::SmallInt(n), Value::IntArray(b)) => {
6871 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
6872 out.push(Some(i32::from(*n)));
6873 out.extend(b.iter().copied());
6874 return Value::IntArray(out);
6875 }
6876 (Value::BigIntArray(a), Value::BigIntArray(b)) => {
6877 let mut out = a.clone();
6878 out.extend(b.iter().copied());
6879 return Value::BigIntArray(out);
6880 }
6881 (Value::BigIntArray(a), Value::IntArray(b)) => {
6882 let mut out = a.clone();
6883 out.extend(b.iter().map(|o| o.map(i64::from)));
6884 return Value::BigIntArray(out);
6885 }
6886 (Value::IntArray(a), Value::BigIntArray(b)) => {
6887 let mut out: alloc::vec::Vec<Option<i64>> =
6888 a.iter().map(|o| o.map(i64::from)).collect();
6889 out.extend(b.iter().copied());
6890 return Value::BigIntArray(out);
6891 }
6892 (Value::BigIntArray(a), Value::BigInt(n)) => {
6893 let mut out = a.clone();
6894 out.push(Some(*n));
6895 return Value::BigIntArray(out);
6896 }
6897 (Value::BigIntArray(a), Value::Int(n)) => {
6898 let mut out = a.clone();
6899 out.push(Some(i64::from(*n)));
6900 return Value::BigIntArray(out);
6901 }
6902 (Value::BigIntArray(a), Value::SmallInt(n)) => {
6903 let mut out = a.clone();
6904 out.push(Some(i64::from(*n)));
6905 return Value::BigIntArray(out);
6906 }
6907 (Value::BigInt(n), Value::BigIntArray(b)) => {
6908 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
6909 out.push(Some(*n));
6910 out.extend(b.iter().copied());
6911 return Value::BigIntArray(out);
6912 }
6913 (Value::Int(n), Value::BigIntArray(b)) => {
6914 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
6915 out.push(Some(i64::from(*n)));
6916 out.extend(b.iter().copied());
6917 return Value::BigIntArray(out);
6918 }
6919 (Value::SmallInt(n), Value::BigIntArray(b)) => {
6920 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
6921 out.push(Some(i64::from(*n)));
6922 out.extend(b.iter().copied());
6923 return Value::BigIntArray(out);
6924 }
6925 (Value::Bytes(a), Value::Bytes(b)) => {
6927 let mut out = a.clone();
6928 out.extend_from_slice(b);
6929 return Value::Bytes(out);
6930 }
6931 _ => {}
6932 }
6933 let a = value_to_text(l);
6934 let b = value_to_text(r);
6935 Value::Text(a + &b)
6936}
6937
6938fn inner_product(l: Value, r: Value) -> Result<Value, EvalError> {
6941 let (a, b) = unwrap_vec_pair(l, r, "<#>")?;
6942 let mut dot: f64 = 0.0;
6943 for (x, y) in a.iter().zip(b.iter()) {
6944 dot += f64::from(*x) * f64::from(*y);
6945 }
6946 Ok(Value::Float(-dot))
6947}
6948
6949fn cosine_distance(l: Value, r: Value) -> Result<Value, EvalError> {
6952 let (a, b) = unwrap_vec_pair(l, r, "<=>")?;
6953 let mut dot: f64 = 0.0;
6954 let mut na: f64 = 0.0;
6955 let mut nb: f64 = 0.0;
6956 for (x, y) in a.iter().zip(b.iter()) {
6957 let xf = f64::from(*x);
6958 let yf = f64::from(*y);
6959 dot += xf * yf;
6960 na += xf * xf;
6961 nb += yf * yf;
6962 }
6963 let denom = sqrt_newton(na) * sqrt_newton(nb);
6964 if denom == 0.0 {
6965 return Ok(Value::Float(f64::NAN));
6966 }
6967 Ok(Value::Float(1.0 - dot / denom))
6968}
6969
6970fn unwrap_vec_pair(l: Value, r: Value, op: &str) -> Result<(Vec<f32>, Vec<f32>), EvalError> {
6971 let to_f32 = |v: Value| -> Option<Vec<f32>> {
6979 match v {
6980 Value::Vector(a) => Some(a),
6981 Value::Sq8Vector(q) => Some(spg_storage::quantize::dequantize(&q)),
6982 Value::HalfVector(h) => Some(h.to_f32_vec()),
6984 _ => None,
6985 }
6986 };
6987 let l_ty = l.data_type();
6988 let r_ty = r.data_type();
6989 match (to_f32(l), to_f32(r)) {
6990 (Some(a), Some(b)) => {
6991 if a.len() != b.len() {
6992 return Err(EvalError::TypeMismatch {
6993 detail: format!("vector dim mismatch in {op}: {} vs {}", a.len(), b.len()),
6994 });
6995 }
6996 Ok((a, b))
6997 }
6998 _ => Err(EvalError::TypeMismatch {
6999 detail: format!("{op} requires two vectors, got {l_ty:?} and {r_ty:?}"),
7000 }),
7001 }
7002}
7003
7004fn arith(
7009 l: Value,
7010 r: Value,
7011 int_op: impl Fn(i64, i64) -> Option<i64>,
7012 float_op: impl Fn(f64, f64) -> f64,
7013 op_name: &str,
7014) -> Result<Value, EvalError> {
7015 let widen = |v: Value| -> Value {
7018 match v {
7019 Value::SmallInt(n) => Value::Int(i32::from(n)),
7020 other => other,
7021 }
7022 };
7023 let l = widen(l);
7024 let r = widen(r);
7025 match (l, r) {
7026 (Value::Int(a), Value::Int(b)) => {
7027 let result = int_op(i64::from(a), i64::from(b)).ok_or(EvalError::TypeMismatch {
7028 detail: format!("integer overflow on {op_name}"),
7029 })?;
7030 if let Ok(small) = i32::try_from(result) {
7031 Ok(Value::Int(small))
7032 } else {
7033 Ok(Value::BigInt(result))
7034 }
7035 }
7036 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
7037 let result = int_op(i64::from(a), b).ok_or(EvalError::TypeMismatch {
7038 detail: format!("bigint overflow on {op_name}"),
7039 })?;
7040 Ok(Value::BigInt(result))
7041 }
7042 (Value::BigInt(a), Value::BigInt(b)) => {
7043 let result = int_op(a, b).ok_or(EvalError::TypeMismatch {
7044 detail: format!("bigint overflow on {op_name}"),
7045 })?;
7046 Ok(Value::BigInt(result))
7047 }
7048 (a, b)
7049 if a.data_type() == Some(DataType::Float) || b.data_type() == Some(DataType::Float) =>
7050 {
7051 let af = as_f64(&a)?;
7052 let bf = as_f64(&b)?;
7053 Ok(Value::Float(float_op(af, bf)))
7054 }
7055 (a, b) => Err(EvalError::TypeMismatch {
7056 detail: format!(
7057 "{op_name} applied to non-numeric: {:?} vs {:?}",
7058 a.data_type(),
7059 b.data_type()
7060 ),
7061 }),
7062 }
7063}
7064
7065#[allow(clippy::many_single_char_names)] fn l2_distance(l: Value, r: Value) -> Result<Value, EvalError> {
7071 let (a, b) = unwrap_vec_pair(l, r, "<->")?;
7076 let mut sum: f64 = 0.0;
7077 for (x, y) in a.iter().zip(b.iter()) {
7078 let d = f64::from(*x) - f64::from(*y);
7079 sum += d * d;
7080 }
7081 Ok(Value::Float(sqrt_newton(sum)))
7082}
7083
7084fn sqrt_newton(x: f64) -> f64 {
7089 if x <= 0.0 {
7090 return 0.0;
7091 }
7092 let mut g = x;
7093 for _ in 0..10 {
7096 g = 0.5 * (g + x / g);
7097 }
7098 g
7099}
7100
7101fn div_op(l: Value, r: Value) -> Result<Value, EvalError> {
7102 let any_float = matches!(l.data_type(), Some(DataType::Float))
7103 || matches!(r.data_type(), Some(DataType::Float));
7104 if any_float {
7105 let a = as_f64(&l)?;
7106 let b = as_f64(&r)?;
7107 if b == 0.0 {
7108 return Err(EvalError::DivisionByZero);
7109 }
7110 return Ok(Value::Float(a / b));
7111 }
7112 arith(
7113 l,
7114 r,
7115 |a, b| {
7116 if b == 0 { None } else { Some(a / b) }
7117 },
7118 |a, b| a / b,
7119 "/",
7120 )
7121 .map_err(|e| match e {
7122 EvalError::TypeMismatch { detail } if detail.contains('/') => EvalError::DivisionByZero,
7125 other => other,
7126 })
7127}
7128
7129fn as_f64(v: &Value) -> Result<f64, EvalError> {
7130 match v {
7131 Value::SmallInt(n) => Ok(f64::from(*n)),
7132 Value::Int(n) => Ok(f64::from(*n)),
7133 #[allow(clippy::cast_precision_loss)]
7134 Value::BigInt(n) => Ok(*n as f64),
7135 Value::Float(x) => Ok(*x),
7136 #[allow(clippy::cast_precision_loss)]
7137 Value::Numeric { scaled, scale } => {
7138 let mut div = 1.0_f64;
7139 for _ in 0..*scale {
7140 div *= 10.0;
7141 }
7142 Ok((*scaled as f64) / div)
7143 }
7144 other => Err(EvalError::TypeMismatch {
7145 detail: format!("cannot convert {:?} to FLOAT", other.data_type()),
7146 }),
7147 }
7148}
7149
7150fn compare(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
7151 let ord = match (l, r) {
7152 (Value::Int(a), Value::Int(b)) => i64::from(*a).cmp(&i64::from(*b)),
7153 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
7154 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
7155 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
7156 (a, b)
7157 if matches!(a.data_type(), Some(DataType::Float))
7158 || matches!(b.data_type(), Some(DataType::Float)) =>
7159 {
7160 let af = as_f64(a)?;
7161 let bf = as_f64(b)?;
7162 af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
7163 detail: "NaN in comparison".into(),
7164 })?
7165 }
7166 (Value::Text(a), Value::Text(b)) => a.cmp(b),
7167 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
7168 (Value::Date(a), Value::Date(b)) => a.cmp(b),
7172 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
7173 (Value::Date(a), Value::Timestamp(b)) => (i64::from(*a) * 86_400_000_000).cmp(b),
7174 (Value::Timestamp(a), Value::Date(b)) => a.cmp(&(i64::from(*b) * 86_400_000_000)),
7175 (Value::Date(a), Value::Text(b)) => {
7179 let bd = parse_date_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7180 detail: format!("cannot parse {b:?} as DATE for comparison"),
7181 })?;
7182 a.cmp(&bd)
7183 }
7184 (Value::Text(a), Value::Date(b)) => {
7185 let ad = parse_date_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7186 detail: format!("cannot parse {a:?} as DATE for comparison"),
7187 })?;
7188 ad.cmp(b)
7189 }
7190 (Value::Timestamp(a), Value::Text(b)) => {
7191 let bt = parse_timestamp_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7192 detail: format!("cannot parse {b:?} as TIMESTAMP for comparison"),
7193 })?;
7194 a.cmp(&bt)
7195 }
7196 (Value::Text(a), Value::Timestamp(b)) => {
7197 let at = parse_timestamp_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7198 detail: format!("cannot parse {a:?} as TIMESTAMP for comparison"),
7199 })?;
7200 at.cmp(b)
7201 }
7202 (Value::Uuid(a), Value::Uuid(b)) => a.cmp(b),
7204 (Value::Uuid(a), Value::Text(b)) => {
7210 let bu = spg_storage::parse_uuid_str(b).ok_or_else(|| EvalError::TypeMismatch {
7211 detail: format!("invalid input syntax for type uuid: {b:?}"),
7212 })?;
7213 a.cmp(&bu)
7214 }
7215 (Value::Text(a), Value::Uuid(b)) => {
7216 let au = spg_storage::parse_uuid_str(a).ok_or_else(|| EvalError::TypeMismatch {
7217 detail: format!("invalid input syntax for type uuid: {a:?}"),
7218 })?;
7219 au.cmp(b)
7220 }
7221 (a, b) => {
7222 return Err(EvalError::TypeMismatch {
7223 detail: format!(
7224 "comparison between {:?} and {:?}",
7225 a.data_type(),
7226 b.data_type()
7227 ),
7228 });
7229 }
7230 };
7231 let result = match op {
7232 BinOp::Eq => ord.is_eq(),
7233 BinOp::NotEq => !ord.is_eq(),
7234 BinOp::Lt => ord.is_lt(),
7235 BinOp::LtEq => ord.is_le(),
7236 BinOp::Gt => ord.is_gt(),
7237 BinOp::GtEq => ord.is_ge(),
7238 BinOp::And
7239 | BinOp::Or
7240 | BinOp::Add
7241 | BinOp::Sub
7242 | BinOp::Mul
7243 | BinOp::Div
7244 | BinOp::L2Distance
7245 | BinOp::InnerProduct
7246 | BinOp::CosineDistance
7247 | BinOp::Concat
7248 | BinOp::JsonGet
7249 | BinOp::JsonGetText
7250 | BinOp::JsonGetPath
7251 | BinOp::JsonGetPathText
7252 | BinOp::JsonContains
7253 | BinOp::TsMatch
7254 | BinOp::IsDistinctFrom
7255 | BinOp::IsNotDistinctFrom
7256 | BinOp::InetContainedBy
7257 | BinOp::InetContainedByEq
7258 | BinOp::InetContains
7259 | BinOp::InetContainsEq
7260 | BinOp::InetOverlap => {
7261 unreachable!("compare() only called with comparison ops")
7262 }
7263 };
7264 Ok(Value::Bool(result))
7265}
7266
7267fn and_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7269 match (l, r) {
7270 (Value::Bool(false), _) | (_, Value::Bool(false)) => Ok(Value::Bool(false)),
7271 (Value::Bool(true), Value::Bool(true)) => Ok(Value::Bool(true)),
7272 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7273 (a, b) => Err(EvalError::TypeMismatch {
7274 detail: format!(
7275 "AND on non-boolean: {:?} and {:?}",
7276 a.data_type(),
7277 b.data_type()
7278 ),
7279 }),
7280 }
7281}
7282
7283fn or_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7284 match (l, r) {
7285 (Value::Bool(true), _) | (_, Value::Bool(true)) => Ok(Value::Bool(true)),
7286 (Value::Bool(false), Value::Bool(false)) => Ok(Value::Bool(false)),
7287 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7288 (a, b) => Err(EvalError::TypeMismatch {
7289 detail: format!(
7290 "OR on non-boolean: {:?} and {:?}",
7291 a.data_type(),
7292 b.data_type()
7293 ),
7294 }),
7295 }
7296}
7297
7298#[cfg(test)]
7299mod tests {
7300 use super::*;
7301 use alloc::vec;
7302 use spg_storage::{ColumnSchema, Row};
7303
7304 fn col(name: &str, ty: DataType) -> ColumnSchema {
7305 ColumnSchema::new(name, ty, true)
7306 }
7307
7308 fn ctx<'a>(cols: &'a [ColumnSchema], alias: Option<&'a str>) -> EvalContext<'a> {
7309 EvalContext::new(cols, alias)
7310 }
7311
7312 fn lit(n: i64) -> Expr {
7313 Expr::Literal(Literal::Integer(n))
7314 }
7315
7316 fn null() -> Expr {
7317 Expr::Literal(Literal::Null)
7318 }
7319
7320 fn col_ref(name: &str) -> Expr {
7321 Expr::Column(ColumnName {
7322 qualifier: None,
7323 name: name.into(),
7324 })
7325 }
7326
7327 #[test]
7328 fn literal_evaluates_to_value() {
7329 let r = Row::new(vec![]);
7330 let cs: [ColumnSchema; 0] = [];
7331 let c = ctx(&cs, None);
7332 assert_eq!(eval_expr(&lit(42), &r, &c).unwrap(), Value::Int(42));
7333 assert_eq!(
7334 eval_expr(&Expr::Literal(Literal::Float(1.5)), &r, &c).unwrap(),
7335 Value::Float(1.5)
7336 );
7337 assert_eq!(eval_expr(&null(), &r, &c).unwrap(), Value::Null);
7338 }
7339
7340 #[test]
7341 fn column_lookup_unqualified() {
7342 let cs = vec![col("a", DataType::Int), col("b", DataType::Text)];
7343 let r = Row::new(vec![Value::Int(7), Value::Text("hi".into())]);
7344 let c = ctx(&cs, None);
7345 assert_eq!(eval_expr(&col_ref("a"), &r, &c).unwrap(), Value::Int(7));
7346 assert_eq!(
7347 eval_expr(&col_ref("b"), &r, &c).unwrap(),
7348 Value::Text("hi".into())
7349 );
7350 }
7351
7352 #[test]
7353 fn column_not_found_errors() {
7354 let cs = vec![col("a", DataType::Int)];
7355 let r = Row::new(vec![Value::Int(0)]);
7356 let c = ctx(&cs, None);
7357 let err = eval_expr(&col_ref("ghost"), &r, &c).unwrap_err();
7358 assert!(matches!(err, EvalError::ColumnNotFound { ref name } if name == "ghost"));
7359 }
7360
7361 #[test]
7362 fn qualified_column_matches_alias() {
7363 let cs = vec![col("a", DataType::Int)];
7364 let r = Row::new(vec![Value::Int(5)]);
7365 let c = ctx(&cs, Some("u"));
7366 let qualified = Expr::Column(ColumnName {
7367 qualifier: Some("u".into()),
7368 name: "a".into(),
7369 });
7370 assert_eq!(eval_expr(&qualified, &r, &c).unwrap(), Value::Int(5));
7371 }
7372
7373 #[test]
7374 fn qualified_column_unknown_alias_errors() {
7375 let cs = vec![col("a", DataType::Int)];
7376 let r = Row::new(vec![Value::Int(5)]);
7377 let c = ctx(&cs, Some("u"));
7378 let wrong = Expr::Column(ColumnName {
7379 qualifier: Some("x".into()),
7380 name: "a".into(),
7381 });
7382 assert!(matches!(
7383 eval_expr(&wrong, &r, &c).unwrap_err(),
7384 EvalError::UnknownQualifier { .. }
7385 ));
7386 }
7387
7388 #[test]
7389 fn arithmetic_with_widening() {
7390 let r = Row::new(vec![]);
7391 let cs: [ColumnSchema; 0] = [];
7392 let c = ctx(&cs, None);
7393 let e = Expr::Binary {
7394 lhs: alloc::boxed::Box::new(lit(2)),
7395 op: BinOp::Add,
7396 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::Float(0.5))),
7397 };
7398 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Float(2.5));
7399 }
7400
7401 #[test]
7402 fn division_by_zero_errors() {
7403 let r = Row::new(vec![]);
7404 let cs: [ColumnSchema; 0] = [];
7405 let c = ctx(&cs, None);
7406 let e = Expr::Binary {
7407 lhs: alloc::boxed::Box::new(lit(1)),
7408 op: BinOp::Div,
7409 rhs: alloc::boxed::Box::new(lit(0)),
7410 };
7411 assert_eq!(
7412 eval_expr(&e, &r, &c).unwrap_err(),
7413 EvalError::DivisionByZero
7414 );
7415 }
7416
7417 #[test]
7418 fn comparison_returns_bool() {
7419 let r = Row::new(vec![]);
7420 let cs: [ColumnSchema; 0] = [];
7421 let c = ctx(&cs, None);
7422 let e = Expr::Binary {
7423 lhs: alloc::boxed::Box::new(lit(1)),
7424 op: BinOp::Lt,
7425 rhs: alloc::boxed::Box::new(lit(2)),
7426 };
7427 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7428 }
7429
7430 #[test]
7431 fn null_propagates_through_arithmetic() {
7432 let r = Row::new(vec![]);
7433 let cs: [ColumnSchema; 0] = [];
7434 let c = ctx(&cs, None);
7435 let e = Expr::Binary {
7436 lhs: alloc::boxed::Box::new(lit(1)),
7437 op: BinOp::Add,
7438 rhs: alloc::boxed::Box::new(null()),
7439 };
7440 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7441 }
7442
7443 #[test]
7444 fn and_three_valued_logic() {
7445 let r = Row::new(vec![]);
7446 let cs: [ColumnSchema; 0] = [];
7447 let c = ctx(&cs, None);
7448 let tt = |a: bool, b_null: bool| Expr::Binary {
7449 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7450 op: BinOp::And,
7451 rhs: alloc::boxed::Box::new(if b_null {
7452 null()
7453 } else {
7454 Expr::Literal(Literal::Bool(true))
7455 }),
7456 };
7457 assert_eq!(
7459 eval_expr(&tt(false, true), &r, &c).unwrap(),
7460 Value::Bool(false)
7461 );
7462 assert_eq!(eval_expr(&tt(true, true), &r, &c).unwrap(), Value::Null);
7464 assert_eq!(
7466 eval_expr(&tt(true, false), &r, &c).unwrap(),
7467 Value::Bool(true)
7468 );
7469 }
7470
7471 #[test]
7472 fn or_three_valued_logic() {
7473 let r = Row::new(vec![]);
7474 let cs: [ColumnSchema; 0] = [];
7475 let c = ctx(&cs, None);
7476 let or_with_null = |a: bool| Expr::Binary {
7477 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7478 op: BinOp::Or,
7479 rhs: alloc::boxed::Box::new(null()),
7480 };
7481 assert_eq!(
7483 eval_expr(&or_with_null(true), &r, &c).unwrap(),
7484 Value::Bool(true)
7485 );
7486 assert_eq!(
7488 eval_expr(&or_with_null(false), &r, &c).unwrap(),
7489 Value::Null
7490 );
7491 }
7492
7493 #[test]
7494 fn not_on_null_is_null() {
7495 let r = Row::new(vec![]);
7496 let cs: [ColumnSchema; 0] = [];
7497 let c = ctx(&cs, None);
7498 let e = Expr::Unary {
7499 op: UnOp::Not,
7500 expr: alloc::boxed::Box::new(null()),
7501 };
7502 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7503 }
7504
7505 #[test]
7506 fn text_comparison_lexicographic() {
7507 let r = Row::new(vec![]);
7508 let cs: [ColumnSchema; 0] = [];
7509 let c = ctx(&cs, None);
7510 let e = Expr::Binary {
7511 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("apple".into()))),
7512 op: BinOp::Lt,
7513 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("banana".into()))),
7514 };
7515 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7516 }
7517
7518 #[test]
7519 fn interval_format_basics() {
7520 assert_eq!(format_interval(0, 0), "0");
7521 assert_eq!(format_interval(0, 86_400_000_000), "1 day");
7522 assert_eq!(format_interval(0, -86_400_000_000), "-1 days");
7523 assert_eq!(format_interval(0, 3_600_000_000), "01:00:00");
7524 assert_eq!(
7525 format_interval(0, 86_400_000_000 + 9_000_000),
7526 "1 day 00:00:09"
7527 );
7528 assert_eq!(format_interval(14, 0), "1 year 2 mons");
7529 assert_eq!(format_interval(-1, 0), "-1 mons");
7530 }
7531
7532 #[test]
7533 fn interval_add_to_timestamp_micros_part() {
7534 let ts = i64::from(days_from_civil(2024, 1, 1)) * 86_400_000_000;
7536 let r = add_interval_to_micros(ts, 0, 3_600_000_000).unwrap();
7537 let expected = ts + 3_600_000_000;
7538 assert_eq!(r, expected);
7539 }
7540
7541 #[test]
7542 fn interval_clamp_month_end() {
7543 let d = days_from_civil(2024, 1, 31);
7545 let shifted = shift_date_by_months(d, 1).unwrap();
7546 let (y, m, day) = civil_from_days(shifted);
7547 assert_eq!((y, m, day), (2024, 2, 29));
7548 let d = days_from_civil(2023, 1, 31);
7550 let shifted = shift_date_by_months(d, 1).unwrap();
7551 let (y, m, day) = civil_from_days(shifted);
7552 assert_eq!((y, m, day), (2023, 2, 28));
7553 let d = days_from_civil(2024, 3, 31);
7555 let shifted = shift_date_by_months(d, -1).unwrap();
7556 let (y, m, day) = civil_from_days(shifted);
7557 assert_eq!((y, m, day), (2024, 2, 29));
7558 }
7559
7560 #[test]
7561 fn interval_date_plus_pure_days_stays_date() {
7562 let d = days_from_civil(2024, 6, 1);
7564 let lhs = Value::Date(d);
7565 let rhs = Value::Interval {
7566 months: 0,
7567 micros: 7 * 86_400_000_000,
7568 };
7569 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7570 .unwrap()
7571 .unwrap();
7572 let expected = days_from_civil(2024, 6, 8);
7573 assert_eq!(v, Value::Date(expected));
7574 }
7575
7576 #[test]
7577 fn interval_date_plus_sub_day_lifts_to_timestamp() {
7578 let d = days_from_civil(2024, 6, 1);
7580 let lhs = Value::Date(d);
7581 let rhs = Value::Interval {
7582 months: 0,
7583 micros: 3_600_000_000,
7584 };
7585 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7586 .unwrap()
7587 .unwrap();
7588 let expected = i64::from(d) * 86_400_000_000 + 3_600_000_000;
7589 assert_eq!(v, Value::Timestamp(expected));
7590 }
7591}