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 F::Epoch => i64::from(months) * 30 * 86_400 + secs_total,
504 };
505 return Ok(Value::BigInt(result));
506 }
507 let (days, day_micros) = match *v {
508 Value::Date(d) => (d, 0_i64),
509 Value::Timestamp(t) => {
510 let days = t.div_euclid(86_400_000_000);
511 let day_micros = t.rem_euclid(86_400_000_000);
512 (i32::try_from(days).unwrap_or(i32::MAX), day_micros)
513 }
514 _ => {
515 return Err(EvalError::TypeMismatch {
516 detail: format!(
517 "EXTRACT requires DATE / TIMESTAMP / INTERVAL, got {:?}",
518 v.data_type()
519 ),
520 });
521 }
522 };
523 let (y, m, d) = civil_components(days);
524 let secs = day_micros / 1_000_000;
525 let hh = secs / 3600;
526 let mm = (secs / 60) % 60;
527 let ss = secs % 60;
528 let frac = day_micros % 1_000_000;
529 let result = match field {
530 F::Year => i64::from(y),
531 F::Month => i64::from(m),
532 F::Day => i64::from(d),
533 F::Hour => hh,
534 F::Minute => mm,
535 F::Second => ss,
536 F::Microsecond => ss * 1_000_000 + frac,
537 F::Epoch => i64::from(days) * 86_400 + secs,
540 };
541 Ok(Value::BigInt(result))
542}
543
544fn civil_components(days: i32) -> (i32, u32, u32) {
547 civil_from_days(days)
548}
549
550fn like_match(text: &str, pattern: &str) -> bool {
555 let text: Vec<char> = text.chars().collect();
556 let pat: Vec<char> = pattern.chars().collect();
557 like_match_inner(&text, 0, &pat, 0)
558}
559
560fn like_match_inner(text: &[char], mut ti: usize, pat: &[char], mut pi: usize) -> bool {
561 while pi < pat.len() {
562 match pat[pi] {
563 '%' => {
564 while pi < pat.len() && pat[pi] == '%' {
566 pi += 1;
567 }
568 if pi == pat.len() {
569 return true;
570 }
571 for k in ti..=text.len() {
572 if like_match_inner(text, k, pat, pi) {
573 return true;
574 }
575 }
576 return false;
577 }
578 '_' => {
579 if ti >= text.len() {
580 return false;
581 }
582 ti += 1;
583 pi += 1;
584 }
585 '\\' if pi + 1 < pat.len() => {
586 let want = pat[pi + 1];
587 if ti >= text.len() || text[ti] != want {
588 return false;
589 }
590 ti += 1;
591 pi += 2;
592 }
593 c => {
594 if ti >= text.len() || text[ti] != c {
595 return false;
596 }
597 ti += 1;
598 pi += 1;
599 }
600 }
601 }
602 ti == text.len()
603}
604
605fn apply_function(name: &str, args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
608 match name.to_ascii_lowercase().as_str() {
609 "nextval" => {
611 if args.len() != 1 {
612 return Err(EvalError::TypeMismatch {
613 detail: format!("nextval() takes 1 arg, got {}", args.len()),
614 });
615 }
616 let seq_name = match &args[0] {
617 Value::Text(s) => s.clone(),
618 Value::Null => return Ok(Value::Null),
619 other => {
620 return Err(EvalError::TypeMismatch {
621 detail: format!(
622 "nextval() argument must be TEXT, got {:?}",
623 other.data_type()
624 ),
625 });
626 }
627 };
628 let resolver = ctx
629 .sequence_resolver
630 .ok_or_else(|| EvalError::TypeMismatch {
631 detail: "nextval() requires a sequence resolver (read-only context)".into(),
632 })?;
633 let v = resolver(SequenceOp::Next(seq_name))?;
634 Ok(Value::BigInt(v))
635 }
636 "currval" => {
637 if args.len() != 1 {
638 return Err(EvalError::TypeMismatch {
639 detail: format!("currval() takes 1 arg, got {}", args.len()),
640 });
641 }
642 let seq_name = match &args[0] {
643 Value::Text(s) => s.clone(),
644 Value::Null => return Ok(Value::Null),
645 other => {
646 return Err(EvalError::TypeMismatch {
647 detail: format!(
648 "currval() argument must be TEXT, got {:?}",
649 other.data_type()
650 ),
651 });
652 }
653 };
654 let resolver = ctx
655 .sequence_resolver
656 .ok_or_else(|| EvalError::TypeMismatch {
657 detail: "currval() requires a sequence resolver (read-only context)".into(),
658 })?;
659 let v = resolver(SequenceOp::Curr(seq_name))?;
660 Ok(Value::BigInt(v))
661 }
662 "setval" => {
663 if args.len() != 2 && args.len() != 3 {
664 return Err(EvalError::TypeMismatch {
665 detail: format!("setval() takes 2 or 3 args, got {}", args.len()),
666 });
667 }
668 let seq_name = match &args[0] {
669 Value::Text(s) => s.clone(),
670 Value::Null => return Ok(Value::Null),
671 other => {
672 return Err(EvalError::TypeMismatch {
673 detail: format!(
674 "setval() name argument must be TEXT, got {:?}",
675 other.data_type()
676 ),
677 });
678 }
679 };
680 let value = match &args[1] {
681 Value::SmallInt(n) => i64::from(*n),
682 Value::Int(n) => i64::from(*n),
683 Value::BigInt(n) => *n,
684 Value::Null => return Ok(Value::Null),
685 other => {
686 return Err(EvalError::TypeMismatch {
687 detail: format!(
688 "setval() value argument must be integer, got {:?}",
689 other.data_type()
690 ),
691 });
692 }
693 };
694 let is_called = if args.len() == 3 {
695 match &args[2] {
696 Value::Bool(b) => *b,
697 Value::Null => return Ok(Value::Null),
698 other => {
699 return Err(EvalError::TypeMismatch {
700 detail: format!(
701 "setval() is_called argument must be BOOL, got {:?}",
702 other.data_type()
703 ),
704 });
705 }
706 }
707 } else {
708 true
709 };
710 let resolver = ctx
711 .sequence_resolver
712 .ok_or_else(|| EvalError::TypeMismatch {
713 detail: "setval() requires a sequence resolver (read-only context)".into(),
714 })?;
715 let v = resolver(SequenceOp::Set {
716 name: seq_name,
717 value,
718 is_called,
719 })?;
720 Ok(Value::BigInt(v))
721 }
722 "length" => {
723 if args.len() != 1 {
724 return Err(EvalError::TypeMismatch {
725 detail: format!("length() takes 1 arg, got {}", args.len()),
726 });
727 }
728 match &args[0] {
729 Value::Null => Ok(Value::Null),
730 Value::Text(s) => {
731 let n = i32::try_from(s.chars().count()).unwrap_or(i32::MAX);
732 Ok(Value::Int(n))
733 }
734 Value::Bytes(b) => {
739 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
740 Ok(Value::Int(n))
741 }
742 other => Err(EvalError::TypeMismatch {
743 detail: format!("length() needs text or bytea, got {:?}", other.data_type()),
744 }),
745 }
746 }
747 "octet_length" => {
751 if args.len() != 1 {
752 return Err(EvalError::TypeMismatch {
753 detail: format!("octet_length() takes 1 arg, got {}", args.len()),
754 });
755 }
756 match &args[0] {
757 Value::Null => Ok(Value::Null),
758 Value::Text(s) => {
759 let n = i32::try_from(s.len()).unwrap_or(i32::MAX);
760 Ok(Value::Int(n))
761 }
762 Value::Bytes(b) => {
763 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
764 Ok(Value::Int(n))
765 }
766 other => Err(EvalError::TypeMismatch {
767 detail: format!(
768 "octet_length() needs text or bytea, got {:?}",
769 other.data_type()
770 ),
771 }),
772 }
773 }
774 "array_length" => {
781 if args.len() != 2 {
782 return Err(EvalError::TypeMismatch {
783 detail: format!("array_length() takes 2 args, got {}", args.len()),
784 });
785 }
786 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
787 return Ok(Value::Null);
788 }
789 let len = match &args[0] {
790 Value::TextArray(items) => items.len(),
791 Value::IntArray(items) => items.len(),
792 Value::BigIntArray(items) => items.len(),
793 _ => {
794 return Err(EvalError::TypeMismatch {
795 detail: format!(
796 "array_length() first arg must be an array, got {:?}",
797 args[0].data_type()
798 ),
799 });
800 }
801 };
802 let dim: i64 = match args[1] {
803 Value::Int(n) => i64::from(n),
804 Value::BigInt(n) => n,
805 Value::SmallInt(n) => i64::from(n),
806 _ => {
807 return Err(EvalError::TypeMismatch {
808 detail: format!(
809 "array_length() second arg must be integer, got {:?}",
810 args[1].data_type()
811 ),
812 });
813 }
814 };
815 if dim != 1 {
816 return Ok(Value::Null);
817 }
818 let n = i32::try_from(len).unwrap_or(i32::MAX);
819 Ok(Value::Int(n))
820 }
821 "array_position" => {
826 if args.len() != 2 {
827 return Err(EvalError::TypeMismatch {
828 detail: format!("array_position() takes 2 args, got {}", args.len()),
829 });
830 }
831 if matches!(args[0], Value::Null) {
832 return Ok(Value::Null);
833 }
834 if matches!(args[1], Value::Null) {
835 return Ok(Value::Null);
836 }
837 match (&args[0], &args[1]) {
838 (Value::TextArray(items), Value::Text(needle)) => {
839 for (idx, item) in items.iter().enumerate() {
840 if let Some(s) = item
841 && s == needle
842 {
843 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
844 }
845 }
846 Ok(Value::Null)
847 }
848 (Value::IntArray(items), needle_v)
849 if matches!(
850 needle_v,
851 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
852 ) =>
853 {
854 let needle: i64 = match *needle_v {
855 Value::Int(n) => i64::from(n),
856 Value::SmallInt(n) => i64::from(n),
857 Value::BigInt(n) => n,
858 _ => unreachable!(),
859 };
860 for (idx, item) in items.iter().enumerate() {
861 if let Some(n) = item
862 && i64::from(*n) == needle
863 {
864 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
865 }
866 }
867 Ok(Value::Null)
868 }
869 (Value::BigIntArray(items), needle_v)
870 if matches!(
871 needle_v,
872 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
873 ) =>
874 {
875 let needle: i64 = match *needle_v {
876 Value::Int(n) => i64::from(n),
877 Value::SmallInt(n) => i64::from(n),
878 Value::BigInt(n) => n,
879 _ => unreachable!(),
880 };
881 for (idx, item) in items.iter().enumerate() {
882 if let Some(n) = item
883 && *n == needle
884 {
885 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
886 }
887 }
888 Ok(Value::Null)
889 }
890 (
891 arr @ (Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_)),
892 other,
893 ) => Err(EvalError::TypeMismatch {
894 detail: format!(
895 "array_position() needle type {:?} doesn't match array {:?}",
896 other.data_type(),
897 arr.data_type()
898 ),
899 }),
900 (other, _) => Err(EvalError::TypeMismatch {
901 detail: format!(
902 "array_position() first arg must be an array, got {:?}",
903 other.data_type()
904 ),
905 }),
906 }
907 }
908 "substring" | "substr" => {
916 if !matches!(args.len(), 2 | 3) {
917 return Err(EvalError::TypeMismatch {
918 detail: format!("substring() takes 2 or 3 args, got {}", args.len()),
919 });
920 }
921 if args.iter().any(|a| matches!(a, Value::Null)) {
922 return Ok(Value::Null);
923 }
924 let start: i64 = match args[1] {
925 Value::Int(n) => i64::from(n),
926 Value::BigInt(n) => n,
927 Value::SmallInt(n) => i64::from(n),
928 _ => {
929 return Err(EvalError::TypeMismatch {
930 detail: format!(
931 "substring() start must be integer, got {:?}",
932 args[1].data_type()
933 ),
934 });
935 }
936 };
937 let length: Option<i64> = if args.len() == 3 {
938 match args[2] {
939 Value::Int(n) => Some(i64::from(n)),
940 Value::BigInt(n) => Some(n),
941 Value::SmallInt(n) => Some(i64::from(n)),
942 _ => {
943 return Err(EvalError::TypeMismatch {
944 detail: format!(
945 "substring() length must be integer, got {:?}",
946 args[2].data_type()
947 ),
948 });
949 }
950 }
951 } else {
952 None
953 };
954 let (effective_start, effective_length): (i64, Option<i64>) = match length {
957 Some(len) => {
958 let end = start.saturating_add(len);
959 if end <= 1 || len < 0 {
960 return Ok(match &args[0] {
961 Value::Text(_) => Value::Text(String::new()),
962 Value::Bytes(_) => Value::Bytes(Vec::new()),
963 other => {
964 return Err(EvalError::TypeMismatch {
965 detail: format!(
966 "substring() needs text or bytea, got {:?}",
967 other.data_type()
968 ),
969 });
970 }
971 });
972 }
973 let eff_start = start.max(1);
974 let eff_len = end - eff_start;
975 (eff_start, Some(eff_len.max(0)))
976 }
977 None => (start.max(1), None),
978 };
979 match &args[0] {
980 Value::Text(s) => {
981 let chars: Vec<char> = s.chars().collect();
983 let skip = (effective_start - 1) as usize;
984 if skip >= chars.len() {
985 return Ok(Value::Text(String::new()));
986 }
987 let take = match effective_length {
988 Some(n) => (n as usize).min(chars.len() - skip),
989 None => chars.len() - skip,
990 };
991 Ok(Value::Text(chars[skip..skip + take].iter().collect()))
992 }
993 Value::Bytes(b) => {
994 let skip = (effective_start - 1) as usize;
995 if skip >= b.len() {
996 return Ok(Value::Bytes(Vec::new()));
997 }
998 let take = match effective_length {
999 Some(n) => (n as usize).min(b.len() - skip),
1000 None => b.len() - skip,
1001 };
1002 Ok(Value::Bytes(b[skip..skip + take].to_vec()))
1003 }
1004 other => Err(EvalError::TypeMismatch {
1005 detail: format!(
1006 "substring() needs text or bytea, got {:?}",
1007 other.data_type()
1008 ),
1009 }),
1010 }
1011 }
1012 "position" => {
1020 if args.len() != 2 {
1021 return Err(EvalError::TypeMismatch {
1022 detail: format!("position() takes 2 args, got {}", args.len()),
1023 });
1024 }
1025 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
1026 return Ok(Value::Null);
1027 }
1028 match (&args[0], &args[1]) {
1029 (Value::Text(needle), Value::Text(haystack)) => {
1030 if needle.is_empty() {
1031 return Ok(Value::Int(1));
1032 }
1033 let h_chars: Vec<char> = haystack.chars().collect();
1035 let n_chars: Vec<char> = needle.chars().collect();
1036 if n_chars.len() > h_chars.len() {
1037 return Ok(Value::Int(0));
1038 }
1039 for i in 0..=h_chars.len() - n_chars.len() {
1040 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1041 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1042 }
1043 }
1044 Ok(Value::Int(0))
1045 }
1046 (Value::Bytes(needle), Value::Bytes(haystack)) => {
1047 if needle.is_empty() {
1048 return Ok(Value::Int(1));
1049 }
1050 if needle.len() > haystack.len() {
1051 return Ok(Value::Int(0));
1052 }
1053 for i in 0..=haystack.len() - needle.len() {
1054 if &haystack[i..i + needle.len()] == needle.as_slice() {
1055 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1056 }
1057 }
1058 Ok(Value::Int(0))
1059 }
1060 (a, b) => Err(EvalError::TypeMismatch {
1061 detail: format!(
1062 "position() operands must both be text or both bytea, got {:?} and {:?}",
1063 a.data_type(),
1064 b.data_type()
1065 ),
1066 }),
1067 }
1068 }
1069 "upper" => {
1070 if args.len() != 1 {
1071 return Err(EvalError::TypeMismatch {
1072 detail: format!("upper() takes 1 arg, got {}", args.len()),
1073 });
1074 }
1075 match &args[0] {
1076 Value::Null => Ok(Value::Null),
1077 Value::Text(s) => Ok(Value::Text(s.to_uppercase())),
1078 other => Err(EvalError::TypeMismatch {
1079 detail: format!("upper() needs text, got {:?}", other.data_type()),
1080 }),
1081 }
1082 }
1083 "lower" => {
1084 if args.len() != 1 {
1085 return Err(EvalError::TypeMismatch {
1086 detail: format!("lower() takes 1 arg, got {}", args.len()),
1087 });
1088 }
1089 match &args[0] {
1090 Value::Null => Ok(Value::Null),
1091 Value::Text(s) => Ok(Value::Text(s.to_lowercase())),
1092 other => Err(EvalError::TypeMismatch {
1093 detail: format!("lower() needs text, got {:?}", other.data_type()),
1094 }),
1095 }
1096 }
1097 "abs" => {
1098 if args.len() != 1 {
1099 return Err(EvalError::TypeMismatch {
1100 detail: format!("abs() takes 1 arg, got {}", args.len()),
1101 });
1102 }
1103 match &args[0] {
1104 Value::Null => Ok(Value::Null),
1105 Value::Int(n) => Ok(Value::Int(n.wrapping_abs())),
1106 Value::BigInt(n) => Ok(Value::BigInt(n.wrapping_abs())),
1107 Value::Float(x) => Ok(Value::Float(x.abs())),
1108 other => Err(EvalError::TypeMismatch {
1109 detail: format!("abs() needs numeric, got {:?}", other.data_type()),
1110 }),
1111 }
1112 }
1113 "coalesce" => {
1114 for a in args {
1115 if !matches!(a, Value::Null) {
1116 return Ok(a.clone());
1117 }
1118 }
1119 Ok(Value::Null)
1120 }
1121 "date_trunc" => date_trunc(args),
1122 "date_part" => date_part(args),
1123 "age" => age(args),
1124 "to_char" => to_char(args),
1125 "date_format" => date_format_mysql(args),
1131 "unix_timestamp" => unix_timestamp_of(args),
1132 "from_unixtime" => from_unixtime(args),
1133 "format" => format_string(args),
1141 "concat" => {
1162 let mut out = String::new();
1163 for v in args {
1164 if matches!(v, Value::Null) {
1165 continue;
1166 }
1167 out.push_str(&value_to_format_text(v));
1168 }
1169 Ok(Value::Text(out))
1170 }
1171 "random" => {
1294 if !args.is_empty() {
1295 return Err(EvalError::TypeMismatch {
1296 detail: alloc::format!("random() takes 0 args, got {}", args.len()),
1297 });
1298 }
1299 Ok(Value::Float(prng_next_f64()))
1300 }
1301 "gen_random_uuid" | "uuid_generate_v4" => {
1308 if !args.is_empty() {
1309 return Err(EvalError::TypeMismatch {
1310 detail: alloc::format!("{name}() takes 0 args, got {}", args.len()),
1311 });
1312 }
1313 Ok(Value::Uuid(gen_random_uuid_bytes()))
1314 }
1315 "sign" => {
1316 if args.len() != 1 {
1317 return Err(EvalError::TypeMismatch {
1318 detail: alloc::format!("sign() takes 1 arg, got {}", args.len()),
1319 });
1320 }
1321 match &args[0] {
1322 Value::Null => Ok(Value::Null),
1323 Value::SmallInt(n) => Ok(Value::SmallInt(n.signum())),
1324 Value::Int(n) => Ok(Value::Int(n.signum())),
1325 Value::BigInt(n) => Ok(Value::BigInt(n.signum())),
1326 Value::Float(x) => {
1327 let s = if *x > 0.0 {
1328 1.0
1329 } else if *x < 0.0 {
1330 -1.0
1331 } else {
1332 0.0
1333 };
1334 Ok(Value::Float(s))
1335 }
1336 Value::Numeric { scaled, scale } => {
1337 let s = scaled.signum();
1338 Ok(Value::Numeric {
1339 scaled: s * pow10_i128(*scale),
1340 scale: *scale,
1341 })
1342 }
1343 other => Err(EvalError::TypeMismatch {
1344 detail: alloc::format!("sign() needs numeric, got {:?}", other.data_type()),
1345 }),
1346 }
1347 }
1348 "sqrt" => {
1349 if args.len() != 1 {
1350 return Err(EvalError::TypeMismatch {
1351 detail: alloc::format!("sqrt() takes 1 arg, got {}", args.len()),
1352 });
1353 }
1354 match &args[0] {
1355 Value::Null => Ok(Value::Null),
1356 v => {
1357 let x = value_to_f64(v).ok_or_else(|| EvalError::TypeMismatch {
1358 detail: alloc::format!("sqrt() needs numeric, got {:?}", v.data_type()),
1359 })?;
1360 if x < 0.0 {
1361 return Err(EvalError::TypeMismatch {
1362 detail: "sqrt(): negative input outside real domain".into(),
1363 });
1364 }
1365 if x == 0.0 {
1366 return Ok(Value::Float(0.0));
1367 }
1368 Ok(Value::Float(f64_sqrt(x)))
1369 }
1370 }
1371 }
1372 "power" | "pow" => {
1373 if args.len() != 2 {
1374 return Err(EvalError::TypeMismatch {
1375 detail: alloc::format!("power() takes 2 args, got {}", args.len()),
1376 });
1377 }
1378 if args.iter().any(|v| matches!(v, Value::Null)) {
1379 return Ok(Value::Null);
1380 }
1381 let x = value_to_f64(&args[0]).ok_or_else(|| EvalError::TypeMismatch {
1382 detail: "power() needs numeric x".into(),
1383 })?;
1384 let y = value_to_f64(&args[1]).ok_or_else(|| EvalError::TypeMismatch {
1385 detail: "power() needs numeric y".into(),
1386 })?;
1387 let y_int = y as i32;
1389 if (y_int as f64) == y && y.abs() < 1024.0 {
1390 let result = f64_powi(x, y_int);
1391 return Ok(Value::Float(result));
1392 }
1393 if x < 0.0 {
1397 return Err(EvalError::TypeMismatch {
1398 detail: "power(): negative base with fractional exponent yields complex result"
1399 .into(),
1400 });
1401 }
1402 if x == 0.0 && y < 0.0 {
1403 return Err(EvalError::TypeMismatch {
1404 detail: "power(): 0 raised to negative power is undefined".into(),
1405 });
1406 }
1407 if x == 0.0 {
1408 return Ok(Value::Float(0.0));
1409 }
1410 Ok(Value::Float(f64_exp(y * f64_ln(x))))
1411 }
1412 "mod" => {
1413 if args.len() != 2 {
1414 return Err(EvalError::TypeMismatch {
1415 detail: alloc::format!("mod() takes 2 args, got {}", args.len()),
1416 });
1417 }
1418 if args.iter().any(|v| matches!(v, Value::Null)) {
1419 return Ok(Value::Null);
1420 }
1421 let to_i64 = |v: &Value| -> Result<i64, EvalError> {
1422 match v {
1423 Value::SmallInt(x) => Ok(i64::from(*x)),
1424 Value::Int(x) => Ok(i64::from(*x)),
1425 Value::BigInt(x) => Ok(*x),
1426 other => Err(EvalError::TypeMismatch {
1427 detail: alloc::format!("mod() needs integer, got {:?}", other.data_type()),
1428 }),
1429 }
1430 };
1431 let y = to_i64(&args[0])?;
1432 let x = to_i64(&args[1])?;
1433 if x == 0 {
1434 return Err(EvalError::TypeMismatch {
1435 detail: "mod(): division by zero".into(),
1436 });
1437 }
1438 let result = y % x;
1441 if let Ok(small) = i16::try_from(result) {
1443 if matches!(args[0], Value::SmallInt(_)) && matches!(args[1], Value::SmallInt(_)) {
1444 return Ok(Value::SmallInt(small));
1445 }
1446 }
1447 if let Ok(int_) = i32::try_from(result) {
1448 if !matches!(args[0], Value::BigInt(_)) && !matches!(args[1], Value::BigInt(_)) {
1449 return Ok(Value::Int(int_));
1450 }
1451 }
1452 Ok(Value::BigInt(result))
1453 }
1454 "greatest" | "least" => {
1455 if args.is_empty() {
1456 return Err(EvalError::TypeMismatch {
1457 detail: alloc::format!(
1458 "{lc}() takes at least 1 arg",
1459 lc = if name.eq_ignore_ascii_case("greatest") {
1460 "greatest"
1461 } else {
1462 "least"
1463 }
1464 ),
1465 });
1466 }
1467 let non_null: alloc::vec::Vec<&Value> =
1468 args.iter().filter(|v| !matches!(v, Value::Null)).collect();
1469 if non_null.is_empty() {
1470 return Ok(Value::Null);
1471 }
1472 let is_greatest = name.eq_ignore_ascii_case("greatest");
1473 let mut best = non_null[0].clone();
1474 for v in &non_null[1..] {
1475 let ord = value_cmp_for_min_max(&best, v);
1476 let take = if is_greatest {
1477 ord == core::cmp::Ordering::Less
1478 } else {
1479 ord == core::cmp::Ordering::Greater
1480 };
1481 if take {
1482 best = (*v).clone();
1483 }
1484 }
1485 Ok(best)
1486 }
1487 "ifnull" => {
1491 if args.len() != 2 {
1492 return Err(EvalError::TypeMismatch {
1493 detail: alloc::format!("ifnull() takes 2 args, got {}", args.len()),
1494 });
1495 }
1496 for v in args {
1497 if !matches!(v, Value::Null) {
1498 return Ok(v.clone());
1499 }
1500 }
1501 Ok(Value::Null)
1502 }
1503 "if" => {
1507 if args.len() != 3 {
1508 return Err(EvalError::TypeMismatch {
1509 detail: alloc::format!(
1510 "if() takes 3 args (cond, then, else), got {}",
1511 args.len()
1512 ),
1513 });
1514 }
1515 let truthy = match &args[0] {
1516 Value::Null => false,
1517 Value::Bool(b) => *b,
1518 Value::SmallInt(n) => *n != 0,
1519 Value::Int(n) => *n != 0,
1520 Value::BigInt(n) => *n != 0,
1521 Value::Float(x) => *x != 0.0,
1522 Value::Text(s) => !s.is_empty() && s != "0",
1523 _ => true,
1524 };
1525 if truthy {
1526 Ok(args[1].clone())
1527 } else {
1528 Ok(args[2].clone())
1529 }
1530 }
1531 "nullif" => {
1532 if args.len() != 2 {
1533 return Err(EvalError::TypeMismatch {
1534 detail: alloc::format!("nullif() takes 2 args, got {}", args.len()),
1535 });
1536 }
1537 match (&args[0], &args[1]) {
1538 (Value::Null, _) => Ok(Value::Null),
1539 (a, Value::Null) => Ok(a.clone()),
1540 (a, b) => {
1541 if values_equal_for_nullif(a, b) {
1545 Ok(Value::Null)
1546 } else {
1547 Ok(a.clone())
1548 }
1549 }
1550 }
1551 }
1552 "trunc" => {
1553 match args.len() {
1554 1 => match &args[0] {
1555 Value::Null => Ok(Value::Null),
1556 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1557 Value::Float(x) => Ok(Value::Float(f64_trunc(*x))),
1558 Value::Numeric { scaled, scale } => {
1559 let factor = pow10_i128(*scale);
1560 let q = scaled / factor;
1562 Ok(Value::Numeric {
1563 scaled: q * factor,
1564 scale: *scale,
1565 })
1566 }
1567 other => Err(EvalError::TypeMismatch {
1568 detail: alloc::format!(
1569 "trunc() needs numeric, got {:?}",
1570 other.data_type()
1571 ),
1572 }),
1573 },
1574 2 => {
1575 if args.iter().any(|v| matches!(v, Value::Null)) {
1576 return Ok(Value::Null);
1577 }
1578 let n = match &args[1] {
1579 Value::SmallInt(x) => i32::from(*x),
1580 Value::Int(x) => *x,
1581 Value::BigInt(x) => {
1582 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1583 detail: "trunc(): scale must fit in i32".into(),
1584 })?
1585 }
1586 other => {
1587 return Err(EvalError::TypeMismatch {
1588 detail: alloc::format!(
1589 "trunc(): scale must be integer, got {:?}",
1590 other.data_type()
1591 ),
1592 });
1593 }
1594 };
1595 let x = match &args[0] {
1596 Value::SmallInt(v) => f64::from(*v),
1597 Value::Int(v) => f64::from(*v),
1598 Value::BigInt(v) => *v as f64,
1599 Value::Float(v) => *v,
1600 Value::Numeric { scaled, scale } => {
1601 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1602 }
1603 other => {
1604 return Err(EvalError::TypeMismatch {
1605 detail: alloc::format!(
1606 "trunc() needs numeric x, got {:?}",
1607 other.data_type()
1608 ),
1609 });
1610 }
1611 };
1612 let result = if n >= 0 {
1613 let factor = f64_powi(10.0, n);
1614 f64_trunc(x * factor) / factor
1615 } else {
1616 let factor = f64_powi(10.0, -n);
1617 f64_trunc(x / factor) * factor
1618 };
1619 Ok(Value::Float(result))
1620 }
1621 _ => Err(EvalError::TypeMismatch {
1622 detail: alloc::format!("trunc() takes 1 or 2 args, got {}", args.len()),
1623 }),
1624 }
1625 }
1626 "round" => {
1627 match args.len() {
1628 1 => match &args[0] {
1629 Value::Null => Ok(Value::Null),
1630 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1631 Value::Float(x) => Ok(Value::Float(f64_round_half_away(*x))),
1632 Value::Numeric { scaled, scale } => {
1633 let factor = pow10_i128(*scale);
1634 let q = scaled.div_euclid(factor);
1635 let r = scaled.rem_euclid(factor);
1636 let result = if 2 * r >= factor { q + 1 } else { q };
1638 Ok(Value::Numeric {
1639 scaled: result * factor,
1640 scale: *scale,
1641 })
1642 }
1643 other => Err(EvalError::TypeMismatch {
1644 detail: alloc::format!(
1645 "round() needs numeric, got {:?}",
1646 other.data_type()
1647 ),
1648 }),
1649 },
1650 2 => {
1651 if args.iter().any(|v| matches!(v, Value::Null)) {
1652 return Ok(Value::Null);
1653 }
1654 let n = match &args[1] {
1655 Value::SmallInt(x) => i32::from(*x),
1656 Value::Int(x) => *x,
1657 Value::BigInt(x) => {
1658 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1659 detail: "round(): scale must fit in i32".into(),
1660 })?
1661 }
1662 other => {
1663 return Err(EvalError::TypeMismatch {
1664 detail: alloc::format!(
1665 "round(): scale must be integer, got {:?}",
1666 other.data_type()
1667 ),
1668 });
1669 }
1670 };
1671 let x = match &args[0] {
1677 Value::SmallInt(v) => f64::from(*v),
1678 Value::Int(v) => f64::from(*v),
1679 Value::BigInt(v) => *v as f64,
1680 Value::Float(v) => *v,
1681 Value::Numeric { scaled, scale } => {
1682 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1683 }
1684 other => {
1685 return Err(EvalError::TypeMismatch {
1686 detail: alloc::format!(
1687 "round() needs numeric x, got {:?}",
1688 other.data_type()
1689 ),
1690 });
1691 }
1692 };
1693 let result = if n >= 0 {
1698 let factor = f64_powi(10.0, n);
1699 f64_round_half_away(x * factor) / factor
1700 } else {
1701 let factor = f64_powi(10.0, -n);
1702 f64_round_half_away(x / factor) * factor
1703 };
1704 Ok(Value::Float(result))
1705 }
1706 _ => Err(EvalError::TypeMismatch {
1707 detail: alloc::format!("round() takes 1 or 2 args, got {}", args.len()),
1708 }),
1709 }
1710 }
1711 "ceil" | "ceiling" => {
1712 if args.len() != 1 {
1713 return Err(EvalError::TypeMismatch {
1714 detail: alloc::format!("ceil() takes 1 arg, got {}", args.len()),
1715 });
1716 }
1717 match &args[0] {
1718 Value::Null => Ok(Value::Null),
1719 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1720 Value::Float(x) => Ok(Value::Float(f64_ceil(*x))),
1721 Value::Numeric { scaled, scale } => {
1722 let factor = pow10_i128(*scale);
1723 let q = scaled.div_euclid(factor);
1724 let r = scaled.rem_euclid(factor);
1725 let result = if r == 0 { q } else { q + 1 };
1726 Ok(Value::Numeric {
1727 scaled: result * factor,
1728 scale: *scale,
1729 })
1730 }
1731 other => Err(EvalError::TypeMismatch {
1732 detail: alloc::format!("ceil() needs numeric, got {:?}", other.data_type()),
1733 }),
1734 }
1735 }
1736 "floor" => {
1737 if args.len() != 1 {
1738 return Err(EvalError::TypeMismatch {
1739 detail: alloc::format!("floor() takes 1 arg, got {}", args.len()),
1740 });
1741 }
1742 match &args[0] {
1743 Value::Null => Ok(Value::Null),
1744 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1745 Value::Float(x) => Ok(Value::Float(f64_floor(*x))),
1746 Value::Numeric { scaled, scale } => {
1747 let factor = pow10_i128(*scale);
1748 let q = scaled.div_euclid(factor);
1749 Ok(Value::Numeric {
1753 scaled: q * factor,
1754 scale: *scale,
1755 })
1756 }
1757 other => Err(EvalError::TypeMismatch {
1758 detail: alloc::format!("floor() needs numeric, got {:?}", other.data_type()),
1759 }),
1760 }
1761 }
1762 "left" => string_left_right(args, true, "left"),
1763 "right" => string_left_right(args, false, "right"),
1764 "strpos" => {
1765 if args.len() != 2 {
1766 return Err(EvalError::TypeMismatch {
1767 detail: alloc::format!(
1768 "strpos() takes 2 args (haystack, needle), got {}",
1769 args.len()
1770 ),
1771 });
1772 }
1773 if args.iter().any(|v| matches!(v, Value::Null)) {
1774 return Ok(Value::Null);
1775 }
1776 let haystack = value_to_format_text(&args[0]);
1777 let needle = value_to_format_text(&args[1]);
1778 if needle.is_empty() {
1779 return Ok(Value::Int(1));
1780 }
1781 let h_chars: Vec<char> = haystack.chars().collect();
1782 let n_chars: Vec<char> = needle.chars().collect();
1783 if n_chars.len() > h_chars.len() {
1784 return Ok(Value::Int(0));
1785 }
1786 for i in 0..=h_chars.len() - n_chars.len() {
1787 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1788 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1789 }
1790 }
1791 Ok(Value::Int(0))
1792 }
1793 "lpad" => string_pad(args, true, "lpad"),
1794 "rpad" => string_pad(args, false, "rpad"),
1795 "repeat" => {
1796 if args.len() != 2 {
1797 return Err(EvalError::TypeMismatch {
1798 detail: alloc::format!("repeat() takes 2 args, got {}", args.len()),
1799 });
1800 }
1801 if args.iter().any(|v| matches!(v, Value::Null)) {
1802 return Ok(Value::Null);
1803 }
1804 let s = value_to_format_text(&args[0]);
1805 let n = match &args[1] {
1806 Value::SmallInt(x) => i64::from(*x),
1807 Value::Int(x) => i64::from(*x),
1808 Value::BigInt(x) => *x,
1809 other => {
1810 return Err(EvalError::TypeMismatch {
1811 detail: alloc::format!(
1812 "repeat(): n must be integer, got {:?}",
1813 other.data_type()
1814 ),
1815 });
1816 }
1817 };
1818 if n <= 0 {
1819 return Ok(Value::Text(String::new()));
1820 }
1821 const MAX_REPEAT_BYTES: usize = 64 * 1024 * 1024;
1825 let needed =
1826 s.len()
1827 .checked_mul(n as usize)
1828 .ok_or_else(|| EvalError::TypeMismatch {
1829 detail: "repeat(): result size overflows usize".into(),
1830 })?;
1831 if needed > MAX_REPEAT_BYTES {
1832 return Err(EvalError::TypeMismatch {
1833 detail: alloc::format!(
1834 "repeat(): result would exceed {MAX_REPEAT_BYTES} bytes"
1835 ),
1836 });
1837 }
1838 Ok(Value::Text(s.repeat(n as usize)))
1839 }
1840 "split_part" => {
1841 if args.len() != 3 {
1842 return Err(EvalError::TypeMismatch {
1843 detail: alloc::format!(
1844 "split_part() takes 3 args (string, delim, n), got {}",
1845 args.len()
1846 ),
1847 });
1848 }
1849 if args.iter().any(|v| matches!(v, Value::Null)) {
1850 return Ok(Value::Null);
1851 }
1852 let s = value_to_format_text(&args[0]);
1853 let delim = value_to_format_text(&args[1]);
1854 if delim.is_empty() {
1855 return Err(EvalError::TypeMismatch {
1856 detail: "split_part(): delimiter cannot be empty".into(),
1857 });
1858 }
1859 let n = match &args[2] {
1860 Value::SmallInt(x) => i64::from(*x),
1861 Value::Int(x) => i64::from(*x),
1862 Value::BigInt(x) => *x,
1863 other => {
1864 return Err(EvalError::TypeMismatch {
1865 detail: alloc::format!(
1866 "split_part(): n must be integer, got {:?}",
1867 other.data_type()
1868 ),
1869 });
1870 }
1871 };
1872 if n == 0 {
1873 return Err(EvalError::TypeMismatch {
1874 detail: "split_part(): n must be nonzero (PG: 1-indexed)".into(),
1875 });
1876 }
1877 let parts: alloc::vec::Vec<&str> = s.split(&delim[..]).collect();
1878 let total = parts.len() as i64;
1879 let idx = if n > 0 {
1880 n - 1
1881 } else {
1882 total + n
1884 };
1885 if idx < 0 || idx >= total {
1886 return Ok(Value::Text(String::new()));
1887 }
1888 Ok(Value::Text(parts[idx as usize].to_string()))
1889 }
1890 "translate" => {
1897 if args.len() != 3 {
1898 return Err(EvalError::TypeMismatch {
1899 detail: alloc::format!("translate() takes 3 args, got {}", args.len()),
1900 });
1901 }
1902 if args.iter().any(|v| matches!(v, Value::Null)) {
1903 return Ok(Value::Null);
1904 }
1905 let s = value_to_format_text(&args[0]);
1906 let from = value_to_format_text(&args[1]);
1907 let to = value_to_format_text(&args[2]);
1908 let from_chars: Vec<char> = from.chars().collect();
1909 let to_chars: Vec<char> = to.chars().collect();
1910 let mut map: alloc::collections::BTreeMap<char, Option<char>> =
1912 alloc::collections::BTreeMap::new();
1913 for (i, &fc) in from_chars.iter().enumerate() {
1914 if map.contains_key(&fc) {
1915 continue;
1916 }
1917 let replacement = to_chars.get(i).copied();
1918 map.insert(fc, replacement);
1919 }
1920 let mut out = String::with_capacity(s.len());
1921 for c in s.chars() {
1922 match map.get(&c) {
1923 Some(Some(r)) => out.push(*r),
1924 Some(None) => {} None => out.push(c),
1926 }
1927 }
1928 Ok(Value::Text(out))
1929 }
1930 "replace" => {
1931 if args.len() != 3 {
1932 return Err(EvalError::TypeMismatch {
1933 detail: alloc::format!(
1934 "replace() takes 3 args (string, from, to), got {}",
1935 args.len()
1936 ),
1937 });
1938 }
1939 if args.iter().any(|v| matches!(v, Value::Null)) {
1940 return Ok(Value::Null);
1941 }
1942 let s = value_to_format_text(&args[0]);
1943 let from = value_to_format_text(&args[1]);
1944 let to = value_to_format_text(&args[2]);
1945 if from.is_empty() {
1946 return Ok(Value::Text(s));
1947 }
1948 Ok(Value::Text(s.replace(&from[..], &to)))
1953 }
1954 "trim" | "btrim" => string_trim(args, TrimSide::Both, "trim"),
1955 "ltrim" => string_trim(args, TrimSide::Left, "ltrim"),
1956 "rtrim" => string_trim(args, TrimSide::Right, "rtrim"),
1957 "concat_ws" => {
1958 if args.is_empty() {
1959 return Err(EvalError::TypeMismatch {
1960 detail: "concat_ws() requires at least 1 arg (the separator)".into(),
1961 });
1962 }
1963 let sep = match &args[0] {
1965 Value::Null => return Ok(Value::Null),
1966 v => value_to_format_text(v),
1967 };
1968 let mut out = String::new();
1969 let mut first = true;
1970 for v in &args[1..] {
1971 if matches!(v, Value::Null) {
1972 continue;
1973 }
1974 if first {
1975 first = false;
1976 } else {
1977 out.push_str(&sep);
1978 }
1979 out.push_str(&value_to_format_text(v));
1980 }
1981 Ok(Value::Text(out))
1982 }
1983 "regexp_matches" => regexp_matches(args),
1985 "regexp_replace" => regexp_replace(args),
1986 "regexp_split_to_array" => regexp_split_to_array(args),
1987 "to_json" | "to_jsonb" => {
1991 if args.len() != 1 {
1992 return Err(EvalError::TypeMismatch {
1993 detail: alloc::format!("to_json() takes 1 arg, got {}", args.len()),
1994 });
1995 }
1996 if let Value::Json(s) = &args[0] {
1998 return Ok(Value::Json(s.clone()));
1999 }
2000 Ok(Value::Json(crate::json::value_to_json_text(&args[0])))
2001 }
2002 "json_build_object" | "jsonb_build_object" => crate::json::build_object(args),
2003 "json_build_array" | "jsonb_build_array" => crate::json::build_array(args),
2004 "jsonb_set" | "json_set" => crate::json::set(args),
2005 "jsonb_insert" | "json_insert" => crate::json::insert(args),
2006 "jsonb_path_query" | "json_path_query" => {
2008 if args.len() != 2 {
2009 return Err(EvalError::TypeMismatch {
2010 detail: alloc::format!("jsonb_path_query() takes 2 args, got {}", args.len()),
2011 });
2012 }
2013 crate::json::path_query(&args[0], &args[1])
2014 }
2015 "jsonb_path_query_first" | "json_path_query_first" => {
2016 if args.len() != 2 {
2017 return Err(EvalError::TypeMismatch {
2018 detail: alloc::format!(
2019 "jsonb_path_query_first() takes 2 args, got {}",
2020 args.len()
2021 ),
2022 });
2023 }
2024 crate::json::path_query_first(&args[0], &args[1])
2025 }
2026 "jsonb_path_query_array" | "json_path_query_array" => {
2027 if args.len() != 2 {
2028 return Err(EvalError::TypeMismatch {
2029 detail: alloc::format!(
2030 "jsonb_path_query_array() takes 2 args, got {}",
2031 args.len()
2032 ),
2033 });
2034 }
2035 crate::json::path_query_array(&args[0], &args[1])
2036 }
2037 "host" => inet_host(args),
2039 "network" => inet_network(args),
2040 "masklen" => inet_masklen(args),
2041 "encode" => encode_text(args),
2043 "decode" => decode_text(args),
2044 "error_on_null" => error_on_null(args),
2045 "to_tsvector" => fts_to_tsvector(args, ctx),
2050 "plainto_tsquery" => fts_plainto_tsquery(args, ctx),
2051 "phraseto_tsquery" => fts_phraseto_tsquery(args, ctx),
2052 "websearch_to_tsquery" => fts_websearch_to_tsquery(args, ctx),
2053 "to_tsquery" => fts_to_tsquery(args, ctx),
2054 "ts_rank" => fts_ts_rank(args),
2057 "ts_rank_cd" => fts_ts_rank_cd(args),
2058 "set_config" => Ok(args.get(1).cloned().unwrap_or(Value::Null)),
2063 "current_setting" => Ok(Value::Text(String::new())),
2064 "pg_get_serial_sequence" | "pg_get_constraintdef" | "pg_get_indexdef" => Ok(Value::Null),
2070 "version" => Ok(Value::Text("PostgreSQL 16 (SPG-compat)".into())),
2071 "current_database" | "database" => Ok(Value::Text("spg".into())),
2079 "current_schema" => Ok(Value::Text("public".into())),
2080 "current_user" | "session_user" | "user" => Ok(Value::Text("admin".into())),
2081 "pg_typeof" => {
2088 if args.len() != 1 {
2089 return Err(EvalError::TypeMismatch {
2090 detail: format!("pg_typeof() takes 1 arg, got {}", args.len()),
2091 });
2092 }
2093 Ok(Value::Text(pg_typeof_name(&args[0]).into()))
2094 }
2095 "lastval" => Ok(Value::Null),
2100 "similarity" => {
2105 if args.len() != 2 {
2106 return Err(EvalError::TypeMismatch {
2107 detail: format!("similarity() takes 2 args, got {}", args.len()),
2108 });
2109 }
2110 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2111 return Ok(Value::Null);
2112 }
2113 let a = match &args[0] {
2114 Value::Text(s) => s.as_str(),
2115 other => {
2116 return Err(EvalError::TypeMismatch {
2117 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2118 });
2119 }
2120 };
2121 let b = match &args[1] {
2122 Value::Text(s) => s.as_str(),
2123 other => {
2124 return Err(EvalError::TypeMismatch {
2125 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2126 });
2127 }
2128 };
2129 Ok(Value::Float(spg_storage::trgm::similarity(a, b)))
2132 }
2133 "show_trgm" => {
2134 if args.len() != 1 {
2135 return Err(EvalError::TypeMismatch {
2136 detail: format!("show_trgm() takes 1 arg, got {}", args.len()),
2137 });
2138 }
2139 if matches!(args[0], Value::Null) {
2140 return Ok(Value::Null);
2141 }
2142 let s = match &args[0] {
2143 Value::Text(s) => s.as_str(),
2144 other => {
2145 return Err(EvalError::TypeMismatch {
2146 detail: format!("show_trgm() needs text, got {:?}", other.data_type()),
2147 });
2148 }
2149 };
2150 let trigrams: Vec<Option<String>> = spg_storage::trgm::extract_trigrams(s)
2154 .into_iter()
2155 .map(Some)
2156 .collect();
2157 Ok(Value::TextArray(trigrams))
2158 }
2159 other => Err(EvalError::TypeMismatch {
2160 detail: format!("unknown function `{other}`"),
2161 }),
2162 }
2163}
2164
2165fn fts_ts_rank(args: &[Value]) -> Result<Value, EvalError> {
2170 let (vec, query) = parse_rank_args("ts_rank", args)?;
2171 match (vec, query) {
2172 (None, _) | (_, None) => Ok(Value::Null),
2173 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank(&v, &q)))),
2174 }
2175}
2176
2177fn fts_ts_rank_cd(args: &[Value]) -> Result<Value, EvalError> {
2178 let (vec, query) = parse_rank_args("ts_rank_cd", args)?;
2179 match (vec, query) {
2180 (None, _) | (_, None) => Ok(Value::Null),
2181 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank_cd(&v, &q)))),
2182 }
2183}
2184
2185fn parse_rank_args(
2186 name: &str,
2187 args: &[Value],
2188) -> Result<
2189 (
2190 Option<Vec<spg_storage::TsLexeme>>,
2191 Option<spg_storage::TsQueryAst>,
2192 ),
2193 EvalError,
2194> {
2195 if args.len() != 2 {
2196 return Err(EvalError::TypeMismatch {
2197 detail: format!(
2198 "{name}() takes 2 args in v7.12.2 (weights array + normalisation flag are v7.12.x carve-out), got {}",
2199 args.len()
2200 ),
2201 });
2202 }
2203 let vec = match &args[0] {
2204 Value::Null => None,
2205 Value::TsVector(v) => Some(v.clone()),
2206 other => {
2207 return Err(EvalError::TypeMismatch {
2208 detail: format!(
2209 "{name}() first arg must be tsvector, got {:?}",
2210 other.data_type()
2211 ),
2212 });
2213 }
2214 };
2215 let query = match &args[1] {
2216 Value::Null => None,
2217 Value::TsQuery(q) => Some(q.clone()),
2218 other => {
2219 return Err(EvalError::TypeMismatch {
2220 detail: format!(
2221 "{name}() second arg must be tsquery, got {:?}",
2222 other.data_type()
2223 ),
2224 });
2225 }
2226 };
2227 Ok((vec, query))
2228}
2229
2230fn ts_match(l: Value, r: Value) -> Result<Value, EvalError> {
2235 let (vec, query) = match (l, r) {
2236 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
2237 (Value::TsVector(v), Value::TsQuery(q)) => (v, q),
2238 (Value::TsQuery(q), Value::TsVector(v)) => (v, q),
2239 (l, r) => {
2240 return Err(EvalError::TypeMismatch {
2241 detail: format!(
2242 "@@ requires (tsvector, tsquery), got ({:?}, {:?})",
2243 l.data_type(),
2244 r.data_type()
2245 ),
2246 });
2247 }
2248 };
2249 Ok(Value::Bool(crate::fts::ts_query_matches(&vec, &query)))
2250}
2251
2252fn fts_to_tsvector(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2257 let (config, text) = parse_fts_args("to_tsvector", args, ctx)?;
2258 match text {
2259 None => Ok(Value::Null),
2260 Some(t) => Ok(Value::TsVector(crate::fts::to_tsvector(config, &t))),
2261 }
2262}
2263
2264fn fts_plainto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2265 let (config, text) = parse_fts_args("plainto_tsquery", args, ctx)?;
2266 match text {
2267 None => Ok(Value::Null),
2268 Some(t) => Ok(Value::TsQuery(crate::fts::plainto_tsquery(config, &t))),
2269 }
2270}
2271
2272fn fts_phraseto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2273 let (config, text) = parse_fts_args("phraseto_tsquery", args, ctx)?;
2274 match text {
2275 None => Ok(Value::Null),
2276 Some(t) => Ok(Value::TsQuery(crate::fts::phraseto_tsquery(config, &t))),
2277 }
2278}
2279
2280fn fts_websearch_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2281 let (config, text) = parse_fts_args("websearch_to_tsquery", args, ctx)?;
2282 match text {
2283 None => Ok(Value::Null),
2284 Some(t) => Ok(Value::TsQuery(crate::fts::websearch_to_tsquery(config, &t))),
2285 }
2286}
2287
2288fn fts_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2289 let (config, text) = parse_fts_args("to_tsquery", args, ctx)?;
2290 match text {
2291 None => Ok(Value::Null),
2292 Some(t) => Ok(Value::TsQuery(crate::fts::to_tsquery(config, &t)?)),
2293 }
2294}
2295
2296fn parse_fts_args(
2301 name: &str,
2302 args: &[Value],
2303 ctx: &EvalContext<'_>,
2304) -> Result<(crate::fts::TsConfig, Option<String>), EvalError> {
2305 let (config_arg, text_arg) = match args {
2306 [t] => (None, t),
2307 [c, t] => (Some(c), t),
2308 _ => {
2309 return Err(EvalError::TypeMismatch {
2310 detail: format!("{name}() takes 1 or 2 args, got {}", args.len()),
2311 });
2312 }
2313 };
2314 let config = match config_arg {
2315 None => match ctx.default_text_search_config {
2316 Some(name_str) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2317 EvalError::TypeMismatch {
2318 detail: format!(
2319 "text search config not implemented: {name_str:?} (supported: simple, english)"
2320 ),
2321 }
2322 })?,
2323 None => crate::fts::TsConfig::Simple,
2324 },
2325 Some(Value::Null) => return Ok((crate::fts::TsConfig::Simple, None)),
2326 Some(Value::Text(name_str)) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2327 EvalError::TypeMismatch {
2328 detail: format!(
2329 "text search config not implemented: {name_str:?} (supported: simple, english)"
2330 ),
2331 }
2332 })?,
2333 Some(other) => {
2334 return Err(EvalError::TypeMismatch {
2335 detail: format!(
2336 "{name}() config arg must be text, got {:?}",
2337 other.data_type()
2338 ),
2339 });
2340 }
2341 };
2342 let text = match text_arg {
2343 Value::Null => None,
2344 Value::Text(s) => Some(s.clone()),
2345 other => {
2346 return Err(EvalError::TypeMismatch {
2347 detail: format!(
2348 "{name}() text arg must be text, got {:?}",
2349 other.data_type()
2350 ),
2351 });
2352 }
2353 };
2354 Ok((config, text))
2355}
2356
2357fn encode_text(args: &[Value]) -> Result<Value, EvalError> {
2363 if args.len() != 2 {
2364 return Err(EvalError::TypeMismatch {
2365 detail: format!("encode() takes 2 args, got {}", args.len()),
2366 });
2367 }
2368 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2369 return Ok(Value::Null);
2370 }
2371 let bytes: &[u8] = match &args[0] {
2372 Value::Text(s) => s.as_bytes(),
2373 other => {
2374 return Err(EvalError::TypeMismatch {
2375 detail: format!("encode() expects text bytes, got {:?}", other.data_type()),
2376 });
2377 }
2378 };
2379 let fmt = match &args[1] {
2380 Value::Text(s) => s.to_ascii_lowercase(),
2381 other => {
2382 return Err(EvalError::TypeMismatch {
2383 detail: format!("encode() format must be text, got {:?}", other.data_type()),
2384 });
2385 }
2386 };
2387 let out = match fmt.as_str() {
2388 "base64" => b64_encode(bytes, B64_STD),
2389 "base64url" => b64_encode(bytes, B64_URL),
2390 "base32hex" => b32hex_encode(bytes),
2391 "hex" => hex_encode(bytes),
2392 other => {
2393 return Err(EvalError::TypeMismatch {
2394 detail: format!("encode(): unknown format `{other}`"),
2395 });
2396 }
2397 };
2398 Ok(Value::Text(out))
2399}
2400
2401fn decode_text(args: &[Value]) -> Result<Value, EvalError> {
2405 if args.len() != 2 {
2406 return Err(EvalError::TypeMismatch {
2407 detail: format!("decode() takes 2 args, got {}", args.len()),
2408 });
2409 }
2410 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2411 return Ok(Value::Null);
2412 }
2413 let text = match &args[0] {
2414 Value::Text(s) => s.as_str(),
2415 other => {
2416 return Err(EvalError::TypeMismatch {
2417 detail: format!("decode() expects text, got {:?}", other.data_type()),
2418 });
2419 }
2420 };
2421 let fmt = match &args[1] {
2422 Value::Text(s) => s.to_ascii_lowercase(),
2423 other => {
2424 return Err(EvalError::TypeMismatch {
2425 detail: format!("decode() format must be text, got {:?}", other.data_type()),
2426 });
2427 }
2428 };
2429 let bytes = match fmt.as_str() {
2430 "base64" => b64_decode(text, B64_STD)?,
2431 "base64url" => b64_decode(text, B64_URL)?,
2432 "base32hex" => b32hex_decode(text)?,
2433 "hex" => hex_decode(text)?,
2434 other => {
2435 return Err(EvalError::TypeMismatch {
2436 detail: format!("decode(): unknown format `{other}`"),
2437 });
2438 }
2439 };
2440 let s = String::from_utf8(bytes).map_err(|_| EvalError::TypeMismatch {
2441 detail: "decode(): result bytes are not valid UTF-8 (SPG stores raw bytes as Text)".into(),
2442 })?;
2443 Ok(Value::Text(s))
2444}
2445
2446fn error_on_null(args: &[Value]) -> Result<Value, EvalError> {
2450 if args.len() != 1 {
2451 return Err(EvalError::TypeMismatch {
2452 detail: format!("error_on_null() takes 1 arg, got {}", args.len()),
2453 });
2454 }
2455 if matches!(args[0], Value::Null) {
2456 return Err(EvalError::TypeMismatch {
2457 detail: "error_on_null(): argument is NULL".into(),
2458 });
2459 }
2460 Ok(args[0].clone())
2461}
2462
2463const B64_STD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2466const B64_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2467const B32HEX_ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHIJKLMNOPQRSTUV";
2468
2469fn b64_encode(bytes: &[u8], alpha: &[u8; 64]) -> String {
2470 let mut out = String::with_capacity((bytes.len() + 2) / 3 * 4);
2471 let mut i = 0;
2472 while i + 3 <= bytes.len() {
2473 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8) | (bytes[i + 2] as u32);
2474 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2475 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2476 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2477 out.push(alpha[(n & 0x3f) as usize] as char);
2478 i += 3;
2479 }
2480 let rem = bytes.len() - i;
2481 if rem == 1 {
2482 let n = (bytes[i] as u32) << 16;
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('=');
2486 out.push('=');
2487 } else if rem == 2 {
2488 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8);
2489 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2490 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2491 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2492 out.push('=');
2493 }
2494 out
2495}
2496
2497fn b64_decode(text: &str, alpha: &[u8; 64]) -> Result<Vec<u8>, EvalError> {
2498 let mut lookup = [255u8; 256];
2499 for (i, &c) in alpha.iter().enumerate() {
2500 lookup[c as usize] = i as u8;
2501 }
2502 let mut out = Vec::with_capacity(text.len() * 3 / 4);
2503 let mut buf: u32 = 0;
2504 let mut bits: u32 = 0;
2505 for c in text.bytes() {
2506 if c == b'=' {
2507 break;
2508 }
2509 if c == b'\n' || c == b'\r' || c == b' ' {
2510 continue;
2511 }
2512 let v = lookup[c as usize];
2513 if v == 255 {
2514 return Err(EvalError::TypeMismatch {
2515 detail: format!("decode(base64): invalid char {:?}", c as char),
2516 });
2517 }
2518 buf = (buf << 6) | v as u32;
2519 bits += 6;
2520 if bits >= 8 {
2521 bits -= 8;
2522 out.push(((buf >> bits) & 0xff) as u8);
2523 }
2524 }
2525 Ok(out)
2526}
2527
2528fn b32hex_encode(bytes: &[u8]) -> String {
2529 let mut out = String::with_capacity((bytes.len() * 8 + 4) / 5);
2530 let mut buf: u64 = 0;
2531 let mut bits: u32 = 0;
2532 for &b in bytes {
2533 buf = (buf << 8) | b as u64;
2534 bits += 8;
2535 while bits >= 5 {
2536 bits -= 5;
2537 out.push(B32HEX_ALPHABET[((buf >> bits) & 0x1f) as usize] as char);
2538 }
2539 }
2540 if bits > 0 {
2541 out.push(B32HEX_ALPHABET[((buf << (5 - bits)) & 0x1f) as usize] as char);
2542 }
2543 while out.len() % 8 != 0 {
2545 out.push('=');
2546 }
2547 out
2548}
2549
2550fn b32hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2551 let mut lookup = [255u8; 256];
2552 for (i, &c) in B32HEX_ALPHABET.iter().enumerate() {
2553 lookup[c as usize] = i as u8;
2554 let lower = (c as char).to_ascii_lowercase() as u8;
2556 lookup[lower as usize] = i as u8;
2557 }
2558 let mut out = Vec::with_capacity(text.len() * 5 / 8);
2559 let mut buf: u64 = 0;
2560 let mut bits: u32 = 0;
2561 for c in text.bytes() {
2562 if c == b'=' {
2563 break;
2564 }
2565 if c == b'\n' || c == b'\r' || c == b' ' {
2566 continue;
2567 }
2568 let v = lookup[c as usize];
2569 if v == 255 {
2570 return Err(EvalError::TypeMismatch {
2571 detail: format!("decode(base32hex): invalid char {:?}", c as char),
2572 });
2573 }
2574 buf = (buf << 5) | v as u64;
2575 bits += 5;
2576 if bits >= 8 {
2577 bits -= 8;
2578 out.push(((buf >> bits) & 0xff) as u8);
2579 }
2580 }
2581 Ok(out)
2582}
2583
2584fn hex_encode(bytes: &[u8]) -> String {
2585 const HEX: &[u8; 16] = b"0123456789abcdef";
2586 let mut out = String::with_capacity(bytes.len() * 2);
2587 for &b in bytes {
2588 out.push(HEX[(b >> 4) as usize] as char);
2589 out.push(HEX[(b & 0xf) as usize] as char);
2590 }
2591 out
2592}
2593
2594fn hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2595 let trimmed = text.trim();
2596 if trimmed.len() % 2 != 0 {
2597 return Err(EvalError::TypeMismatch {
2598 detail: "decode(hex): input length must be even".into(),
2599 });
2600 }
2601 let mut out = Vec::with_capacity(trimmed.len() / 2);
2602 let mut hi: u8 = 0;
2603 for (i, c) in trimmed.bytes().enumerate() {
2604 let v = match c {
2605 b'0'..=b'9' => c - b'0',
2606 b'a'..=b'f' => c - b'a' + 10,
2607 b'A'..=b'F' => c - b'A' + 10,
2608 _ => {
2609 return Err(EvalError::TypeMismatch {
2610 detail: format!("decode(hex): invalid char {:?}", c as char),
2611 });
2612 }
2613 };
2614 if i % 2 == 0 {
2615 hi = v;
2616 } else {
2617 out.push((hi << 4) | v);
2618 }
2619 }
2620 Ok(out)
2621}
2622
2623fn date_part(args: &[Value]) -> Result<Value, EvalError> {
2628 use spg_sql::ast::ExtractField as F;
2629 if args.len() != 2 {
2630 return Err(EvalError::TypeMismatch {
2631 detail: format!("date_part() takes 2 args, got {}", args.len()),
2632 });
2633 }
2634 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
2635 return Ok(Value::Null);
2636 }
2637 let Value::Text(field_name) = &args[0] else {
2638 return Err(EvalError::TypeMismatch {
2639 detail: format!(
2640 "date_part() needs a text field, got {:?}",
2641 args[0].data_type()
2642 ),
2643 });
2644 };
2645 let field = match field_name.to_ascii_lowercase().as_str() {
2646 "year" => F::Year,
2647 "month" => F::Month,
2648 "day" => F::Day,
2649 "hour" => F::Hour,
2650 "minute" => F::Minute,
2651 "second" => F::Second,
2652 "microsecond" | "microseconds" => F::Microsecond,
2653 "epoch" => F::Epoch,
2654 other => {
2655 return Err(EvalError::TypeMismatch {
2656 detail: format!(
2657 "unknown date_part field {other:?}; \
2658 supported: year, month, day, hour, minute, second, microsecond"
2659 ),
2660 });
2661 }
2662 };
2663 extract_field(field, &args[1])
2664}
2665
2666fn age(args: &[Value]) -> Result<Value, EvalError> {
2676 if args.is_empty() || args.len() > 2 {
2677 return Err(EvalError::TypeMismatch {
2678 detail: format!("age() takes 1 or 2 args, got {}", args.len()),
2679 });
2680 }
2681 if args.iter().any(|v| matches!(v, Value::Null)) {
2682 return Ok(Value::Null);
2683 }
2684 let to_micros = |v: &Value| -> Result<i64, EvalError> {
2687 match v {
2688 Value::Timestamp(t) => Ok(*t),
2689 Value::Date(d) => Ok(i64::from(*d) * 86_400_000_000),
2690 other => Err(EvalError::TypeMismatch {
2691 detail: format!("age() needs DATE or TIMESTAMP, got {:?}", other.data_type()),
2692 }),
2693 }
2694 };
2695 if args.len() == 1 {
2696 return Err(EvalError::TypeMismatch {
2697 detail: "single-arg age() is unsupported in v2.12 \
2698 (use age(CURRENT_DATE, t) explicitly)"
2699 .into(),
2700 });
2701 }
2702 let a = to_micros(&args[0])?;
2703 let b = to_micros(&args[1])?;
2704 let delta = a.checked_sub(b).ok_or(EvalError::TypeMismatch {
2705 detail: "age() subtraction overflows i64 microseconds".into(),
2706 })?;
2707 Ok(Value::Interval {
2708 months: 0,
2709 micros: delta,
2710 })
2711}
2712
2713fn inet_host(args: &[Value]) -> Result<Value, EvalError> {
2727 let s = match args {
2728 [Value::Text(s)] => s.clone(),
2729 [Value::Null] => return Ok(Value::Null),
2730 _ => {
2731 return Err(EvalError::TypeMismatch {
2732 detail: alloc::format!("host() takes one TEXT arg, got {} args", args.len()),
2733 });
2734 }
2735 };
2736 let host = s.split('/').next().unwrap_or("").to_string();
2737 Ok(Value::Text(host))
2738}
2739
2740fn inet_network(args: &[Value]) -> Result<Value, EvalError> {
2741 let s = match args {
2742 [Value::Text(s)] => s.clone(),
2743 [Value::Null] => return Ok(Value::Null),
2744 _ => {
2745 return Err(EvalError::TypeMismatch {
2746 detail: alloc::format!("network() takes one TEXT arg, got {} args", args.len()),
2747 });
2748 }
2749 };
2750 let mut split = s.splitn(2, '/');
2754 let host = split.next().unwrap_or("").to_string();
2755 let mask: u32 = split.next().and_then(|m| m.parse().ok()).unwrap_or(32);
2756 if !host.contains('.') {
2757 return Ok(Value::Text(s));
2759 }
2760 let octets: Vec<&str> = host.split('.').collect();
2761 if octets.len() != 4 {
2762 return Ok(Value::Text(s));
2763 }
2764 let keep_bytes = ((mask + 7) / 8) as usize;
2765 let mut out = alloc::string::String::new();
2766 for (i, oct) in octets.iter().enumerate() {
2767 if i > 0 {
2768 out.push('.');
2769 }
2770 if i < keep_bytes {
2771 out.push_str(oct);
2772 } else {
2773 out.push('0');
2774 }
2775 }
2776 out.push('/');
2777 out.push_str(&mask.to_string());
2778 Ok(Value::Text(out))
2779}
2780
2781fn inet_masklen(args: &[Value]) -> Result<Value, EvalError> {
2782 let s = match args {
2783 [Value::Text(s)] => s.clone(),
2784 [Value::Null] => return Ok(Value::Null),
2785 _ => {
2786 return Err(EvalError::TypeMismatch {
2787 detail: alloc::format!("masklen() takes one TEXT arg, got {} args", args.len()),
2788 });
2789 }
2790 };
2791 let mask: i32 = s
2792 .split_once('/')
2793 .and_then(|(_, m)| m.parse().ok())
2794 .unwrap_or(32);
2795 Ok(Value::Int(mask))
2796}
2797
2798struct InetNet {
2817 bytes: [u8; 16],
2818 family_bytes: u8,
2820 prefix_bits: u8,
2822}
2823
2824fn parse_inet_text(s: &str) -> Option<InetNet> {
2825 let mut split = s.splitn(2, '/');
2826 let host = split.next()?;
2827 let mask_str = split.next();
2828 if host.contains(':') {
2829 let bytes = parse_ipv6(host)?;
2830 let prefix_bits = match mask_str {
2831 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 128)?,
2832 None => 128,
2833 };
2834 let mut out = [0u8; 16];
2835 out.copy_from_slice(&bytes);
2836 Some(InetNet {
2837 bytes: out,
2838 family_bytes: 16,
2839 prefix_bits,
2840 })
2841 } else {
2842 let bytes = parse_ipv4(host)?;
2843 let prefix_bits = match mask_str {
2844 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 32)?,
2845 None => 32,
2846 };
2847 let mut out = [0u8; 16];
2848 out[..4].copy_from_slice(&bytes);
2849 Some(InetNet {
2850 bytes: out,
2851 family_bytes: 4,
2852 prefix_bits,
2853 })
2854 }
2855}
2856
2857fn parse_ipv4(s: &str) -> Option<[u8; 4]> {
2858 let parts: Vec<&str> = s.split('.').collect();
2859 if parts.len() != 4 {
2860 return None;
2861 }
2862 let mut out = [0u8; 4];
2863 for (i, p) in parts.iter().enumerate() {
2864 out[i] = p.parse::<u8>().ok()?;
2865 }
2866 Some(out)
2867}
2868
2869fn parse_ipv6(s: &str) -> Option<[u8; 16]> {
2870 let (head, tail) = match s.find("::") {
2872 Some(idx) => (&s[..idx], Some(&s[idx + 2..])),
2873 None => (s, None),
2874 };
2875 let head_groups: Vec<&str> = if head.is_empty() {
2876 Vec::new()
2877 } else {
2878 head.split(':').collect()
2879 };
2880 let tail_groups: Vec<&str> = match tail {
2881 Some(t) if !t.is_empty() => t.split(':').collect(),
2882 _ => Vec::new(),
2883 };
2884 let head_len = head_groups.len();
2885 let tail_len = tail_groups.len();
2886 if tail.is_none() {
2888 if head_len != 8 {
2889 return None;
2890 }
2891 } else if head_len + tail_len > 7 {
2892 return None;
2893 }
2894 let mut words = [0u16; 8];
2895 for (i, g) in head_groups.iter().enumerate() {
2896 words[i] = u16::from_str_radix(g, 16).ok()?;
2897 }
2898 let tail_start = 8 - tail_len;
2899 for (i, g) in tail_groups.iter().enumerate() {
2900 words[tail_start + i] = u16::from_str_radix(g, 16).ok()?;
2901 }
2902 let mut out = [0u8; 16];
2903 for (i, w) in words.iter().enumerate() {
2904 out[i * 2] = (w >> 8) as u8;
2905 out[i * 2 + 1] = (w & 0xff) as u8;
2906 }
2907 Some(out)
2908}
2909
2910fn network_prefix_eq(a: &InetNet, b: &InetNet, prefix_bits: u8) -> bool {
2913 let full_bytes = (prefix_bits / 8) as usize;
2914 if a.bytes[..full_bytes] != b.bytes[..full_bytes] {
2915 return false;
2916 }
2917 let extra = prefix_bits % 8;
2918 if extra == 0 {
2919 return true;
2920 }
2921 let mask: u8 = 0xff << (8 - extra);
2922 (a.bytes[full_bytes] & mask) == (b.bytes[full_bytes] & mask)
2923}
2924
2925fn inet_contained_eq(a: &InetNet, b: &InetNet) -> bool {
2927 if a.family_bytes != b.family_bytes {
2928 return false;
2929 }
2930 if a.prefix_bits < b.prefix_bits {
2931 return false;
2932 }
2933 network_prefix_eq(a, b, b.prefix_bits)
2934}
2935
2936fn inet_networks_equal(a: &InetNet, b: &InetNet) -> bool {
2939 if a.family_bytes != b.family_bytes {
2940 return false;
2941 }
2942 if a.prefix_bits != b.prefix_bits {
2943 return false;
2944 }
2945 network_prefix_eq(a, b, a.prefix_bits)
2946}
2947
2948fn inet_op_bool_result(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
2949 if matches!(l, Value::Null) || matches!(r, Value::Null) {
2950 return Ok(Value::Null);
2951 }
2952 let (lt, rt) = match (l, r) {
2953 (Value::Text(a), Value::Text(b)) => (a, b),
2954 _ => {
2955 return Err(EvalError::TypeMismatch {
2956 detail: format!(
2957 "inet operator requires TEXT/INET operands, got {:?} and {:?}",
2958 l.data_type(),
2959 r.data_type()
2960 ),
2961 });
2962 }
2963 };
2964 let a = parse_inet_text(lt).ok_or_else(|| EvalError::TypeMismatch {
2965 detail: format!("invalid inet text: {:?}", lt),
2966 })?;
2967 let b = parse_inet_text(rt).ok_or_else(|| EvalError::TypeMismatch {
2968 detail: format!("invalid inet text: {:?}", rt),
2969 })?;
2970 let result = match op {
2971 BinOp::InetContainedByEq => inet_contained_eq(&a, &b),
2972 BinOp::InetContainedBy => inet_contained_eq(&a, &b) && !inet_networks_equal(&a, &b),
2973 BinOp::InetContainsEq => inet_contained_eq(&b, &a),
2974 BinOp::InetContains => inet_contained_eq(&b, &a) && !inet_networks_equal(&a, &b),
2975 BinOp::InetOverlap => inet_contained_eq(&a, &b) || inet_contained_eq(&b, &a),
2976 _ => unreachable!("inet_op_bool_result called with non-inet op"),
2977 };
2978 Ok(Value::Bool(result))
2979}
2980
2981#[derive(Debug, Clone)]
3012enum ReNode {
3013 Literal(char),
3016 AnyChar,
3018 Class {
3020 members: Vec<ClassMember>,
3021 negated: bool,
3022 },
3023 Start,
3025 End,
3027 Quant {
3029 inner: Box<ReNode>,
3030 min: usize,
3031 max: Option<usize>,
3032 },
3033 Concat(Vec<ReNode>),
3035 Alt(Vec<ReNode>),
3037}
3038
3039#[derive(Debug, Clone)]
3040enum ClassMember {
3041 Single(char),
3042 Range(char, char),
3043}
3044
3045fn re_compile(pat: &str) -> Result<ReNode, EvalError> {
3046 let chars: Vec<char> = pat.chars().collect();
3047 let mut p = 0;
3048 let n = re_parse_alt(&chars, &mut p)?;
3049 if p != chars.len() {
3050 return Err(EvalError::TypeMismatch {
3051 detail: alloc::format!("regex compile: trailing chars at pos {p} in {pat:?}"),
3052 });
3053 }
3054 Ok(n)
3055}
3056
3057fn re_parse_alt(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3058 let mut branches = alloc::vec![re_parse_concat(chars, p)?];
3059 while *p < chars.len() && chars[*p] == '|' {
3060 *p += 1;
3061 branches.push(re_parse_concat(chars, p)?);
3062 }
3063 if branches.len() == 1 {
3064 Ok(branches.pop().unwrap())
3065 } else {
3066 Ok(ReNode::Alt(branches))
3067 }
3068}
3069
3070fn re_parse_concat(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3071 let mut items: Vec<ReNode> = Vec::new();
3072 while *p < chars.len() {
3073 let c = chars[*p];
3074 if c == '|' || c == ')' {
3075 break;
3076 }
3077 let atom = re_parse_atom(chars, p)?;
3078 let quantified = if *p < chars.len() {
3080 match chars[*p] {
3081 '*' => {
3082 *p += 1;
3083 if *p < chars.len() && chars[*p] == '?' {
3087 *p += 1;
3088 }
3089 ReNode::Quant {
3090 inner: Box::new(atom),
3091 min: 0,
3092 max: None,
3093 }
3094 }
3095 '+' => {
3096 *p += 1;
3097 if *p < chars.len() && chars[*p] == '?' {
3098 *p += 1;
3099 }
3100 ReNode::Quant {
3101 inner: Box::new(atom),
3102 min: 1,
3103 max: None,
3104 }
3105 }
3106 '?' => {
3107 *p += 1;
3108 ReNode::Quant {
3109 inner: Box::new(atom),
3110 min: 0,
3111 max: Some(1),
3112 }
3113 }
3114 _ => atom,
3115 }
3116 } else {
3117 atom
3118 };
3119 items.push(quantified);
3120 }
3121 if items.len() == 1 {
3122 Ok(items.pop().unwrap())
3123 } else {
3124 Ok(ReNode::Concat(items))
3125 }
3126}
3127
3128fn re_parse_atom(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3129 let c = chars[*p];
3130 match c {
3131 '(' => {
3132 *p += 1;
3133 let inner = re_parse_alt(chars, p)?;
3134 if *p >= chars.len() || chars[*p] != ')' {
3135 return Err(EvalError::TypeMismatch {
3136 detail: "regex compile: unmatched '('".into(),
3137 });
3138 }
3139 *p += 1;
3140 Ok(inner)
3141 }
3142 '[' => {
3143 *p += 1;
3144 let mut negated = false;
3145 if *p < chars.len() && chars[*p] == '^' {
3146 negated = true;
3147 *p += 1;
3148 }
3149 let mut members: Vec<ClassMember> = Vec::new();
3150 while *p < chars.len() && chars[*p] != ']' {
3151 let start = chars[*p];
3152 *p += 1;
3153 if *p + 1 < chars.len() && chars[*p] == '-' && chars[*p + 1] != ']' {
3154 let end = chars[*p + 1];
3155 *p += 2;
3156 members.push(ClassMember::Range(start, end));
3157 } else {
3158 members.push(ClassMember::Single(start));
3159 }
3160 }
3161 if *p >= chars.len() {
3162 return Err(EvalError::TypeMismatch {
3163 detail: "regex compile: unmatched '['".into(),
3164 });
3165 }
3166 *p += 1; Ok(ReNode::Class { members, negated })
3168 }
3169 '.' => {
3170 *p += 1;
3171 Ok(ReNode::AnyChar)
3172 }
3173 '^' => {
3174 *p += 1;
3175 Ok(ReNode::Start)
3176 }
3177 '$' => {
3178 *p += 1;
3179 Ok(ReNode::End)
3180 }
3181 '\\' => {
3182 *p += 1;
3183 if *p >= chars.len() {
3184 return Err(EvalError::TypeMismatch {
3185 detail: "regex compile: dangling backslash".into(),
3186 });
3187 }
3188 let esc = chars[*p];
3189 *p += 1;
3190 match esc {
3191 'd' => Ok(ReNode::Class {
3192 members: alloc::vec![ClassMember::Range('0', '9')],
3193 negated: false,
3194 }),
3195 'D' => Ok(ReNode::Class {
3196 members: alloc::vec![ClassMember::Range('0', '9')],
3197 negated: true,
3198 }),
3199 'w' => Ok(ReNode::Class {
3200 members: alloc::vec![
3201 ClassMember::Range('a', 'z'),
3202 ClassMember::Range('A', 'Z'),
3203 ClassMember::Range('0', '9'),
3204 ClassMember::Single('_'),
3205 ],
3206 negated: false,
3207 }),
3208 'W' => Ok(ReNode::Class {
3209 members: alloc::vec![
3210 ClassMember::Range('a', 'z'),
3211 ClassMember::Range('A', 'Z'),
3212 ClassMember::Range('0', '9'),
3213 ClassMember::Single('_'),
3214 ],
3215 negated: true,
3216 }),
3217 's' => Ok(ReNode::Class {
3218 members: alloc::vec![
3219 ClassMember::Single(' '),
3220 ClassMember::Single('\t'),
3221 ClassMember::Single('\n'),
3222 ClassMember::Single('\r'),
3223 ],
3224 negated: false,
3225 }),
3226 'S' => Ok(ReNode::Class {
3227 members: alloc::vec![
3228 ClassMember::Single(' '),
3229 ClassMember::Single('\t'),
3230 ClassMember::Single('\n'),
3231 ClassMember::Single('\r'),
3232 ],
3233 negated: true,
3234 }),
3235 other => Ok(ReNode::Literal(other)),
3236 }
3237 }
3238 other => {
3239 *p += 1;
3240 Ok(ReNode::Literal(other))
3241 }
3242 }
3243}
3244
3245fn class_matches(member: &ClassMember, c: char) -> bool {
3246 match member {
3247 ClassMember::Single(s) => *s == c,
3248 ClassMember::Range(a, b) => c >= *a && c <= *b,
3249 }
3250}
3251
3252fn re_match_at(node: &ReNode, s: &[char], pos: usize) -> Option<usize> {
3257 match node {
3258 ReNode::Literal(c) => {
3259 if s.get(pos).copied() == Some(*c) {
3260 Some(pos + 1)
3261 } else {
3262 None
3263 }
3264 }
3265 ReNode::AnyChar => {
3266 if pos < s.len() && s[pos] != '\n' {
3267 Some(pos + 1)
3268 } else {
3269 None
3270 }
3271 }
3272 ReNode::Class { members, negated } => {
3273 let c = *s.get(pos)?;
3274 let hit = members.iter().any(|m| class_matches(m, c));
3275 if hit ^ negated { Some(pos + 1) } else { None }
3276 }
3277 ReNode::Start => {
3278 if pos == 0 {
3279 Some(pos)
3280 } else {
3281 None
3282 }
3283 }
3284 ReNode::End => {
3285 if pos == s.len() {
3286 Some(pos)
3287 } else {
3288 None
3289 }
3290 }
3291 ReNode::Concat(items) => {
3292 let mut p = pos;
3293 for it in items {
3294 p = re_match_at(it, s, p)?;
3295 }
3296 Some(p)
3297 }
3298 ReNode::Alt(branches) => {
3299 for b in branches {
3300 if let Some(p) = re_match_at(b, s, pos) {
3301 return Some(p);
3302 }
3303 }
3304 None
3305 }
3306 ReNode::Quant { inner, min, max } => {
3307 let mut count = 0usize;
3312 let mut p = pos;
3313 loop {
3314 if let Some(cap) = max {
3315 if count >= *cap {
3316 break;
3317 }
3318 }
3319 match re_match_at(inner, s, p) {
3320 Some(np) if np > p => {
3321 p = np;
3322 count += 1;
3323 }
3324 _ => break,
3325 }
3326 }
3327 if count < *min {
3328 return None;
3329 }
3330 Some(p)
3331 }
3332 }
3333}
3334
3335fn re_find(node: &ReNode, s: &[char], from: usize) -> Option<(usize, usize)> {
3338 let mut start = from;
3339 loop {
3340 if let Some(end) = re_match_at(node, s, start) {
3341 return Some((start, end));
3342 }
3343 if start >= s.len() {
3344 return None;
3345 }
3346 start += 1;
3347 }
3348}
3349
3350fn regexp_matches(args: &[Value]) -> Result<Value, EvalError> {
3356 let (text, pat, all_matches) = match args.len() {
3357 2 => (text_arg(&args[0])?, text_arg(&args[1])?, false),
3358 3 => {
3359 let flags = text_arg(&args[2])?.unwrap_or_default();
3360 (
3361 text_arg(&args[0])?,
3362 text_arg(&args[1])?,
3363 flags.contains('g'),
3364 )
3365 }
3366 n => {
3367 return Err(EvalError::TypeMismatch {
3368 detail: alloc::format!("regexp_matches() takes 2 or 3 args, got {n}"),
3369 });
3370 }
3371 };
3372 let Some(text) = text else {
3373 return Ok(Value::Null);
3374 };
3375 let Some(pat) = pat else {
3376 return Ok(Value::Null);
3377 };
3378 let node = re_compile(&pat)?;
3379 let chars: Vec<char> = text.chars().collect();
3380 let mut out: Vec<Option<String>> = Vec::new();
3381 let mut from = 0usize;
3382 while let Some((s_pos, e_pos)) = re_find(&node, &chars, from) {
3383 out.push(Some(chars[s_pos..e_pos].iter().collect()));
3384 if !all_matches {
3385 break;
3386 }
3387 from = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3389 if from > chars.len() {
3390 break;
3391 }
3392 }
3393 Ok(Value::TextArray(out))
3394}
3395
3396fn regexp_replace(args: &[Value]) -> Result<Value, EvalError> {
3400 let (text, pat, repl, flags) = match args.len() {
3401 3 => (
3402 text_arg(&args[0])?,
3403 text_arg(&args[1])?,
3404 text_arg(&args[2])?,
3405 String::new(),
3406 ),
3407 4 => (
3408 text_arg(&args[0])?,
3409 text_arg(&args[1])?,
3410 text_arg(&args[2])?,
3411 text_arg(&args[3])?.unwrap_or_default(),
3412 ),
3413 n => {
3414 return Err(EvalError::TypeMismatch {
3415 detail: alloc::format!("regexp_replace() takes 3 or 4 args, got {n}"),
3416 });
3417 }
3418 };
3419 let Some(text) = text else {
3420 return Ok(Value::Null);
3421 };
3422 let Some(pat) = pat else {
3423 return Ok(Value::Null);
3424 };
3425 let Some(repl) = repl else {
3426 return Ok(Value::Null);
3427 };
3428 let global = flags.contains('g');
3429 let node = re_compile(&pat)?;
3430 let chars: Vec<char> = text.chars().collect();
3431 let mut out = String::with_capacity(text.len());
3432 let mut from = 0usize;
3433 loop {
3434 match re_find(&node, &chars, from) {
3435 Some((s_pos, e_pos)) => {
3436 out.extend(chars[from..s_pos].iter());
3437 out.push_str(&repl);
3438 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3439 from = step;
3440 if !global {
3441 if from <= chars.len() {
3442 out.extend(chars[from..].iter());
3443 }
3444 return Ok(Value::Text(out));
3445 }
3446 if from > chars.len() {
3447 break;
3448 }
3449 }
3450 None => {
3451 out.extend(chars[from..].iter());
3452 break;
3453 }
3454 }
3455 }
3456 Ok(Value::Text(out))
3457}
3458
3459fn regexp_split_to_array(args: &[Value]) -> Result<Value, EvalError> {
3462 if args.len() != 2 {
3463 return Err(EvalError::TypeMismatch {
3464 detail: alloc::format!("regexp_split_to_array() takes 2 args, got {}", args.len()),
3465 });
3466 }
3467 let text = text_arg(&args[0])?;
3468 let pat = text_arg(&args[1])?;
3469 let Some(text) = text else {
3470 return Ok(Value::Null);
3471 };
3472 let Some(pat) = pat else {
3473 return Ok(Value::Null);
3474 };
3475 let node = re_compile(&pat)?;
3476 let chars: Vec<char> = text.chars().collect();
3477 let mut out: Vec<Option<String>> = Vec::new();
3478 let mut piece_start = 0usize;
3479 let mut from = 0usize;
3480 loop {
3481 match re_find(&node, &chars, from) {
3482 Some((s_pos, e_pos)) => {
3483 let piece: String = chars[piece_start..s_pos].iter().collect();
3484 out.push(Some(piece));
3485 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3486 from = step;
3487 piece_start = step;
3488 if from > chars.len() {
3489 break;
3490 }
3491 }
3492 None => {
3493 let tail: String = chars[piece_start..].iter().collect();
3494 out.push(Some(tail));
3495 break;
3496 }
3497 }
3498 }
3499 Ok(Value::TextArray(out))
3500}
3501
3502fn text_arg(v: &Value) -> Result<Option<String>, EvalError> {
3505 match v {
3506 Value::Text(s) => Ok(Some(s.clone())),
3507 Value::Null => Ok(None),
3508 other => Err(EvalError::TypeMismatch {
3509 detail: alloc::format!(
3510 "regex function expects TEXT arg, got {:?}",
3511 other.data_type()
3512 ),
3513 }),
3514 }
3515}
3516
3517#[derive(Debug, Clone, Copy)]
3519enum TrimSide {
3520 Left,
3521 Right,
3522 Both,
3523}
3524
3525fn string_left_right(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
3529 if args.len() != 2 {
3530 return Err(EvalError::TypeMismatch {
3531 detail: alloc::format!("{fn_name}() takes 2 args, got {}", args.len()),
3532 });
3533 }
3534 if args.iter().any(|v| matches!(v, Value::Null)) {
3535 return Ok(Value::Null);
3536 }
3537 let s = value_to_format_text(&args[0]);
3538 let n = match &args[1] {
3539 Value::SmallInt(x) => i64::from(*x),
3540 Value::Int(x) => i64::from(*x),
3541 Value::BigInt(x) => *x,
3542 other => {
3543 return Err(EvalError::TypeMismatch {
3544 detail: alloc::format!(
3545 "{fn_name}(): n must be integer, got {:?}",
3546 other.data_type()
3547 ),
3548 });
3549 }
3550 };
3551 let chars: Vec<char> = s.chars().collect();
3552 let len = chars.len() as i64;
3553 if n == 0 {
3554 return Ok(Value::Text(String::new()));
3555 }
3556 let (start, end) = if is_left {
3557 if n > 0 {
3558 (0usize, (n.min(len)) as usize)
3559 } else {
3560 let drop = (-n).min(len);
3562 (0usize, (len - drop) as usize)
3563 }
3564 } else if n > 0 {
3565 let start = (len - n).max(0);
3567 (start as usize, len as usize)
3568 } else {
3569 let drop = (-n).min(len);
3571 (drop as usize, len as usize)
3572 };
3573 if start >= end {
3574 return Ok(Value::Text(String::new()));
3575 }
3576 Ok(Value::Text(chars[start..end].iter().collect()))
3577}
3578
3579fn value_cmp_for_min_max(a: &Value, b: &Value) -> core::cmp::Ordering {
3583 use core::cmp::Ordering;
3584 let a_int = match a {
3586 Value::SmallInt(x) => Some(i64::from(*x)),
3587 Value::Int(x) => Some(i64::from(*x)),
3588 Value::BigInt(x) => Some(*x),
3589 _ => None,
3590 };
3591 let b_int = match b {
3592 Value::SmallInt(x) => Some(i64::from(*x)),
3593 Value::Int(x) => Some(i64::from(*x)),
3594 Value::BigInt(x) => Some(*x),
3595 _ => None,
3596 };
3597 if let (Some(av), Some(bv)) = (a_int, b_int) {
3598 return av.cmp(&bv);
3599 }
3600 let a_f = value_to_f64(a);
3602 let b_f = value_to_f64(b);
3603 if let (Some(av), Some(bv)) = (a_f, b_f) {
3604 return av.partial_cmp(&bv).unwrap_or(Ordering::Equal);
3605 }
3606 match (a, b) {
3608 (Value::Text(av), Value::Text(bv)) => av.cmp(bv),
3609 (Value::Bytes(av), Value::Bytes(bv)) => av.cmp(bv),
3610 _ => Ordering::Equal,
3611 }
3612}
3613
3614fn value_to_f64(v: &Value) -> Option<f64> {
3615 match v {
3616 Value::Float(x) => Some(*x),
3617 Value::SmallInt(x) => Some(f64::from(*x)),
3618 Value::Int(x) => Some(f64::from(*x)),
3619 Value::BigInt(x) => Some(*x as f64),
3620 Value::Numeric { scaled, scale } => {
3621 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3622 }
3623 _ => None,
3624 }
3625}
3626
3627fn values_equal_for_nullif(a: &Value, b: &Value) -> bool {
3632 if a == b {
3634 return true;
3635 }
3636 let a_int = match a {
3638 Value::SmallInt(x) => Some(i64::from(*x)),
3639 Value::Int(x) => Some(i64::from(*x)),
3640 Value::BigInt(x) => Some(*x),
3641 _ => None,
3642 };
3643 let b_int = match b {
3644 Value::SmallInt(x) => Some(i64::from(*x)),
3645 Value::Int(x) => Some(i64::from(*x)),
3646 Value::BigInt(x) => Some(*x),
3647 _ => None,
3648 };
3649 if let (Some(a), Some(b)) = (a_int, b_int) {
3650 return a == b;
3651 }
3652 let a_f = match a {
3654 Value::Float(x) => Some(*x),
3655 Value::SmallInt(x) => Some(f64::from(*x)),
3656 Value::Int(x) => Some(f64::from(*x)),
3657 Value::BigInt(x) => Some(*x as f64),
3658 Value::Numeric { scaled, scale } => {
3659 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3660 }
3661 _ => None,
3662 };
3663 let b_f = match b {
3664 Value::Float(x) => Some(*x),
3665 Value::SmallInt(x) => Some(f64::from(*x)),
3666 Value::Int(x) => Some(f64::from(*x)),
3667 Value::BigInt(x) => Some(*x as f64),
3668 Value::Numeric { scaled, scale } => {
3669 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3670 }
3671 _ => None,
3672 };
3673 if let (Some(a), Some(b)) = (a_f, b_f) {
3674 return a == b;
3675 }
3676 false
3677}
3678
3679fn f64_trunc(x: f64) -> f64 {
3684 if x.is_nan() || x.is_infinite() {
3685 return x;
3686 }
3687 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3688 return x;
3689 }
3690 (x as i64) as f64
3691}
3692
3693static PRNG_STATE: core::sync::atomic::AtomicU64 =
3698 core::sync::atomic::AtomicU64::new(0x2545_F491_4F6C_DD1D);
3699
3700fn prng_next_u64() -> u64 {
3706 use core::sync::atomic::Ordering;
3707 let mut x = PRNG_STATE.load(Ordering::Relaxed);
3708 loop {
3709 if x == 0 {
3710 x = 0x2545_F491_4F6C_DD1D;
3711 }
3712 let mut next = x;
3713 next ^= next << 13;
3714 next ^= next >> 7;
3715 next ^= next << 17;
3716 match PRNG_STATE.compare_exchange_weak(x, next, Ordering::Relaxed, Ordering::Relaxed) {
3717 Ok(_) => return next,
3718 Err(seen) => x = seen,
3719 }
3720 }
3721}
3722
3723fn prng_next_f64() -> f64 {
3725 let mantissa = prng_next_u64() >> 11;
3727 let denom = (1u64 << 53) as f64;
3728 mantissa as f64 / denom
3729}
3730
3731pub fn gen_random_uuid_bytes() -> [u8; 16] {
3738 let mut out = [0u8; 16];
3739 let hi = prng_next_u64().to_be_bytes();
3740 let lo = prng_next_u64().to_be_bytes();
3741 out[..8].copy_from_slice(&hi);
3742 out[8..].copy_from_slice(&lo);
3743 out[6] = (out[6] & 0x0f) | 0x40;
3745 out[8] = (out[8] & 0x3f) | 0x80;
3747 out
3748}
3749
3750fn f64_sqrt(x: f64) -> f64 {
3755 if x == 0.0 || x.is_nan() {
3756 return x;
3757 }
3758 if x.is_infinite() {
3759 return x;
3760 }
3761 let bits = x.to_bits();
3765 let exp = ((bits >> 52) & 0x7ff) as i64 - 1023;
3766 let new_exp = (exp / 2) + 1023;
3767 let mut guess = f64::from_bits(((new_exp as u64) & 0x7ff) << 52);
3768 for _ in 0..8 {
3770 guess = 0.5 * (guess + x / guess);
3771 }
3772 guess
3773}
3774
3775fn f64_exp(x: f64) -> f64 {
3780 if x.is_nan() {
3781 return x;
3782 }
3783 if x > 709.0 {
3784 return f64::INFINITY;
3785 }
3786 if x < -745.0 {
3787 return 0.0;
3788 }
3789 const LN2: f64 = 0.6931471805599453;
3791 let k = f64_round_half_away(x / LN2) as i32;
3792 let r = x - (k as f64) * LN2;
3793 let mut term = 1.0;
3795 let mut sum = 1.0;
3796 for n in 1..=20 {
3797 term *= r / (n as f64);
3798 sum += term;
3799 if term.abs() < 1e-18 {
3800 break;
3801 }
3802 }
3803 f64_powi(2.0, k) * sum
3805}
3806
3807fn f64_ln(x: f64) -> f64 {
3810 if x <= 0.0 {
3811 return f64::NAN;
3812 }
3813 if x == 1.0 {
3814 return 0.0;
3815 }
3816 const LN2: f64 = 0.6931471805599453;
3818 let mut k = 0i32;
3819 let mut m = x;
3820 while m >= 2.0 {
3821 m *= 0.5;
3822 k += 1;
3823 }
3824 while m < 1.0 {
3825 m *= 2.0;
3826 k -= 1;
3827 }
3828 let u = (m - 1.0) / (m + 1.0);
3831 let u2 = u * u;
3832 let mut term = u;
3833 let mut sum = u;
3834 for k_iter in 1..50 {
3835 term *= u2;
3836 let denom = (2 * k_iter + 1) as f64;
3837 sum += term / denom;
3838 if (term / denom).abs() < 1e-18 {
3839 break;
3840 }
3841 }
3842 2.0 * sum + (k as f64) * LN2
3843}
3844
3845fn f64_powi(base: f64, exp: i32) -> f64 {
3849 if exp == 0 {
3850 return 1.0;
3851 }
3852 let mut result = 1.0;
3853 let mut b = if exp > 0 { base } else { 1.0 / base };
3854 let mut e = exp.unsigned_abs();
3855 while e > 0 {
3856 if e & 1 == 1 {
3857 result *= b;
3858 }
3859 e >>= 1;
3860 if e > 0 {
3861 b *= b;
3862 }
3863 }
3864 result
3865}
3866
3867fn f64_round_half_away(x: f64) -> f64 {
3870 if x.is_nan() || x.is_infinite() {
3871 return x;
3872 }
3873 if x >= 0.0 {
3874 f64_floor(x + 0.5)
3875 } else {
3876 f64_ceil(x - 0.5)
3877 }
3878}
3879
3880fn f64_ceil(x: f64) -> f64 {
3885 if x.is_nan() || x.is_infinite() {
3886 return x;
3887 }
3888 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3889 return x;
3890 }
3891 let trunc = (x as i64) as f64;
3892 if x > 0.0 && x != trunc {
3893 trunc + 1.0
3894 } else {
3895 trunc
3896 }
3897}
3898
3899fn f64_floor(x: f64) -> f64 {
3907 if x.is_nan() || x.is_infinite() {
3908 return x;
3909 }
3910 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3913 return x;
3914 }
3915 let trunc = (x as i64) as f64;
3916 if x < 0.0 && x != trunc {
3917 trunc - 1.0
3918 } else {
3919 trunc
3920 }
3921}
3922
3923fn string_pad(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
3931 if args.len() != 2 && args.len() != 3 {
3932 return Err(EvalError::TypeMismatch {
3933 detail: alloc::format!("{fn_name}() takes 2 or 3 args, got {}", args.len()),
3934 });
3935 }
3936 if args.iter().any(|v| matches!(v, Value::Null)) {
3937 return Ok(Value::Null);
3938 }
3939 let s = value_to_format_text(&args[0]);
3940 let target = match &args[1] {
3941 Value::SmallInt(x) => i64::from(*x),
3942 Value::Int(x) => i64::from(*x),
3943 Value::BigInt(x) => *x,
3944 other => {
3945 return Err(EvalError::TypeMismatch {
3946 detail: alloc::format!(
3947 "{fn_name}(): length must be integer, got {:?}",
3948 other.data_type()
3949 ),
3950 });
3951 }
3952 };
3953 let fill = if args.len() == 3 {
3954 value_to_format_text(&args[2])
3955 } else {
3956 String::from(" ")
3957 };
3958 if target <= 0 {
3959 return Ok(Value::Text(String::new()));
3960 }
3961 let target = target as usize;
3962 let s_chars: Vec<char> = s.chars().collect();
3963 if s_chars.len() >= target {
3964 return Ok(Value::Text(s_chars[..target].iter().collect()));
3967 }
3968 if fill.is_empty() {
3969 return Ok(Value::Text(s));
3970 }
3971 let pad_needed = target - s_chars.len();
3972 let fill_chars: Vec<char> = fill.chars().collect();
3973 let mut padding = String::with_capacity(pad_needed * 4);
3974 for i in 0..pad_needed {
3975 padding.push(fill_chars[i % fill_chars.len()]);
3976 }
3977 if is_left {
3978 Ok(Value::Text(padding + &s))
3979 } else {
3980 Ok(Value::Text(s + &padding))
3981 }
3982}
3983
3984fn string_trim(args: &[Value], side: TrimSide, fn_name: &str) -> Result<Value, EvalError> {
3990 let (input, chars_str) = match args {
3991 [v] => (v.clone(), String::from(" ")),
3992 [v, c] => (v.clone(), {
3993 if matches!(c, Value::Null) {
3995 return Ok(Value::Null);
3996 }
3997 value_to_format_text(c)
3998 }),
3999 _ => {
4000 return Err(EvalError::TypeMismatch {
4001 detail: alloc::format!("{fn_name}() takes 1 or 2 args, got {}", args.len()),
4002 });
4003 }
4004 };
4005 if matches!(input, Value::Null) {
4006 return Ok(Value::Null);
4007 }
4008 let s = value_to_format_text(&input);
4009 let charset: alloc::collections::BTreeSet<char> = chars_str.chars().collect();
4010 let chars: Vec<char> = s.chars().collect();
4011 let mut start = 0usize;
4012 let mut end = chars.len();
4013 if matches!(side, TrimSide::Left | TrimSide::Both) {
4014 while start < end && charset.contains(&chars[start]) {
4015 start += 1;
4016 }
4017 }
4018 if matches!(side, TrimSide::Right | TrimSide::Both) {
4019 while end > start && charset.contains(&chars[end - 1]) {
4020 end -= 1;
4021 }
4022 }
4023 Ok(Value::Text(chars[start..end].iter().collect()))
4024}
4025
4026fn format_string(args: &[Value]) -> Result<Value, EvalError> {
4037 if args.is_empty() {
4038 return Err(EvalError::TypeMismatch {
4039 detail: "format() takes at least 1 arg (format string)".into(),
4040 });
4041 }
4042 let fmt = match &args[0] {
4043 Value::Text(s) => s.clone(),
4044 Value::Null => return Ok(Value::Null),
4045 other => {
4046 return Err(EvalError::TypeMismatch {
4047 detail: format!(
4048 "format(): first arg must be text, got {:?}",
4049 other.data_type()
4050 ),
4051 });
4052 }
4053 };
4054 let arg_values = &args[1..];
4055 let mut out = String::new();
4056 let mut chars = fmt.chars().peekable();
4057 let mut implicit_cursor: usize = 0;
4061 while let Some(c) = chars.next() {
4062 if c != '%' {
4063 out.push(c);
4064 continue;
4065 }
4066 let mut explicit_pos: Option<usize> = None;
4068 let mut digit_buf = String::new();
4070 while let Some(&d) = chars.peek() {
4071 if d.is_ascii_digit() {
4072 digit_buf.push(d);
4073 chars.next();
4074 } else {
4075 break;
4076 }
4077 }
4078 if !digit_buf.is_empty() && matches!(chars.peek(), Some(&'$')) {
4079 chars.next(); explicit_pos =
4081 Some(
4082 digit_buf
4083 .parse::<usize>()
4084 .map_err(|_| EvalError::TypeMismatch {
4085 detail: format!("format(): invalid arg position {digit_buf:?}"),
4086 })?,
4087 );
4088 digit_buf.clear();
4089 }
4090 let spec = match chars.next() {
4092 Some(c) => c,
4093 None => {
4094 return Err(EvalError::TypeMismatch {
4095 detail: "format(): trailing `%` with no specifier".into(),
4096 });
4097 }
4098 };
4099 let _ = digit_buf;
4106 if spec == '%' {
4107 out.push('%');
4108 continue;
4109 }
4110 let arg_index = match explicit_pos {
4111 Some(p) => p.saturating_sub(1),
4112 None => {
4113 let i = implicit_cursor;
4114 implicit_cursor += 1;
4115 i
4116 }
4117 };
4118 let arg = arg_values.get(arg_index).cloned().unwrap_or(Value::Null);
4119 match spec {
4120 's' => match arg {
4121 Value::Null => {} v => out.push_str(&value_to_format_text(&v)),
4123 },
4124 'I' => match arg {
4125 Value::Null => {
4126 return Err(EvalError::TypeMismatch {
4127 detail: "format(): NULL is not a valid identifier (%I)".into(),
4128 });
4129 }
4130 v => {
4131 let s = value_to_format_text(&v);
4132 out.push('"');
4133 for ch in s.chars() {
4134 if ch == '"' {
4135 out.push('"');
4136 out.push('"');
4137 } else {
4138 out.push(ch);
4139 }
4140 }
4141 out.push('"');
4142 }
4143 },
4144 'L' => match arg {
4145 Value::Null => out.push_str("NULL"),
4146 v => {
4147 let s = value_to_format_text(&v);
4148 out.push('\'');
4149 for ch in s.chars() {
4150 if ch == '\'' {
4151 out.push('\'');
4152 out.push('\'');
4153 } else {
4154 out.push(ch);
4155 }
4156 }
4157 out.push('\'');
4158 }
4159 },
4160 other => {
4161 return Err(EvalError::TypeMismatch {
4162 detail: format!(
4163 "format(): unknown specifier '%{other}' \
4164 (v7.17 supports %s %I %L %%)"
4165 ),
4166 });
4167 }
4168 }
4169 }
4170 Ok(Value::Text(out))
4171}
4172
4173fn pg_typeof_name(v: &Value) -> &'static str {
4179 match v {
4180 Value::SmallInt(_) => "smallint",
4181 Value::Int(_) => "integer",
4182 Value::BigInt(_) => "bigint",
4183 Value::Float(_) => "double precision",
4184 Value::Text(_) => "text",
4185 Value::Bool(_) => "boolean",
4186 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => "vector",
4187 Value::Numeric { .. } => "numeric",
4188 Value::Date(_) => "date",
4189 Value::Timestamp(_) => "timestamp without time zone",
4190 Value::Interval { .. } => "interval",
4191 Value::Json(_) => {
4192 "json"
4203 }
4204 Value::Bytes(_) => "bytea",
4205 Value::TextArray(_) => "text[]",
4206 Value::IntArray(_) => "integer[]",
4207 Value::BigIntArray(_) => "bigint[]",
4208 Value::TsVector(_) => "tsvector",
4209 Value::TsQuery(_) => "tsquery",
4210 Value::Uuid(_) => "uuid",
4211 Value::Null => "unknown",
4212 _ => "unknown",
4215 }
4216}
4217
4218fn value_to_format_text(v: &Value) -> String {
4219 match v {
4220 Value::Text(s) | Value::Json(s) => s.clone(),
4221 Value::SmallInt(n) => n.to_string(),
4222 Value::Int(n) => n.to_string(),
4223 Value::BigInt(n) => n.to_string(),
4224 Value::Float(x) => format!("{x}"),
4225 Value::Bool(b) => {
4226 if *b {
4227 "t".into()
4228 } else {
4229 "f".into()
4230 }
4231 }
4232 Value::Null => String::new(),
4233 other => format!("{other:?}"),
4234 }
4235}
4236
4237fn to_char(args: &[Value]) -> Result<Value, EvalError> {
4238 use core::fmt::Write as _;
4239 if args.len() != 2 {
4240 return Err(EvalError::TypeMismatch {
4241 detail: format!("to_char() takes 2 args, got {}", args.len()),
4242 });
4243 }
4244 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4245 return Ok(Value::Null);
4246 }
4247 let Value::Text(fmt) = &args[1] else {
4248 return Err(EvalError::TypeMismatch {
4249 detail: format!(
4250 "to_char() needs a text format, got {:?}",
4251 args[1].data_type()
4252 ),
4253 });
4254 };
4255 let (days, day_micros) = match &args[0] {
4256 Value::Date(d) => (*d, 0_i64),
4257 Value::Timestamp(t) => {
4258 let days = t.div_euclid(86_400_000_000);
4259 (
4260 i32::try_from(days).unwrap_or(i32::MAX),
4261 t.rem_euclid(86_400_000_000),
4262 )
4263 }
4264 other => {
4265 return Err(EvalError::TypeMismatch {
4266 detail: format!(
4267 "to_char() needs DATE or TIMESTAMP, got {:?}",
4268 other.data_type()
4269 ),
4270 });
4271 }
4272 };
4273 let (y, mo, d) = civil_from_days(days);
4274 let secs = day_micros / 1_000_000;
4275 let frac = day_micros % 1_000_000;
4276 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4280 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4281 let ss = u32::try_from(secs % 60).unwrap_or(0);
4282 let hh12 = match hh24 % 12 {
4283 0 => 12,
4284 x => x,
4285 };
4286 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4287 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);
4291 let bytes = fmt.as_bytes();
4292 let mut i = 0;
4293 while i < bytes.len() {
4295 let rest = &bytes[i..];
4297 if rest.starts_with(b"YYYY") {
4298 let _ = write!(out, "{y:04}");
4299 i += 4;
4300 } else if rest.starts_with(b"YY") {
4301 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4302 let yy = (y.rem_euclid(100)) as u32;
4303 let _ = write!(out, "{yy:02}");
4304 i += 2;
4305 } else if rest.starts_with(b"Month") {
4306 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4307 i += 5;
4308 } else if rest.starts_with(b"Mon") {
4309 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4310 i += 3;
4311 } else if rest.starts_with(b"MM") {
4312 let _ = write!(out, "{mo:02}");
4313 i += 2;
4314 } else if rest.starts_with(b"DD") {
4315 let _ = write!(out, "{d:02}");
4316 i += 2;
4317 } else if rest.starts_with(b"HH24") {
4318 let _ = write!(out, "{hh24:02}");
4319 i += 4;
4320 } else if rest.starts_with(b"HH12") {
4321 let _ = write!(out, "{hh12:02}");
4322 i += 4;
4323 } else if rest.starts_with(b"MI") {
4324 let _ = write!(out, "{mi:02}");
4325 i += 2;
4326 } else if rest.starts_with(b"SS") {
4327 let _ = write!(out, "{ss:02}");
4328 i += 2;
4329 } else if rest.starts_with(b"MS") {
4330 let _ = write!(out, "{ms:03}");
4331 i += 2;
4332 } else if rest.starts_with(b"US") {
4333 let _ = write!(out, "{us:06}");
4334 i += 2;
4335 } else if rest.starts_with(b"AM") || rest.starts_with(b"PM") {
4336 out.push_str(ampm);
4337 i += 2;
4338 } else {
4339 out.push(bytes[i] as char);
4341 i += 1;
4342 }
4343 }
4344 Ok(Value::Text(out))
4345}
4346
4347const MONTH_FULL: [&str; 12] = [
4348 "January",
4349 "February",
4350 "March",
4351 "April",
4352 "May",
4353 "June",
4354 "July",
4355 "August",
4356 "September",
4357 "October",
4358 "November",
4359 "December",
4360];
4361const MONTH_ABBR: [&str; 12] = [
4362 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
4363];
4364
4365fn date_format_mysql(args: &[Value]) -> Result<Value, EvalError> {
4384 use core::fmt::Write as _;
4385 if args.len() != 2 {
4386 return Err(EvalError::TypeMismatch {
4387 detail: format!("date_format() takes 2 args, got {}", args.len()),
4388 });
4389 }
4390 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4391 return Ok(Value::Null);
4392 }
4393 let Value::Text(fmt) = &args[1] else {
4394 return Err(EvalError::TypeMismatch {
4395 detail: format!(
4396 "date_format() needs a text format, got {:?}",
4397 args[1].data_type()
4398 ),
4399 });
4400 };
4401 let (days, day_micros) = match &args[0] {
4402 Value::Date(d) => (*d, 0_i64),
4403 Value::Timestamp(t) => {
4404 let days = t.div_euclid(86_400_000_000);
4405 (
4406 i32::try_from(days).unwrap_or(i32::MAX),
4407 t.rem_euclid(86_400_000_000),
4408 )
4409 }
4410 other => {
4411 return Err(EvalError::TypeMismatch {
4412 detail: format!(
4413 "date_format() needs DATE or TIMESTAMP, got {:?}",
4414 other.data_type()
4415 ),
4416 });
4417 }
4418 };
4419 let (y, mo, d) = civil_from_days(days);
4420 let secs = day_micros / 1_000_000;
4421 let frac = day_micros % 1_000_000;
4422 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4423 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4424 let ss = u32::try_from(secs % 60).unwrap_or(0);
4425 let hh12 = match hh24 % 12 {
4426 0 => 12,
4427 x => x,
4428 };
4429 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4430 let us = u32::try_from(frac).unwrap_or(0);
4431
4432 let mut out = String::with_capacity(fmt.len() + 8);
4433 let bytes = fmt.as_bytes();
4434 let mut i = 0;
4435 while i < bytes.len() {
4436 if bytes[i] != b'%' {
4437 out.push(bytes[i] as char);
4438 i += 1;
4439 continue;
4440 }
4441 if i + 1 >= bytes.len() {
4442 out.push('%');
4444 i += 1;
4445 continue;
4446 }
4447 let token = bytes[i + 1];
4448 match token {
4449 b'Y' => {
4450 let _ = write!(out, "{y:04}");
4451 }
4452 b'y' => {
4453 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4454 let yy = (y.rem_euclid(100)) as u32;
4455 let _ = write!(out, "{yy:02}");
4456 }
4457 b'm' => {
4458 let _ = write!(out, "{mo:02}");
4459 }
4460 b'c' => {
4461 let _ = write!(out, "{mo}");
4462 }
4463 b'd' => {
4464 let _ = write!(out, "{d:02}");
4465 }
4466 b'e' => {
4467 let _ = write!(out, "{d}");
4468 }
4469 b'H' => {
4470 let _ = write!(out, "{hh24:02}");
4471 }
4472 b'h' | b'I' => {
4473 let _ = write!(out, "{hh12:02}");
4474 }
4475 b'i' => {
4476 let _ = write!(out, "{mi:02}");
4479 }
4480 b's' | b'S' => {
4481 let _ = write!(out, "{ss:02}");
4482 }
4483 b'f' => {
4484 let _ = write!(out, "{us:06}");
4485 }
4486 b'p' => {
4487 out.push_str(ampm);
4488 }
4489 b'M' => {
4490 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4491 }
4492 b'b' => {
4493 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4494 }
4495 b'%' => {
4496 out.push('%');
4497 }
4498 other => {
4499 out.push(other as char);
4502 }
4503 }
4504 i += 2;
4505 }
4506 Ok(Value::Text(out))
4507}
4508
4509fn unix_timestamp_of(args: &[Value]) -> Result<Value, EvalError> {
4516 if args.len() != 1 {
4517 return Err(EvalError::TypeMismatch {
4518 detail: format!("unix_timestamp() takes 0 or 1 arg, got {}", args.len()),
4519 });
4520 }
4521 match &args[0] {
4522 Value::Null => Ok(Value::Null),
4523 Value::Timestamp(t) => Ok(Value::BigInt(t.div_euclid(1_000_000))),
4524 Value::Date(d) => Ok(Value::BigInt(i64::from(*d) * 86_400)),
4525 other => Err(EvalError::TypeMismatch {
4526 detail: format!(
4527 "unix_timestamp() needs DATE or TIMESTAMP, got {:?}",
4528 other.data_type()
4529 ),
4530 }),
4531 }
4532}
4533
4534fn from_unixtime(args: &[Value]) -> Result<Value, EvalError> {
4538 if !(1..=2).contains(&args.len()) {
4539 return Err(EvalError::TypeMismatch {
4540 detail: format!("from_unixtime() takes 1 or 2 args, got {}", args.len()),
4541 });
4542 }
4543 if args.iter().any(|v| matches!(v, Value::Null)) {
4544 return Ok(Value::Null);
4545 }
4546 let secs: i64 = match &args[0] {
4547 Value::SmallInt(n) => i64::from(*n),
4548 Value::Int(n) => i64::from(*n),
4549 Value::BigInt(n) => *n,
4550 Value::Float(x) => *x as i64,
4551 Value::Numeric { scaled, scale } => {
4552 let denom = 10_i128.pow(u32::from(*scale));
4553 i64::try_from(scaled.div_euclid(denom)).unwrap_or(i64::MAX)
4554 }
4555 other => {
4556 return Err(EvalError::TypeMismatch {
4557 detail: format!(
4558 "from_unixtime() needs a numeric epoch second count, got {:?}",
4559 other.data_type()
4560 ),
4561 });
4562 }
4563 };
4564 let ts = Value::Timestamp(secs.saturating_mul(1_000_000));
4565 if args.len() == 1 {
4566 Ok(ts)
4567 } else {
4568 date_format_mysql(&[ts, args[1].clone()])
4569 }
4570}
4571
4572fn date_trunc(args: &[Value]) -> Result<Value, EvalError> {
4577 if args.len() != 2 {
4578 return Err(EvalError::TypeMismatch {
4579 detail: format!("date_trunc() takes 2 args, got {}", args.len()),
4580 });
4581 }
4582 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4583 return Ok(Value::Null);
4584 }
4585 let Value::Text(unit) = &args[0] else {
4586 return Err(EvalError::TypeMismatch {
4587 detail: format!(
4588 "date_trunc() needs a text unit, got {:?}",
4589 args[0].data_type()
4590 ),
4591 });
4592 };
4593 let micros = match &args[1] {
4596 Value::Timestamp(t) => *t,
4597 Value::Date(d) => i64::from(*d) * 86_400_000_000,
4598 other => {
4599 return Err(EvalError::TypeMismatch {
4600 detail: format!(
4601 "date_trunc() needs DATE or TIMESTAMP, got {:?}",
4602 other.data_type()
4603 ),
4604 });
4605 }
4606 };
4607 let unit_lc = unit.to_ascii_lowercase();
4608 let days = micros.div_euclid(86_400_000_000);
4609 let day_micros = micros.rem_euclid(86_400_000_000);
4610 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
4611 let (y, m, _) = civil_from_days(day_i32);
4612 let truncated = match unit_lc.as_str() {
4613 "year" => i64::from(days_from_civil(y, 1, 1)) * 86_400_000_000,
4614 "month" => i64::from(days_from_civil(y, m, 1)) * 86_400_000_000,
4615 "day" => days * 86_400_000_000,
4616 "hour" => days * 86_400_000_000 + (day_micros / 3_600_000_000) * 3_600_000_000,
4617 "minute" => days * 86_400_000_000 + (day_micros / 60_000_000) * 60_000_000,
4618 "second" => days * 86_400_000_000 + (day_micros / 1_000_000) * 1_000_000,
4619 other => {
4620 return Err(EvalError::TypeMismatch {
4621 detail: format!(
4622 "unknown date_trunc unit {other:?}; \
4623 supported: year, month, day, hour, minute, second"
4624 ),
4625 });
4626 }
4627 };
4628 Ok(Value::Timestamp(truncated))
4629}
4630
4631pub fn cast_value(v: Value, target: CastTarget) -> Result<Value, EvalError> {
4633 if matches!(v, Value::Null) {
4634 return Ok(Value::Null);
4635 }
4636 match target {
4637 CastTarget::Vector => cast_to_vector(v),
4638 CastTarget::Text => Ok(Value::Text(value_to_text(&v))),
4639 CastTarget::Int => cast_numeric_to_int(v),
4640 CastTarget::BigInt => cast_numeric_to_bigint(v),
4641 CastTarget::Float => cast_numeric_to_float(v),
4642 CastTarget::Bool => cast_to_bool(v),
4643 CastTarget::Date => cast_to_date(v),
4644 CastTarget::Timestamp | CastTarget::Timestamptz => cast_to_timestamp(v),
4647 CastTarget::Interval => cast_to_interval(v),
4651 CastTarget::Json | CastTarget::Jsonb => match v {
4655 Value::Json(s) => Ok(Value::Json(s)),
4656 Value::Text(s) => Ok(Value::Json(s)),
4657 other => Err(EvalError::TypeMismatch {
4658 detail: alloc::format!(
4659 "::json / ::jsonb only accepts TEXT-shape inputs, got {:?}",
4660 other.data_type()
4661 ),
4662 }),
4663 },
4664 CastTarget::RegType | CastTarget::RegClass => match v {
4682 Value::Text(s) => {
4683 let bare = s.rsplit('.').next().unwrap_or(&s).to_string();
4688 Ok(Value::Text(bare))
4689 }
4690 Value::Int(n) => Ok(Value::Text(alloc::format!("{n}"))),
4691 Value::BigInt(n) => Ok(Value::Text(alloc::format!("{n}"))),
4692 other => Err(EvalError::TypeMismatch {
4693 detail: alloc::format!(
4694 "::regtype / ::regclass accepts TEXT (name) or integer (oid), got {:?}",
4695 other.data_type()
4696 ),
4697 }),
4698 },
4699 CastTarget::TextArray => match v {
4703 Value::TextArray(items) => Ok(Value::TextArray(items)),
4704 Value::Text(s) => decode_text_array_external(&s).map(Value::TextArray),
4705 other => Err(EvalError::TypeMismatch {
4706 detail: alloc::format!(
4707 "::TEXT[] only accepts TEXT / TEXT[] inputs, got {:?}",
4708 other.data_type()
4709 ),
4710 }),
4711 },
4712 CastTarget::IntArray => cast_to_int_array(v),
4716 CastTarget::BigIntArray => cast_to_bigint_array(v),
4717 CastTarget::TsVector => match v {
4724 Value::TsVector(items) => Ok(Value::TsVector(items)),
4725 Value::Text(s) => decode_tsvector_external(&s).map(Value::TsVector),
4726 other => Err(EvalError::TypeMismatch {
4727 detail: alloc::format!(
4728 "::tsvector only accepts TEXT / tsvector inputs, got {:?}",
4729 other.data_type()
4730 ),
4731 }),
4732 },
4733 CastTarget::TsQuery => match v {
4734 Value::TsQuery(ast) => Ok(Value::TsQuery(ast)),
4735 Value::Text(s) => decode_tsquery_external(&s).map(Value::TsQuery),
4736 other => Err(EvalError::TypeMismatch {
4737 detail: alloc::format!(
4738 "::tsquery only accepts TEXT / tsquery inputs, got {:?}",
4739 other.data_type()
4740 ),
4741 }),
4742 },
4743 CastTarget::Uuid => match v {
4748 Value::Uuid(b) => Ok(Value::Uuid(b)),
4749 Value::Text(s) => match spg_storage::parse_uuid_str(&s) {
4750 Some(b) => Ok(Value::Uuid(b)),
4751 None => Err(EvalError::TypeMismatch {
4752 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4753 }),
4754 },
4755 other => Err(EvalError::TypeMismatch {
4756 detail: alloc::format!(
4757 "::uuid only accepts TEXT / uuid inputs, got {:?}",
4758 other.data_type()
4759 ),
4760 }),
4761 },
4762 CastTarget::Bytea => match v {
4768 Value::Bytes(b) => Ok(Value::Bytes(b)),
4769 Value::Text(s) => match crate::decode_bytea_literal(&s) {
4770 Ok(b) => Ok(Value::Bytes(b)),
4771 Err(msg) => Err(EvalError::TypeMismatch {
4772 detail: alloc::format!("invalid input syntax for type bytea: {msg}"),
4773 }),
4774 },
4775 other => Err(EvalError::TypeMismatch {
4776 detail: alloc::format!(
4777 "::bytea only accepts TEXT / bytea inputs, got {:?}",
4778 other.data_type()
4779 ),
4780 }),
4781 },
4782 }
4783}
4784
4785fn cast_to_int_array(v: Value) -> Result<Value, EvalError> {
4786 match v {
4787 Value::IntArray(items) => Ok(Value::IntArray(items)),
4788 Value::BigIntArray(items) => {
4789 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4790 for item in items {
4791 match item {
4792 None => out.push(None),
4793 Some(n) => match i32::try_from(n) {
4794 Ok(x) => out.push(Some(x)),
4795 Err(_) => {
4796 return Err(EvalError::TypeMismatch {
4797 detail: alloc::format!("::INT[] element {n} overflows i32"),
4798 });
4799 }
4800 },
4801 }
4802 }
4803 Ok(Value::IntArray(out))
4804 }
4805 Value::Text(s) => decode_int_array_external(&s).map(Value::IntArray),
4806 Value::TextArray(items) => {
4807 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4808 for item in items {
4809 match item {
4810 None => out.push(None),
4811 Some(s) => match s.parse::<i32>() {
4812 Ok(n) => out.push(Some(n)),
4813 Err(_) => {
4814 return Err(EvalError::TypeMismatch {
4815 detail: alloc::format!("::INT[] cannot parse {s:?}"),
4816 });
4817 }
4818 },
4819 }
4820 }
4821 Ok(Value::IntArray(out))
4822 }
4823 other => Err(EvalError::TypeMismatch {
4824 detail: alloc::format!("::INT[] does not accept {:?}", other.data_type()),
4825 }),
4826 }
4827}
4828
4829fn cast_to_bigint_array(v: Value) -> Result<Value, EvalError> {
4830 match v {
4831 Value::BigIntArray(items) => Ok(Value::BigIntArray(items)),
4832 Value::IntArray(items) => Ok(Value::BigIntArray(
4833 items.into_iter().map(|x| x.map(i64::from)).collect(),
4834 )),
4835 Value::Text(s) => decode_bigint_array_external(&s).map(Value::BigIntArray),
4836 Value::TextArray(items) => {
4837 let mut out: Vec<Option<i64>> = Vec::with_capacity(items.len());
4838 for item in items {
4839 match item {
4840 None => out.push(None),
4841 Some(s) => match s.parse::<i64>() {
4842 Ok(n) => out.push(Some(n)),
4843 Err(_) => {
4844 return Err(EvalError::TypeMismatch {
4845 detail: alloc::format!("::BIGINT[] cannot parse {s:?}"),
4846 });
4847 }
4848 },
4849 }
4850 }
4851 Ok(Value::BigIntArray(out))
4852 }
4853 other => Err(EvalError::TypeMismatch {
4854 detail: alloc::format!("::BIGINT[] does not accept {:?}", other.data_type()),
4855 }),
4856 }
4857}
4858
4859fn decode_int_array_external(s: &str) -> Result<Vec<Option<i32>>, EvalError> {
4860 let trimmed = s.trim();
4861 let inner = trimmed
4862 .strip_prefix('{')
4863 .and_then(|x| x.strip_suffix('}'))
4864 .ok_or_else(|| EvalError::TypeMismatch {
4865 detail: alloc::format!("INT[] literal {s:?} must be enclosed in '{{...}}'"),
4866 })?;
4867 if inner.trim().is_empty() {
4868 return Ok(Vec::new());
4869 }
4870 inner
4871 .split(',')
4872 .map(|part| {
4873 let p = part.trim();
4874 if p.eq_ignore_ascii_case("NULL") {
4875 Ok(None)
4876 } else {
4877 p.parse::<i32>()
4878 .map(Some)
4879 .map_err(|_| EvalError::TypeMismatch {
4880 detail: alloc::format!("INT[] element {p:?} is not an i32"),
4881 })
4882 }
4883 })
4884 .collect()
4885}
4886
4887fn decode_bigint_array_external(s: &str) -> Result<Vec<Option<i64>>, EvalError> {
4888 let trimmed = s.trim();
4889 let inner = trimmed
4890 .strip_prefix('{')
4891 .and_then(|x| x.strip_suffix('}'))
4892 .ok_or_else(|| EvalError::TypeMismatch {
4893 detail: alloc::format!("BIGINT[] literal {s:?} must be enclosed in '{{...}}'"),
4894 })?;
4895 if inner.trim().is_empty() {
4896 return Ok(Vec::new());
4897 }
4898 inner
4899 .split(',')
4900 .map(|part| {
4901 let p = part.trim();
4902 if p.eq_ignore_ascii_case("NULL") {
4903 Ok(None)
4904 } else {
4905 p.parse::<i64>()
4906 .map(Some)
4907 .map_err(|_| EvalError::TypeMismatch {
4908 detail: alloc::format!("BIGINT[] element {p:?} is not an i64"),
4909 })
4910 }
4911 })
4912 .collect()
4913}
4914
4915fn decode_text_array_external(s: &str) -> Result<Vec<Option<String>>, EvalError> {
4920 let trimmed = s.trim();
4921 let inner = trimmed
4922 .strip_prefix('{')
4923 .and_then(|x| x.strip_suffix('}'))
4924 .ok_or_else(|| EvalError::TypeMismatch {
4925 detail: alloc::format!("TEXT[] literal {s:?} must be enclosed in '{{...}}'"),
4926 })?;
4927 let mut out: Vec<Option<String>> = Vec::new();
4928 if inner.trim().is_empty() {
4929 return Ok(out);
4930 }
4931 let bytes = inner.as_bytes();
4932 let mut i = 0;
4933 while i <= bytes.len() {
4934 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
4935 i += 1;
4936 }
4937 if i < bytes.len() && bytes[i] == b'"' {
4938 i += 1;
4939 let mut buf = String::new();
4940 while i < bytes.len() && bytes[i] != b'"' {
4941 if bytes[i] == b'\\' && i + 1 < bytes.len() {
4942 buf.push(bytes[i + 1] as char);
4943 i += 2;
4944 } else {
4945 buf.push(bytes[i] as char);
4946 i += 1;
4947 }
4948 }
4949 if i >= bytes.len() {
4950 return Err(EvalError::TypeMismatch {
4951 detail: "unterminated quoted element in TEXT[] literal".into(),
4952 });
4953 }
4954 i += 1;
4955 out.push(Some(buf));
4956 } else {
4957 let start = i;
4958 while i < bytes.len() && bytes[i] != b',' {
4959 i += 1;
4960 }
4961 let raw = inner[start..i].trim();
4962 if raw.eq_ignore_ascii_case("NULL") {
4963 out.push(None);
4964 } else {
4965 out.push(Some(raw.to_string()));
4966 }
4967 }
4968 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
4969 i += 1;
4970 }
4971 if i >= bytes.len() {
4972 break;
4973 }
4974 if bytes[i] != b',' {
4975 return Err(EvalError::TypeMismatch {
4976 detail: "expected ',' between TEXT[] elements".into(),
4977 });
4978 }
4979 i += 1;
4980 }
4981 Ok(out)
4982}
4983
4984fn cast_to_interval(v: Value) -> Result<Value, EvalError> {
4985 match v {
4986 Value::Interval { months, micros } => Ok(Value::Interval { months, micros }),
4987 Value::Text(s) => {
4988 let (months, micros) = spg_sql::parser::parse_interval_text(&s).ok_or_else(|| {
4989 EvalError::TypeMismatch {
4990 detail: alloc::format!("cannot parse {s:?} as INTERVAL"),
4991 }
4992 })?;
4993 Ok(Value::Interval { months, micros })
4994 }
4995 other => Err(EvalError::TypeMismatch {
4996 detail: alloc::format!(
4997 "::INTERVAL only accepts TEXT-shape inputs, got {:?}",
4998 other.data_type()
4999 ),
5000 }),
5001 }
5002}
5003
5004fn cast_to_date(v: Value) -> Result<Value, EvalError> {
5005 match v {
5006 Value::Date(d) => Ok(Value::Date(d)),
5007 Value::Int(n) => Ok(Value::Date(n)),
5010 Value::BigInt(n) => {
5011 i32::try_from(n)
5012 .map(Value::Date)
5013 .map_err(|_| EvalError::TypeMismatch {
5014 detail: "bigint days-since-epoch out of DATE range".into(),
5015 })
5016 }
5017 Value::Timestamp(t) => {
5019 let days = t.div_euclid(86_400_000_000);
5020 i32::try_from(days)
5021 .map(Value::Date)
5022 .map_err(|_| EvalError::TypeMismatch {
5023 detail: "timestamp out of DATE range".into(),
5024 })
5025 }
5026 Value::Text(s) => parse_date_literal(&s)
5027 .map(Value::Date)
5028 .ok_or(EvalError::TypeMismatch {
5029 detail: format!("cannot parse {s:?} as DATE (expected YYYY-MM-DD)"),
5030 }),
5031 other => Err(EvalError::TypeMismatch {
5032 detail: format!("cannot cast {:?} to DATE", other.data_type()),
5033 }),
5034 }
5035}
5036
5037fn cast_to_timestamp(v: Value) -> Result<Value, EvalError> {
5038 match v {
5039 Value::Timestamp(t) => Ok(Value::Timestamp(t)),
5040 Value::Int(n) => Ok(Value::Timestamp(i64::from(n))),
5044 Value::BigInt(n) => Ok(Value::Timestamp(n)),
5045 Value::Date(d) => Ok(Value::Timestamp(i64::from(d) * 86_400_000_000)),
5047 Value::Text(s) => {
5048 parse_timestamp_literal(&s)
5049 .map(Value::Timestamp)
5050 .ok_or(EvalError::TypeMismatch {
5051 detail: format!(
5052 "cannot parse {s:?} as TIMESTAMP \
5053 (expected YYYY-MM-DD[ HH:MM:SS[.ffffff]])"
5054 ),
5055 })
5056 }
5057 other => Err(EvalError::TypeMismatch {
5058 detail: format!("cannot cast {:?} to TIMESTAMP", other.data_type()),
5059 }),
5060 }
5061}
5062
5063fn value_to_text(v: &Value) -> String {
5064 match v {
5065 Value::SmallInt(n) => format!("{n}"),
5069 Value::Int(n) => format!("{n}"),
5070 Value::BigInt(n) => format!("{n}"),
5071 Value::Float(x) => format!("{x}"),
5072 Value::Text(s) | Value::Json(s) => s.clone(),
5074 Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
5075 Value::Vector(v) => {
5076 let cells: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
5077 format!("[{}]", cells.join(", "))
5078 }
5079 Value::Sq8Vector(q) => {
5084 let cells: Vec<String> = spg_storage::quantize::dequantize(q)
5085 .iter()
5086 .map(|x| format!("{x}"))
5087 .collect();
5088 format!("[{}]", cells.join(", "))
5089 }
5090 Value::HalfVector(h) => {
5093 let cells: Vec<String> = h.to_f32_vec().iter().map(|x| format!("{x}")).collect();
5094 format!("[{}]", cells.join(", "))
5095 }
5096 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
5097 Value::Date(d) => format_date(*d),
5098 Value::Timestamp(t) => format_timestamp(*t),
5099 Value::Interval { months, micros } => format_interval(*months, *micros),
5100 Value::Null => "NULL".into(),
5101 Value::Bytes(b) => format_bytea_hex(b),
5103 Value::TextArray(items) => format_text_array(items),
5105 Value::IntArray(items) => format_int_array(items),
5106 Value::BigIntArray(items) => format_bigint_array(items),
5107 Value::TsVector(lexs) => format_tsvector(lexs),
5109 Value::TsQuery(ast) => format_tsquery(ast),
5110 Value::Uuid(b) => spg_storage::format_uuid(b),
5113 Value::Time(us) => format_time(*us),
5115 Value::TimeTz { us, offset_secs } => format_timetz(*us, *offset_secs),
5117 Value::Year(y) => format!("{y:04}"),
5119 Value::Money(c) => format_money(*c),
5121 Value::Range { .. } => crate::format_range_text(v),
5125 Value::Hstore(pairs) => crate::format_hstore_text(pairs),
5127 Value::IntArray2D(rows) => crate::format_int_2d_text_pub(rows),
5129 Value::BigIntArray2D(rows) => crate::format_bigint_2d_text_pub(rows),
5130 Value::TextArray2D(rows) => crate::format_text_2d_text_pub(rows),
5131 _ => format!("{v:?}"),
5133 }
5134}
5135
5136pub fn format_date(days: i32) -> String {
5139 let (y, m, d) = civil_from_days(days);
5140 format!("{y:04}-{m:02}-{d:02}")
5141}
5142
5143pub fn format_timestamptz(micros: i64) -> String {
5154 let base = format_timestamp(micros);
5155 let mut s = String::with_capacity(base.len() + 3);
5156 s.push_str(&base);
5157 s.push_str("+00");
5158 s
5159}
5160
5161pub fn format_money(cents: i64) -> String {
5165 let neg = cents < 0;
5166 let abs = cents.unsigned_abs();
5167 let dollars = abs / 100;
5168 let cc = abs % 100;
5169 let dollar_str = dollars.to_string();
5171 let bytes = dollar_str.as_bytes();
5172 let mut int_part = String::with_capacity(dollar_str.len() + dollar_str.len() / 3);
5173 for (i, b) in bytes.iter().enumerate() {
5174 let from_right = bytes.len() - i;
5177 if i > 0 && from_right % 3 == 0 {
5178 int_part.push(',');
5179 }
5180 int_part.push(*b as char);
5181 }
5182 let sign = if neg { "-" } else { "" };
5183 format!("{sign}${int_part}.{cc:02}")
5184}
5185
5186pub fn format_timetz(us: i64, offset_secs: i32) -> String {
5191 let time = format_time(us);
5192 let sign = if offset_secs < 0 { '-' } else { '+' };
5193 let abs = offset_secs.unsigned_abs();
5194 let oh = abs / 3600;
5195 let om = (abs % 3600) / 60;
5196 if om == 0 {
5197 format!("{time}{sign}{oh:02}")
5198 } else {
5199 format!("{time}{sign}{oh:02}:{om:02}")
5200 }
5201}
5202
5203pub fn format_time(us: i64) -> String {
5208 let total_secs = us.div_euclid(1_000_000);
5209 let frac = us.rem_euclid(1_000_000);
5210 let hh = total_secs / 3600;
5211 let mm = (total_secs / 60) % 60;
5212 let ss = total_secs % 60;
5213 if frac == 0 {
5214 format!("{hh:02}:{mm:02}:{ss:02}")
5215 } else {
5216 let raw = format!("{frac:06}");
5217 let trimmed = raw.trim_end_matches('0');
5218 format!("{hh:02}:{mm:02}:{ss:02}.{trimmed}")
5219 }
5220}
5221
5222pub fn format_timestamp(micros: i64) -> String {
5223 const MICROS_PER_DAY: i64 = 86_400_000_000;
5224 let days = micros.div_euclid(MICROS_PER_DAY);
5227 let day_micros = micros.rem_euclid(MICROS_PER_DAY);
5228 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
5229 let (y, m, d) = civil_from_days(day_i32);
5230 let secs = day_micros / 1_000_000;
5231 let frac = day_micros % 1_000_000;
5232 let hh = secs / 3600;
5233 let mm = (secs / 60) % 60;
5234 let ss = secs % 60;
5235 if frac == 0 {
5236 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}")
5237 } else {
5238 let raw = format!("{frac:06}");
5240 let trimmed = raw.trim_end_matches('0');
5241 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}.{trimmed}")
5242 }
5243}
5244
5245#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5250fn civil_from_days(days: i32) -> (i32, u32, u32) {
5251 let z = i64::from(days) + 719_468;
5252 let era = z.div_euclid(146_097);
5253 let doe = (z - era * 146_097) as u32;
5257 let yoe = (doe.saturating_sub(doe / 1460) + doe / 36524 - doe / 146_096) / 365;
5258 let y_base = i64::from(yoe) + era * 400;
5259 let doy = doe.saturating_sub(365 * yoe + yoe / 4 - yoe / 100);
5260 let mp = (5 * doy + 2) / 153;
5261 let d = doy.saturating_sub((153 * mp + 2) / 5) + 1;
5262 let m = if mp < 10 { mp + 3 } else { mp - 9 };
5263 let y = if m <= 2 { y_base + 1 } else { y_base };
5264 (y as i32, m, d)
5265}
5266
5267#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5270pub fn days_from_civil(y: i32, m: u32, d: u32) -> i32 {
5271 let y_adj = if m <= 2 {
5272 i64::from(y) - 1
5273 } else {
5274 i64::from(y)
5275 };
5276 let era = y_adj.div_euclid(400);
5277 let yoe = (y_adj - era * 400) as u32;
5278 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d.saturating_sub(1);
5279 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
5280 let total = era * 146_097 + i64::from(doe) - 719_468;
5281 i32::try_from(total).unwrap_or(i32::MAX)
5282}
5283
5284pub fn parse_date_literal(s: &str) -> Option<i32> {
5288 let bytes = s.as_bytes();
5289 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
5290 return None;
5291 }
5292 let y: i32 = s[0..4].parse().ok()?;
5293 let m: u32 = s[5..7].parse().ok()?;
5294 let d: u32 = s[8..10].parse().ok()?;
5295 if !(1..=12).contains(&m) || !(1..=31).contains(&d) {
5296 return None;
5297 }
5298 Some(days_from_civil(y, m, d))
5299}
5300
5301pub fn parse_timestamp_literal(s: &str) -> Option<i64> {
5306 let trimmed = s.trim();
5307 let (date_part, time_part) = match trimmed.find([' ', 'T']) {
5308 Some(i) => (&trimmed[..i], Some(&trimmed[i + 1..])),
5309 None => (trimmed, None),
5310 };
5311 let days = parse_date_literal(date_part)?;
5312 let (day_micros, tz_offset_micros) = match time_part {
5313 None => (0, 0),
5314 Some(t) => parse_time_of_day_micros(t)?,
5315 };
5316 Some(i64::from(days) * 86_400_000_000 + day_micros - tz_offset_micros)
5326}
5327
5328fn parse_time_of_day_micros(t: &str) -> Option<(i64, i64)> {
5341 let t = t.trim();
5342 let (core, tz_micros) = if let Some(rest) = t.strip_suffix('Z') {
5348 (rest, 0i64)
5349 } else if let Some(rest) = t.strip_suffix(" UTC").or_else(|| t.strip_suffix("UTC")) {
5350 (rest, 0i64)
5351 } else if let Some((idx, sign_byte)) = find_offset_sign(t) {
5352 let suffix = &t[idx..];
5353 let micros = parse_tz_offset_suffix(suffix, sign_byte == b'+')?;
5354 (&t[..idx], micros)
5355 } else {
5356 (t, 0i64)
5357 };
5358 let (time, frac_str) = match core.split_once('.') {
5359 Some((a, b)) => (a, Some(b)),
5360 None => (core, None),
5361 };
5362 let bytes = time.as_bytes();
5363 if bytes.len() != 8 || bytes[2] != b':' || bytes[5] != b':' {
5364 return None;
5365 }
5366 let hh: i64 = time[0..2].parse().ok()?;
5367 let mm: i64 = time[3..5].parse().ok()?;
5368 let ss: i64 = time[6..8].parse().ok()?;
5369 if !(0..24).contains(&hh) || !(0..60).contains(&mm) || !(0..60).contains(&ss) {
5370 return None;
5371 }
5372 let frac_micros: i64 = match frac_str {
5373 None => 0,
5374 Some(f) => {
5375 if f.is_empty() || f.len() > 9 {
5377 return None;
5378 }
5379 let mut padded = String::with_capacity(6);
5380 padded.push_str(&f[..f.len().min(6)]);
5381 while padded.len() < 6 {
5382 padded.push('0');
5383 }
5384 padded.parse().ok()?
5385 }
5386 };
5387 Some((
5388 ((hh * 3600 + mm * 60 + ss) * 1_000_000) + frac_micros,
5389 tz_micros,
5390 ))
5391}
5392
5393fn find_offset_sign(t: &str) -> Option<(usize, u8)> {
5399 let bytes = t.as_bytes();
5400 if bytes.len() < 9 {
5402 return None;
5403 }
5404 for i in 8..bytes.len() {
5405 match bytes[i] {
5406 b'+' | b'-' => return Some((i, bytes[i])),
5407 _ => {}
5408 }
5409 }
5410 None
5411}
5412
5413fn parse_tz_offset_suffix(suffix: &str, is_positive: bool) -> Option<i64> {
5417 let body = &suffix[1..];
5419 let (hh, mm): (i64, i64) = if let Some((h, m)) = body.split_once(':') {
5420 (h.parse().ok()?, m.parse().ok()?)
5421 } else {
5422 match body.len() {
5423 2 => (body.parse().ok()?, 0),
5424 3 => {
5425 return None;
5429 }
5430 4 => {
5431 let h: i64 = body[0..2].parse().ok()?;
5432 let m: i64 = body[2..4].parse().ok()?;
5433 (h, m)
5434 }
5435 _ => return None,
5436 }
5437 };
5438 if !(0..=18).contains(&hh) || !(0..60).contains(&mm) {
5439 return None;
5440 }
5441 let abs = (hh * 3600 + mm * 60) * 1_000_000;
5442 Some(if is_positive { abs } else { -abs })
5443}
5444
5445pub fn format_interval(months: i32, micros: i64) -> String {
5450 const MICROS_PER_DAY: i64 = 86_400_000_000;
5451 let mut parts: Vec<String> = Vec::new();
5452 let years = months / 12;
5453 let mons = months % 12;
5454 let unit = |n: i64, singular: &'static str, plural: &'static str| -> &'static str {
5457 if n == 1 { singular } else { plural }
5458 };
5459 if years != 0 {
5460 parts.push(format!(
5461 "{years} {}",
5462 unit(i64::from(years), "year", "years")
5463 ));
5464 }
5465 if mons != 0 {
5466 parts.push(format!("{mons} {}", unit(i64::from(mons), "mon", "mons")));
5467 }
5468 let days = micros / MICROS_PER_DAY;
5469 let mut rem = micros % MICROS_PER_DAY;
5470 if days != 0 {
5471 parts.push(format!("{days} {}", unit(days, "day", "days")));
5472 }
5473 if rem != 0 {
5474 let neg = rem < 0;
5475 if neg {
5476 rem = -rem;
5477 }
5478 let secs = rem / 1_000_000;
5479 let frac = rem % 1_000_000;
5480 let hh = secs / 3600;
5481 let mm = (secs / 60) % 60;
5482 let ss = secs % 60;
5483 let sign = if neg { "-" } else { "" };
5484 if frac == 0 {
5485 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}"));
5486 } else {
5487 let raw = format!("{frac:06}");
5488 let trimmed = raw.trim_end_matches('0');
5489 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}.{trimmed}"));
5490 }
5491 }
5492 if parts.is_empty() {
5493 "0".into()
5494 } else {
5495 parts.join(" ")
5496 }
5497}
5498
5499fn add_months_to_civil(y: i32, m: u32, d: u32, months: i32) -> (i32, u32, u32) {
5502 let total_months = i64::from(y) * 12 + i64::from(m) - 1 + i64::from(months);
5503 let new_year = i32::try_from(total_months.div_euclid(12)).unwrap_or(i32::MAX);
5504 let new_month_zero = total_months.rem_euclid(12);
5505 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5506 let new_month = (new_month_zero as u32) + 1;
5507 let max_day = days_in_month(new_year, new_month);
5508 (new_year, new_month, d.min(max_day))
5509}
5510
5511const fn days_in_month(y: i32, m: u32) -> u32 {
5512 match m {
5513 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
5514 2 => {
5515 if y.rem_euclid(4) == 0 && (y.rem_euclid(100) != 0 || y.rem_euclid(400) == 0) {
5517 29
5518 } else {
5519 28
5520 }
5521 }
5522 _ => 30,
5525 }
5526}
5527
5528pub fn format_text_array(items: &[Option<String>]) -> String {
5534 let mut out = String::with_capacity(2 + items.len() * 8);
5535 out.push('{');
5536 for (i, item) in items.iter().enumerate() {
5537 if i > 0 {
5538 out.push(',');
5539 }
5540 match item {
5541 None => out.push_str("NULL"),
5542 Some(s) => {
5543 let needs_quote = s.is_empty()
5544 || s.eq_ignore_ascii_case("NULL")
5545 || s.chars()
5546 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
5547 if needs_quote {
5548 out.push('"');
5549 for c in s.chars() {
5550 if c == '"' || c == '\\' {
5551 out.push('\\');
5552 }
5553 out.push(c);
5554 }
5555 out.push('"');
5556 } else {
5557 out.push_str(s);
5558 }
5559 }
5560 }
5561 }
5562 out.push('}');
5563 out
5564}
5565
5566pub fn format_int_array(items: &[Option<i32>]) -> String {
5570 let mut out = String::with_capacity(2 + items.len() * 4);
5571 out.push('{');
5572 for (i, item) in items.iter().enumerate() {
5573 if i > 0 {
5574 out.push(',');
5575 }
5576 match item {
5577 None => out.push_str("NULL"),
5578 Some(n) => out.push_str(&n.to_string()),
5579 }
5580 }
5581 out.push('}');
5582 out
5583}
5584
5585pub fn format_bigint_array(items: &[Option<i64>]) -> String {
5588 let mut out = String::with_capacity(2 + items.len() * 6);
5589 out.push('{');
5590 for (i, item) in items.iter().enumerate() {
5591 if i > 0 {
5592 out.push(',');
5593 }
5594 match item {
5595 None => out.push_str("NULL"),
5596 Some(n) => out.push_str(&n.to_string()),
5597 }
5598 }
5599 out.push('}');
5600 out
5601}
5602
5603pub fn format_tsvector(lexs: &[TsLexeme]) -> String {
5609 let mut out = String::with_capacity(lexs.len() * 12);
5610 for (i, l) in lexs.iter().enumerate() {
5611 if i > 0 {
5612 out.push(' ');
5613 }
5614 out.push('\'');
5615 for c in l.word.chars() {
5616 if c == '\'' {
5617 out.push('\'');
5618 }
5619 out.push(c);
5620 }
5621 out.push('\'');
5622 if !l.positions.is_empty() {
5623 for (pi, p) in l.positions.iter().enumerate() {
5624 out.push(if pi == 0 { ':' } else { ',' });
5625 out.push_str(&p.to_string());
5626 }
5627 match l.weight {
5632 3 => out.push('A'),
5633 2 => out.push('B'),
5634 1 => out.push('C'),
5635 _ => {}
5636 }
5637 }
5638 }
5639 out
5640}
5641
5642pub fn format_tsquery(ast: &TsQueryAst) -> String {
5645 fn go(ast: &TsQueryAst, parent_prec: u8, out: &mut String) {
5646 let (own_prec, write_self): (u8, &dyn Fn(&mut String)) = match ast {
5648 TsQueryAst::Or(_, _) => (1, &|_| {}),
5649 TsQueryAst::And(_, _) | TsQueryAst::Phrase { .. } => (2, &|_| {}),
5650 TsQueryAst::Not(_) => (3, &|_| {}),
5651 TsQueryAst::Term { .. } => (4, &|_| {}),
5652 };
5653 let need_parens = own_prec < parent_prec;
5654 if need_parens {
5655 out.push('(');
5656 }
5657 match ast {
5658 TsQueryAst::Term { word, .. } => {
5659 out.push('\'');
5660 for c in word.chars() {
5661 if c == '\'' {
5662 out.push('\'');
5663 }
5664 out.push(c);
5665 }
5666 out.push('\'');
5667 }
5668 TsQueryAst::And(a, b) => {
5669 go(a, own_prec, out);
5670 out.push_str(" & ");
5671 go(b, own_prec, out);
5672 }
5673 TsQueryAst::Or(a, b) => {
5674 go(a, own_prec, out);
5675 out.push_str(" | ");
5676 go(b, own_prec, out);
5677 }
5678 TsQueryAst::Not(x) => {
5679 out.push('!');
5680 go(x, own_prec, out);
5681 }
5682 TsQueryAst::Phrase {
5683 left,
5684 right,
5685 distance,
5686 } => {
5687 go(left, own_prec, out);
5688 out.push_str(&alloc::format!(" <{distance}> "));
5689 go(right, own_prec, out);
5690 }
5691 }
5692 write_self(out);
5693 if need_parens {
5694 out.push(')');
5695 }
5696 }
5697 let mut out = String::new();
5698 go(ast, 0, &mut out);
5699 out
5700}
5701
5702pub fn decode_tsvector_external(s: &str) -> Result<Vec<TsLexeme>, EvalError> {
5711 let mut out: Vec<TsLexeme> = Vec::new();
5712 let mut i = 0;
5713 let bytes = s.as_bytes();
5714 while i < bytes.len() {
5715 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
5716 i += 1;
5717 }
5718 if i >= bytes.len() {
5719 break;
5720 }
5721 let word = if bytes[i] == b'\'' {
5724 i += 1;
5725 let mut w = String::new();
5726 loop {
5727 if i >= bytes.len() {
5728 return Err(EvalError::TypeMismatch {
5729 detail: "tsvector literal: unterminated quoted lexeme".into(),
5730 });
5731 }
5732 let b = bytes[i];
5733 if b == b'\'' {
5734 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
5735 w.push('\'');
5736 i += 2;
5737 } else {
5738 i += 1;
5739 break;
5740 }
5741 } else {
5742 w.push(b as char);
5743 i += 1;
5744 }
5745 }
5746 w
5747 } else {
5748 let start = i;
5750 while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b':' {
5751 i += 1;
5752 }
5753 core::str::from_utf8(&bytes[start..i])
5754 .map_err(|_| EvalError::TypeMismatch {
5755 detail: "tsvector literal: non-UTF-8 lexeme".into(),
5756 })?
5757 .to_string()
5758 };
5759 if word.is_empty() {
5760 return Err(EvalError::TypeMismatch {
5761 detail: "tsvector literal: empty lexeme".into(),
5762 });
5763 }
5764 let mut positions: Vec<u16> = Vec::new();
5767 let mut weight: u8 = 0;
5768 if i < bytes.len() && bytes[i] == b':' {
5769 i += 1;
5770 loop {
5771 let start = i;
5772 while i < bytes.len() && bytes[i].is_ascii_digit() {
5773 i += 1;
5774 }
5775 if start == i {
5776 return Err(EvalError::TypeMismatch {
5777 detail: "tsvector literal: expected digit after ':'".into(),
5778 });
5779 }
5780 let num: u16 = core::str::from_utf8(&bytes[start..i])
5781 .expect("ascii digits")
5782 .parse()
5783 .map_err(|_| EvalError::TypeMismatch {
5784 detail: alloc::format!(
5785 "tsvector literal: position {} overflows u16",
5786 core::str::from_utf8(&bytes[start..i]).unwrap_or("?")
5787 ),
5788 })?;
5789 positions.push(num);
5790 if i < bytes.len() {
5791 let w = bytes[i];
5792 if matches!(w, b'A' | b'B' | b'C' | b'D') {
5793 weight = match w {
5794 b'A' => 3,
5795 b'B' => 2,
5796 b'C' => 1,
5797 _ => 0,
5798 };
5799 i += 1;
5800 }
5801 }
5802 if i < bytes.len() && bytes[i] == b',' {
5803 i += 1;
5804 continue;
5805 }
5806 break;
5807 }
5808 }
5809 positions.sort_unstable();
5810 positions.dedup();
5811 match out.binary_search_by(|l| l.word.as_str().cmp(word.as_str())) {
5814 Ok(idx) => {
5815 for p in positions {
5816 if !out[idx].positions.contains(&p) {
5817 out[idx].positions.push(p);
5818 }
5819 }
5820 out[idx].positions.sort_unstable();
5821 if weight != 0 {
5822 out[idx].weight = weight;
5823 }
5824 }
5825 Err(idx) => {
5826 out.insert(
5827 idx,
5828 TsLexeme {
5829 word,
5830 positions,
5831 weight,
5832 },
5833 );
5834 }
5835 }
5836 }
5837 Ok(out)
5838}
5839
5840pub fn decode_tsquery_external(s: &str) -> Result<TsQueryAst, EvalError> {
5846 let mut p = TsQueryParser {
5847 bytes: s.as_bytes(),
5848 pos: 0,
5849 };
5850 p.skip_ws();
5851 if p.pos >= p.bytes.len() {
5852 return Err(EvalError::TypeMismatch {
5853 detail: "tsquery literal: empty".into(),
5854 });
5855 }
5856 let ast = p.parse_or()?;
5857 p.skip_ws();
5858 if p.pos < p.bytes.len() {
5859 return Err(EvalError::TypeMismatch {
5860 detail: alloc::format!("tsquery literal: trailing garbage at offset {}", p.pos),
5861 });
5862 }
5863 Ok(ast)
5864}
5865
5866struct TsQueryParser<'a> {
5867 bytes: &'a [u8],
5868 pos: usize,
5869}
5870
5871impl<'a> TsQueryParser<'a> {
5872 fn skip_ws(&mut self) {
5873 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_whitespace() {
5874 self.pos += 1;
5875 }
5876 }
5877 fn peek(&self) -> Option<u8> {
5878 self.bytes.get(self.pos).copied()
5879 }
5880 fn parse_or(&mut self) -> Result<TsQueryAst, EvalError> {
5881 let mut lhs = self.parse_and()?;
5882 loop {
5883 self.skip_ws();
5884 if self.peek() != Some(b'|') {
5885 return Ok(lhs);
5886 }
5887 self.pos += 1;
5888 let rhs = self.parse_and()?;
5889 lhs = TsQueryAst::Or(Box::new(lhs), Box::new(rhs));
5890 }
5891 }
5892 fn parse_and(&mut self) -> Result<TsQueryAst, EvalError> {
5893 let mut lhs = self.parse_unary()?;
5894 loop {
5895 self.skip_ws();
5896 match self.peek() {
5897 Some(b'&') => {
5898 self.pos += 1;
5899 let rhs = self.parse_unary()?;
5900 lhs = TsQueryAst::And(Box::new(lhs), Box::new(rhs));
5901 }
5902 Some(b'<') => {
5903 self.pos += 1;
5905 let start = self.pos;
5906 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
5907 self.pos += 1;
5908 }
5909 if start == self.pos || self.peek() != Some(b'>') {
5910 return Err(EvalError::TypeMismatch {
5911 detail: "tsquery literal: malformed <N> phrase operator".into(),
5912 });
5913 }
5914 let n: u16 = core::str::from_utf8(&self.bytes[start..self.pos])
5915 .expect("ascii digits")
5916 .parse()
5917 .map_err(|_| EvalError::TypeMismatch {
5918 detail: "tsquery literal: phrase distance overflows u16".into(),
5919 })?;
5920 self.pos += 1; let rhs = self.parse_unary()?;
5922 lhs = TsQueryAst::Phrase {
5923 left: Box::new(lhs),
5924 right: Box::new(rhs),
5925 distance: n,
5926 };
5927 }
5928 _ => return Ok(lhs),
5929 }
5930 }
5931 }
5932 fn parse_unary(&mut self) -> Result<TsQueryAst, EvalError> {
5933 self.skip_ws();
5934 if self.peek() == Some(b'!') {
5935 self.pos += 1;
5936 let inner = self.parse_unary()?;
5937 return Ok(TsQueryAst::Not(Box::new(inner)));
5938 }
5939 self.parse_atom()
5940 }
5941 fn parse_atom(&mut self) -> Result<TsQueryAst, EvalError> {
5942 self.skip_ws();
5943 match self.peek() {
5944 Some(b'(') => {
5945 self.pos += 1;
5946 let inner = self.parse_or()?;
5947 self.skip_ws();
5948 if self.peek() != Some(b')') {
5949 return Err(EvalError::TypeMismatch {
5950 detail: "tsquery literal: missing ')'".into(),
5951 });
5952 }
5953 self.pos += 1;
5954 Ok(inner)
5955 }
5956 Some(b'\'') => {
5957 self.pos += 1;
5958 let mut w = String::new();
5959 loop {
5960 match self.peek() {
5961 None => {
5962 return Err(EvalError::TypeMismatch {
5963 detail: "tsquery literal: unterminated quoted lexeme".into(),
5964 });
5965 }
5966 Some(b'\'') => {
5967 if self.bytes.get(self.pos + 1) == Some(&b'\'') {
5968 w.push('\'');
5969 self.pos += 2;
5970 } else {
5971 self.pos += 1;
5972 break;
5973 }
5974 }
5975 Some(b) => {
5976 w.push(b as char);
5977 self.pos += 1;
5978 }
5979 }
5980 }
5981 self.skip_weight_suffix();
5984 Ok(TsQueryAst::Term {
5985 word: w,
5986 weight_mask: 0,
5987 })
5988 }
5989 Some(b) if b.is_ascii_alphanumeric() || b == b'_' => {
5990 let start = self.pos;
5991 while self.pos < self.bytes.len() {
5992 let c = self.bytes[self.pos];
5993 if c.is_ascii_alphanumeric() || c == b'_' {
5994 self.pos += 1;
5995 } else {
5996 break;
5997 }
5998 }
5999 let w = core::str::from_utf8(&self.bytes[start..self.pos])
6000 .map_err(|_| EvalError::TypeMismatch {
6001 detail: "tsquery literal: non-UTF-8 lexeme".into(),
6002 })?
6003 .to_string();
6004 self.skip_weight_suffix();
6005 Ok(TsQueryAst::Term {
6006 word: w,
6007 weight_mask: 0,
6008 })
6009 }
6010 Some(b) => Err(EvalError::TypeMismatch {
6011 detail: alloc::format!(
6012 "tsquery literal: unexpected byte {:?} at offset {}",
6013 b as char,
6014 self.pos
6015 ),
6016 }),
6017 None => Err(EvalError::TypeMismatch {
6018 detail: "tsquery literal: expected term".into(),
6019 }),
6020 }
6021 }
6022 fn skip_weight_suffix(&mut self) {
6023 if self.peek() != Some(b':') {
6024 return;
6025 }
6026 self.pos += 1;
6027 while let Some(b) = self.peek() {
6028 if matches!(
6029 b,
6030 b'A' | b'B' | b'C' | b'D' | b'a' | b'b' | b'c' | b'd' | b'*'
6031 ) || b.is_ascii_digit()
6032 {
6033 self.pos += 1;
6034 } else {
6035 break;
6036 }
6037 }
6038 }
6039}
6040
6041pub fn format_bytea_hex(b: &[u8]) -> String {
6045 let mut out = String::with_capacity(2 + 2 * b.len());
6046 out.push_str("\\x");
6047 const HEX: &[u8; 16] = b"0123456789abcdef";
6048 for byte in b {
6049 out.push(HEX[(byte >> 4) as usize] as char);
6050 out.push(HEX[(byte & 0x0F) as usize] as char);
6051 }
6052 out
6053}
6054
6055pub fn format_numeric(scaled: i128, scale: u8) -> String {
6060 if scale == 0 {
6061 return format!("{scaled}");
6062 }
6063 let negative = scaled < 0;
6064 let mag_str = scaled.unsigned_abs().to_string();
6065 let mag_bytes = mag_str.as_bytes();
6066 let scale_u = scale as usize;
6067 let mut out = String::with_capacity(mag_str.len() + 3);
6068 if negative {
6069 out.push('-');
6070 }
6071 if mag_bytes.len() <= scale_u {
6072 out.push('0');
6073 out.push('.');
6074 for _ in mag_bytes.len()..scale_u {
6075 out.push('0');
6076 }
6077 out.push_str(&mag_str);
6078 } else {
6079 let split = mag_bytes.len() - scale_u;
6080 out.push_str(&mag_str[..split]);
6081 out.push('.');
6082 out.push_str(&mag_str[split..]);
6083 }
6084 out
6085}
6086
6087fn cast_numeric_to_int(v: Value) -> Result<Value, EvalError> {
6088 match v {
6089 Value::Int(n) => Ok(Value::Int(n)),
6090 Value::BigInt(n) => i32::try_from(n)
6091 .map(Value::Int)
6092 .map_err(|_| EvalError::TypeMismatch {
6093 detail: format!("bigint {n} does not fit in int"),
6094 }),
6095 #[allow(clippy::cast_possible_truncation)]
6096 Value::Float(x) => Ok(Value::Int(x as i32)),
6097 Value::Text(s) => {
6098 s.trim()
6099 .parse::<i32>()
6100 .map(Value::Int)
6101 .map_err(|_| EvalError::TypeMismatch {
6102 detail: format!("cannot parse {s:?} as int"),
6103 })
6104 }
6105 Value::Bool(b) => Ok(Value::Int(i32::from(b))),
6106 other => Err(EvalError::TypeMismatch {
6107 detail: format!("cannot cast {:?} to int", other.data_type()),
6108 }),
6109 }
6110}
6111
6112fn cast_numeric_to_bigint(v: Value) -> Result<Value, EvalError> {
6113 match v {
6114 Value::Int(n) => Ok(Value::BigInt(i64::from(n))),
6115 Value::BigInt(n) => Ok(Value::BigInt(n)),
6116 #[allow(clippy::cast_possible_truncation)]
6117 Value::Float(x) => Ok(Value::BigInt(x as i64)),
6118 Value::Text(s) => {
6119 s.trim()
6120 .parse::<i64>()
6121 .map(Value::BigInt)
6122 .map_err(|_| EvalError::TypeMismatch {
6123 detail: format!("cannot parse {s:?} as bigint"),
6124 })
6125 }
6126 Value::Bool(b) => Ok(Value::BigInt(i64::from(b))),
6127 other => Err(EvalError::TypeMismatch {
6128 detail: format!("cannot cast {:?} to bigint", other.data_type()),
6129 }),
6130 }
6131}
6132
6133fn cast_numeric_to_float(v: Value) -> Result<Value, EvalError> {
6134 match v {
6135 Value::Int(n) => Ok(Value::Float(f64::from(n))),
6136 #[allow(clippy::cast_precision_loss)]
6137 Value::BigInt(n) => Ok(Value::Float(n as f64)),
6138 Value::Float(x) => Ok(Value::Float(x)),
6139 Value::Text(s) => {
6140 s.trim()
6141 .parse::<f64>()
6142 .map(Value::Float)
6143 .map_err(|_| EvalError::TypeMismatch {
6144 detail: format!("cannot parse {s:?} as float"),
6145 })
6146 }
6147 other => Err(EvalError::TypeMismatch {
6148 detail: format!("cannot cast {:?} to float", other.data_type()),
6149 }),
6150 }
6151}
6152
6153fn cast_to_bool(v: Value) -> Result<Value, EvalError> {
6154 match v {
6155 Value::Bool(b) => Ok(Value::Bool(b)),
6156 Value::Int(n) => Ok(Value::Bool(n != 0)),
6157 Value::BigInt(n) => Ok(Value::Bool(n != 0)),
6158 Value::Text(s) => {
6159 let lo = s.trim().to_ascii_lowercase();
6160 match lo.as_str() {
6161 "true" | "t" | "yes" | "y" | "1" | "on" => Ok(Value::Bool(true)),
6162 "false" | "f" | "no" | "n" | "0" | "off" => Ok(Value::Bool(false)),
6163 _ => Err(EvalError::TypeMismatch {
6164 detail: format!("cannot parse {s:?} as bool"),
6165 }),
6166 }
6167 }
6168 other => Err(EvalError::TypeMismatch {
6169 detail: format!("cannot cast {:?} to bool", other.data_type()),
6170 }),
6171 }
6172}
6173
6174pub fn cast_to_vector(v: Value) -> Result<Value, EvalError> {
6177 match v {
6178 Value::Null => Ok(Value::Null),
6179 Value::Vector(v) => Ok(Value::Vector(v)),
6180 Value::Text(s) => parse_vector_text(&s)
6181 .map(Value::Vector)
6182 .ok_or(EvalError::TypeMismatch {
6183 detail: format!("cannot parse {s:?} as a vector literal"),
6184 }),
6185 other => Err(EvalError::TypeMismatch {
6186 detail: format!("::vector requires text input, got {:?}", other.data_type()),
6187 }),
6188 }
6189}
6190
6191pub fn parse_vector_text(s: &str) -> Option<Vec<f32>> {
6193 let trimmed = s.trim();
6194 let inner = trimmed.strip_prefix('[')?.strip_suffix(']')?;
6195 let trimmed_inner = inner.trim();
6196 if trimmed_inner.is_empty() {
6197 return Some(Vec::new());
6198 }
6199 let mut out = Vec::new();
6200 for part in trimmed_inner.split(',') {
6201 let f: f32 = part.trim().parse().ok()?;
6202 out.push(f);
6203 }
6204 Some(out)
6205}
6206
6207fn literal_to_value(l: &Literal) -> Value {
6208 match l {
6209 Literal::Integer(n) => {
6210 if let Ok(small) = i32::try_from(*n) {
6211 Value::Int(small)
6212 } else {
6213 Value::BigInt(*n)
6214 }
6215 }
6216 Literal::Float(x) => Value::Float(*x),
6217 Literal::String(s) => Value::Text(s.clone()),
6218 Literal::Vector(v) => Value::Vector(v.clone()),
6219 Literal::TextArray(items) => Value::TextArray(items.clone()),
6220 Literal::IntArray(items) => Value::IntArray(items.clone()),
6221 Literal::BigIntArray(items) => Value::BigIntArray(items.clone()),
6222 Literal::Bool(b) => Value::Bool(*b),
6223 Literal::Null => Value::Null,
6224 Literal::Interval { months, micros, .. } => Value::Interval {
6225 months: *months,
6226 micros: *micros,
6227 },
6228 }
6229}
6230
6231pub(crate) fn column_collation(e: &Expr, ctx: &EvalContext<'_>) -> Option<spg_storage::Collation> {
6237 let Expr::Column(c) = e else {
6238 return None;
6239 };
6240 if let Some(q) = &c.qualifier {
6241 let composite = alloc::format!("{q}.{name}", name = c.name);
6242 if let Some(s) = ctx.columns.iter().find(|s| s.name == composite) {
6243 return Some(s.collation);
6244 }
6245 }
6246 if let Some(s) = ctx.columns.iter().find(|s| s.name == c.name) {
6247 return Some(s.collation);
6248 }
6249 let suffix = alloc::format!(".{name}", name = c.name);
6253 let mut matches = ctx.columns.iter().filter(|s| s.name.ends_with(&suffix));
6254 let first = matches.next();
6255 let extra = matches.next();
6256 match (first, extra) {
6257 (Some(s), None) => Some(s.collation),
6258 _ => None,
6259 }
6260}
6261
6262fn collation_fold_for_compare(
6269 op: BinOp,
6270 lhs: &Expr,
6271 rhs: &Expr,
6272 l: Value,
6273 r: Value,
6274 ctx: &EvalContext<'_>,
6275) -> (Value, Value) {
6276 if !matches!(
6277 op,
6278 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq
6279 ) {
6280 return (l, r);
6281 }
6282 let lhs_col = column_collation(lhs, ctx);
6283 let rhs_col = column_collation(rhs, ctx);
6284 let ci = matches!(lhs_col, Some(spg_storage::Collation::CaseInsensitive))
6285 || matches!(rhs_col, Some(spg_storage::Collation::CaseInsensitive));
6286 if !ci {
6287 return (l, r);
6288 }
6289 let fold = |v: Value| match v {
6290 Value::Text(s) => Value::Text(s.to_ascii_lowercase()),
6291 other => other,
6292 };
6293 (fold(l), fold(r))
6294}
6295
6296fn resolve_column(c: &ColumnName, row: &Row, ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
6297 if let Some(q) = &c.qualifier {
6298 let composite = alloc::format!("{q}.{name}", name = c.name);
6303 if let Some(pos) = ctx.columns.iter().position(|s| s.name == composite) {
6304 return Ok(row.values[pos].clone());
6305 }
6306 let expected = ctx.table_alias.ok_or_else(|| EvalError::UnknownQualifier {
6307 qualifier: q.clone(),
6308 })?;
6309 if q != expected {
6310 return Err(EvalError::UnknownQualifier {
6311 qualifier: q.clone(),
6312 });
6313 }
6314 }
6315 if let Some(pos) = ctx.columns.iter().position(|s| s.name == c.name) {
6316 return Ok(row.values[pos].clone());
6317 }
6318 let suffix = alloc::format!(".{name}", name = c.name);
6321 let mut matches = ctx
6322 .columns
6323 .iter()
6324 .enumerate()
6325 .filter(|(_, s)| s.name.ends_with(&suffix));
6326 let first = matches.next();
6327 let extra = matches.next();
6328 match (first, extra) {
6329 (Some((pos, _)), None) => Ok(row.values[pos].clone()),
6330 (Some(_), Some(_)) => Err(EvalError::TypeMismatch {
6331 detail: alloc::format!("ambiguous column reference: {}", c.name),
6332 }),
6333 _ => Err(EvalError::ColumnNotFound {
6334 name: c.name.clone(),
6335 }),
6336 }
6337}
6338
6339fn apply_unary(op: UnOp, v: Value) -> Result<Value, EvalError> {
6340 match (op, v) {
6341 (_, Value::Null) => Ok(Value::Null),
6342 (UnOp::Neg, Value::Int(n)) => {
6343 n.checked_neg()
6344 .map(Value::Int)
6345 .ok_or(EvalError::TypeMismatch {
6346 detail: "integer overflow on unary -".into(),
6347 })
6348 }
6349 (UnOp::Neg, Value::BigInt(n)) => {
6350 n.checked_neg()
6351 .map(Value::BigInt)
6352 .ok_or(EvalError::TypeMismatch {
6353 detail: "bigint overflow on unary -".into(),
6354 })
6355 }
6356 (UnOp::Neg, Value::Float(x)) => Ok(Value::Float(-x)),
6357 (UnOp::Neg, other) => Err(EvalError::TypeMismatch {
6358 detail: format!("unary - applied to {:?}", other.data_type()),
6359 }),
6360 (UnOp::BitNot, Value::SmallInt(n)) => Ok(Value::Int(!i32::from(n))),
6361 (UnOp::BitNot, Value::Int(n)) => Ok(Value::Int(!n)),
6362 (UnOp::BitNot, Value::BigInt(n)) => Ok(Value::BigInt(!n)),
6363 (UnOp::BitNot, other) => Err(EvalError::TypeMismatch {
6364 detail: format!("cannot apply ~ to {other:?}"),
6365 }),
6366 (UnOp::Not, Value::Bool(b)) => Ok(Value::Bool(!b)),
6367 (UnOp::Not, other) => Err(EvalError::TypeMismatch {
6368 detail: format!("NOT applied to {:?}", other.data_type()),
6369 }),
6370 }
6371}
6372
6373fn values_not_distinct(l: &Value, r: &Value) -> bool {
6376 match (l, r) {
6377 (Value::Null, Value::Null) => true,
6378 (Value::Null, _) | (_, Value::Null) => false,
6379 _ => l == r,
6380 }
6381}
6382
6383fn apply_binary(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6384 if let BinOp::And = op {
6387 return and_3vl(l, r);
6388 }
6389 if let BinOp::Or = op {
6390 return or_3vl(l, r);
6391 }
6392 if let BinOp::IsNotDistinctFrom = op {
6395 return Ok(Value::Bool(values_not_distinct(&l, &r)));
6396 }
6397 if let BinOp::IsDistinctFrom = op {
6398 return Ok(Value::Bool(!values_not_distinct(&l, &r)));
6399 }
6400 if l.is_null() || r.is_null() {
6402 return Ok(Value::Null);
6403 }
6404 if matches!(l, Value::Numeric { .. }) || matches!(r, Value::Numeric { .. }) {
6407 return apply_binary_numeric(op, l, r);
6408 }
6409 if let Some(result) = apply_binary_calendar(op, &l, &r)? {
6417 return Ok(result);
6418 }
6419 match op {
6420 BinOp::Add => arith(l, r, i64::checked_add, |a, b| a + b, "+"),
6421 BinOp::Sub => arith(l, r, i64::checked_sub, |a, b| a - b, "-"),
6422 BinOp::Mul => arith(l, r, i64::checked_mul, |a, b| a * b, "*"),
6423 BinOp::Div => div_op(l, r),
6424 BinOp::L2Distance => l2_distance(l, r),
6425 BinOp::InnerProduct => inner_product(l, r),
6426 BinOp::CosineDistance => cosine_distance(l, r),
6427 BinOp::Concat => Ok(text_concat(&l, &r)),
6428 BinOp::BitOr => bitop(l, r, |a, b| a | b, "|"),
6429 BinOp::BitAnd => bitop(l, r, |a, b| a & b, "&"),
6430 BinOp::JsonGet => crate::json::path_get(&l, &r, false),
6431 BinOp::JsonGetText => crate::json::path_get(&l, &r, true),
6432 BinOp::JsonGetPath => crate::json::path_walk(&l, &r, false),
6433 BinOp::JsonGetPathText => crate::json::path_walk(&l, &r, true),
6434 BinOp::JsonContains => crate::json::contains(&l, &r),
6435 BinOp::TsMatch => ts_match(l, r),
6438 BinOp::InetContainedBy
6440 | BinOp::InetContainedByEq
6441 | BinOp::InetContains
6442 | BinOp::InetContainsEq
6443 | BinOp::InetOverlap => inet_op_bool_result(op, &l, &r),
6444 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6445 compare(op, &l, &r)
6446 }
6447 BinOp::And | BinOp::Or | BinOp::IsDistinctFrom | BinOp::IsNotDistinctFrom => {
6448 unreachable!("handled above")
6449 }
6450 }
6451}
6452
6453fn apply_binary_calendar(op: BinOp, l: &Value, r: &Value) -> Result<Option<Value>, EvalError> {
6457 let int_value = |v: &Value| -> Option<i64> {
6458 match v {
6459 Value::SmallInt(n) => Some(i64::from(*n)),
6460 Value::Int(n) => Some(i64::from(*n)),
6461 Value::BigInt(n) => Some(*n),
6462 _ => None,
6463 }
6464 };
6465 match (l, r) {
6469 (Value::Date(a), Value::Date(b)) if op == BinOp::Sub => {
6470 return Ok(Some(Value::BigInt(i64::from(*a) - i64::from(*b))));
6471 }
6472 (Value::Timestamp(a), Value::Timestamp(b)) if op == BinOp::Sub => {
6473 let delta = a.checked_sub(*b).ok_or(EvalError::TypeMismatch {
6474 detail: "TIMESTAMP - TIMESTAMP overflows i64 microseconds".into(),
6475 })?;
6476 return Ok(Some(Value::BigInt(delta)));
6477 }
6478 _ => {}
6479 }
6480 if let Some(out) = apply_binary_interval(op, l, r)? {
6484 return Ok(Some(out));
6485 }
6486 match (l, r) {
6487 (Value::Date(d), other) if op == BinOp::Add => {
6488 if let Some(n) = int_value(other) {
6489 let days = i64::from(*d).saturating_add(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 (other, Value::Date(d)) if op == BinOp::Add => {
6497 if let Some(n) = int_value(other) {
6498 let days = i64::from(*d).saturating_add(n);
6499 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6500 detail: "integer + DATE overflows DATE range".into(),
6501 })?;
6502 return Ok(Some(Value::Date(days32)));
6503 }
6504 }
6505 (Value::Date(d), other) if op == BinOp::Sub => {
6506 if let Some(n) = int_value(other) {
6507 let days = i64::from(*d).saturating_sub(n);
6508 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6509 detail: "DATE - integer overflows DATE range".into(),
6510 })?;
6511 return Ok(Some(Value::Date(days32)));
6512 }
6513 }
6514 _ => {}
6515 }
6516 Ok(None)
6517}
6518
6519pub(crate) fn apply_binary_interval(
6527 op: BinOp,
6528 l: &Value,
6529 r: &Value,
6530) -> Result<Option<Value>, EvalError> {
6531 let (lhs, rhs, sign): (&Value, &Value, i64) = match (l, r, op) {
6534 (Value::Interval { .. }, _, BinOp::Add) => (r, l, 1),
6535 (_, Value::Interval { .. }, BinOp::Add) => (l, r, 1),
6536 (_, Value::Interval { .. }, BinOp::Sub) => (l, r, -1),
6537 _ => return Ok(None),
6538 };
6539 let Value::Interval {
6540 months: rhs_months,
6541 micros: rhs_us,
6542 } = rhs
6543 else {
6544 unreachable!("rhs guaranteed to be Interval by the match above");
6545 };
6546 let signed_months = i64::from(*rhs_months) * sign;
6547 let signed_micros = rhs_us.checked_mul(sign).ok_or(EvalError::TypeMismatch {
6548 detail: "INTERVAL micros overflows on negation".into(),
6549 })?;
6550 match lhs {
6551 Value::Timestamp(t) => Ok(Some(Value::Timestamp(add_interval_to_micros(
6552 *t,
6553 signed_months,
6554 signed_micros,
6555 )?))),
6556 Value::Date(d) => {
6557 let day_aligned = signed_micros.rem_euclid(86_400_000_000) == 0;
6561 if day_aligned {
6562 let micros_per_day = 86_400_000_000_i64;
6563 let days_delta = signed_micros / micros_per_day;
6564 let shifted = shift_date_by_months(*d, signed_months)?;
6565 let new_days =
6566 i64::from(shifted)
6567 .checked_add(days_delta)
6568 .ok_or(EvalError::TypeMismatch {
6569 detail: "DATE ± INTERVAL overflows DATE range".into(),
6570 })?;
6571 let days32 = i32::try_from(new_days).map_err(|_| EvalError::TypeMismatch {
6572 detail: "DATE ± INTERVAL overflows DATE range".into(),
6573 })?;
6574 Ok(Some(Value::Date(days32)))
6575 } else {
6576 let base =
6577 i64::from(*d)
6578 .checked_mul(86_400_000_000)
6579 .ok_or(EvalError::TypeMismatch {
6580 detail: "DATE → TIMESTAMP lift overflows for INTERVAL math".into(),
6581 })?;
6582 Ok(Some(Value::Timestamp(add_interval_to_micros(
6583 base,
6584 signed_months,
6585 signed_micros,
6586 )?)))
6587 }
6588 }
6589 Value::Interval {
6590 months: lhs_months,
6591 micros: lhs_us,
6592 } => {
6593 let new_months = i64::from(*lhs_months)
6594 .checked_add(signed_months)
6595 .and_then(|n| i32::try_from(n).ok())
6596 .ok_or(EvalError::TypeMismatch {
6597 detail: "INTERVAL ± INTERVAL months overflows i32".into(),
6598 })?;
6599 let new_micros = lhs_us
6600 .checked_add(signed_micros)
6601 .ok_or(EvalError::TypeMismatch {
6602 detail: "INTERVAL ± INTERVAL micros overflows i64".into(),
6603 })?;
6604 Ok(Some(Value::Interval {
6605 months: new_months,
6606 micros: new_micros,
6607 }))
6608 }
6609 _ => Err(EvalError::TypeMismatch {
6610 detail: format!(
6611 "operator {op:?} not defined for {:?} and INTERVAL",
6612 lhs.data_type()
6613 ),
6614 }),
6615 }
6616}
6617
6618fn shift_date_by_months(d: i32, months: i64) -> Result<i32, EvalError> {
6620 let (y, m, day) = civil_from_days(d);
6621 let months_i32 = i32::try_from(months).map_err(|_| EvalError::TypeMismatch {
6622 detail: "INTERVAL months delta out of i32 range".into(),
6623 })?;
6624 let (ny, nm, nd) = add_months_to_civil(y, m, day, months_i32);
6625 Ok(days_from_civil(ny, nm, nd))
6626}
6627
6628fn add_interval_to_micros(t: i64, months: i64, micros: i64) -> Result<i64, EvalError> {
6632 let mut out = t;
6633 if months != 0 {
6634 const MICROS_PER_DAY: i64 = 86_400_000_000;
6635 let days = out.div_euclid(MICROS_PER_DAY);
6636 let day_micros = out.rem_euclid(MICROS_PER_DAY);
6637 let day_i32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6638 detail: "TIMESTAMP day component out of i32 range for INTERVAL months math".into(),
6639 })?;
6640 let shifted_days = shift_date_by_months(day_i32, months)?;
6641 out = i64::from(shifted_days)
6642 .checked_mul(MICROS_PER_DAY)
6643 .and_then(|n| n.checked_add(day_micros))
6644 .ok_or(EvalError::TypeMismatch {
6645 detail: "TIMESTAMP ± INTERVAL months overflows i64 microseconds".into(),
6646 })?;
6647 }
6648 out.checked_add(micros).ok_or(EvalError::TypeMismatch {
6649 detail: "TIMESTAMP ± INTERVAL micros overflows i64".into(),
6650 })
6651}
6652
6653#[allow(clippy::needless_pass_by_value)] fn apply_binary_numeric(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6658 let float_path = matches!(l, Value::Float(_)) || matches!(r, Value::Float(_));
6662 if float_path {
6663 let af = as_f64(&l)?;
6664 let bf = as_f64(&r)?;
6665 return match op {
6666 BinOp::Add => Ok(Value::Float(af + bf)),
6667 BinOp::Sub => Ok(Value::Float(af - bf)),
6668 BinOp::Mul => Ok(Value::Float(af * bf)),
6669 BinOp::Div => {
6670 if bf == 0.0 {
6671 Err(EvalError::DivisionByZero)
6672 } else {
6673 Ok(Value::Float(af / bf))
6674 }
6675 }
6676 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6677 let ord = af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
6678 detail: "NaN in NUMERIC/Float comparison".into(),
6679 })?;
6680 Ok(Value::Bool(cmp_to_bool(op, ord)))
6681 }
6682 BinOp::Concat => Ok(text_concat(&l, &r)),
6683 other => Err(EvalError::TypeMismatch {
6684 detail: format!("operator {other:?} not defined for NUMERIC and Float"),
6685 }),
6686 };
6687 }
6688 let (a, sa) = numeric_or_widen(&l).ok_or_else(|| EvalError::TypeMismatch {
6690 detail: format!("NUMERIC op against non-numeric {:?}", l.data_type()),
6691 })?;
6692 let (b, sb) = numeric_or_widen(&r).ok_or_else(|| EvalError::TypeMismatch {
6693 detail: format!("NUMERIC op against non-numeric {:?}", r.data_type()),
6694 })?;
6695 match op {
6696 BinOp::Add | BinOp::Sub => {
6697 let target_scale = sa.max(sb);
6698 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
6699 detail: "NUMERIC overflow on rescale".into(),
6700 })?;
6701 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
6702 detail: "NUMERIC overflow on rescale".into(),
6703 })?;
6704 let r = match op {
6705 BinOp::Add => lhs.checked_add(rhs),
6706 BinOp::Sub => lhs.checked_sub(rhs),
6707 _ => unreachable!(),
6708 }
6709 .ok_or(EvalError::TypeMismatch {
6710 detail: "NUMERIC overflow on +/-".into(),
6711 })?;
6712 Ok(Value::Numeric {
6713 scaled: r,
6714 scale: target_scale,
6715 })
6716 }
6717 BinOp::Mul => {
6718 let scaled = a.checked_mul(b).ok_or(EvalError::TypeMismatch {
6719 detail: "NUMERIC overflow on *".into(),
6720 })?;
6721 Ok(Value::Numeric {
6722 scaled,
6723 scale: sa.saturating_add(sb),
6724 })
6725 }
6726 BinOp::Div => {
6727 if b == 0 {
6728 return Err(EvalError::DivisionByZero);
6729 }
6730 let target_scale = sa.max(sb);
6734 let bump = pow10_i128(target_scale.saturating_add(sb).saturating_sub(sa));
6738 let num = a.checked_mul(bump).ok_or(EvalError::TypeMismatch {
6739 detail: "NUMERIC overflow on / scaling".into(),
6740 })?;
6741 let half = if b >= 0 { b / 2 } else { -(b / 2) };
6742 let adj = if (num >= 0) == (b >= 0) {
6743 num + half
6744 } else {
6745 num - half
6746 };
6747 Ok(Value::Numeric {
6748 scaled: adj / b,
6749 scale: target_scale,
6750 })
6751 }
6752 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6753 let target_scale = sa.max(sb);
6754 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
6755 detail: "NUMERIC overflow on rescale".into(),
6756 })?;
6757 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
6758 detail: "NUMERIC overflow on rescale".into(),
6759 })?;
6760 Ok(Value::Bool(cmp_to_bool(op, lhs.cmp(&rhs))))
6761 }
6762 BinOp::Concat => Ok(text_concat(&l, &r)),
6763 other => Err(EvalError::TypeMismatch {
6764 detail: format!("operator {other:?} not defined for NUMERIC"),
6765 }),
6766 }
6767}
6768
6769fn numeric_or_widen(v: &Value) -> Option<(i128, u8)> {
6773 match v {
6774 Value::Numeric { scaled, scale } => Some((*scaled, *scale)),
6775 Value::Int(n) => Some((i128::from(*n), 0)),
6776 Value::SmallInt(n) => Some((i128::from(*n), 0)),
6777 Value::BigInt(n) => Some((i128::from(*n), 0)),
6778 _ => None,
6779 }
6780}
6781
6782fn rescale(scaled: i128, src: u8, dst: u8) -> Option<i128> {
6783 if src == dst {
6784 return Some(scaled);
6785 }
6786 if dst > src {
6787 scaled.checked_mul(pow10_i128(dst - src))
6788 } else {
6789 let drop = pow10_i128(src - dst);
6790 let half = drop / 2;
6791 let r = if scaled >= 0 {
6792 scaled + half
6793 } else {
6794 scaled - half
6795 };
6796 Some(r / drop)
6797 }
6798}
6799
6800const fn pow10_i128(p: u8) -> i128 {
6801 let mut acc: i128 = 1;
6802 let mut i = 0;
6803 while i < p {
6804 acc *= 10;
6805 i += 1;
6806 }
6807 acc
6808}
6809
6810const fn cmp_to_bool(op: BinOp, ord: core::cmp::Ordering) -> bool {
6811 use core::cmp::Ordering::{Equal, Greater, Less};
6812 match op {
6813 BinOp::Eq => matches!(ord, Equal),
6814 BinOp::NotEq => !matches!(ord, Equal),
6815 BinOp::Lt => matches!(ord, Less),
6816 BinOp::LtEq => matches!(ord, Less | Equal),
6817 BinOp::Gt => matches!(ord, Greater),
6818 BinOp::GtEq => matches!(ord, Greater | Equal),
6819 _ => false,
6820 }
6821}
6822
6823fn text_concat(l: &Value, r: &Value) -> Value {
6827 match (l, r) {
6832 (Value::Null, _) | (_, Value::Null) => {
6833 if matches!(
6837 l,
6838 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
6839 ) || matches!(
6840 r,
6841 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
6842 ) {
6843 return Value::Null;
6844 }
6845 }
6846 (Value::TextArray(a), Value::TextArray(b)) => {
6847 let mut out = a.clone();
6848 out.extend(b.iter().cloned());
6849 return Value::TextArray(out);
6850 }
6851 (Value::TextArray(a), Value::Text(s)) => {
6852 let mut out = a.clone();
6853 out.push(Some(s.clone()));
6854 return Value::TextArray(out);
6855 }
6856 (Value::Text(s), Value::TextArray(b)) => {
6857 let mut out: alloc::vec::Vec<Option<alloc::string::String>> =
6858 alloc::vec::Vec::with_capacity(1 + b.len());
6859 out.push(Some(s.clone()));
6860 out.extend(b.iter().cloned());
6861 return Value::TextArray(out);
6862 }
6863 (Value::IntArray(a), Value::IntArray(b)) => {
6868 let mut out = a.clone();
6869 out.extend(b.iter().copied());
6870 return Value::IntArray(out);
6871 }
6872 (Value::IntArray(a), Value::Int(n)) => {
6873 let mut out = a.clone();
6874 out.push(Some(*n));
6875 return Value::IntArray(out);
6876 }
6877 (Value::IntArray(a), Value::SmallInt(n)) => {
6878 let mut out = a.clone();
6879 out.push(Some(i32::from(*n)));
6880 return Value::IntArray(out);
6881 }
6882 (Value::Int(n), Value::IntArray(b)) => {
6883 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
6884 out.push(Some(*n));
6885 out.extend(b.iter().copied());
6886 return Value::IntArray(out);
6887 }
6888 (Value::SmallInt(n), Value::IntArray(b)) => {
6889 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
6890 out.push(Some(i32::from(*n)));
6891 out.extend(b.iter().copied());
6892 return Value::IntArray(out);
6893 }
6894 (Value::BigIntArray(a), Value::BigIntArray(b)) => {
6895 let mut out = a.clone();
6896 out.extend(b.iter().copied());
6897 return Value::BigIntArray(out);
6898 }
6899 (Value::BigIntArray(a), Value::IntArray(b)) => {
6900 let mut out = a.clone();
6901 out.extend(b.iter().map(|o| o.map(i64::from)));
6902 return Value::BigIntArray(out);
6903 }
6904 (Value::IntArray(a), Value::BigIntArray(b)) => {
6905 let mut out: alloc::vec::Vec<Option<i64>> =
6906 a.iter().map(|o| o.map(i64::from)).collect();
6907 out.extend(b.iter().copied());
6908 return Value::BigIntArray(out);
6909 }
6910 (Value::BigIntArray(a), Value::BigInt(n)) => {
6911 let mut out = a.clone();
6912 out.push(Some(*n));
6913 return Value::BigIntArray(out);
6914 }
6915 (Value::BigIntArray(a), Value::Int(n)) => {
6916 let mut out = a.clone();
6917 out.push(Some(i64::from(*n)));
6918 return Value::BigIntArray(out);
6919 }
6920 (Value::BigIntArray(a), Value::SmallInt(n)) => {
6921 let mut out = a.clone();
6922 out.push(Some(i64::from(*n)));
6923 return Value::BigIntArray(out);
6924 }
6925 (Value::BigInt(n), Value::BigIntArray(b)) => {
6926 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
6927 out.push(Some(*n));
6928 out.extend(b.iter().copied());
6929 return Value::BigIntArray(out);
6930 }
6931 (Value::Int(n), Value::BigIntArray(b)) => {
6932 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
6933 out.push(Some(i64::from(*n)));
6934 out.extend(b.iter().copied());
6935 return Value::BigIntArray(out);
6936 }
6937 (Value::SmallInt(n), Value::BigIntArray(b)) => {
6938 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
6939 out.push(Some(i64::from(*n)));
6940 out.extend(b.iter().copied());
6941 return Value::BigIntArray(out);
6942 }
6943 (Value::Bytes(a), Value::Bytes(b)) => {
6945 let mut out = a.clone();
6946 out.extend_from_slice(b);
6947 return Value::Bytes(out);
6948 }
6949 _ => {}
6950 }
6951 let a = value_to_text(l);
6952 let b = value_to_text(r);
6953 Value::Text(a + &b)
6954}
6955
6956fn inner_product(l: Value, r: Value) -> Result<Value, EvalError> {
6959 let (a, b) = unwrap_vec_pair(l, r, "<#>")?;
6960 let mut dot: f64 = 0.0;
6961 for (x, y) in a.iter().zip(b.iter()) {
6962 dot += f64::from(*x) * f64::from(*y);
6963 }
6964 Ok(Value::Float(-dot))
6965}
6966
6967fn cosine_distance(l: Value, r: Value) -> Result<Value, EvalError> {
6970 let (a, b) = unwrap_vec_pair(l, r, "<=>")?;
6971 let mut dot: f64 = 0.0;
6972 let mut na: f64 = 0.0;
6973 let mut nb: f64 = 0.0;
6974 for (x, y) in a.iter().zip(b.iter()) {
6975 let xf = f64::from(*x);
6976 let yf = f64::from(*y);
6977 dot += xf * yf;
6978 na += xf * xf;
6979 nb += yf * yf;
6980 }
6981 let denom = sqrt_newton(na) * sqrt_newton(nb);
6982 if denom == 0.0 {
6983 return Ok(Value::Float(f64::NAN));
6984 }
6985 Ok(Value::Float(1.0 - dot / denom))
6986}
6987
6988fn unwrap_vec_pair(l: Value, r: Value, op: &str) -> Result<(Vec<f32>, Vec<f32>), EvalError> {
6989 let to_f32 = |v: Value| -> Option<Vec<f32>> {
6997 match v {
6998 Value::Vector(a) => Some(a),
6999 Value::Sq8Vector(q) => Some(spg_storage::quantize::dequantize(&q)),
7000 Value::HalfVector(h) => Some(h.to_f32_vec()),
7002 _ => None,
7003 }
7004 };
7005 let l_ty = l.data_type();
7006 let r_ty = r.data_type();
7007 match (to_f32(l), to_f32(r)) {
7008 (Some(a), Some(b)) => {
7009 if a.len() != b.len() {
7010 return Err(EvalError::TypeMismatch {
7011 detail: format!("vector dim mismatch in {op}: {} vs {}", a.len(), b.len()),
7012 });
7013 }
7014 Ok((a, b))
7015 }
7016 _ => Err(EvalError::TypeMismatch {
7017 detail: format!("{op} requires two vectors, got {l_ty:?} and {r_ty:?}"),
7018 }),
7019 }
7020}
7021
7022fn bitop(
7030 l: Value,
7031 r: Value,
7032 f: impl Fn(i64, i64) -> i64,
7033 op_name: &str,
7034) -> Result<Value, EvalError> {
7035 let widen = |v: Value| -> Value {
7036 match v {
7037 Value::SmallInt(n) => Value::Int(i32::from(n)),
7038 other => other,
7039 }
7040 };
7041 match (widen(l), widen(r)) {
7042 (Value::Int(a), Value::Int(b)) => {
7043 let result = f(i64::from(a), i64::from(b));
7044 Ok(Value::Int(result as i32))
7046 }
7047 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
7048 Ok(Value::BigInt(f(i64::from(a), b)))
7049 }
7050 (Value::BigInt(a), Value::BigInt(b)) => Ok(Value::BigInt(f(a, b))),
7051 (a, b) => Err(EvalError::TypeMismatch {
7052 detail: format!("cannot apply {op_name} to {a:?} and {b:?}"),
7053 }),
7054 }
7055}
7056
7057fn arith(
7058 l: Value,
7059 r: Value,
7060 int_op: impl Fn(i64, i64) -> Option<i64>,
7061 float_op: impl Fn(f64, f64) -> f64,
7062 op_name: &str,
7063) -> Result<Value, EvalError> {
7064 let widen = |v: Value| -> Value {
7067 match v {
7068 Value::SmallInt(n) => Value::Int(i32::from(n)),
7069 other => other,
7070 }
7071 };
7072 let l = widen(l);
7073 let r = widen(r);
7074 match (l, r) {
7075 (Value::Int(a), Value::Int(b)) => {
7076 let result = int_op(i64::from(a), i64::from(b)).ok_or(EvalError::TypeMismatch {
7077 detail: format!("integer overflow on {op_name}"),
7078 })?;
7079 if let Ok(small) = i32::try_from(result) {
7080 Ok(Value::Int(small))
7081 } else {
7082 Ok(Value::BigInt(result))
7083 }
7084 }
7085 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
7086 let result = int_op(i64::from(a), b).ok_or(EvalError::TypeMismatch {
7087 detail: format!("bigint overflow on {op_name}"),
7088 })?;
7089 Ok(Value::BigInt(result))
7090 }
7091 (Value::BigInt(a), Value::BigInt(b)) => {
7092 let result = int_op(a, b).ok_or(EvalError::TypeMismatch {
7093 detail: format!("bigint overflow on {op_name}"),
7094 })?;
7095 Ok(Value::BigInt(result))
7096 }
7097 (a, b)
7098 if a.data_type() == Some(DataType::Float) || b.data_type() == Some(DataType::Float) =>
7099 {
7100 let af = as_f64(&a)?;
7101 let bf = as_f64(&b)?;
7102 Ok(Value::Float(float_op(af, bf)))
7103 }
7104 (a, b) => Err(EvalError::TypeMismatch {
7105 detail: format!(
7106 "{op_name} applied to non-numeric: {:?} vs {:?}",
7107 a.data_type(),
7108 b.data_type()
7109 ),
7110 }),
7111 }
7112}
7113
7114#[allow(clippy::many_single_char_names)] fn l2_distance(l: Value, r: Value) -> Result<Value, EvalError> {
7120 let (a, b) = unwrap_vec_pair(l, r, "<->")?;
7125 let mut sum: f64 = 0.0;
7126 for (x, y) in a.iter().zip(b.iter()) {
7127 let d = f64::from(*x) - f64::from(*y);
7128 sum += d * d;
7129 }
7130 Ok(Value::Float(sqrt_newton(sum)))
7131}
7132
7133fn sqrt_newton(x: f64) -> f64 {
7138 if x <= 0.0 {
7139 return 0.0;
7140 }
7141 let mut g = x;
7142 for _ in 0..10 {
7145 g = 0.5 * (g + x / g);
7146 }
7147 g
7148}
7149
7150fn div_op(l: Value, r: Value) -> Result<Value, EvalError> {
7151 let any_float = matches!(l.data_type(), Some(DataType::Float))
7152 || matches!(r.data_type(), Some(DataType::Float));
7153 if any_float {
7154 let a = as_f64(&l)?;
7155 let b = as_f64(&r)?;
7156 if b == 0.0 {
7157 return Err(EvalError::DivisionByZero);
7158 }
7159 return Ok(Value::Float(a / b));
7160 }
7161 arith(
7162 l,
7163 r,
7164 |a, b| {
7165 if b == 0 { None } else { Some(a / b) }
7166 },
7167 |a, b| a / b,
7168 "/",
7169 )
7170 .map_err(|e| match e {
7171 EvalError::TypeMismatch { detail } if detail.contains('/') => EvalError::DivisionByZero,
7174 other => other,
7175 })
7176}
7177
7178fn as_f64(v: &Value) -> Result<f64, EvalError> {
7179 match v {
7180 Value::SmallInt(n) => Ok(f64::from(*n)),
7181 Value::Int(n) => Ok(f64::from(*n)),
7182 #[allow(clippy::cast_precision_loss)]
7183 Value::BigInt(n) => Ok(*n as f64),
7184 Value::Float(x) => Ok(*x),
7185 #[allow(clippy::cast_precision_loss)]
7186 Value::Numeric { scaled, scale } => {
7187 let mut div = 1.0_f64;
7188 for _ in 0..*scale {
7189 div *= 10.0;
7190 }
7191 Ok((*scaled as f64) / div)
7192 }
7193 other => Err(EvalError::TypeMismatch {
7194 detail: format!("cannot convert {:?} to FLOAT", other.data_type()),
7195 }),
7196 }
7197}
7198
7199fn compare(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
7200 let ord = match (l, r) {
7201 (Value::Int(a), Value::Int(b)) => i64::from(*a).cmp(&i64::from(*b)),
7202 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
7203 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
7204 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
7205 (a, b)
7206 if matches!(a.data_type(), Some(DataType::Float))
7207 || matches!(b.data_type(), Some(DataType::Float)) =>
7208 {
7209 let af = as_f64(a)?;
7210 let bf = as_f64(b)?;
7211 af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
7212 detail: "NaN in comparison".into(),
7213 })?
7214 }
7215 (Value::Text(a), Value::Text(b)) => a.cmp(b),
7216 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
7217 (Value::Date(a), Value::Date(b)) => a.cmp(b),
7221 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
7222 (Value::Date(a), Value::Timestamp(b)) => (i64::from(*a) * 86_400_000_000).cmp(b),
7223 (Value::Timestamp(a), Value::Date(b)) => a.cmp(&(i64::from(*b) * 86_400_000_000)),
7224 (Value::Date(a), Value::Text(b)) => {
7228 let bd = parse_date_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7229 detail: format!("cannot parse {b:?} as DATE for comparison"),
7230 })?;
7231 a.cmp(&bd)
7232 }
7233 (Value::Text(a), Value::Date(b)) => {
7234 let ad = parse_date_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7235 detail: format!("cannot parse {a:?} as DATE for comparison"),
7236 })?;
7237 ad.cmp(b)
7238 }
7239 (Value::Timestamp(a), Value::Text(b)) => {
7240 let bt = parse_timestamp_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7241 detail: format!("cannot parse {b:?} as TIMESTAMP for comparison"),
7242 })?;
7243 a.cmp(&bt)
7244 }
7245 (Value::Text(a), Value::Timestamp(b)) => {
7246 let at = parse_timestamp_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7247 detail: format!("cannot parse {a:?} as TIMESTAMP for comparison"),
7248 })?;
7249 at.cmp(b)
7250 }
7251 (Value::Uuid(a), Value::Uuid(b)) => a.cmp(b),
7253 (Value::Uuid(a), Value::Text(b)) => {
7259 let bu = spg_storage::parse_uuid_str(b).ok_or_else(|| EvalError::TypeMismatch {
7260 detail: format!("invalid input syntax for type uuid: {b:?}"),
7261 })?;
7262 a.cmp(&bu)
7263 }
7264 (Value::Text(a), Value::Uuid(b)) => {
7265 let au = spg_storage::parse_uuid_str(a).ok_or_else(|| EvalError::TypeMismatch {
7266 detail: format!("invalid input syntax for type uuid: {a:?}"),
7267 })?;
7268 au.cmp(b)
7269 }
7270 (a, b) => {
7271 return Err(EvalError::TypeMismatch {
7272 detail: format!(
7273 "comparison between {:?} and {:?}",
7274 a.data_type(),
7275 b.data_type()
7276 ),
7277 });
7278 }
7279 };
7280 let result = match op {
7281 BinOp::Eq => ord.is_eq(),
7282 BinOp::NotEq => !ord.is_eq(),
7283 BinOp::Lt => ord.is_lt(),
7284 BinOp::LtEq => ord.is_le(),
7285 BinOp::Gt => ord.is_gt(),
7286 BinOp::GtEq => ord.is_ge(),
7287 BinOp::And
7288 | BinOp::Or
7289 | BinOp::BitOr
7290 | BinOp::BitAnd
7291 | BinOp::Add
7292 | BinOp::Sub
7293 | BinOp::Mul
7294 | BinOp::Div
7295 | BinOp::L2Distance
7296 | BinOp::InnerProduct
7297 | BinOp::CosineDistance
7298 | BinOp::Concat
7299 | BinOp::JsonGet
7300 | BinOp::JsonGetText
7301 | BinOp::JsonGetPath
7302 | BinOp::JsonGetPathText
7303 | BinOp::JsonContains
7304 | BinOp::TsMatch
7305 | BinOp::IsDistinctFrom
7306 | BinOp::IsNotDistinctFrom
7307 | BinOp::InetContainedBy
7308 | BinOp::InetContainedByEq
7309 | BinOp::InetContains
7310 | BinOp::InetContainsEq
7311 | BinOp::InetOverlap => {
7312 unreachable!("compare() only called with comparison ops")
7313 }
7314 };
7315 Ok(Value::Bool(result))
7316}
7317
7318fn and_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7320 match (l, r) {
7321 (Value::Bool(false), _) | (_, Value::Bool(false)) => Ok(Value::Bool(false)),
7322 (Value::Bool(true), Value::Bool(true)) => Ok(Value::Bool(true)),
7323 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7324 (a, b) => Err(EvalError::TypeMismatch {
7325 detail: format!(
7326 "AND on non-boolean: {:?} and {:?}",
7327 a.data_type(),
7328 b.data_type()
7329 ),
7330 }),
7331 }
7332}
7333
7334fn or_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7335 match (l, r) {
7336 (Value::Bool(true), _) | (_, Value::Bool(true)) => Ok(Value::Bool(true)),
7337 (Value::Bool(false), Value::Bool(false)) => Ok(Value::Bool(false)),
7338 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7339 (a, b) => Err(EvalError::TypeMismatch {
7340 detail: format!(
7341 "OR on non-boolean: {:?} and {:?}",
7342 a.data_type(),
7343 b.data_type()
7344 ),
7345 }),
7346 }
7347}
7348
7349#[cfg(test)]
7350mod tests {
7351 use super::*;
7352 use alloc::vec;
7353 use spg_storage::{ColumnSchema, Row};
7354
7355 fn col(name: &str, ty: DataType) -> ColumnSchema {
7356 ColumnSchema::new(name, ty, true)
7357 }
7358
7359 fn ctx<'a>(cols: &'a [ColumnSchema], alias: Option<&'a str>) -> EvalContext<'a> {
7360 EvalContext::new(cols, alias)
7361 }
7362
7363 fn lit(n: i64) -> Expr {
7364 Expr::Literal(Literal::Integer(n))
7365 }
7366
7367 fn null() -> Expr {
7368 Expr::Literal(Literal::Null)
7369 }
7370
7371 fn col_ref(name: &str) -> Expr {
7372 Expr::Column(ColumnName {
7373 qualifier: None,
7374 name: name.into(),
7375 })
7376 }
7377
7378 #[test]
7379 fn literal_evaluates_to_value() {
7380 let r = Row::new(vec![]);
7381 let cs: [ColumnSchema; 0] = [];
7382 let c = ctx(&cs, None);
7383 assert_eq!(eval_expr(&lit(42), &r, &c).unwrap(), Value::Int(42));
7384 assert_eq!(
7385 eval_expr(&Expr::Literal(Literal::Float(1.5)), &r, &c).unwrap(),
7386 Value::Float(1.5)
7387 );
7388 assert_eq!(eval_expr(&null(), &r, &c).unwrap(), Value::Null);
7389 }
7390
7391 #[test]
7392 fn column_lookup_unqualified() {
7393 let cs = vec![col("a", DataType::Int), col("b", DataType::Text)];
7394 let r = Row::new(vec![Value::Int(7), Value::Text("hi".into())]);
7395 let c = ctx(&cs, None);
7396 assert_eq!(eval_expr(&col_ref("a"), &r, &c).unwrap(), Value::Int(7));
7397 assert_eq!(
7398 eval_expr(&col_ref("b"), &r, &c).unwrap(),
7399 Value::Text("hi".into())
7400 );
7401 }
7402
7403 #[test]
7404 fn column_not_found_errors() {
7405 let cs = vec![col("a", DataType::Int)];
7406 let r = Row::new(vec![Value::Int(0)]);
7407 let c = ctx(&cs, None);
7408 let err = eval_expr(&col_ref("ghost"), &r, &c).unwrap_err();
7409 assert!(matches!(err, EvalError::ColumnNotFound { ref name } if name == "ghost"));
7410 }
7411
7412 #[test]
7413 fn qualified_column_matches_alias() {
7414 let cs = vec![col("a", DataType::Int)];
7415 let r = Row::new(vec![Value::Int(5)]);
7416 let c = ctx(&cs, Some("u"));
7417 let qualified = Expr::Column(ColumnName {
7418 qualifier: Some("u".into()),
7419 name: "a".into(),
7420 });
7421 assert_eq!(eval_expr(&qualified, &r, &c).unwrap(), Value::Int(5));
7422 }
7423
7424 #[test]
7425 fn qualified_column_unknown_alias_errors() {
7426 let cs = vec![col("a", DataType::Int)];
7427 let r = Row::new(vec![Value::Int(5)]);
7428 let c = ctx(&cs, Some("u"));
7429 let wrong = Expr::Column(ColumnName {
7430 qualifier: Some("x".into()),
7431 name: "a".into(),
7432 });
7433 assert!(matches!(
7434 eval_expr(&wrong, &r, &c).unwrap_err(),
7435 EvalError::UnknownQualifier { .. }
7436 ));
7437 }
7438
7439 #[test]
7440 fn arithmetic_with_widening() {
7441 let r = Row::new(vec![]);
7442 let cs: [ColumnSchema; 0] = [];
7443 let c = ctx(&cs, None);
7444 let e = Expr::Binary {
7445 lhs: alloc::boxed::Box::new(lit(2)),
7446 op: BinOp::Add,
7447 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::Float(0.5))),
7448 };
7449 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Float(2.5));
7450 }
7451
7452 #[test]
7453 fn division_by_zero_errors() {
7454 let r = Row::new(vec![]);
7455 let cs: [ColumnSchema; 0] = [];
7456 let c = ctx(&cs, None);
7457 let e = Expr::Binary {
7458 lhs: alloc::boxed::Box::new(lit(1)),
7459 op: BinOp::Div,
7460 rhs: alloc::boxed::Box::new(lit(0)),
7461 };
7462 assert_eq!(
7463 eval_expr(&e, &r, &c).unwrap_err(),
7464 EvalError::DivisionByZero
7465 );
7466 }
7467
7468 #[test]
7469 fn comparison_returns_bool() {
7470 let r = Row::new(vec![]);
7471 let cs: [ColumnSchema; 0] = [];
7472 let c = ctx(&cs, None);
7473 let e = Expr::Binary {
7474 lhs: alloc::boxed::Box::new(lit(1)),
7475 op: BinOp::Lt,
7476 rhs: alloc::boxed::Box::new(lit(2)),
7477 };
7478 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7479 }
7480
7481 #[test]
7482 fn null_propagates_through_arithmetic() {
7483 let r = Row::new(vec![]);
7484 let cs: [ColumnSchema; 0] = [];
7485 let c = ctx(&cs, None);
7486 let e = Expr::Binary {
7487 lhs: alloc::boxed::Box::new(lit(1)),
7488 op: BinOp::Add,
7489 rhs: alloc::boxed::Box::new(null()),
7490 };
7491 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7492 }
7493
7494 #[test]
7495 fn and_three_valued_logic() {
7496 let r = Row::new(vec![]);
7497 let cs: [ColumnSchema; 0] = [];
7498 let c = ctx(&cs, None);
7499 let tt = |a: bool, b_null: bool| Expr::Binary {
7500 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7501 op: BinOp::And,
7502 rhs: alloc::boxed::Box::new(if b_null {
7503 null()
7504 } else {
7505 Expr::Literal(Literal::Bool(true))
7506 }),
7507 };
7508 assert_eq!(
7510 eval_expr(&tt(false, true), &r, &c).unwrap(),
7511 Value::Bool(false)
7512 );
7513 assert_eq!(eval_expr(&tt(true, true), &r, &c).unwrap(), Value::Null);
7515 assert_eq!(
7517 eval_expr(&tt(true, false), &r, &c).unwrap(),
7518 Value::Bool(true)
7519 );
7520 }
7521
7522 #[test]
7523 fn or_three_valued_logic() {
7524 let r = Row::new(vec![]);
7525 let cs: [ColumnSchema; 0] = [];
7526 let c = ctx(&cs, None);
7527 let or_with_null = |a: bool| Expr::Binary {
7528 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7529 op: BinOp::Or,
7530 rhs: alloc::boxed::Box::new(null()),
7531 };
7532 assert_eq!(
7534 eval_expr(&or_with_null(true), &r, &c).unwrap(),
7535 Value::Bool(true)
7536 );
7537 assert_eq!(
7539 eval_expr(&or_with_null(false), &r, &c).unwrap(),
7540 Value::Null
7541 );
7542 }
7543
7544 #[test]
7545 fn not_on_null_is_null() {
7546 let r = Row::new(vec![]);
7547 let cs: [ColumnSchema; 0] = [];
7548 let c = ctx(&cs, None);
7549 let e = Expr::Unary {
7550 op: UnOp::Not,
7551 expr: alloc::boxed::Box::new(null()),
7552 };
7553 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7554 }
7555
7556 #[test]
7557 fn text_comparison_lexicographic() {
7558 let r = Row::new(vec![]);
7559 let cs: [ColumnSchema; 0] = [];
7560 let c = ctx(&cs, None);
7561 let e = Expr::Binary {
7562 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("apple".into()))),
7563 op: BinOp::Lt,
7564 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("banana".into()))),
7565 };
7566 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7567 }
7568
7569 #[test]
7570 fn interval_format_basics() {
7571 assert_eq!(format_interval(0, 0), "0");
7572 assert_eq!(format_interval(0, 86_400_000_000), "1 day");
7573 assert_eq!(format_interval(0, -86_400_000_000), "-1 days");
7574 assert_eq!(format_interval(0, 3_600_000_000), "01:00:00");
7575 assert_eq!(
7576 format_interval(0, 86_400_000_000 + 9_000_000),
7577 "1 day 00:00:09"
7578 );
7579 assert_eq!(format_interval(14, 0), "1 year 2 mons");
7580 assert_eq!(format_interval(-1, 0), "-1 mons");
7581 }
7582
7583 #[test]
7584 fn interval_add_to_timestamp_micros_part() {
7585 let ts = i64::from(days_from_civil(2024, 1, 1)) * 86_400_000_000;
7587 let r = add_interval_to_micros(ts, 0, 3_600_000_000).unwrap();
7588 let expected = ts + 3_600_000_000;
7589 assert_eq!(r, expected);
7590 }
7591
7592 #[test]
7593 fn interval_clamp_month_end() {
7594 let d = days_from_civil(2024, 1, 31);
7596 let shifted = shift_date_by_months(d, 1).unwrap();
7597 let (y, m, day) = civil_from_days(shifted);
7598 assert_eq!((y, m, day), (2024, 2, 29));
7599 let d = days_from_civil(2023, 1, 31);
7601 let shifted = shift_date_by_months(d, 1).unwrap();
7602 let (y, m, day) = civil_from_days(shifted);
7603 assert_eq!((y, m, day), (2023, 2, 28));
7604 let d = days_from_civil(2024, 3, 31);
7606 let shifted = shift_date_by_months(d, -1).unwrap();
7607 let (y, m, day) = civil_from_days(shifted);
7608 assert_eq!((y, m, day), (2024, 2, 29));
7609 }
7610
7611 #[test]
7612 fn interval_date_plus_pure_days_stays_date() {
7613 let d = days_from_civil(2024, 6, 1);
7615 let lhs = Value::Date(d);
7616 let rhs = Value::Interval {
7617 months: 0,
7618 micros: 7 * 86_400_000_000,
7619 };
7620 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7621 .unwrap()
7622 .unwrap();
7623 let expected = days_from_civil(2024, 6, 8);
7624 assert_eq!(v, Value::Date(expected));
7625 }
7626
7627 #[test]
7628 fn interval_date_plus_sub_day_lifts_to_timestamp() {
7629 let d = days_from_civil(2024, 6, 1);
7631 let lhs = Value::Date(d);
7632 let rhs = Value::Interval {
7633 months: 0,
7634 micros: 3_600_000_000,
7635 };
7636 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7637 .unwrap()
7638 .unwrap();
7639 let expected = i64::from(d) * 86_400_000_000 + 3_600_000_000;
7640 assert_eq!(v, Value::Timestamp(expected));
7641 }
7642}