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::AggregateOrdered { .. } => Err(EvalError::TypeMismatch {
152 detail: "aggregate ORDER BY is only valid inside an aggregating SELECT".into(),
153 }),
154 Expr::Literal(l) => Ok(literal_to_value(l)),
155 Expr::Column(c) => resolve_column(c, row, ctx),
156 Expr::Placeholder(n) => {
157 let idx = usize::from(*n).saturating_sub(1);
158 ctx.params
159 .get(idx)
160 .cloned()
161 .ok_or_else(|| EvalError::PlaceholderOutOfRange {
162 n: *n,
163 bound: u16::try_from(ctx.params.len()).unwrap_or(u16::MAX),
164 })
165 }
166 Expr::Unary { op, expr } => {
167 let v = eval_expr(expr, row, ctx)?;
168 apply_unary(*op, v)
169 }
170 Expr::Binary { lhs, op, rhs } => {
171 let l = eval_expr(lhs, row, ctx)?;
172 let r = eval_expr(rhs, row, ctx)?;
173 let (l, r) = collation_fold_for_compare(*op, lhs, rhs, l, r, ctx);
182 apply_binary(*op, l, r)
183 }
184 Expr::Cast { expr, target } => {
185 let v = eval_expr(expr, row, ctx)?;
186 cast_value(v, *target)
187 }
188 Expr::IsNull { expr, negated } => {
189 let v = eval_expr(expr, row, ctx)?;
190 let is_null = matches!(v, Value::Null);
191 Ok(Value::Bool(if *negated { !is_null } else { is_null }))
192 }
193 Expr::FunctionCall { name, args } => {
194 if args.len() == 2
200 && name.eq_ignore_ascii_case("left")
201 && let Expr::Column(c) = &args[0]
202 && let Some(cell) = resolve_column_borrowed(c, row, ctx)?
203 {
204 {
205 match cell {
206 Value::Null => return Ok(Value::Null),
207 Value::Text(t) => {
208 let n_v = eval_expr(&args[1], row, ctx)?;
209 if let Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) = n_v {
210 let n = match n_v {
211 Value::SmallInt(x) => i64::from(x),
212 Value::Int(x) => i64::from(x),
213 Value::BigInt(x) => x,
214 _ => 0,
215 };
216 return Ok(Value::Text(text_prefix_chars(t, n)));
217 }
218 }
219 _ => {}
220 }
221 }
222 }
223 let evaluated: Result<Vec<Value>, _> =
224 args.iter().map(|a| eval_expr(a, row, ctx)).collect();
225 apply_function(name, &evaluated?, ctx)
226 }
227 Expr::Like {
228 expr,
229 pattern,
230 negated,
231 case_insensitive,
232 } => {
233 let v = eval_expr(expr, row, ctx)?;
234 let p = eval_expr(pattern, row, ctx)?;
235 let (text, pat) = match (v, p) {
237 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
238 (Value::Text(a), Value::Text(b)) => (a, b),
239 (Value::Text(_), other) | (other, _) => {
240 return Err(EvalError::TypeMismatch {
241 detail: format!("LIKE requires text operands, got {:?}", other.data_type()),
242 });
243 }
244 };
245 let m = if *case_insensitive {
248 like_match(&text.to_lowercase(), &pat.to_lowercase())
249 } else {
250 like_match(&text, &pat)
251 };
252 Ok(Value::Bool(if *negated { !m } else { m }))
253 }
254 Expr::Extract { field, source } => {
255 let v = eval_expr(source, row, ctx)?;
256 extract_field(*field, &v)
257 }
258 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
262 Err(EvalError::TypeMismatch {
263 detail: "subquery reached row eval — engine resolver bug".into(),
264 })
265 }
266 Expr::InList {
273 expr,
274 list,
275 negated,
276 } => {
277 let needle = eval_expr(expr, row, ctx)?;
278 let needle_null = matches!(needle, Value::Null);
279 let mut saw_null = needle_null && !list.is_empty();
280 let mut matched = false;
281 if !needle_null {
282 for item in list {
283 let v = eval_expr(item, row, ctx)?;
284 if matches!(v, Value::Null) {
285 saw_null = true;
286 continue;
287 }
288 match apply_binary(BinOp::Eq, needle.clone(), v)? {
289 Value::Bool(true) => {
290 matched = true;
291 break;
292 }
293 Value::Bool(false) => {}
294 Value::Null => saw_null = true,
295 other => {
296 return Err(EvalError::TypeMismatch {
297 detail: format!(
298 "IN comparison didn't return Bool: {:?}",
299 other.data_type()
300 ),
301 });
302 }
303 }
304 }
305 }
306 let inner = if matched {
307 Value::Bool(true)
308 } else if saw_null {
309 Value::Null
310 } else {
311 Value::Bool(false)
312 };
313 Ok(match (negated, inner) {
314 (true, Value::Bool(b)) => Value::Bool(!b),
315 (_, v) => v,
316 })
317 }
318 Expr::WindowFunction { .. } => Err(EvalError::TypeMismatch {
323 detail: "window function reached row eval — engine rewrite bug".into(),
324 }),
325 Expr::Array(items) => {
331 let mut materialised: Vec<Value> = Vec::with_capacity(items.len());
332 for elem in items {
333 materialised.push(eval_expr(elem, row, ctx)?);
334 }
335 let mut has_text = false;
336 let mut has_bigint = false;
337 let mut has_int = false;
338 for v in &materialised {
339 match v {
340 Value::Null => {}
341 Value::Int(_) | Value::SmallInt(_) => has_int = true,
342 Value::BigInt(_) => has_bigint = true,
343 Value::Text(_) | Value::Json(_) => has_text = true,
344 _ => has_text = true,
345 }
346 }
347 if has_text || (!has_int && !has_bigint) {
348 let out: Vec<Option<String>> = materialised
349 .into_iter()
350 .map(|v| match v {
351 Value::Null => None,
352 Value::Text(s) | Value::Json(s) => Some(s),
353 other => Some(value_to_text_for_array(&other)),
354 })
355 .collect();
356 return Ok(Value::TextArray(out));
357 }
358 if has_bigint {
359 let out: Vec<Option<i64>> = materialised
360 .into_iter()
361 .map(|v| match v {
362 Value::Null => None,
363 Value::Int(n) => Some(i64::from(n)),
364 Value::SmallInt(n) => Some(i64::from(n)),
365 Value::BigInt(n) => Some(n),
366 _ => unreachable!(),
367 })
368 .collect();
369 return Ok(Value::BigIntArray(out));
370 }
371 let out: Vec<Option<i32>> = materialised
372 .into_iter()
373 .map(|v| match v {
374 Value::Null => None,
375 Value::Int(n) => Some(n),
376 Value::SmallInt(n) => Some(i32::from(n)),
377 _ => unreachable!(),
378 })
379 .collect();
380 Ok(Value::IntArray(out))
381 }
382 Expr::ArraySubscript { target, index } => {
385 let target_v = eval_expr(target, row, ctx)?;
386 let idx_v = eval_expr(index, row, ctx)?;
387 if matches!(target_v, Value::Null) || matches!(idx_v, Value::Null) {
388 return Ok(Value::Null);
389 }
390 let i: i64 = match idx_v {
391 Value::Int(n) => i64::from(n),
392 Value::BigInt(n) => n,
393 Value::SmallInt(n) => i64::from(n),
394 other => {
395 return Err(EvalError::TypeMismatch {
396 detail: format!(
397 "array subscript must be integer, got {:?}",
398 other.data_type()
399 ),
400 });
401 }
402 };
403 if i < 1 {
404 return Ok(Value::Null);
405 }
406 let pos = (i - 1) as usize;
407 match target_v {
408 Value::TextArray(items) => match items.get(pos) {
409 Some(Some(s)) => Ok(Value::Text(s.clone())),
410 Some(None) | None => Ok(Value::Null),
411 },
412 Value::IntArray(items) => match items.get(pos) {
413 Some(Some(n)) => Ok(Value::Int(*n)),
414 Some(None) | None => Ok(Value::Null),
415 },
416 Value::BigIntArray(items) => match items.get(pos) {
417 Some(Some(n)) => Ok(Value::BigInt(*n)),
418 Some(None) | None => Ok(Value::Null),
419 },
420 other => Err(EvalError::TypeMismatch {
421 detail: format!(
422 "subscript target must be an array, got {:?}",
423 other.data_type()
424 ),
425 }),
426 }
427 }
428 Expr::AnyAll {
434 expr,
435 op,
436 array,
437 is_any,
438 } => {
439 let lhs = eval_expr(expr, row, ctx)?;
440 let arr = eval_expr(array, row, ctx)?;
441 if matches!(arr, Value::Null) {
442 return Ok(Value::Null);
443 }
444 let elems: Vec<Option<Value>> = match arr {
445 Value::TextArray(items) => items.into_iter().map(|o| o.map(Value::Text)).collect(),
446 Value::IntArray(items) => items.into_iter().map(|o| o.map(Value::Int)).collect(),
447 Value::BigIntArray(items) => {
448 items.into_iter().map(|o| o.map(Value::BigInt)).collect()
449 }
450 other => {
451 return Err(EvalError::TypeMismatch {
452 detail: format!(
453 "ANY/ALL right-hand side must be an array, got {:?}",
454 other.data_type()
455 ),
456 });
457 }
458 };
459 let mut saw_null = matches!(lhs, Value::Null);
460 let mut saw_match = false;
461 let mut saw_mismatch = false;
462 for elem in elems {
463 let elem_v = match elem {
464 Some(v) => v,
465 None => {
466 saw_null = true;
467 continue;
468 }
469 };
470 if matches!(lhs, Value::Null) {
471 saw_null = true;
472 continue;
473 }
474 match apply_binary(*op, lhs.clone(), elem_v) {
475 Ok(Value::Bool(true)) => saw_match = true,
476 Ok(Value::Bool(false)) => saw_mismatch = true,
477 Ok(Value::Null) => saw_null = true,
478 Ok(other) => {
479 return Err(EvalError::TypeMismatch {
480 detail: format!(
481 "ANY/ALL comparison didn't return Bool: {:?}",
482 other.data_type()
483 ),
484 });
485 }
486 Err(e) => return Err(e),
487 }
488 }
489 let result = if *is_any {
490 if saw_match {
491 Value::Bool(true)
492 } else if saw_null {
493 Value::Null
494 } else {
495 Value::Bool(false)
496 }
497 } else if saw_mismatch {
498 Value::Bool(false)
499 } else if saw_null {
500 Value::Null
501 } else {
502 Value::Bool(true)
503 };
504 Ok(result)
505 }
506 Expr::Case {
512 operand,
513 branches,
514 else_branch,
515 } => {
516 let operand_value = match operand {
517 Some(o) => Some(eval_expr(o, row, ctx)?),
518 None => None,
519 };
520 for (when_expr, then_expr) in branches {
521 let when_value = eval_expr(when_expr, row, ctx)?;
522 let matched = match &operand_value {
523 None => matches!(when_value, Value::Bool(true)),
524 Some(op_v) => matches!(
525 apply_binary(spg_sql::ast::BinOp::Eq, op_v.clone(), when_value)?,
526 Value::Bool(true)
527 ),
528 };
529 if matched {
530 return eval_expr(then_expr, row, ctx);
531 }
532 }
533 match else_branch {
534 Some(e) => eval_expr(e, row, ctx),
535 None => Ok(Value::Null),
536 }
537 }
538 }
539}
540
541fn value_to_text_for_array(v: &Value) -> String {
548 match v {
549 Value::Text(s) | Value::Json(s) => s.clone(),
550 Value::Int(n) => n.to_string(),
551 Value::BigInt(n) => n.to_string(),
552 Value::SmallInt(n) => n.to_string(),
553 Value::Bool(b) => {
554 if *b {
555 "true".into()
556 } else {
557 "false".into()
558 }
559 }
560 Value::Float(x) => format!("{x}"),
561 Value::Date(d) => format_date(*d),
562 Value::Timestamp(t) => format_timestamp(*t),
563 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
564 _ => format!("{v:?}"),
565 }
566}
567
568fn extract_field(field: spg_sql::ast::ExtractField, v: &Value) -> Result<Value, EvalError> {
572 use spg_sql::ast::ExtractField as F;
573 if matches!(v, Value::Null) {
574 return Ok(Value::Null);
575 }
576 if let Value::Interval { months, micros } = *v {
580 let years = months / 12;
581 let mons = months % 12;
582 let secs_total = micros / 1_000_000;
583 let frac = micros % 1_000_000;
584 let result = match field {
585 F::Year => i64::from(years),
586 F::Month => i64::from(mons),
587 F::Day => micros / 86_400_000_000,
588 F::Hour => (secs_total / 3600) % 24,
589 F::Minute => (secs_total / 60) % 60,
590 F::Second => secs_total % 60,
591 F::Microsecond => (secs_total % 60) * 1_000_000 + frac,
592 F::Epoch => i64::from(months) * 30 * 86_400 + secs_total,
595 };
596 return Ok(Value::BigInt(result));
597 }
598 let (days, day_micros) = match *v {
599 Value::Date(d) => (d, 0_i64),
600 Value::Timestamp(t) => {
601 let days = t.div_euclid(86_400_000_000);
602 let day_micros = t.rem_euclid(86_400_000_000);
603 (i32::try_from(days).unwrap_or(i32::MAX), day_micros)
604 }
605 _ => {
606 return Err(EvalError::TypeMismatch {
607 detail: format!(
608 "EXTRACT requires DATE / TIMESTAMP / INTERVAL, got {:?}",
609 v.data_type()
610 ),
611 });
612 }
613 };
614 let (y, m, d) = civil_components(days);
615 let secs = day_micros / 1_000_000;
616 let hh = secs / 3600;
617 let mm = (secs / 60) % 60;
618 let ss = secs % 60;
619 let frac = day_micros % 1_000_000;
620 let result = match field {
621 F::Year => i64::from(y),
622 F::Month => i64::from(m),
623 F::Day => i64::from(d),
624 F::Hour => hh,
625 F::Minute => mm,
626 F::Second => ss,
627 F::Microsecond => ss * 1_000_000 + frac,
628 F::Epoch => i64::from(days) * 86_400 + secs,
631 };
632 Ok(Value::BigInt(result))
633}
634
635fn civil_components(days: i32) -> (i32, u32, u32) {
638 civil_from_days(days)
639}
640
641fn like_match(text: &str, pattern: &str) -> bool {
646 let text: Vec<char> = text.chars().collect();
647 let pat: Vec<char> = pattern.chars().collect();
648 like_match_inner(&text, 0, &pat, 0)
649}
650
651fn like_match_inner(text: &[char], mut ti: usize, pat: &[char], mut pi: usize) -> bool {
652 while pi < pat.len() {
653 match pat[pi] {
654 '%' => {
655 while pi < pat.len() && pat[pi] == '%' {
657 pi += 1;
658 }
659 if pi == pat.len() {
660 return true;
661 }
662 for k in ti..=text.len() {
663 if like_match_inner(text, k, pat, pi) {
664 return true;
665 }
666 }
667 return false;
668 }
669 '_' => {
670 if ti >= text.len() {
671 return false;
672 }
673 ti += 1;
674 pi += 1;
675 }
676 '\\' if pi + 1 < pat.len() => {
677 let want = pat[pi + 1];
678 if ti >= text.len() || text[ti] != want {
679 return false;
680 }
681 ti += 1;
682 pi += 2;
683 }
684 c => {
685 if ti >= text.len() || text[ti] != c {
686 return false;
687 }
688 ti += 1;
689 pi += 1;
690 }
691 }
692 }
693 ti == text.len()
694}
695
696fn apply_function(name: &str, args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
699 match name.to_ascii_lowercase().as_str() {
700 "nextval" => {
702 if args.len() != 1 {
703 return Err(EvalError::TypeMismatch {
704 detail: format!("nextval() takes 1 arg, got {}", args.len()),
705 });
706 }
707 let seq_name = match &args[0] {
708 Value::Text(s) => s.clone(),
709 Value::Null => return Ok(Value::Null),
710 other => {
711 return Err(EvalError::TypeMismatch {
712 detail: format!(
713 "nextval() argument must be TEXT, got {:?}",
714 other.data_type()
715 ),
716 });
717 }
718 };
719 let resolver = ctx
720 .sequence_resolver
721 .ok_or_else(|| EvalError::TypeMismatch {
722 detail: "nextval() requires a sequence resolver (read-only context)".into(),
723 })?;
724 let v = resolver(SequenceOp::Next(seq_name))?;
725 Ok(Value::BigInt(v))
726 }
727 "currval" => {
728 if args.len() != 1 {
729 return Err(EvalError::TypeMismatch {
730 detail: format!("currval() takes 1 arg, got {}", args.len()),
731 });
732 }
733 let seq_name = match &args[0] {
734 Value::Text(s) => s.clone(),
735 Value::Null => return Ok(Value::Null),
736 other => {
737 return Err(EvalError::TypeMismatch {
738 detail: format!(
739 "currval() argument must be TEXT, got {:?}",
740 other.data_type()
741 ),
742 });
743 }
744 };
745 let resolver = ctx
746 .sequence_resolver
747 .ok_or_else(|| EvalError::TypeMismatch {
748 detail: "currval() requires a sequence resolver (read-only context)".into(),
749 })?;
750 let v = resolver(SequenceOp::Curr(seq_name))?;
751 Ok(Value::BigInt(v))
752 }
753 "setval" => {
754 if args.len() != 2 && args.len() != 3 {
755 return Err(EvalError::TypeMismatch {
756 detail: format!("setval() takes 2 or 3 args, got {}", args.len()),
757 });
758 }
759 let seq_name = match &args[0] {
760 Value::Text(s) => s.clone(),
761 Value::Null => return Ok(Value::Null),
762 other => {
763 return Err(EvalError::TypeMismatch {
764 detail: format!(
765 "setval() name argument must be TEXT, got {:?}",
766 other.data_type()
767 ),
768 });
769 }
770 };
771 let value = match &args[1] {
772 Value::SmallInt(n) => i64::from(*n),
773 Value::Int(n) => i64::from(*n),
774 Value::BigInt(n) => *n,
775 Value::Null => return Ok(Value::Null),
776 other => {
777 return Err(EvalError::TypeMismatch {
778 detail: format!(
779 "setval() value argument must be integer, got {:?}",
780 other.data_type()
781 ),
782 });
783 }
784 };
785 let is_called = if args.len() == 3 {
786 match &args[2] {
787 Value::Bool(b) => *b,
788 Value::Null => return Ok(Value::Null),
789 other => {
790 return Err(EvalError::TypeMismatch {
791 detail: format!(
792 "setval() is_called argument must be BOOL, got {:?}",
793 other.data_type()
794 ),
795 });
796 }
797 }
798 } else {
799 true
800 };
801 let resolver = ctx
802 .sequence_resolver
803 .ok_or_else(|| EvalError::TypeMismatch {
804 detail: "setval() requires a sequence resolver (read-only context)".into(),
805 })?;
806 let v = resolver(SequenceOp::Set {
807 name: seq_name,
808 value,
809 is_called,
810 })?;
811 Ok(Value::BigInt(v))
812 }
813 "length" | "char_length" | "character_length" => {
817 if args.len() != 1 {
818 return Err(EvalError::TypeMismatch {
819 detail: format!("length() takes 1 arg, got {}", args.len()),
820 });
821 }
822 match &args[0] {
823 Value::Null => Ok(Value::Null),
824 Value::Text(s) => {
825 let n = i32::try_from(s.chars().count()).unwrap_or(i32::MAX);
826 Ok(Value::Int(n))
827 }
828 Value::Bytes(b) => {
833 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
834 Ok(Value::Int(n))
835 }
836 other => Err(EvalError::TypeMismatch {
837 detail: format!("length() needs text or bytea, got {:?}", other.data_type()),
838 }),
839 }
840 }
841 "octet_length" => {
845 if args.len() != 1 {
846 return Err(EvalError::TypeMismatch {
847 detail: format!("octet_length() takes 1 arg, got {}", args.len()),
848 });
849 }
850 match &args[0] {
851 Value::Null => Ok(Value::Null),
852 Value::Text(s) => {
853 let n = i32::try_from(s.len()).unwrap_or(i32::MAX);
854 Ok(Value::Int(n))
855 }
856 Value::Bytes(b) => {
857 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
858 Ok(Value::Int(n))
859 }
860 other => Err(EvalError::TypeMismatch {
861 detail: format!(
862 "octet_length() needs text or bytea, got {:?}",
863 other.data_type()
864 ),
865 }),
866 }
867 }
868 "array_length" => {
875 if args.len() != 2 {
876 return Err(EvalError::TypeMismatch {
877 detail: format!("array_length() takes 2 args, got {}", args.len()),
878 });
879 }
880 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
881 return Ok(Value::Null);
882 }
883 let len = match &args[0] {
884 Value::TextArray(items) => items.len(),
885 Value::IntArray(items) => items.len(),
886 Value::BigIntArray(items) => items.len(),
887 _ => {
888 return Err(EvalError::TypeMismatch {
889 detail: format!(
890 "array_length() first arg must be an array, got {:?}",
891 args[0].data_type()
892 ),
893 });
894 }
895 };
896 let dim: i64 = match args[1] {
897 Value::Int(n) => i64::from(n),
898 Value::BigInt(n) => n,
899 Value::SmallInt(n) => i64::from(n),
900 _ => {
901 return Err(EvalError::TypeMismatch {
902 detail: format!(
903 "array_length() second arg must be integer, got {:?}",
904 args[1].data_type()
905 ),
906 });
907 }
908 };
909 if dim != 1 {
910 return Ok(Value::Null);
911 }
912 let n = i32::try_from(len).unwrap_or(i32::MAX);
913 Ok(Value::Int(n))
914 }
915 "array_position" => {
920 if args.len() != 2 {
921 return Err(EvalError::TypeMismatch {
922 detail: format!("array_position() takes 2 args, got {}", args.len()),
923 });
924 }
925 if matches!(args[0], Value::Null) {
926 return Ok(Value::Null);
927 }
928 if matches!(args[1], Value::Null) {
929 return Ok(Value::Null);
930 }
931 match (&args[0], &args[1]) {
932 (Value::TextArray(items), Value::Text(needle)) => {
933 for (idx, item) in items.iter().enumerate() {
934 if let Some(s) = item
935 && s == needle
936 {
937 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
938 }
939 }
940 Ok(Value::Null)
941 }
942 (Value::IntArray(items), needle_v)
943 if matches!(
944 needle_v,
945 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
946 ) =>
947 {
948 let needle: i64 = match *needle_v {
949 Value::Int(n) => i64::from(n),
950 Value::SmallInt(n) => i64::from(n),
951 Value::BigInt(n) => n,
952 _ => unreachable!(),
953 };
954 for (idx, item) in items.iter().enumerate() {
955 if let Some(n) = item
956 && i64::from(*n) == needle
957 {
958 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
959 }
960 }
961 Ok(Value::Null)
962 }
963 (Value::BigIntArray(items), needle_v)
964 if matches!(
965 needle_v,
966 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
967 ) =>
968 {
969 let needle: i64 = match *needle_v {
970 Value::Int(n) => i64::from(n),
971 Value::SmallInt(n) => i64::from(n),
972 Value::BigInt(n) => n,
973 _ => unreachable!(),
974 };
975 for (idx, item) in items.iter().enumerate() {
976 if let Some(n) = item
977 && *n == needle
978 {
979 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
980 }
981 }
982 Ok(Value::Null)
983 }
984 (
985 arr @ (Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_)),
986 other,
987 ) => Err(EvalError::TypeMismatch {
988 detail: format!(
989 "array_position() needle type {:?} doesn't match array {:?}",
990 other.data_type(),
991 arr.data_type()
992 ),
993 }),
994 (other, _) => Err(EvalError::TypeMismatch {
995 detail: format!(
996 "array_position() first arg must be an array, got {:?}",
997 other.data_type()
998 ),
999 }),
1000 }
1001 }
1002 "substring" | "substr" => {
1010 if !matches!(args.len(), 2 | 3) {
1011 return Err(EvalError::TypeMismatch {
1012 detail: format!("substring() takes 2 or 3 args, got {}", args.len()),
1013 });
1014 }
1015 if args.iter().any(|a| matches!(a, Value::Null)) {
1016 return Ok(Value::Null);
1017 }
1018 let start: i64 = match args[1] {
1019 Value::Int(n) => i64::from(n),
1020 Value::BigInt(n) => n,
1021 Value::SmallInt(n) => i64::from(n),
1022 _ => {
1023 return Err(EvalError::TypeMismatch {
1024 detail: format!(
1025 "substring() start must be integer, got {:?}",
1026 args[1].data_type()
1027 ),
1028 });
1029 }
1030 };
1031 let length: Option<i64> = if args.len() == 3 {
1032 match args[2] {
1033 Value::Int(n) => Some(i64::from(n)),
1034 Value::BigInt(n) => Some(n),
1035 Value::SmallInt(n) => Some(i64::from(n)),
1036 _ => {
1037 return Err(EvalError::TypeMismatch {
1038 detail: format!(
1039 "substring() length must be integer, got {:?}",
1040 args[2].data_type()
1041 ),
1042 });
1043 }
1044 }
1045 } else {
1046 None
1047 };
1048 let (effective_start, effective_length): (i64, Option<i64>) = match length {
1051 Some(len) => {
1052 let end = start.saturating_add(len);
1053 if end <= 1 || len < 0 {
1054 return Ok(match &args[0] {
1055 Value::Text(_) => Value::Text(String::new()),
1056 Value::Bytes(_) => Value::Bytes(Vec::new()),
1057 other => {
1058 return Err(EvalError::TypeMismatch {
1059 detail: format!(
1060 "substring() needs text or bytea, got {:?}",
1061 other.data_type()
1062 ),
1063 });
1064 }
1065 });
1066 }
1067 let eff_start = start.max(1);
1068 let eff_len = end - eff_start;
1069 (eff_start, Some(eff_len.max(0)))
1070 }
1071 None => (start.max(1), None),
1072 };
1073 match &args[0] {
1074 Value::Text(s) => {
1075 let chars: Vec<char> = s.chars().collect();
1077 let skip = (effective_start - 1) as usize;
1078 if skip >= chars.len() {
1079 return Ok(Value::Text(String::new()));
1080 }
1081 let take = match effective_length {
1082 Some(n) => (n as usize).min(chars.len() - skip),
1083 None => chars.len() - skip,
1084 };
1085 Ok(Value::Text(chars[skip..skip + take].iter().collect()))
1086 }
1087 Value::Bytes(b) => {
1088 let skip = (effective_start - 1) as usize;
1089 if skip >= b.len() {
1090 return Ok(Value::Bytes(Vec::new()));
1091 }
1092 let take = match effective_length {
1093 Some(n) => (n as usize).min(b.len() - skip),
1094 None => b.len() - skip,
1095 };
1096 Ok(Value::Bytes(b[skip..skip + take].to_vec()))
1097 }
1098 other => Err(EvalError::TypeMismatch {
1099 detail: format!(
1100 "substring() needs text or bytea, got {:?}",
1101 other.data_type()
1102 ),
1103 }),
1104 }
1105 }
1106 "position" => {
1114 if args.len() != 2 {
1115 return Err(EvalError::TypeMismatch {
1116 detail: format!("position() takes 2 args, got {}", args.len()),
1117 });
1118 }
1119 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
1120 return Ok(Value::Null);
1121 }
1122 match (&args[0], &args[1]) {
1123 (Value::Text(needle), Value::Text(haystack)) => {
1124 if needle.is_empty() {
1125 return Ok(Value::Int(1));
1126 }
1127 let h_chars: Vec<char> = haystack.chars().collect();
1129 let n_chars: Vec<char> = needle.chars().collect();
1130 if n_chars.len() > h_chars.len() {
1131 return Ok(Value::Int(0));
1132 }
1133 for i in 0..=h_chars.len() - n_chars.len() {
1134 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1135 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1136 }
1137 }
1138 Ok(Value::Int(0))
1139 }
1140 (Value::Bytes(needle), Value::Bytes(haystack)) => {
1141 if needle.is_empty() {
1142 return Ok(Value::Int(1));
1143 }
1144 if needle.len() > haystack.len() {
1145 return Ok(Value::Int(0));
1146 }
1147 for i in 0..=haystack.len() - needle.len() {
1148 if &haystack[i..i + needle.len()] == needle.as_slice() {
1149 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1150 }
1151 }
1152 Ok(Value::Int(0))
1153 }
1154 (a, b) => Err(EvalError::TypeMismatch {
1155 detail: format!(
1156 "position() operands must both be text or both bytea, got {:?} and {:?}",
1157 a.data_type(),
1158 b.data_type()
1159 ),
1160 }),
1161 }
1162 }
1163 "upper" => {
1164 if args.len() != 1 {
1165 return Err(EvalError::TypeMismatch {
1166 detail: format!("upper() takes 1 arg, got {}", args.len()),
1167 });
1168 }
1169 match &args[0] {
1170 Value::Null => Ok(Value::Null),
1171 Value::Text(s) => Ok(Value::Text(s.to_uppercase())),
1172 other => Err(EvalError::TypeMismatch {
1173 detail: format!("upper() needs text, got {:?}", other.data_type()),
1174 }),
1175 }
1176 }
1177 "lower" => {
1178 if args.len() != 1 {
1179 return Err(EvalError::TypeMismatch {
1180 detail: format!("lower() takes 1 arg, got {}", args.len()),
1181 });
1182 }
1183 match &args[0] {
1184 Value::Null => Ok(Value::Null),
1185 Value::Text(s) => Ok(Value::Text(s.to_lowercase())),
1186 other => Err(EvalError::TypeMismatch {
1187 detail: format!("lower() needs text, got {:?}", other.data_type()),
1188 }),
1189 }
1190 }
1191 "abs" => {
1192 if args.len() != 1 {
1193 return Err(EvalError::TypeMismatch {
1194 detail: format!("abs() takes 1 arg, got {}", args.len()),
1195 });
1196 }
1197 match &args[0] {
1198 Value::Null => Ok(Value::Null),
1199 Value::Int(n) => Ok(Value::Int(n.wrapping_abs())),
1200 Value::BigInt(n) => Ok(Value::BigInt(n.wrapping_abs())),
1201 Value::Float(x) => Ok(Value::Float(x.abs())),
1202 other => Err(EvalError::TypeMismatch {
1203 detail: format!("abs() needs numeric, got {:?}", other.data_type()),
1204 }),
1205 }
1206 }
1207 "coalesce" => {
1208 for a in args {
1209 if !matches!(a, Value::Null) {
1210 return Ok(a.clone());
1211 }
1212 }
1213 Ok(Value::Null)
1214 }
1215 "date_trunc" => date_trunc(args),
1216 "date_part" => date_part(args),
1217 "age" => age(args),
1218 "to_char" => to_char(args),
1219 "date_format" => date_format_mysql(args),
1225 "unix_timestamp" => unix_timestamp_of(args),
1226 "from_unixtime" => from_unixtime(args),
1227 "format" => format_string(args),
1235 "concat" => {
1256 let mut out = String::new();
1257 for v in args {
1258 if matches!(v, Value::Null) {
1259 continue;
1260 }
1261 out.push_str(&value_to_format_text(v));
1262 }
1263 Ok(Value::Text(out))
1264 }
1265 "random" => {
1388 if !args.is_empty() {
1389 return Err(EvalError::TypeMismatch {
1390 detail: alloc::format!("random() takes 0 args, got {}", args.len()),
1391 });
1392 }
1393 Ok(Value::Float(prng_next_f64()))
1394 }
1395 "gen_random_uuid" | "uuid_generate_v4" => {
1402 if !args.is_empty() {
1403 return Err(EvalError::TypeMismatch {
1404 detail: alloc::format!("{name}() takes 0 args, got {}", args.len()),
1405 });
1406 }
1407 Ok(Value::Uuid(gen_random_uuid_bytes()))
1408 }
1409 "sign" => {
1410 if args.len() != 1 {
1411 return Err(EvalError::TypeMismatch {
1412 detail: alloc::format!("sign() takes 1 arg, got {}", args.len()),
1413 });
1414 }
1415 match &args[0] {
1416 Value::Null => Ok(Value::Null),
1417 Value::SmallInt(n) => Ok(Value::SmallInt(n.signum())),
1418 Value::Int(n) => Ok(Value::Int(n.signum())),
1419 Value::BigInt(n) => Ok(Value::BigInt(n.signum())),
1420 Value::Float(x) => {
1421 let s = if *x > 0.0 {
1422 1.0
1423 } else if *x < 0.0 {
1424 -1.0
1425 } else {
1426 0.0
1427 };
1428 Ok(Value::Float(s))
1429 }
1430 Value::Numeric { scaled, scale } => {
1431 let s = scaled.signum();
1432 Ok(Value::Numeric {
1433 scaled: s * pow10_i128(*scale),
1434 scale: *scale,
1435 })
1436 }
1437 other => Err(EvalError::TypeMismatch {
1438 detail: alloc::format!("sign() needs numeric, got {:?}", other.data_type()),
1439 }),
1440 }
1441 }
1442 "sqrt" => {
1443 if args.len() != 1 {
1444 return Err(EvalError::TypeMismatch {
1445 detail: alloc::format!("sqrt() takes 1 arg, got {}", args.len()),
1446 });
1447 }
1448 match &args[0] {
1449 Value::Null => Ok(Value::Null),
1450 v => {
1451 let x = value_to_f64(v).ok_or_else(|| EvalError::TypeMismatch {
1452 detail: alloc::format!("sqrt() needs numeric, got {:?}", v.data_type()),
1453 })?;
1454 if x < 0.0 {
1455 return Err(EvalError::TypeMismatch {
1456 detail: "sqrt(): negative input outside real domain".into(),
1457 });
1458 }
1459 if x == 0.0 {
1460 return Ok(Value::Float(0.0));
1461 }
1462 Ok(Value::Float(f64_sqrt(x)))
1463 }
1464 }
1465 }
1466 "power" | "pow" => {
1467 if args.len() != 2 {
1468 return Err(EvalError::TypeMismatch {
1469 detail: alloc::format!("power() takes 2 args, got {}", args.len()),
1470 });
1471 }
1472 if args.iter().any(|v| matches!(v, Value::Null)) {
1473 return Ok(Value::Null);
1474 }
1475 let x = value_to_f64(&args[0]).ok_or_else(|| EvalError::TypeMismatch {
1476 detail: "power() needs numeric x".into(),
1477 })?;
1478 let y = value_to_f64(&args[1]).ok_or_else(|| EvalError::TypeMismatch {
1479 detail: "power() needs numeric y".into(),
1480 })?;
1481 let y_int = y as i32;
1483 if (y_int as f64) == y && y.abs() < 1024.0 {
1484 let result = f64_powi(x, y_int);
1485 return Ok(Value::Float(result));
1486 }
1487 if x < 0.0 {
1491 return Err(EvalError::TypeMismatch {
1492 detail: "power(): negative base with fractional exponent yields complex result"
1493 .into(),
1494 });
1495 }
1496 if x == 0.0 && y < 0.0 {
1497 return Err(EvalError::TypeMismatch {
1498 detail: "power(): 0 raised to negative power is undefined".into(),
1499 });
1500 }
1501 if x == 0.0 {
1502 return Ok(Value::Float(0.0));
1503 }
1504 Ok(Value::Float(f64_exp(y * f64_ln(x))))
1505 }
1506 "mod" => {
1507 if args.len() != 2 {
1508 return Err(EvalError::TypeMismatch {
1509 detail: alloc::format!("mod() takes 2 args, got {}", args.len()),
1510 });
1511 }
1512 if args.iter().any(|v| matches!(v, Value::Null)) {
1513 return Ok(Value::Null);
1514 }
1515 let to_i64 = |v: &Value| -> Result<i64, EvalError> {
1516 match v {
1517 Value::SmallInt(x) => Ok(i64::from(*x)),
1518 Value::Int(x) => Ok(i64::from(*x)),
1519 Value::BigInt(x) => Ok(*x),
1520 other => Err(EvalError::TypeMismatch {
1521 detail: alloc::format!("mod() needs integer, got {:?}", other.data_type()),
1522 }),
1523 }
1524 };
1525 let y = to_i64(&args[0])?;
1526 let x = to_i64(&args[1])?;
1527 if x == 0 {
1528 return Err(EvalError::TypeMismatch {
1529 detail: "mod(): division by zero".into(),
1530 });
1531 }
1532 let result = y % x;
1535 if let Ok(small) = i16::try_from(result) {
1537 if matches!(args[0], Value::SmallInt(_)) && matches!(args[1], Value::SmallInt(_)) {
1538 return Ok(Value::SmallInt(small));
1539 }
1540 }
1541 if let Ok(int_) = i32::try_from(result) {
1542 if !matches!(args[0], Value::BigInt(_)) && !matches!(args[1], Value::BigInt(_)) {
1543 return Ok(Value::Int(int_));
1544 }
1545 }
1546 Ok(Value::BigInt(result))
1547 }
1548 "greatest" | "least" => {
1549 if args.is_empty() {
1550 return Err(EvalError::TypeMismatch {
1551 detail: alloc::format!(
1552 "{lc}() takes at least 1 arg",
1553 lc = if name.eq_ignore_ascii_case("greatest") {
1554 "greatest"
1555 } else {
1556 "least"
1557 }
1558 ),
1559 });
1560 }
1561 let non_null: alloc::vec::Vec<&Value> =
1562 args.iter().filter(|v| !matches!(v, Value::Null)).collect();
1563 if non_null.is_empty() {
1564 return Ok(Value::Null);
1565 }
1566 let is_greatest = name.eq_ignore_ascii_case("greatest");
1567 let mut best = non_null[0].clone();
1568 for v in &non_null[1..] {
1569 let ord = value_cmp_for_min_max(&best, v);
1570 let take = if is_greatest {
1571 ord == core::cmp::Ordering::Less
1572 } else {
1573 ord == core::cmp::Ordering::Greater
1574 };
1575 if take {
1576 best = (*v).clone();
1577 }
1578 }
1579 Ok(best)
1580 }
1581 "ifnull" => {
1585 if args.len() != 2 {
1586 return Err(EvalError::TypeMismatch {
1587 detail: alloc::format!("ifnull() takes 2 args, got {}", args.len()),
1588 });
1589 }
1590 for v in args {
1591 if !matches!(v, Value::Null) {
1592 return Ok(v.clone());
1593 }
1594 }
1595 Ok(Value::Null)
1596 }
1597 "if" => {
1601 if args.len() != 3 {
1602 return Err(EvalError::TypeMismatch {
1603 detail: alloc::format!(
1604 "if() takes 3 args (cond, then, else), got {}",
1605 args.len()
1606 ),
1607 });
1608 }
1609 let truthy = match &args[0] {
1610 Value::Null => false,
1611 Value::Bool(b) => *b,
1612 Value::SmallInt(n) => *n != 0,
1613 Value::Int(n) => *n != 0,
1614 Value::BigInt(n) => *n != 0,
1615 Value::Float(x) => *x != 0.0,
1616 Value::Text(s) => !s.is_empty() && s != "0",
1617 _ => true,
1618 };
1619 if truthy {
1620 Ok(args[1].clone())
1621 } else {
1622 Ok(args[2].clone())
1623 }
1624 }
1625 "nullif" => {
1626 if args.len() != 2 {
1627 return Err(EvalError::TypeMismatch {
1628 detail: alloc::format!("nullif() takes 2 args, got {}", args.len()),
1629 });
1630 }
1631 match (&args[0], &args[1]) {
1632 (Value::Null, _) => Ok(Value::Null),
1633 (a, Value::Null) => Ok(a.clone()),
1634 (a, b) => {
1635 if values_equal_for_nullif(a, b) {
1639 Ok(Value::Null)
1640 } else {
1641 Ok(a.clone())
1642 }
1643 }
1644 }
1645 }
1646 "trunc" => {
1647 match args.len() {
1648 1 => match &args[0] {
1649 Value::Null => Ok(Value::Null),
1650 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1651 Value::Float(x) => Ok(Value::Float(f64_trunc(*x))),
1652 Value::Numeric { scaled, scale } => {
1653 let factor = pow10_i128(*scale);
1654 let q = scaled / factor;
1656 Ok(Value::Numeric {
1657 scaled: q * factor,
1658 scale: *scale,
1659 })
1660 }
1661 other => Err(EvalError::TypeMismatch {
1662 detail: alloc::format!(
1663 "trunc() needs numeric, got {:?}",
1664 other.data_type()
1665 ),
1666 }),
1667 },
1668 2 => {
1669 if args.iter().any(|v| matches!(v, Value::Null)) {
1670 return Ok(Value::Null);
1671 }
1672 let n = match &args[1] {
1673 Value::SmallInt(x) => i32::from(*x),
1674 Value::Int(x) => *x,
1675 Value::BigInt(x) => {
1676 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1677 detail: "trunc(): scale must fit in i32".into(),
1678 })?
1679 }
1680 other => {
1681 return Err(EvalError::TypeMismatch {
1682 detail: alloc::format!(
1683 "trunc(): scale must be integer, got {:?}",
1684 other.data_type()
1685 ),
1686 });
1687 }
1688 };
1689 let x = match &args[0] {
1690 Value::SmallInt(v) => f64::from(*v),
1691 Value::Int(v) => f64::from(*v),
1692 Value::BigInt(v) => *v as f64,
1693 Value::Float(v) => *v,
1694 Value::Numeric { scaled, scale } => {
1695 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1696 }
1697 other => {
1698 return Err(EvalError::TypeMismatch {
1699 detail: alloc::format!(
1700 "trunc() needs numeric x, got {:?}",
1701 other.data_type()
1702 ),
1703 });
1704 }
1705 };
1706 let result = if n >= 0 {
1707 let factor = f64_powi(10.0, n);
1708 f64_trunc(x * factor) / factor
1709 } else {
1710 let factor = f64_powi(10.0, -n);
1711 f64_trunc(x / factor) * factor
1712 };
1713 Ok(Value::Float(result))
1714 }
1715 _ => Err(EvalError::TypeMismatch {
1716 detail: alloc::format!("trunc() takes 1 or 2 args, got {}", args.len()),
1717 }),
1718 }
1719 }
1720 "round" => {
1721 match args.len() {
1722 1 => match &args[0] {
1723 Value::Null => Ok(Value::Null),
1724 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1725 Value::Float(x) => Ok(Value::Float(f64_round_half_away(*x))),
1726 Value::Numeric { scaled, scale } => {
1727 let factor = pow10_i128(*scale);
1728 let q = scaled.div_euclid(factor);
1729 let r = scaled.rem_euclid(factor);
1730 let result = if 2 * r >= factor { q + 1 } else { q };
1732 Ok(Value::Numeric {
1733 scaled: result * factor,
1734 scale: *scale,
1735 })
1736 }
1737 other => Err(EvalError::TypeMismatch {
1738 detail: alloc::format!(
1739 "round() needs numeric, got {:?}",
1740 other.data_type()
1741 ),
1742 }),
1743 },
1744 2 => {
1745 if args.iter().any(|v| matches!(v, Value::Null)) {
1746 return Ok(Value::Null);
1747 }
1748 let n = match &args[1] {
1749 Value::SmallInt(x) => i32::from(*x),
1750 Value::Int(x) => *x,
1751 Value::BigInt(x) => {
1752 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1753 detail: "round(): scale must fit in i32".into(),
1754 })?
1755 }
1756 other => {
1757 return Err(EvalError::TypeMismatch {
1758 detail: alloc::format!(
1759 "round(): scale must be integer, got {:?}",
1760 other.data_type()
1761 ),
1762 });
1763 }
1764 };
1765 let x = match &args[0] {
1771 Value::SmallInt(v) => f64::from(*v),
1772 Value::Int(v) => f64::from(*v),
1773 Value::BigInt(v) => *v as f64,
1774 Value::Float(v) => *v,
1775 Value::Numeric { scaled, scale } => {
1776 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1777 }
1778 other => {
1779 return Err(EvalError::TypeMismatch {
1780 detail: alloc::format!(
1781 "round() needs numeric x, got {:?}",
1782 other.data_type()
1783 ),
1784 });
1785 }
1786 };
1787 let result = if n >= 0 {
1792 let factor = f64_powi(10.0, n);
1793 f64_round_half_away(x * factor) / factor
1794 } else {
1795 let factor = f64_powi(10.0, -n);
1796 f64_round_half_away(x / factor) * factor
1797 };
1798 Ok(Value::Float(result))
1799 }
1800 _ => Err(EvalError::TypeMismatch {
1801 detail: alloc::format!("round() takes 1 or 2 args, got {}", args.len()),
1802 }),
1803 }
1804 }
1805 "ceil" | "ceiling" => {
1806 if args.len() != 1 {
1807 return Err(EvalError::TypeMismatch {
1808 detail: alloc::format!("ceil() takes 1 arg, got {}", args.len()),
1809 });
1810 }
1811 match &args[0] {
1812 Value::Null => Ok(Value::Null),
1813 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1814 Value::Float(x) => Ok(Value::Float(f64_ceil(*x))),
1815 Value::Numeric { scaled, scale } => {
1816 let factor = pow10_i128(*scale);
1817 let q = scaled.div_euclid(factor);
1818 let r = scaled.rem_euclid(factor);
1819 let result = if r == 0 { q } else { q + 1 };
1820 Ok(Value::Numeric {
1821 scaled: result * factor,
1822 scale: *scale,
1823 })
1824 }
1825 other => Err(EvalError::TypeMismatch {
1826 detail: alloc::format!("ceil() needs numeric, got {:?}", other.data_type()),
1827 }),
1828 }
1829 }
1830 "floor" => {
1831 if args.len() != 1 {
1832 return Err(EvalError::TypeMismatch {
1833 detail: alloc::format!("floor() takes 1 arg, got {}", args.len()),
1834 });
1835 }
1836 match &args[0] {
1837 Value::Null => Ok(Value::Null),
1838 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1839 Value::Float(x) => Ok(Value::Float(f64_floor(*x))),
1840 Value::Numeric { scaled, scale } => {
1841 let factor = pow10_i128(*scale);
1842 let q = scaled.div_euclid(factor);
1843 Ok(Value::Numeric {
1847 scaled: q * factor,
1848 scale: *scale,
1849 })
1850 }
1851 other => Err(EvalError::TypeMismatch {
1852 detail: alloc::format!("floor() needs numeric, got {:?}", other.data_type()),
1853 }),
1854 }
1855 }
1856 "left" => string_left_right(args, true, "left"),
1857 "right" => string_left_right(args, false, "right"),
1858 "strpos" => {
1859 if args.len() != 2 {
1860 return Err(EvalError::TypeMismatch {
1861 detail: alloc::format!(
1862 "strpos() takes 2 args (haystack, needle), got {}",
1863 args.len()
1864 ),
1865 });
1866 }
1867 if args.iter().any(|v| matches!(v, Value::Null)) {
1868 return Ok(Value::Null);
1869 }
1870 let haystack = value_to_format_text(&args[0]);
1871 let needle = value_to_format_text(&args[1]);
1872 if needle.is_empty() {
1873 return Ok(Value::Int(1));
1874 }
1875 let h_chars: Vec<char> = haystack.chars().collect();
1876 let n_chars: Vec<char> = needle.chars().collect();
1877 if n_chars.len() > h_chars.len() {
1878 return Ok(Value::Int(0));
1879 }
1880 for i in 0..=h_chars.len() - n_chars.len() {
1881 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1882 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1883 }
1884 }
1885 Ok(Value::Int(0))
1886 }
1887 "lpad" => string_pad(args, true, "lpad"),
1888 "rpad" => string_pad(args, false, "rpad"),
1889 "repeat" => {
1890 if args.len() != 2 {
1891 return Err(EvalError::TypeMismatch {
1892 detail: alloc::format!("repeat() takes 2 args, got {}", args.len()),
1893 });
1894 }
1895 if args.iter().any(|v| matches!(v, Value::Null)) {
1896 return Ok(Value::Null);
1897 }
1898 let s = value_to_format_text(&args[0]);
1899 let n = match &args[1] {
1900 Value::SmallInt(x) => i64::from(*x),
1901 Value::Int(x) => i64::from(*x),
1902 Value::BigInt(x) => *x,
1903 other => {
1904 return Err(EvalError::TypeMismatch {
1905 detail: alloc::format!(
1906 "repeat(): n must be integer, got {:?}",
1907 other.data_type()
1908 ),
1909 });
1910 }
1911 };
1912 if n <= 0 {
1913 return Ok(Value::Text(String::new()));
1914 }
1915 const MAX_REPEAT_BYTES: usize = 64 * 1024 * 1024;
1919 let needed =
1920 s.len()
1921 .checked_mul(n as usize)
1922 .ok_or_else(|| EvalError::TypeMismatch {
1923 detail: "repeat(): result size overflows usize".into(),
1924 })?;
1925 if needed > MAX_REPEAT_BYTES {
1926 return Err(EvalError::TypeMismatch {
1927 detail: alloc::format!(
1928 "repeat(): result would exceed {MAX_REPEAT_BYTES} bytes"
1929 ),
1930 });
1931 }
1932 Ok(Value::Text(s.repeat(n as usize)))
1933 }
1934 "split_part" => {
1935 if args.len() != 3 {
1936 return Err(EvalError::TypeMismatch {
1937 detail: alloc::format!(
1938 "split_part() takes 3 args (string, delim, n), got {}",
1939 args.len()
1940 ),
1941 });
1942 }
1943 if args.iter().any(|v| matches!(v, Value::Null)) {
1944 return Ok(Value::Null);
1945 }
1946 let s = value_to_format_text(&args[0]);
1947 let delim = value_to_format_text(&args[1]);
1948 if delim.is_empty() {
1949 return Err(EvalError::TypeMismatch {
1950 detail: "split_part(): delimiter cannot be empty".into(),
1951 });
1952 }
1953 let n = match &args[2] {
1954 Value::SmallInt(x) => i64::from(*x),
1955 Value::Int(x) => i64::from(*x),
1956 Value::BigInt(x) => *x,
1957 other => {
1958 return Err(EvalError::TypeMismatch {
1959 detail: alloc::format!(
1960 "split_part(): n must be integer, got {:?}",
1961 other.data_type()
1962 ),
1963 });
1964 }
1965 };
1966 if n == 0 {
1967 return Err(EvalError::TypeMismatch {
1968 detail: "split_part(): n must be nonzero (PG: 1-indexed)".into(),
1969 });
1970 }
1971 let parts: alloc::vec::Vec<&str> = s.split(&delim[..]).collect();
1972 let total = parts.len() as i64;
1973 let idx = if n > 0 {
1974 n - 1
1975 } else {
1976 total + n
1978 };
1979 if idx < 0 || idx >= total {
1980 return Ok(Value::Text(String::new()));
1981 }
1982 Ok(Value::Text(parts[idx as usize].to_string()))
1983 }
1984 "translate" => {
1991 if args.len() != 3 {
1992 return Err(EvalError::TypeMismatch {
1993 detail: alloc::format!("translate() takes 3 args, got {}", args.len()),
1994 });
1995 }
1996 if args.iter().any(|v| matches!(v, Value::Null)) {
1997 return Ok(Value::Null);
1998 }
1999 let s = value_to_format_text(&args[0]);
2000 let from = value_to_format_text(&args[1]);
2001 let to = value_to_format_text(&args[2]);
2002 let from_chars: Vec<char> = from.chars().collect();
2003 let to_chars: Vec<char> = to.chars().collect();
2004 let mut map: alloc::collections::BTreeMap<char, Option<char>> =
2006 alloc::collections::BTreeMap::new();
2007 for (i, &fc) in from_chars.iter().enumerate() {
2008 if map.contains_key(&fc) {
2009 continue;
2010 }
2011 let replacement = to_chars.get(i).copied();
2012 map.insert(fc, replacement);
2013 }
2014 let mut out = String::with_capacity(s.len());
2015 for c in s.chars() {
2016 match map.get(&c) {
2017 Some(Some(r)) => out.push(*r),
2018 Some(None) => {} None => out.push(c),
2020 }
2021 }
2022 Ok(Value::Text(out))
2023 }
2024 "replace" => {
2025 if args.len() != 3 {
2026 return Err(EvalError::TypeMismatch {
2027 detail: alloc::format!(
2028 "replace() takes 3 args (string, from, to), got {}",
2029 args.len()
2030 ),
2031 });
2032 }
2033 if args.iter().any(|v| matches!(v, Value::Null)) {
2034 return Ok(Value::Null);
2035 }
2036 let s = value_to_format_text(&args[0]);
2037 let from = value_to_format_text(&args[1]);
2038 let to = value_to_format_text(&args[2]);
2039 if from.is_empty() {
2040 return Ok(Value::Text(s));
2041 }
2042 Ok(Value::Text(s.replace(&from[..], &to)))
2047 }
2048 "trim" | "btrim" => string_trim(args, TrimSide::Both, "trim"),
2049 "ltrim" => string_trim(args, TrimSide::Left, "ltrim"),
2050 "rtrim" => string_trim(args, TrimSide::Right, "rtrim"),
2051 "concat_ws" => {
2052 if args.is_empty() {
2053 return Err(EvalError::TypeMismatch {
2054 detail: "concat_ws() requires at least 1 arg (the separator)".into(),
2055 });
2056 }
2057 let sep = match &args[0] {
2059 Value::Null => return Ok(Value::Null),
2060 v => value_to_format_text(v),
2061 };
2062 let mut out = String::new();
2063 let mut first = true;
2064 for v in &args[1..] {
2065 if matches!(v, Value::Null) {
2066 continue;
2067 }
2068 if first {
2069 first = false;
2070 } else {
2071 out.push_str(&sep);
2072 }
2073 out.push_str(&value_to_format_text(v));
2074 }
2075 Ok(Value::Text(out))
2076 }
2077 "regexp_matches" => regexp_matches(args),
2079 "regexp_replace" => regexp_replace(args),
2080 "regexp_split_to_array" => regexp_split_to_array(args),
2081 "to_json" | "to_jsonb" => {
2085 if args.len() != 1 {
2086 return Err(EvalError::TypeMismatch {
2087 detail: alloc::format!("to_json() takes 1 arg, got {}", args.len()),
2088 });
2089 }
2090 if let Value::Json(s) = &args[0] {
2092 return Ok(Value::Json(s.clone()));
2093 }
2094 Ok(Value::Json(crate::json::value_to_json_text(&args[0])))
2095 }
2096 "json_build_object" | "jsonb_build_object" => crate::json::build_object(args),
2097 "json_build_array" | "jsonb_build_array" => crate::json::build_array(args),
2098 "jsonb_set" | "json_set" => crate::json::set(args),
2099 "jsonb_insert" | "json_insert" => crate::json::insert(args),
2100 "jsonb_path_query" | "json_path_query" => {
2102 if args.len() != 2 {
2103 return Err(EvalError::TypeMismatch {
2104 detail: alloc::format!("jsonb_path_query() takes 2 args, got {}", args.len()),
2105 });
2106 }
2107 crate::json::path_query(&args[0], &args[1])
2108 }
2109 "jsonb_path_query_first" | "json_path_query_first" => {
2110 if args.len() != 2 {
2111 return Err(EvalError::TypeMismatch {
2112 detail: alloc::format!(
2113 "jsonb_path_query_first() takes 2 args, got {}",
2114 args.len()
2115 ),
2116 });
2117 }
2118 crate::json::path_query_first(&args[0], &args[1])
2119 }
2120 "jsonb_path_query_array" | "json_path_query_array" => {
2121 if args.len() != 2 {
2122 return Err(EvalError::TypeMismatch {
2123 detail: alloc::format!(
2124 "jsonb_path_query_array() takes 2 args, got {}",
2125 args.len()
2126 ),
2127 });
2128 }
2129 crate::json::path_query_array(&args[0], &args[1])
2130 }
2131 "host" => inet_host(args),
2133 "network" => inet_network(args),
2134 "masklen" => inet_masklen(args),
2135 "encode" => encode_text(args),
2137 "decode" => decode_text(args),
2138 "error_on_null" => error_on_null(args),
2139 "to_tsvector" => fts_to_tsvector(args, ctx),
2144 "setweight" => fts_setweight(args),
2148 "string_to_array" => fn_string_to_array(args),
2152 "plainto_tsquery" => fts_plainto_tsquery(args, ctx),
2153 "phraseto_tsquery" => fts_phraseto_tsquery(args, ctx),
2154 "websearch_to_tsquery" => fts_websearch_to_tsquery(args, ctx),
2155 "to_tsquery" => fts_to_tsquery(args, ctx),
2156 "ts_rank" => fts_ts_rank(args),
2159 "ts_rank_cd" => fts_ts_rank_cd(args),
2160 "set_config" => Ok(args.get(1).cloned().unwrap_or(Value::Null)),
2165 "current_setting" => Ok(Value::Text(String::new())),
2166 "pg_get_serial_sequence" | "pg_get_constraintdef" | "pg_get_indexdef" => Ok(Value::Null),
2172 "version" => Ok(Value::Text("PostgreSQL 16 (SPG-compat)".into())),
2173 "current_database" | "database" => Ok(Value::Text("spg".into())),
2181 "current_schema" => Ok(Value::Text("public".into())),
2182 "current_user" | "session_user" | "user" => Ok(Value::Text("admin".into())),
2183 "pg_typeof" => {
2190 if args.len() != 1 {
2191 return Err(EvalError::TypeMismatch {
2192 detail: format!("pg_typeof() takes 1 arg, got {}", args.len()),
2193 });
2194 }
2195 Ok(Value::Text(pg_typeof_name(&args[0]).into()))
2196 }
2197 "lastval" => Ok(Value::Null),
2202 "similarity" => {
2207 if args.len() != 2 {
2208 return Err(EvalError::TypeMismatch {
2209 detail: format!("similarity() takes 2 args, got {}", args.len()),
2210 });
2211 }
2212 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2213 return Ok(Value::Null);
2214 }
2215 let a = match &args[0] {
2216 Value::Text(s) => s.as_str(),
2217 other => {
2218 return Err(EvalError::TypeMismatch {
2219 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2220 });
2221 }
2222 };
2223 let b = match &args[1] {
2224 Value::Text(s) => s.as_str(),
2225 other => {
2226 return Err(EvalError::TypeMismatch {
2227 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2228 });
2229 }
2230 };
2231 Ok(Value::Float(spg_storage::trgm::similarity(a, b)))
2234 }
2235 "show_trgm" => {
2236 if args.len() != 1 {
2237 return Err(EvalError::TypeMismatch {
2238 detail: format!("show_trgm() takes 1 arg, got {}", args.len()),
2239 });
2240 }
2241 if matches!(args[0], Value::Null) {
2242 return Ok(Value::Null);
2243 }
2244 let s = match &args[0] {
2245 Value::Text(s) => s.as_str(),
2246 other => {
2247 return Err(EvalError::TypeMismatch {
2248 detail: format!("show_trgm() needs text, got {:?}", other.data_type()),
2249 });
2250 }
2251 };
2252 let trigrams: Vec<Option<String>> = spg_storage::trgm::extract_trigrams(s)
2256 .into_iter()
2257 .map(Some)
2258 .collect();
2259 Ok(Value::TextArray(trigrams))
2260 }
2261 other => Err(EvalError::TypeMismatch {
2262 detail: format!("unknown function `{other}`"),
2263 }),
2264 }
2265}
2266
2267fn fts_ts_rank(args: &[Value]) -> Result<Value, EvalError> {
2272 let (vec, query) = parse_rank_args("ts_rank", args)?;
2273 match (vec, query) {
2274 (None, _) | (_, None) => Ok(Value::Null),
2275 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank(&v, &q)))),
2276 }
2277}
2278
2279fn fts_ts_rank_cd(args: &[Value]) -> Result<Value, EvalError> {
2280 let (vec, query) = parse_rank_args("ts_rank_cd", args)?;
2281 match (vec, query) {
2282 (None, _) | (_, None) => Ok(Value::Null),
2283 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank_cd(&v, &q)))),
2284 }
2285}
2286
2287fn parse_rank_args(
2288 name: &str,
2289 args: &[Value],
2290) -> Result<
2291 (
2292 Option<Vec<spg_storage::TsLexeme>>,
2293 Option<spg_storage::TsQueryAst>,
2294 ),
2295 EvalError,
2296> {
2297 if args.len() != 2 {
2298 return Err(EvalError::TypeMismatch {
2299 detail: format!(
2300 "{name}() takes 2 args in v7.12.2 (weights array + normalisation flag are v7.12.x carve-out), got {}",
2301 args.len()
2302 ),
2303 });
2304 }
2305 let vec = match &args[0] {
2306 Value::Null => None,
2307 Value::TsVector(v) => Some(v.clone()),
2308 other => {
2309 return Err(EvalError::TypeMismatch {
2310 detail: format!(
2311 "{name}() first arg must be tsvector, got {:?}",
2312 other.data_type()
2313 ),
2314 });
2315 }
2316 };
2317 let query = match &args[1] {
2318 Value::Null => None,
2319 Value::TsQuery(q) => Some(q.clone()),
2320 other => {
2321 return Err(EvalError::TypeMismatch {
2322 detail: format!(
2323 "{name}() second arg must be tsquery, got {:?}",
2324 other.data_type()
2325 ),
2326 });
2327 }
2328 };
2329 Ok((vec, query))
2330}
2331
2332fn ts_match(l: Value, r: Value) -> Result<Value, EvalError> {
2337 let (vec, query) = match (l, r) {
2338 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
2339 (Value::TsVector(v), Value::TsQuery(q)) => (v, q),
2340 (Value::TsQuery(q), Value::TsVector(v)) => (v, q),
2341 (l, r) => {
2342 return Err(EvalError::TypeMismatch {
2343 detail: format!(
2344 "@@ requires (tsvector, tsquery), got ({:?}, {:?})",
2345 l.data_type(),
2346 r.data_type()
2347 ),
2348 });
2349 }
2350 };
2351 Ok(Value::Bool(crate::fts::ts_query_matches(&vec, &query)))
2352}
2353
2354fn fts_to_tsvector(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2359 let (config, text) = parse_fts_args("to_tsvector", args, ctx)?;
2360 match text {
2361 None => Ok(Value::Null),
2362 Some(t) => Ok(Value::TsVector(crate::fts::to_tsvector(config, &t))),
2363 }
2364}
2365
2366fn fts_setweight(args: &[Value]) -> Result<Value, EvalError> {
2369 let [vec_arg, weight_arg] = args else {
2370 return Err(EvalError::TypeMismatch {
2371 detail: alloc::format!("setweight expects 2 arguments, got {}", args.len()),
2372 });
2373 };
2374 if matches!(vec_arg, Value::Null) || matches!(weight_arg, Value::Null) {
2375 return Ok(Value::Null);
2376 }
2377 let Value::TsVector(lexemes) = vec_arg else {
2378 return Err(EvalError::TypeMismatch {
2379 detail: alloc::format!(
2380 "setweight expects a tsvector, got {:?}",
2381 vec_arg.data_type()
2382 ),
2383 });
2384 };
2385 let Value::Text(w) = weight_arg else {
2386 return Err(EvalError::TypeMismatch {
2387 detail: alloc::format!(
2388 "setweight expects a weight letter, got {:?}",
2389 weight_arg.data_type()
2390 ),
2391 });
2392 };
2393 let weight = match w.to_ascii_uppercase().as_str() {
2394 "A" => 3,
2395 "B" => 2,
2396 "C" => 1,
2397 "D" => 0,
2398 other => {
2399 return Err(EvalError::TypeMismatch {
2400 detail: alloc::format!("unrecognized weight: {other:?} (expected A, B, C or D)"),
2401 });
2402 }
2403 };
2404 let mut out = lexemes.clone();
2405 for lex in &mut out {
2406 lex.weight = weight;
2407 }
2408 Ok(Value::TsVector(out))
2409}
2410
2411fn fn_string_to_array(args: &[Value]) -> Result<Value, EvalError> {
2413 let [text_arg, delim_arg] = args else {
2414 return Err(EvalError::TypeMismatch {
2415 detail: alloc::format!("string_to_array expects 2 arguments, got {}", args.len()),
2416 });
2417 };
2418 let text = match text_arg {
2419 Value::Null => return Ok(Value::Null),
2420 Value::Text(t) => t,
2421 other => {
2422 return Err(EvalError::TypeMismatch {
2423 detail: alloc::format!("string_to_array expects text, got {:?}", other.data_type()),
2424 });
2425 }
2426 };
2427 if text.is_empty() {
2429 return Ok(Value::TextArray(Vec::new()));
2430 }
2431 let parts: Vec<Option<String>> = match delim_arg {
2432 Value::Null => text.chars().map(|c| Some(c.to_string())).collect(),
2434 Value::Text(d) if d.is_empty() => alloc::vec![Some(text.clone())],
2435 Value::Text(d) => text
2436 .split(d.as_str())
2437 .map(|p| Some(p.to_string()))
2438 .collect(),
2439 other => {
2440 return Err(EvalError::TypeMismatch {
2441 detail: alloc::format!(
2442 "string_to_array delimiter must be text, got {:?}",
2443 other.data_type()
2444 ),
2445 });
2446 }
2447 };
2448 Ok(Value::TextArray(parts))
2449}
2450
2451fn fts_plainto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2452 let (config, text) = parse_fts_args("plainto_tsquery", args, ctx)?;
2453 match text {
2454 None => Ok(Value::Null),
2455 Some(t) => Ok(Value::TsQuery(crate::fts::plainto_tsquery(config, &t))),
2456 }
2457}
2458
2459fn fts_phraseto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2460 let (config, text) = parse_fts_args("phraseto_tsquery", args, ctx)?;
2461 match text {
2462 None => Ok(Value::Null),
2463 Some(t) => Ok(Value::TsQuery(crate::fts::phraseto_tsquery(config, &t))),
2464 }
2465}
2466
2467fn fts_websearch_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2468 let (config, text) = parse_fts_args("websearch_to_tsquery", args, ctx)?;
2469 match text {
2470 None => Ok(Value::Null),
2471 Some(t) => Ok(Value::TsQuery(crate::fts::websearch_to_tsquery(config, &t))),
2472 }
2473}
2474
2475fn fts_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2476 let (config, text) = parse_fts_args("to_tsquery", args, ctx)?;
2477 match text {
2478 None => Ok(Value::Null),
2479 Some(t) => Ok(Value::TsQuery(crate::fts::to_tsquery(config, &t)?)),
2480 }
2481}
2482
2483fn parse_fts_args(
2488 name: &str,
2489 args: &[Value],
2490 ctx: &EvalContext<'_>,
2491) -> Result<(crate::fts::TsConfig, Option<String>), EvalError> {
2492 let (config_arg, text_arg) = match args {
2493 [t] => (None, t),
2494 [c, t] => (Some(c), t),
2495 _ => {
2496 return Err(EvalError::TypeMismatch {
2497 detail: format!("{name}() takes 1 or 2 args, got {}", args.len()),
2498 });
2499 }
2500 };
2501 let config = match config_arg {
2502 None => match ctx.default_text_search_config {
2503 Some(name_str) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2504 EvalError::TypeMismatch {
2505 detail: format!(
2506 "text search config not implemented: {name_str:?} (supported: simple, english)"
2507 ),
2508 }
2509 })?,
2510 None => crate::fts::TsConfig::Simple,
2511 },
2512 Some(Value::Null) => return Ok((crate::fts::TsConfig::Simple, None)),
2513 Some(Value::Text(name_str)) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2514 EvalError::TypeMismatch {
2515 detail: format!(
2516 "text search config not implemented: {name_str:?} (supported: simple, english)"
2517 ),
2518 }
2519 })?,
2520 Some(other) => {
2521 return Err(EvalError::TypeMismatch {
2522 detail: format!(
2523 "{name}() config arg must be text, got {:?}",
2524 other.data_type()
2525 ),
2526 });
2527 }
2528 };
2529 let text = match text_arg {
2530 Value::Null => None,
2531 Value::Text(s) => Some(s.clone()),
2532 other => {
2533 return Err(EvalError::TypeMismatch {
2534 detail: format!(
2535 "{name}() text arg must be text, got {:?}",
2536 other.data_type()
2537 ),
2538 });
2539 }
2540 };
2541 Ok((config, text))
2542}
2543
2544fn encode_text(args: &[Value]) -> Result<Value, EvalError> {
2550 if args.len() != 2 {
2551 return Err(EvalError::TypeMismatch {
2552 detail: format!("encode() takes 2 args, got {}", args.len()),
2553 });
2554 }
2555 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2556 return Ok(Value::Null);
2557 }
2558 let bytes: &[u8] = match &args[0] {
2559 Value::Text(s) => s.as_bytes(),
2560 other => {
2561 return Err(EvalError::TypeMismatch {
2562 detail: format!("encode() expects text bytes, got {:?}", other.data_type()),
2563 });
2564 }
2565 };
2566 let fmt = match &args[1] {
2567 Value::Text(s) => s.to_ascii_lowercase(),
2568 other => {
2569 return Err(EvalError::TypeMismatch {
2570 detail: format!("encode() format must be text, got {:?}", other.data_type()),
2571 });
2572 }
2573 };
2574 let out = match fmt.as_str() {
2575 "base64" => b64_encode(bytes, B64_STD),
2576 "base64url" => b64_encode(bytes, B64_URL),
2577 "base32hex" => b32hex_encode(bytes),
2578 "hex" => hex_encode(bytes),
2579 other => {
2580 return Err(EvalError::TypeMismatch {
2581 detail: format!("encode(): unknown format `{other}`"),
2582 });
2583 }
2584 };
2585 Ok(Value::Text(out))
2586}
2587
2588fn decode_text(args: &[Value]) -> Result<Value, EvalError> {
2592 if args.len() != 2 {
2593 return Err(EvalError::TypeMismatch {
2594 detail: format!("decode() takes 2 args, got {}", args.len()),
2595 });
2596 }
2597 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2598 return Ok(Value::Null);
2599 }
2600 let text = match &args[0] {
2601 Value::Text(s) => s.as_str(),
2602 other => {
2603 return Err(EvalError::TypeMismatch {
2604 detail: format!("decode() expects text, got {:?}", other.data_type()),
2605 });
2606 }
2607 };
2608 let fmt = match &args[1] {
2609 Value::Text(s) => s.to_ascii_lowercase(),
2610 other => {
2611 return Err(EvalError::TypeMismatch {
2612 detail: format!("decode() format must be text, got {:?}", other.data_type()),
2613 });
2614 }
2615 };
2616 let bytes = match fmt.as_str() {
2617 "base64" => b64_decode(text, B64_STD)?,
2618 "base64url" => b64_decode(text, B64_URL)?,
2619 "base32hex" => b32hex_decode(text)?,
2620 "hex" => hex_decode(text)?,
2621 other => {
2622 return Err(EvalError::TypeMismatch {
2623 detail: format!("decode(): unknown format `{other}`"),
2624 });
2625 }
2626 };
2627 let s = String::from_utf8(bytes).map_err(|_| EvalError::TypeMismatch {
2628 detail: "decode(): result bytes are not valid UTF-8 (SPG stores raw bytes as Text)".into(),
2629 })?;
2630 Ok(Value::Text(s))
2631}
2632
2633fn error_on_null(args: &[Value]) -> Result<Value, EvalError> {
2637 if args.len() != 1 {
2638 return Err(EvalError::TypeMismatch {
2639 detail: format!("error_on_null() takes 1 arg, got {}", args.len()),
2640 });
2641 }
2642 if matches!(args[0], Value::Null) {
2643 return Err(EvalError::TypeMismatch {
2644 detail: "error_on_null(): argument is NULL".into(),
2645 });
2646 }
2647 Ok(args[0].clone())
2648}
2649
2650const B64_STD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2653const B64_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2654const B32HEX_ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHIJKLMNOPQRSTUV";
2655
2656fn b64_encode(bytes: &[u8], alpha: &[u8; 64]) -> String {
2657 let mut out = String::with_capacity((bytes.len() + 2) / 3 * 4);
2658 let mut i = 0;
2659 while i + 3 <= bytes.len() {
2660 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8) | (bytes[i + 2] as u32);
2661 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2662 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2663 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2664 out.push(alpha[(n & 0x3f) as usize] as char);
2665 i += 3;
2666 }
2667 let rem = bytes.len() - i;
2668 if rem == 1 {
2669 let n = (bytes[i] as u32) << 16;
2670 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2671 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2672 out.push('=');
2673 out.push('=');
2674 } else if rem == 2 {
2675 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8);
2676 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2677 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2678 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2679 out.push('=');
2680 }
2681 out
2682}
2683
2684fn b64_decode(text: &str, alpha: &[u8; 64]) -> Result<Vec<u8>, EvalError> {
2685 let mut lookup = [255u8; 256];
2686 for (i, &c) in alpha.iter().enumerate() {
2687 lookup[c as usize] = i as u8;
2688 }
2689 let mut out = Vec::with_capacity(text.len() * 3 / 4);
2690 let mut buf: u32 = 0;
2691 let mut bits: u32 = 0;
2692 for c in text.bytes() {
2693 if c == b'=' {
2694 break;
2695 }
2696 if c == b'\n' || c == b'\r' || c == b' ' {
2697 continue;
2698 }
2699 let v = lookup[c as usize];
2700 if v == 255 {
2701 return Err(EvalError::TypeMismatch {
2702 detail: format!("decode(base64): invalid char {:?}", c as char),
2703 });
2704 }
2705 buf = (buf << 6) | v as u32;
2706 bits += 6;
2707 if bits >= 8 {
2708 bits -= 8;
2709 out.push(((buf >> bits) & 0xff) as u8);
2710 }
2711 }
2712 Ok(out)
2713}
2714
2715fn b32hex_encode(bytes: &[u8]) -> String {
2716 let mut out = String::with_capacity((bytes.len() * 8 + 4) / 5);
2717 let mut buf: u64 = 0;
2718 let mut bits: u32 = 0;
2719 for &b in bytes {
2720 buf = (buf << 8) | b as u64;
2721 bits += 8;
2722 while bits >= 5 {
2723 bits -= 5;
2724 out.push(B32HEX_ALPHABET[((buf >> bits) & 0x1f) as usize] as char);
2725 }
2726 }
2727 if bits > 0 {
2728 out.push(B32HEX_ALPHABET[((buf << (5 - bits)) & 0x1f) as usize] as char);
2729 }
2730 while out.len() % 8 != 0 {
2732 out.push('=');
2733 }
2734 out
2735}
2736
2737fn b32hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2738 let mut lookup = [255u8; 256];
2739 for (i, &c) in B32HEX_ALPHABET.iter().enumerate() {
2740 lookup[c as usize] = i as u8;
2741 let lower = (c as char).to_ascii_lowercase() as u8;
2743 lookup[lower as usize] = i as u8;
2744 }
2745 let mut out = Vec::with_capacity(text.len() * 5 / 8);
2746 let mut buf: u64 = 0;
2747 let mut bits: u32 = 0;
2748 for c in text.bytes() {
2749 if c == b'=' {
2750 break;
2751 }
2752 if c == b'\n' || c == b'\r' || c == b' ' {
2753 continue;
2754 }
2755 let v = lookup[c as usize];
2756 if v == 255 {
2757 return Err(EvalError::TypeMismatch {
2758 detail: format!("decode(base32hex): invalid char {:?}", c as char),
2759 });
2760 }
2761 buf = (buf << 5) | v as u64;
2762 bits += 5;
2763 if bits >= 8 {
2764 bits -= 8;
2765 out.push(((buf >> bits) & 0xff) as u8);
2766 }
2767 }
2768 Ok(out)
2769}
2770
2771fn hex_encode(bytes: &[u8]) -> String {
2772 const HEX: &[u8; 16] = b"0123456789abcdef";
2773 let mut out = String::with_capacity(bytes.len() * 2);
2774 for &b in bytes {
2775 out.push(HEX[(b >> 4) as usize] as char);
2776 out.push(HEX[(b & 0xf) as usize] as char);
2777 }
2778 out
2779}
2780
2781fn hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2782 let trimmed = text.trim();
2783 if trimmed.len() % 2 != 0 {
2784 return Err(EvalError::TypeMismatch {
2785 detail: "decode(hex): input length must be even".into(),
2786 });
2787 }
2788 let mut out = Vec::with_capacity(trimmed.len() / 2);
2789 let mut hi: u8 = 0;
2790 for (i, c) in trimmed.bytes().enumerate() {
2791 let v = match c {
2792 b'0'..=b'9' => c - b'0',
2793 b'a'..=b'f' => c - b'a' + 10,
2794 b'A'..=b'F' => c - b'A' + 10,
2795 _ => {
2796 return Err(EvalError::TypeMismatch {
2797 detail: format!("decode(hex): invalid char {:?}", c as char),
2798 });
2799 }
2800 };
2801 if i % 2 == 0 {
2802 hi = v;
2803 } else {
2804 out.push((hi << 4) | v);
2805 }
2806 }
2807 Ok(out)
2808}
2809
2810fn date_part(args: &[Value]) -> Result<Value, EvalError> {
2815 use spg_sql::ast::ExtractField as F;
2816 if args.len() != 2 {
2817 return Err(EvalError::TypeMismatch {
2818 detail: format!("date_part() takes 2 args, got {}", args.len()),
2819 });
2820 }
2821 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
2822 return Ok(Value::Null);
2823 }
2824 let Value::Text(field_name) = &args[0] else {
2825 return Err(EvalError::TypeMismatch {
2826 detail: format!(
2827 "date_part() needs a text field, got {:?}",
2828 args[0].data_type()
2829 ),
2830 });
2831 };
2832 let field = match field_name.to_ascii_lowercase().as_str() {
2833 "year" => F::Year,
2834 "month" => F::Month,
2835 "day" => F::Day,
2836 "hour" => F::Hour,
2837 "minute" => F::Minute,
2838 "second" => F::Second,
2839 "microsecond" | "microseconds" => F::Microsecond,
2840 "epoch" => F::Epoch,
2841 other => {
2842 return Err(EvalError::TypeMismatch {
2843 detail: format!(
2844 "unknown date_part field {other:?}; \
2845 supported: year, month, day, hour, minute, second, microsecond"
2846 ),
2847 });
2848 }
2849 };
2850 extract_field(field, &args[1])
2851}
2852
2853fn age(args: &[Value]) -> Result<Value, EvalError> {
2863 if args.is_empty() || args.len() > 2 {
2864 return Err(EvalError::TypeMismatch {
2865 detail: format!("age() takes 1 or 2 args, got {}", args.len()),
2866 });
2867 }
2868 if args.iter().any(|v| matches!(v, Value::Null)) {
2869 return Ok(Value::Null);
2870 }
2871 let to_micros = |v: &Value| -> Result<i64, EvalError> {
2874 match v {
2875 Value::Timestamp(t) => Ok(*t),
2876 Value::Date(d) => Ok(i64::from(*d) * 86_400_000_000),
2877 other => Err(EvalError::TypeMismatch {
2878 detail: format!("age() needs DATE or TIMESTAMP, got {:?}", other.data_type()),
2879 }),
2880 }
2881 };
2882 if args.len() == 1 {
2883 return Err(EvalError::TypeMismatch {
2884 detail: "single-arg age() is unsupported in v2.12 \
2885 (use age(CURRENT_DATE, t) explicitly)"
2886 .into(),
2887 });
2888 }
2889 let a = to_micros(&args[0])?;
2890 let b = to_micros(&args[1])?;
2891 let delta = a.checked_sub(b).ok_or(EvalError::TypeMismatch {
2892 detail: "age() subtraction overflows i64 microseconds".into(),
2893 })?;
2894 Ok(Value::Interval {
2895 months: 0,
2896 micros: delta,
2897 })
2898}
2899
2900fn inet_host(args: &[Value]) -> Result<Value, EvalError> {
2914 let s = match args {
2915 [Value::Text(s)] => s.clone(),
2916 [Value::Null] => return Ok(Value::Null),
2917 _ => {
2918 return Err(EvalError::TypeMismatch {
2919 detail: alloc::format!("host() takes one TEXT arg, got {} args", args.len()),
2920 });
2921 }
2922 };
2923 let host = s.split('/').next().unwrap_or("").to_string();
2924 Ok(Value::Text(host))
2925}
2926
2927fn inet_network(args: &[Value]) -> Result<Value, EvalError> {
2928 let s = match args {
2929 [Value::Text(s)] => s.clone(),
2930 [Value::Null] => return Ok(Value::Null),
2931 _ => {
2932 return Err(EvalError::TypeMismatch {
2933 detail: alloc::format!("network() takes one TEXT arg, got {} args", args.len()),
2934 });
2935 }
2936 };
2937 let mut split = s.splitn(2, '/');
2941 let host = split.next().unwrap_or("").to_string();
2942 let mask: u32 = split.next().and_then(|m| m.parse().ok()).unwrap_or(32);
2943 if !host.contains('.') {
2944 return Ok(Value::Text(s));
2946 }
2947 let octets: Vec<&str> = host.split('.').collect();
2948 if octets.len() != 4 {
2949 return Ok(Value::Text(s));
2950 }
2951 let keep_bytes = ((mask + 7) / 8) as usize;
2952 let mut out = alloc::string::String::new();
2953 for (i, oct) in octets.iter().enumerate() {
2954 if i > 0 {
2955 out.push('.');
2956 }
2957 if i < keep_bytes {
2958 out.push_str(oct);
2959 } else {
2960 out.push('0');
2961 }
2962 }
2963 out.push('/');
2964 out.push_str(&mask.to_string());
2965 Ok(Value::Text(out))
2966}
2967
2968fn inet_masklen(args: &[Value]) -> Result<Value, EvalError> {
2969 let s = match args {
2970 [Value::Text(s)] => s.clone(),
2971 [Value::Null] => return Ok(Value::Null),
2972 _ => {
2973 return Err(EvalError::TypeMismatch {
2974 detail: alloc::format!("masklen() takes one TEXT arg, got {} args", args.len()),
2975 });
2976 }
2977 };
2978 let mask: i32 = s
2979 .split_once('/')
2980 .and_then(|(_, m)| m.parse().ok())
2981 .unwrap_or(32);
2982 Ok(Value::Int(mask))
2983}
2984
2985struct InetNet {
3004 bytes: [u8; 16],
3005 family_bytes: u8,
3007 prefix_bits: u8,
3009}
3010
3011fn parse_inet_text(s: &str) -> Option<InetNet> {
3012 let mut split = s.splitn(2, '/');
3013 let host = split.next()?;
3014 let mask_str = split.next();
3015 if host.contains(':') {
3016 let bytes = parse_ipv6(host)?;
3017 let prefix_bits = match mask_str {
3018 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 128)?,
3019 None => 128,
3020 };
3021 let mut out = [0u8; 16];
3022 out.copy_from_slice(&bytes);
3023 Some(InetNet {
3024 bytes: out,
3025 family_bytes: 16,
3026 prefix_bits,
3027 })
3028 } else {
3029 let bytes = parse_ipv4(host)?;
3030 let prefix_bits = match mask_str {
3031 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 32)?,
3032 None => 32,
3033 };
3034 let mut out = [0u8; 16];
3035 out[..4].copy_from_slice(&bytes);
3036 Some(InetNet {
3037 bytes: out,
3038 family_bytes: 4,
3039 prefix_bits,
3040 })
3041 }
3042}
3043
3044fn parse_ipv4(s: &str) -> Option<[u8; 4]> {
3045 let parts: Vec<&str> = s.split('.').collect();
3046 if parts.len() != 4 {
3047 return None;
3048 }
3049 let mut out = [0u8; 4];
3050 for (i, p) in parts.iter().enumerate() {
3051 out[i] = p.parse::<u8>().ok()?;
3052 }
3053 Some(out)
3054}
3055
3056fn parse_ipv6(s: &str) -> Option<[u8; 16]> {
3057 let (head, tail) = match s.find("::") {
3059 Some(idx) => (&s[..idx], Some(&s[idx + 2..])),
3060 None => (s, None),
3061 };
3062 let head_groups: Vec<&str> = if head.is_empty() {
3063 Vec::new()
3064 } else {
3065 head.split(':').collect()
3066 };
3067 let tail_groups: Vec<&str> = match tail {
3068 Some(t) if !t.is_empty() => t.split(':').collect(),
3069 _ => Vec::new(),
3070 };
3071 let head_len = head_groups.len();
3072 let tail_len = tail_groups.len();
3073 if tail.is_none() {
3075 if head_len != 8 {
3076 return None;
3077 }
3078 } else if head_len + tail_len > 7 {
3079 return None;
3080 }
3081 let mut words = [0u16; 8];
3082 for (i, g) in head_groups.iter().enumerate() {
3083 words[i] = u16::from_str_radix(g, 16).ok()?;
3084 }
3085 let tail_start = 8 - tail_len;
3086 for (i, g) in tail_groups.iter().enumerate() {
3087 words[tail_start + i] = u16::from_str_radix(g, 16).ok()?;
3088 }
3089 let mut out = [0u8; 16];
3090 for (i, w) in words.iter().enumerate() {
3091 out[i * 2] = (w >> 8) as u8;
3092 out[i * 2 + 1] = (w & 0xff) as u8;
3093 }
3094 Some(out)
3095}
3096
3097fn network_prefix_eq(a: &InetNet, b: &InetNet, prefix_bits: u8) -> bool {
3100 let full_bytes = (prefix_bits / 8) as usize;
3101 if a.bytes[..full_bytes] != b.bytes[..full_bytes] {
3102 return false;
3103 }
3104 let extra = prefix_bits % 8;
3105 if extra == 0 {
3106 return true;
3107 }
3108 let mask: u8 = 0xff << (8 - extra);
3109 (a.bytes[full_bytes] & mask) == (b.bytes[full_bytes] & mask)
3110}
3111
3112fn inet_contained_eq(a: &InetNet, b: &InetNet) -> bool {
3114 if a.family_bytes != b.family_bytes {
3115 return false;
3116 }
3117 if a.prefix_bits < b.prefix_bits {
3118 return false;
3119 }
3120 network_prefix_eq(a, b, b.prefix_bits)
3121}
3122
3123fn inet_networks_equal(a: &InetNet, b: &InetNet) -> bool {
3126 if a.family_bytes != b.family_bytes {
3127 return false;
3128 }
3129 if a.prefix_bits != b.prefix_bits {
3130 return false;
3131 }
3132 network_prefix_eq(a, b, a.prefix_bits)
3133}
3134
3135fn inet_op_bool_result(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
3136 if matches!(l, Value::Null) || matches!(r, Value::Null) {
3137 return Ok(Value::Null);
3138 }
3139 let (lt, rt) = match (l, r) {
3140 (Value::Text(a), Value::Text(b)) => (a, b),
3141 _ => {
3142 return Err(EvalError::TypeMismatch {
3143 detail: format!(
3144 "inet operator requires TEXT/INET operands, got {:?} and {:?}",
3145 l.data_type(),
3146 r.data_type()
3147 ),
3148 });
3149 }
3150 };
3151 let a = parse_inet_text(lt).ok_or_else(|| EvalError::TypeMismatch {
3152 detail: format!("invalid inet text: {:?}", lt),
3153 })?;
3154 let b = parse_inet_text(rt).ok_or_else(|| EvalError::TypeMismatch {
3155 detail: format!("invalid inet text: {:?}", rt),
3156 })?;
3157 let result = match op {
3158 BinOp::InetContainedByEq => inet_contained_eq(&a, &b),
3159 BinOp::InetContainedBy => inet_contained_eq(&a, &b) && !inet_networks_equal(&a, &b),
3160 BinOp::InetContainsEq => inet_contained_eq(&b, &a),
3161 BinOp::InetContains => inet_contained_eq(&b, &a) && !inet_networks_equal(&a, &b),
3162 BinOp::InetOverlap => inet_contained_eq(&a, &b) || inet_contained_eq(&b, &a),
3163 _ => unreachable!("inet_op_bool_result called with non-inet op"),
3164 };
3165 Ok(Value::Bool(result))
3166}
3167
3168#[derive(Debug, Clone)]
3199enum ReNode {
3200 Literal(char),
3203 AnyChar,
3205 Class {
3207 members: Vec<ClassMember>,
3208 negated: bool,
3209 },
3210 Start,
3212 End,
3214 Quant {
3216 inner: Box<ReNode>,
3217 min: usize,
3218 max: Option<usize>,
3219 },
3220 Concat(Vec<ReNode>),
3222 Alt(Vec<ReNode>),
3224}
3225
3226#[derive(Debug, Clone)]
3227enum ClassMember {
3228 Single(char),
3229 Range(char, char),
3230}
3231
3232fn re_compile(pat: &str) -> Result<ReNode, EvalError> {
3233 let chars: Vec<char> = pat.chars().collect();
3234 let mut p = 0;
3235 let n = re_parse_alt(&chars, &mut p)?;
3236 if p != chars.len() {
3237 return Err(EvalError::TypeMismatch {
3238 detail: alloc::format!("regex compile: trailing chars at pos {p} in {pat:?}"),
3239 });
3240 }
3241 Ok(n)
3242}
3243
3244fn re_parse_alt(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3245 let mut branches = alloc::vec![re_parse_concat(chars, p)?];
3246 while *p < chars.len() && chars[*p] == '|' {
3247 *p += 1;
3248 branches.push(re_parse_concat(chars, p)?);
3249 }
3250 if branches.len() == 1 {
3251 Ok(branches.pop().unwrap())
3252 } else {
3253 Ok(ReNode::Alt(branches))
3254 }
3255}
3256
3257fn re_parse_concat(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3258 let mut items: Vec<ReNode> = Vec::new();
3259 while *p < chars.len() {
3260 let c = chars[*p];
3261 if c == '|' || c == ')' {
3262 break;
3263 }
3264 let atom = re_parse_atom(chars, p)?;
3265 let quantified = if *p < chars.len() {
3267 match chars[*p] {
3268 '*' => {
3269 *p += 1;
3270 if *p < chars.len() && chars[*p] == '?' {
3274 *p += 1;
3275 }
3276 ReNode::Quant {
3277 inner: Box::new(atom),
3278 min: 0,
3279 max: None,
3280 }
3281 }
3282 '+' => {
3283 *p += 1;
3284 if *p < chars.len() && chars[*p] == '?' {
3285 *p += 1;
3286 }
3287 ReNode::Quant {
3288 inner: Box::new(atom),
3289 min: 1,
3290 max: None,
3291 }
3292 }
3293 '?' => {
3294 *p += 1;
3295 ReNode::Quant {
3296 inner: Box::new(atom),
3297 min: 0,
3298 max: Some(1),
3299 }
3300 }
3301 _ => atom,
3302 }
3303 } else {
3304 atom
3305 };
3306 items.push(quantified);
3307 }
3308 if items.len() == 1 {
3309 Ok(items.pop().unwrap())
3310 } else {
3311 Ok(ReNode::Concat(items))
3312 }
3313}
3314
3315fn re_parse_atom(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3316 let c = chars[*p];
3317 match c {
3318 '(' => {
3319 *p += 1;
3320 let inner = re_parse_alt(chars, p)?;
3321 if *p >= chars.len() || chars[*p] != ')' {
3322 return Err(EvalError::TypeMismatch {
3323 detail: "regex compile: unmatched '('".into(),
3324 });
3325 }
3326 *p += 1;
3327 Ok(inner)
3328 }
3329 '[' => {
3330 *p += 1;
3331 let mut negated = false;
3332 if *p < chars.len() && chars[*p] == '^' {
3333 negated = true;
3334 *p += 1;
3335 }
3336 let mut members: Vec<ClassMember> = Vec::new();
3337 while *p < chars.len() && chars[*p] != ']' {
3338 let start = chars[*p];
3339 *p += 1;
3340 if *p + 1 < chars.len() && chars[*p] == '-' && chars[*p + 1] != ']' {
3341 let end = chars[*p + 1];
3342 *p += 2;
3343 members.push(ClassMember::Range(start, end));
3344 } else {
3345 members.push(ClassMember::Single(start));
3346 }
3347 }
3348 if *p >= chars.len() {
3349 return Err(EvalError::TypeMismatch {
3350 detail: "regex compile: unmatched '['".into(),
3351 });
3352 }
3353 *p += 1; Ok(ReNode::Class { members, negated })
3355 }
3356 '.' => {
3357 *p += 1;
3358 Ok(ReNode::AnyChar)
3359 }
3360 '^' => {
3361 *p += 1;
3362 Ok(ReNode::Start)
3363 }
3364 '$' => {
3365 *p += 1;
3366 Ok(ReNode::End)
3367 }
3368 '\\' => {
3369 *p += 1;
3370 if *p >= chars.len() {
3371 return Err(EvalError::TypeMismatch {
3372 detail: "regex compile: dangling backslash".into(),
3373 });
3374 }
3375 let esc = chars[*p];
3376 *p += 1;
3377 match esc {
3378 'd' => Ok(ReNode::Class {
3379 members: alloc::vec![ClassMember::Range('0', '9')],
3380 negated: false,
3381 }),
3382 'D' => Ok(ReNode::Class {
3383 members: alloc::vec![ClassMember::Range('0', '9')],
3384 negated: true,
3385 }),
3386 'w' => Ok(ReNode::Class {
3387 members: alloc::vec![
3388 ClassMember::Range('a', 'z'),
3389 ClassMember::Range('A', 'Z'),
3390 ClassMember::Range('0', '9'),
3391 ClassMember::Single('_'),
3392 ],
3393 negated: false,
3394 }),
3395 'W' => Ok(ReNode::Class {
3396 members: alloc::vec![
3397 ClassMember::Range('a', 'z'),
3398 ClassMember::Range('A', 'Z'),
3399 ClassMember::Range('0', '9'),
3400 ClassMember::Single('_'),
3401 ],
3402 negated: true,
3403 }),
3404 's' => Ok(ReNode::Class {
3405 members: alloc::vec![
3406 ClassMember::Single(' '),
3407 ClassMember::Single('\t'),
3408 ClassMember::Single('\n'),
3409 ClassMember::Single('\r'),
3410 ],
3411 negated: false,
3412 }),
3413 'S' => Ok(ReNode::Class {
3414 members: alloc::vec![
3415 ClassMember::Single(' '),
3416 ClassMember::Single('\t'),
3417 ClassMember::Single('\n'),
3418 ClassMember::Single('\r'),
3419 ],
3420 negated: true,
3421 }),
3422 other => Ok(ReNode::Literal(other)),
3423 }
3424 }
3425 other => {
3426 *p += 1;
3427 Ok(ReNode::Literal(other))
3428 }
3429 }
3430}
3431
3432fn class_matches(member: &ClassMember, c: char) -> bool {
3433 match member {
3434 ClassMember::Single(s) => *s == c,
3435 ClassMember::Range(a, b) => c >= *a && c <= *b,
3436 }
3437}
3438
3439fn re_match_at(node: &ReNode, s: &[char], pos: usize) -> Option<usize> {
3444 match node {
3445 ReNode::Literal(c) => {
3446 if s.get(pos).copied() == Some(*c) {
3447 Some(pos + 1)
3448 } else {
3449 None
3450 }
3451 }
3452 ReNode::AnyChar => {
3453 if pos < s.len() && s[pos] != '\n' {
3454 Some(pos + 1)
3455 } else {
3456 None
3457 }
3458 }
3459 ReNode::Class { members, negated } => {
3460 let c = *s.get(pos)?;
3461 let hit = members.iter().any(|m| class_matches(m, c));
3462 if hit ^ negated { Some(pos + 1) } else { None }
3463 }
3464 ReNode::Start => {
3465 if pos == 0 {
3466 Some(pos)
3467 } else {
3468 None
3469 }
3470 }
3471 ReNode::End => {
3472 if pos == s.len() {
3473 Some(pos)
3474 } else {
3475 None
3476 }
3477 }
3478 ReNode::Concat(items) => {
3479 let mut p = pos;
3480 for it in items {
3481 p = re_match_at(it, s, p)?;
3482 }
3483 Some(p)
3484 }
3485 ReNode::Alt(branches) => {
3486 for b in branches {
3487 if let Some(p) = re_match_at(b, s, pos) {
3488 return Some(p);
3489 }
3490 }
3491 None
3492 }
3493 ReNode::Quant { inner, min, max } => {
3494 let mut count = 0usize;
3499 let mut p = pos;
3500 loop {
3501 if let Some(cap) = max {
3502 if count >= *cap {
3503 break;
3504 }
3505 }
3506 match re_match_at(inner, s, p) {
3507 Some(np) if np > p => {
3508 p = np;
3509 count += 1;
3510 }
3511 _ => break,
3512 }
3513 }
3514 if count < *min {
3515 return None;
3516 }
3517 Some(p)
3518 }
3519 }
3520}
3521
3522fn re_find(node: &ReNode, s: &[char], from: usize) -> Option<(usize, usize)> {
3525 let mut start = from;
3526 loop {
3527 if let Some(end) = re_match_at(node, s, start) {
3528 return Some((start, end));
3529 }
3530 if start >= s.len() {
3531 return None;
3532 }
3533 start += 1;
3534 }
3535}
3536
3537fn regexp_matches(args: &[Value]) -> Result<Value, EvalError> {
3543 let (text, pat, all_matches) = match args.len() {
3544 2 => (text_arg(&args[0])?, text_arg(&args[1])?, false),
3545 3 => {
3546 let flags = text_arg(&args[2])?.unwrap_or_default();
3547 (
3548 text_arg(&args[0])?,
3549 text_arg(&args[1])?,
3550 flags.contains('g'),
3551 )
3552 }
3553 n => {
3554 return Err(EvalError::TypeMismatch {
3555 detail: alloc::format!("regexp_matches() takes 2 or 3 args, got {n}"),
3556 });
3557 }
3558 };
3559 let Some(text) = text else {
3560 return Ok(Value::Null);
3561 };
3562 let Some(pat) = pat else {
3563 return Ok(Value::Null);
3564 };
3565 let node = re_compile(&pat)?;
3566 let chars: Vec<char> = text.chars().collect();
3567 let mut out: Vec<Option<String>> = Vec::new();
3568 let mut from = 0usize;
3569 while let Some((s_pos, e_pos)) = re_find(&node, &chars, from) {
3570 out.push(Some(chars[s_pos..e_pos].iter().collect()));
3571 if !all_matches {
3572 break;
3573 }
3574 from = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3576 if from > chars.len() {
3577 break;
3578 }
3579 }
3580 Ok(Value::TextArray(out))
3581}
3582
3583fn regexp_replace(args: &[Value]) -> Result<Value, EvalError> {
3587 let (text, pat, repl, flags) = match args.len() {
3588 3 => (
3589 text_arg(&args[0])?,
3590 text_arg(&args[1])?,
3591 text_arg(&args[2])?,
3592 String::new(),
3593 ),
3594 4 => (
3595 text_arg(&args[0])?,
3596 text_arg(&args[1])?,
3597 text_arg(&args[2])?,
3598 text_arg(&args[3])?.unwrap_or_default(),
3599 ),
3600 n => {
3601 return Err(EvalError::TypeMismatch {
3602 detail: alloc::format!("regexp_replace() takes 3 or 4 args, got {n}"),
3603 });
3604 }
3605 };
3606 let Some(text) = text else {
3607 return Ok(Value::Null);
3608 };
3609 let Some(pat) = pat else {
3610 return Ok(Value::Null);
3611 };
3612 let Some(repl) = repl else {
3613 return Ok(Value::Null);
3614 };
3615 let global = flags.contains('g');
3616 let node = re_compile(&pat)?;
3617 let chars: Vec<char> = text.chars().collect();
3618 let mut out = String::with_capacity(text.len());
3619 let mut from = 0usize;
3620 loop {
3621 match re_find(&node, &chars, from) {
3622 Some((s_pos, e_pos)) => {
3623 out.extend(chars[from..s_pos].iter());
3624 out.push_str(&repl);
3625 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3626 from = step;
3627 if !global {
3628 if from <= chars.len() {
3629 out.extend(chars[from..].iter());
3630 }
3631 return Ok(Value::Text(out));
3632 }
3633 if from > chars.len() {
3634 break;
3635 }
3636 }
3637 None => {
3638 out.extend(chars[from..].iter());
3639 break;
3640 }
3641 }
3642 }
3643 Ok(Value::Text(out))
3644}
3645
3646fn regexp_split_to_array(args: &[Value]) -> Result<Value, EvalError> {
3649 if args.len() != 2 {
3650 return Err(EvalError::TypeMismatch {
3651 detail: alloc::format!("regexp_split_to_array() takes 2 args, got {}", args.len()),
3652 });
3653 }
3654 let text = text_arg(&args[0])?;
3655 let pat = text_arg(&args[1])?;
3656 let Some(text) = text else {
3657 return Ok(Value::Null);
3658 };
3659 let Some(pat) = pat else {
3660 return Ok(Value::Null);
3661 };
3662 let node = re_compile(&pat)?;
3663 let chars: Vec<char> = text.chars().collect();
3664 let mut out: Vec<Option<String>> = Vec::new();
3665 let mut piece_start = 0usize;
3666 let mut from = 0usize;
3667 loop {
3668 match re_find(&node, &chars, from) {
3669 Some((s_pos, e_pos)) => {
3670 let piece: String = chars[piece_start..s_pos].iter().collect();
3671 out.push(Some(piece));
3672 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3673 from = step;
3674 piece_start = step;
3675 if from > chars.len() {
3676 break;
3677 }
3678 }
3679 None => {
3680 let tail: String = chars[piece_start..].iter().collect();
3681 out.push(Some(tail));
3682 break;
3683 }
3684 }
3685 }
3686 Ok(Value::TextArray(out))
3687}
3688
3689fn text_arg(v: &Value) -> Result<Option<String>, EvalError> {
3692 match v {
3693 Value::Text(s) => Ok(Some(s.clone())),
3694 Value::Null => Ok(None),
3695 other => Err(EvalError::TypeMismatch {
3696 detail: alloc::format!(
3697 "regex function expects TEXT arg, got {:?}",
3698 other.data_type()
3699 ),
3700 }),
3701 }
3702}
3703
3704#[derive(Debug, Clone, Copy)]
3706enum TrimSide {
3707 Left,
3708 Right,
3709 Both,
3710}
3711
3712fn string_left_right(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
3716 if args.len() != 2 {
3717 return Err(EvalError::TypeMismatch {
3718 detail: alloc::format!("{fn_name}() takes 2 args, got {}", args.len()),
3719 });
3720 }
3721 if args.iter().any(|v| matches!(v, Value::Null)) {
3722 return Ok(Value::Null);
3723 }
3724 let s = value_to_format_text(&args[0]);
3725 let n = match &args[1] {
3726 Value::SmallInt(x) => i64::from(*x),
3727 Value::Int(x) => i64::from(*x),
3728 Value::BigInt(x) => *x,
3729 other => {
3730 return Err(EvalError::TypeMismatch {
3731 detail: alloc::format!(
3732 "{fn_name}(): n must be integer, got {:?}",
3733 other.data_type()
3734 ),
3735 });
3736 }
3737 };
3738 let chars: Vec<char> = s.chars().collect();
3739 let len = chars.len() as i64;
3740 if n == 0 {
3741 return Ok(Value::Text(String::new()));
3742 }
3743 let (start, end) = if is_left {
3744 if n > 0 {
3745 (0usize, (n.min(len)) as usize)
3746 } else {
3747 let drop = (-n).min(len);
3749 (0usize, (len - drop) as usize)
3750 }
3751 } else if n > 0 {
3752 let start = (len - n).max(0);
3754 (start as usize, len as usize)
3755 } else {
3756 let drop = (-n).min(len);
3758 (drop as usize, len as usize)
3759 };
3760 if start >= end {
3761 return Ok(Value::Text(String::new()));
3762 }
3763 Ok(Value::Text(chars[start..end].iter().collect()))
3764}
3765
3766fn value_cmp_for_min_max(a: &Value, b: &Value) -> core::cmp::Ordering {
3770 use core::cmp::Ordering;
3771 let a_int = match a {
3773 Value::SmallInt(x) => Some(i64::from(*x)),
3774 Value::Int(x) => Some(i64::from(*x)),
3775 Value::BigInt(x) => Some(*x),
3776 _ => None,
3777 };
3778 let b_int = match b {
3779 Value::SmallInt(x) => Some(i64::from(*x)),
3780 Value::Int(x) => Some(i64::from(*x)),
3781 Value::BigInt(x) => Some(*x),
3782 _ => None,
3783 };
3784 if let (Some(av), Some(bv)) = (a_int, b_int) {
3785 return av.cmp(&bv);
3786 }
3787 let a_f = value_to_f64(a);
3789 let b_f = value_to_f64(b);
3790 if let (Some(av), Some(bv)) = (a_f, b_f) {
3791 return av.partial_cmp(&bv).unwrap_or(Ordering::Equal);
3792 }
3793 match (a, b) {
3795 (Value::Text(av), Value::Text(bv)) => av.cmp(bv),
3796 (Value::Bytes(av), Value::Bytes(bv)) => av.cmp(bv),
3797 _ => Ordering::Equal,
3798 }
3799}
3800
3801fn value_to_f64(v: &Value) -> Option<f64> {
3802 match v {
3803 Value::Float(x) => Some(*x),
3804 Value::SmallInt(x) => Some(f64::from(*x)),
3805 Value::Int(x) => Some(f64::from(*x)),
3806 Value::BigInt(x) => Some(*x as f64),
3807 Value::Numeric { scaled, scale } => {
3808 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3809 }
3810 _ => None,
3811 }
3812}
3813
3814fn values_equal_for_nullif(a: &Value, b: &Value) -> bool {
3819 if a == b {
3821 return true;
3822 }
3823 let a_int = match a {
3825 Value::SmallInt(x) => Some(i64::from(*x)),
3826 Value::Int(x) => Some(i64::from(*x)),
3827 Value::BigInt(x) => Some(*x),
3828 _ => None,
3829 };
3830 let b_int = match b {
3831 Value::SmallInt(x) => Some(i64::from(*x)),
3832 Value::Int(x) => Some(i64::from(*x)),
3833 Value::BigInt(x) => Some(*x),
3834 _ => None,
3835 };
3836 if let (Some(a), Some(b)) = (a_int, b_int) {
3837 return a == b;
3838 }
3839 let a_f = match a {
3841 Value::Float(x) => Some(*x),
3842 Value::SmallInt(x) => Some(f64::from(*x)),
3843 Value::Int(x) => Some(f64::from(*x)),
3844 Value::BigInt(x) => Some(*x as f64),
3845 Value::Numeric { scaled, scale } => {
3846 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3847 }
3848 _ => None,
3849 };
3850 let b_f = match b {
3851 Value::Float(x) => Some(*x),
3852 Value::SmallInt(x) => Some(f64::from(*x)),
3853 Value::Int(x) => Some(f64::from(*x)),
3854 Value::BigInt(x) => Some(*x as f64),
3855 Value::Numeric { scaled, scale } => {
3856 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3857 }
3858 _ => None,
3859 };
3860 if let (Some(a), Some(b)) = (a_f, b_f) {
3861 return a == b;
3862 }
3863 false
3864}
3865
3866fn f64_trunc(x: f64) -> f64 {
3871 if x.is_nan() || x.is_infinite() {
3872 return x;
3873 }
3874 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3875 return x;
3876 }
3877 (x as i64) as f64
3878}
3879
3880static PRNG_STATE: core::sync::atomic::AtomicU64 =
3885 core::sync::atomic::AtomicU64::new(0x2545_F491_4F6C_DD1D);
3886
3887fn prng_next_u64() -> u64 {
3893 use core::sync::atomic::Ordering;
3894 let mut x = PRNG_STATE.load(Ordering::Relaxed);
3895 loop {
3896 if x == 0 {
3897 x = 0x2545_F491_4F6C_DD1D;
3898 }
3899 let mut next = x;
3900 next ^= next << 13;
3901 next ^= next >> 7;
3902 next ^= next << 17;
3903 match PRNG_STATE.compare_exchange_weak(x, next, Ordering::Relaxed, Ordering::Relaxed) {
3904 Ok(_) => return next,
3905 Err(seen) => x = seen,
3906 }
3907 }
3908}
3909
3910fn prng_next_f64() -> f64 {
3912 let mantissa = prng_next_u64() >> 11;
3914 let denom = (1u64 << 53) as f64;
3915 mantissa as f64 / denom
3916}
3917
3918pub fn gen_random_uuid_bytes() -> [u8; 16] {
3925 let mut out = [0u8; 16];
3926 let hi = prng_next_u64().to_be_bytes();
3927 let lo = prng_next_u64().to_be_bytes();
3928 out[..8].copy_from_slice(&hi);
3929 out[8..].copy_from_slice(&lo);
3930 out[6] = (out[6] & 0x0f) | 0x40;
3932 out[8] = (out[8] & 0x3f) | 0x80;
3934 out
3935}
3936
3937fn f64_sqrt(x: f64) -> f64 {
3942 if x == 0.0 || x.is_nan() {
3943 return x;
3944 }
3945 if x.is_infinite() {
3946 return x;
3947 }
3948 let bits = x.to_bits();
3952 let exp = ((bits >> 52) & 0x7ff) as i64 - 1023;
3953 let new_exp = (exp / 2) + 1023;
3954 let mut guess = f64::from_bits(((new_exp as u64) & 0x7ff) << 52);
3955 for _ in 0..8 {
3957 guess = 0.5 * (guess + x / guess);
3958 }
3959 guess
3960}
3961
3962fn f64_exp(x: f64) -> f64 {
3967 if x.is_nan() {
3968 return x;
3969 }
3970 if x > 709.0 {
3971 return f64::INFINITY;
3972 }
3973 if x < -745.0 {
3974 return 0.0;
3975 }
3976 const LN2: f64 = 0.6931471805599453;
3978 let k = f64_round_half_away(x / LN2) as i32;
3979 let r = x - (k as f64) * LN2;
3980 let mut term = 1.0;
3982 let mut sum = 1.0;
3983 for n in 1..=20 {
3984 term *= r / (n as f64);
3985 sum += term;
3986 if term.abs() < 1e-18 {
3987 break;
3988 }
3989 }
3990 f64_powi(2.0, k) * sum
3992}
3993
3994fn f64_ln(x: f64) -> f64 {
3997 if x <= 0.0 {
3998 return f64::NAN;
3999 }
4000 if x == 1.0 {
4001 return 0.0;
4002 }
4003 const LN2: f64 = 0.6931471805599453;
4005 let mut k = 0i32;
4006 let mut m = x;
4007 while m >= 2.0 {
4008 m *= 0.5;
4009 k += 1;
4010 }
4011 while m < 1.0 {
4012 m *= 2.0;
4013 k -= 1;
4014 }
4015 let u = (m - 1.0) / (m + 1.0);
4018 let u2 = u * u;
4019 let mut term = u;
4020 let mut sum = u;
4021 for k_iter in 1..50 {
4022 term *= u2;
4023 let denom = (2 * k_iter + 1) as f64;
4024 sum += term / denom;
4025 if (term / denom).abs() < 1e-18 {
4026 break;
4027 }
4028 }
4029 2.0 * sum + (k as f64) * LN2
4030}
4031
4032fn f64_powi(base: f64, exp: i32) -> f64 {
4036 if exp == 0 {
4037 return 1.0;
4038 }
4039 let mut result = 1.0;
4040 let mut b = if exp > 0 { base } else { 1.0 / base };
4041 let mut e = exp.unsigned_abs();
4042 while e > 0 {
4043 if e & 1 == 1 {
4044 result *= b;
4045 }
4046 e >>= 1;
4047 if e > 0 {
4048 b *= b;
4049 }
4050 }
4051 result
4052}
4053
4054fn f64_round_half_away(x: f64) -> f64 {
4057 if x.is_nan() || x.is_infinite() {
4058 return x;
4059 }
4060 if x >= 0.0 {
4061 f64_floor(x + 0.5)
4062 } else {
4063 f64_ceil(x - 0.5)
4064 }
4065}
4066
4067fn f64_ceil(x: f64) -> f64 {
4072 if x.is_nan() || x.is_infinite() {
4073 return x;
4074 }
4075 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
4076 return x;
4077 }
4078 let trunc = (x as i64) as f64;
4079 if x > 0.0 && x != trunc {
4080 trunc + 1.0
4081 } else {
4082 trunc
4083 }
4084}
4085
4086fn f64_floor(x: f64) -> f64 {
4094 if x.is_nan() || x.is_infinite() {
4095 return x;
4096 }
4097 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
4100 return x;
4101 }
4102 let trunc = (x as i64) as f64;
4103 if x < 0.0 && x != trunc {
4104 trunc - 1.0
4105 } else {
4106 trunc
4107 }
4108}
4109
4110fn string_pad(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
4118 if args.len() != 2 && args.len() != 3 {
4119 return Err(EvalError::TypeMismatch {
4120 detail: alloc::format!("{fn_name}() takes 2 or 3 args, got {}", args.len()),
4121 });
4122 }
4123 if args.iter().any(|v| matches!(v, Value::Null)) {
4124 return Ok(Value::Null);
4125 }
4126 let s = value_to_format_text(&args[0]);
4127 let target = match &args[1] {
4128 Value::SmallInt(x) => i64::from(*x),
4129 Value::Int(x) => i64::from(*x),
4130 Value::BigInt(x) => *x,
4131 other => {
4132 return Err(EvalError::TypeMismatch {
4133 detail: alloc::format!(
4134 "{fn_name}(): length must be integer, got {:?}",
4135 other.data_type()
4136 ),
4137 });
4138 }
4139 };
4140 let fill = if args.len() == 3 {
4141 value_to_format_text(&args[2])
4142 } else {
4143 String::from(" ")
4144 };
4145 if target <= 0 {
4146 return Ok(Value::Text(String::new()));
4147 }
4148 let target = target as usize;
4149 let s_chars: Vec<char> = s.chars().collect();
4150 if s_chars.len() >= target {
4151 return Ok(Value::Text(s_chars[..target].iter().collect()));
4154 }
4155 if fill.is_empty() {
4156 return Ok(Value::Text(s));
4157 }
4158 let pad_needed = target - s_chars.len();
4159 let fill_chars: Vec<char> = fill.chars().collect();
4160 let mut padding = String::with_capacity(pad_needed * 4);
4161 for i in 0..pad_needed {
4162 padding.push(fill_chars[i % fill_chars.len()]);
4163 }
4164 if is_left {
4165 Ok(Value::Text(padding + &s))
4166 } else {
4167 Ok(Value::Text(s + &padding))
4168 }
4169}
4170
4171fn string_trim(args: &[Value], side: TrimSide, fn_name: &str) -> Result<Value, EvalError> {
4177 let (input, chars_str) = match args {
4178 [v] => (v.clone(), String::from(" ")),
4179 [v, c] => (v.clone(), {
4180 if matches!(c, Value::Null) {
4182 return Ok(Value::Null);
4183 }
4184 value_to_format_text(c)
4185 }),
4186 _ => {
4187 return Err(EvalError::TypeMismatch {
4188 detail: alloc::format!("{fn_name}() takes 1 or 2 args, got {}", args.len()),
4189 });
4190 }
4191 };
4192 if matches!(input, Value::Null) {
4193 return Ok(Value::Null);
4194 }
4195 let s = value_to_format_text(&input);
4196 let charset: alloc::collections::BTreeSet<char> = chars_str.chars().collect();
4197 let chars: Vec<char> = s.chars().collect();
4198 let mut start = 0usize;
4199 let mut end = chars.len();
4200 if matches!(side, TrimSide::Left | TrimSide::Both) {
4201 while start < end && charset.contains(&chars[start]) {
4202 start += 1;
4203 }
4204 }
4205 if matches!(side, TrimSide::Right | TrimSide::Both) {
4206 while end > start && charset.contains(&chars[end - 1]) {
4207 end -= 1;
4208 }
4209 }
4210 Ok(Value::Text(chars[start..end].iter().collect()))
4211}
4212
4213fn format_string(args: &[Value]) -> Result<Value, EvalError> {
4224 if args.is_empty() {
4225 return Err(EvalError::TypeMismatch {
4226 detail: "format() takes at least 1 arg (format string)".into(),
4227 });
4228 }
4229 let fmt = match &args[0] {
4230 Value::Text(s) => s.clone(),
4231 Value::Null => return Ok(Value::Null),
4232 other => {
4233 return Err(EvalError::TypeMismatch {
4234 detail: format!(
4235 "format(): first arg must be text, got {:?}",
4236 other.data_type()
4237 ),
4238 });
4239 }
4240 };
4241 let arg_values = &args[1..];
4242 let mut out = String::new();
4243 let mut chars = fmt.chars().peekable();
4244 let mut implicit_cursor: usize = 0;
4248 while let Some(c) = chars.next() {
4249 if c != '%' {
4250 out.push(c);
4251 continue;
4252 }
4253 let mut explicit_pos: Option<usize> = None;
4255 let mut digit_buf = String::new();
4257 while let Some(&d) = chars.peek() {
4258 if d.is_ascii_digit() {
4259 digit_buf.push(d);
4260 chars.next();
4261 } else {
4262 break;
4263 }
4264 }
4265 if !digit_buf.is_empty() && matches!(chars.peek(), Some(&'$')) {
4266 chars.next(); explicit_pos =
4268 Some(
4269 digit_buf
4270 .parse::<usize>()
4271 .map_err(|_| EvalError::TypeMismatch {
4272 detail: format!("format(): invalid arg position {digit_buf:?}"),
4273 })?,
4274 );
4275 digit_buf.clear();
4276 }
4277 let spec = match chars.next() {
4279 Some(c) => c,
4280 None => {
4281 return Err(EvalError::TypeMismatch {
4282 detail: "format(): trailing `%` with no specifier".into(),
4283 });
4284 }
4285 };
4286 let _ = digit_buf;
4293 if spec == '%' {
4294 out.push('%');
4295 continue;
4296 }
4297 let arg_index = match explicit_pos {
4298 Some(p) => p.saturating_sub(1),
4299 None => {
4300 let i = implicit_cursor;
4301 implicit_cursor += 1;
4302 i
4303 }
4304 };
4305 let arg = arg_values.get(arg_index).cloned().unwrap_or(Value::Null);
4306 match spec {
4307 's' => match arg {
4308 Value::Null => {} v => out.push_str(&value_to_format_text(&v)),
4310 },
4311 'I' => match arg {
4312 Value::Null => {
4313 return Err(EvalError::TypeMismatch {
4314 detail: "format(): NULL is not a valid identifier (%I)".into(),
4315 });
4316 }
4317 v => {
4318 let s = value_to_format_text(&v);
4319 out.push('"');
4320 for ch in s.chars() {
4321 if ch == '"' {
4322 out.push('"');
4323 out.push('"');
4324 } else {
4325 out.push(ch);
4326 }
4327 }
4328 out.push('"');
4329 }
4330 },
4331 'L' => match arg {
4332 Value::Null => out.push_str("NULL"),
4333 v => {
4334 let s = value_to_format_text(&v);
4335 out.push('\'');
4336 for ch in s.chars() {
4337 if ch == '\'' {
4338 out.push('\'');
4339 out.push('\'');
4340 } else {
4341 out.push(ch);
4342 }
4343 }
4344 out.push('\'');
4345 }
4346 },
4347 other => {
4348 return Err(EvalError::TypeMismatch {
4349 detail: format!(
4350 "format(): unknown specifier '%{other}' \
4351 (v7.17 supports %s %I %L %%)"
4352 ),
4353 });
4354 }
4355 }
4356 }
4357 Ok(Value::Text(out))
4358}
4359
4360fn pg_typeof_name(v: &Value) -> &'static str {
4366 match v {
4367 Value::SmallInt(_) => "smallint",
4368 Value::Int(_) => "integer",
4369 Value::BigInt(_) => "bigint",
4370 Value::Float(_) => "double precision",
4371 Value::Text(_) => "text",
4372 Value::Bool(_) => "boolean",
4373 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => "vector",
4374 Value::Numeric { .. } => "numeric",
4375 Value::Date(_) => "date",
4376 Value::Timestamp(_) => "timestamp without time zone",
4377 Value::Interval { .. } => "interval",
4378 Value::Json(_) => {
4379 "json"
4390 }
4391 Value::Bytes(_) => "bytea",
4392 Value::TextArray(_) => "text[]",
4393 Value::IntArray(_) => "integer[]",
4394 Value::BigIntArray(_) => "bigint[]",
4395 Value::TsVector(_) => "tsvector",
4396 Value::TsQuery(_) => "tsquery",
4397 Value::Uuid(_) => "uuid",
4398 Value::Null => "unknown",
4399 _ => "unknown",
4402 }
4403}
4404
4405fn value_to_format_text(v: &Value) -> String {
4406 match v {
4407 Value::Text(s) | Value::Json(s) => s.clone(),
4408 Value::SmallInt(n) => n.to_string(),
4409 Value::Int(n) => n.to_string(),
4410 Value::BigInt(n) => n.to_string(),
4411 Value::Float(x) => format!("{x}"),
4412 Value::Bool(b) => {
4413 if *b {
4414 "t".into()
4415 } else {
4416 "f".into()
4417 }
4418 }
4419 Value::Null => String::new(),
4420 other => format!("{other:?}"),
4421 }
4422}
4423
4424fn to_char(args: &[Value]) -> Result<Value, EvalError> {
4425 use core::fmt::Write as _;
4426 if args.len() != 2 {
4427 return Err(EvalError::TypeMismatch {
4428 detail: format!("to_char() takes 2 args, got {}", args.len()),
4429 });
4430 }
4431 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4432 return Ok(Value::Null);
4433 }
4434 let Value::Text(fmt) = &args[1] else {
4435 return Err(EvalError::TypeMismatch {
4436 detail: format!(
4437 "to_char() needs a text format, got {:?}",
4438 args[1].data_type()
4439 ),
4440 });
4441 };
4442 let (days, day_micros) = match &args[0] {
4443 Value::Date(d) => (*d, 0_i64),
4444 Value::Timestamp(t) => {
4445 let days = t.div_euclid(86_400_000_000);
4446 (
4447 i32::try_from(days).unwrap_or(i32::MAX),
4448 t.rem_euclid(86_400_000_000),
4449 )
4450 }
4451 other => {
4452 return Err(EvalError::TypeMismatch {
4453 detail: format!(
4454 "to_char() needs DATE or TIMESTAMP, got {:?}",
4455 other.data_type()
4456 ),
4457 });
4458 }
4459 };
4460 let (y, mo, d) = civil_from_days(days);
4461 let secs = day_micros / 1_000_000;
4462 let frac = day_micros % 1_000_000;
4463 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4467 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4468 let ss = u32::try_from(secs % 60).unwrap_or(0);
4469 let hh12 = match hh24 % 12 {
4470 0 => 12,
4471 x => x,
4472 };
4473 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4474 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);
4478 let bytes = fmt.as_bytes();
4479 let mut i = 0;
4480 while i < bytes.len() {
4482 let rest = &bytes[i..];
4484 if rest.starts_with(b"YYYY") {
4485 let _ = write!(out, "{y:04}");
4486 i += 4;
4487 } else if rest.starts_with(b"YY") {
4488 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4489 let yy = (y.rem_euclid(100)) as u32;
4490 let _ = write!(out, "{yy:02}");
4491 i += 2;
4492 } else if rest.starts_with(b"Month") {
4493 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4494 i += 5;
4495 } else if rest.starts_with(b"Mon") {
4496 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4497 i += 3;
4498 } else if rest.starts_with(b"MM") {
4499 let _ = write!(out, "{mo:02}");
4500 i += 2;
4501 } else if rest.starts_with(b"DD") {
4502 let _ = write!(out, "{d:02}");
4503 i += 2;
4504 } else if rest.starts_with(b"HH24") {
4505 let _ = write!(out, "{hh24:02}");
4506 i += 4;
4507 } else if rest.starts_with(b"HH12") {
4508 let _ = write!(out, "{hh12:02}");
4509 i += 4;
4510 } else if rest.starts_with(b"MI") {
4511 let _ = write!(out, "{mi:02}");
4512 i += 2;
4513 } else if rest.starts_with(b"SS") {
4514 let _ = write!(out, "{ss:02}");
4515 i += 2;
4516 } else if rest.starts_with(b"MS") {
4517 let _ = write!(out, "{ms:03}");
4518 i += 2;
4519 } else if rest.starts_with(b"US") {
4520 let _ = write!(out, "{us:06}");
4521 i += 2;
4522 } else if rest.starts_with(b"AM") || rest.starts_with(b"PM") {
4523 out.push_str(ampm);
4524 i += 2;
4525 } else {
4526 out.push(bytes[i] as char);
4528 i += 1;
4529 }
4530 }
4531 Ok(Value::Text(out))
4532}
4533
4534const MONTH_FULL: [&str; 12] = [
4535 "January",
4536 "February",
4537 "March",
4538 "April",
4539 "May",
4540 "June",
4541 "July",
4542 "August",
4543 "September",
4544 "October",
4545 "November",
4546 "December",
4547];
4548const MONTH_ABBR: [&str; 12] = [
4549 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
4550];
4551
4552fn date_format_mysql(args: &[Value]) -> Result<Value, EvalError> {
4571 use core::fmt::Write as _;
4572 if args.len() != 2 {
4573 return Err(EvalError::TypeMismatch {
4574 detail: format!("date_format() takes 2 args, got {}", args.len()),
4575 });
4576 }
4577 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4578 return Ok(Value::Null);
4579 }
4580 let Value::Text(fmt) = &args[1] else {
4581 return Err(EvalError::TypeMismatch {
4582 detail: format!(
4583 "date_format() needs a text format, got {:?}",
4584 args[1].data_type()
4585 ),
4586 });
4587 };
4588 let (days, day_micros) = match &args[0] {
4589 Value::Date(d) => (*d, 0_i64),
4590 Value::Timestamp(t) => {
4591 let days = t.div_euclid(86_400_000_000);
4592 (
4593 i32::try_from(days).unwrap_or(i32::MAX),
4594 t.rem_euclid(86_400_000_000),
4595 )
4596 }
4597 other => {
4598 return Err(EvalError::TypeMismatch {
4599 detail: format!(
4600 "date_format() needs DATE or TIMESTAMP, got {:?}",
4601 other.data_type()
4602 ),
4603 });
4604 }
4605 };
4606 let (y, mo, d) = civil_from_days(days);
4607 let secs = day_micros / 1_000_000;
4608 let frac = day_micros % 1_000_000;
4609 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4610 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4611 let ss = u32::try_from(secs % 60).unwrap_or(0);
4612 let hh12 = match hh24 % 12 {
4613 0 => 12,
4614 x => x,
4615 };
4616 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4617 let us = u32::try_from(frac).unwrap_or(0);
4618
4619 let mut out = String::with_capacity(fmt.len() + 8);
4620 let bytes = fmt.as_bytes();
4621 let mut i = 0;
4622 while i < bytes.len() {
4623 if bytes[i] != b'%' {
4624 out.push(bytes[i] as char);
4625 i += 1;
4626 continue;
4627 }
4628 if i + 1 >= bytes.len() {
4629 out.push('%');
4631 i += 1;
4632 continue;
4633 }
4634 let token = bytes[i + 1];
4635 match token {
4636 b'Y' => {
4637 let _ = write!(out, "{y:04}");
4638 }
4639 b'y' => {
4640 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4641 let yy = (y.rem_euclid(100)) as u32;
4642 let _ = write!(out, "{yy:02}");
4643 }
4644 b'm' => {
4645 let _ = write!(out, "{mo:02}");
4646 }
4647 b'c' => {
4648 let _ = write!(out, "{mo}");
4649 }
4650 b'd' => {
4651 let _ = write!(out, "{d:02}");
4652 }
4653 b'e' => {
4654 let _ = write!(out, "{d}");
4655 }
4656 b'H' => {
4657 let _ = write!(out, "{hh24:02}");
4658 }
4659 b'h' | b'I' => {
4660 let _ = write!(out, "{hh12:02}");
4661 }
4662 b'i' => {
4663 let _ = write!(out, "{mi:02}");
4666 }
4667 b's' | b'S' => {
4668 let _ = write!(out, "{ss:02}");
4669 }
4670 b'f' => {
4671 let _ = write!(out, "{us:06}");
4672 }
4673 b'p' => {
4674 out.push_str(ampm);
4675 }
4676 b'M' => {
4677 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4678 }
4679 b'b' => {
4680 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4681 }
4682 b'%' => {
4683 out.push('%');
4684 }
4685 other => {
4686 out.push(other as char);
4689 }
4690 }
4691 i += 2;
4692 }
4693 Ok(Value::Text(out))
4694}
4695
4696fn unix_timestamp_of(args: &[Value]) -> Result<Value, EvalError> {
4703 if args.len() != 1 {
4704 return Err(EvalError::TypeMismatch {
4705 detail: format!("unix_timestamp() takes 0 or 1 arg, got {}", args.len()),
4706 });
4707 }
4708 match &args[0] {
4709 Value::Null => Ok(Value::Null),
4710 Value::Timestamp(t) => Ok(Value::BigInt(t.div_euclid(1_000_000))),
4711 Value::Date(d) => Ok(Value::BigInt(i64::from(*d) * 86_400)),
4712 other => Err(EvalError::TypeMismatch {
4713 detail: format!(
4714 "unix_timestamp() needs DATE or TIMESTAMP, got {:?}",
4715 other.data_type()
4716 ),
4717 }),
4718 }
4719}
4720
4721fn from_unixtime(args: &[Value]) -> Result<Value, EvalError> {
4725 if !(1..=2).contains(&args.len()) {
4726 return Err(EvalError::TypeMismatch {
4727 detail: format!("from_unixtime() takes 1 or 2 args, got {}", args.len()),
4728 });
4729 }
4730 if args.iter().any(|v| matches!(v, Value::Null)) {
4731 return Ok(Value::Null);
4732 }
4733 let secs: i64 = match &args[0] {
4734 Value::SmallInt(n) => i64::from(*n),
4735 Value::Int(n) => i64::from(*n),
4736 Value::BigInt(n) => *n,
4737 Value::Float(x) => *x as i64,
4738 Value::Numeric { scaled, scale } => {
4739 let denom = 10_i128.pow(u32::from(*scale));
4740 i64::try_from(scaled.div_euclid(denom)).unwrap_or(i64::MAX)
4741 }
4742 other => {
4743 return Err(EvalError::TypeMismatch {
4744 detail: format!(
4745 "from_unixtime() needs a numeric epoch second count, got {:?}",
4746 other.data_type()
4747 ),
4748 });
4749 }
4750 };
4751 let ts = Value::Timestamp(secs.saturating_mul(1_000_000));
4752 if args.len() == 1 {
4753 Ok(ts)
4754 } else {
4755 date_format_mysql(&[ts, args[1].clone()])
4756 }
4757}
4758
4759fn date_trunc(args: &[Value]) -> Result<Value, EvalError> {
4764 if args.len() != 2 {
4765 return Err(EvalError::TypeMismatch {
4766 detail: format!("date_trunc() takes 2 args, got {}", args.len()),
4767 });
4768 }
4769 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4770 return Ok(Value::Null);
4771 }
4772 let Value::Text(unit) = &args[0] else {
4773 return Err(EvalError::TypeMismatch {
4774 detail: format!(
4775 "date_trunc() needs a text unit, got {:?}",
4776 args[0].data_type()
4777 ),
4778 });
4779 };
4780 let micros = match &args[1] {
4783 Value::Timestamp(t) => *t,
4784 Value::Date(d) => i64::from(*d) * 86_400_000_000,
4785 other => {
4786 return Err(EvalError::TypeMismatch {
4787 detail: format!(
4788 "date_trunc() needs DATE or TIMESTAMP, got {:?}",
4789 other.data_type()
4790 ),
4791 });
4792 }
4793 };
4794 let unit_lc = unit.to_ascii_lowercase();
4795 let days = micros.div_euclid(86_400_000_000);
4796 let day_micros = micros.rem_euclid(86_400_000_000);
4797 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
4798 let (y, m, _) = civil_from_days(day_i32);
4799 let truncated = match unit_lc.as_str() {
4800 "year" => i64::from(days_from_civil(y, 1, 1)) * 86_400_000_000,
4801 "month" => i64::from(days_from_civil(y, m, 1)) * 86_400_000_000,
4802 "day" => days * 86_400_000_000,
4803 "hour" => days * 86_400_000_000 + (day_micros / 3_600_000_000) * 3_600_000_000,
4804 "minute" => days * 86_400_000_000 + (day_micros / 60_000_000) * 60_000_000,
4805 "second" => days * 86_400_000_000 + (day_micros / 1_000_000) * 1_000_000,
4806 other => {
4807 return Err(EvalError::TypeMismatch {
4808 detail: format!(
4809 "unknown date_trunc unit {other:?}; \
4810 supported: year, month, day, hour, minute, second"
4811 ),
4812 });
4813 }
4814 };
4815 Ok(Value::Timestamp(truncated))
4816}
4817
4818pub fn cast_value(v: Value, target: CastTarget) -> Result<Value, EvalError> {
4820 if matches!(v, Value::Null) {
4821 return Ok(Value::Null);
4822 }
4823 match target {
4824 CastTarget::Vector => cast_to_vector(v),
4825 CastTarget::Text => Ok(Value::Text(value_to_text(&v))),
4826 CastTarget::Int => cast_numeric_to_int(v),
4827 CastTarget::BigInt => cast_numeric_to_bigint(v),
4828 CastTarget::Float => cast_numeric_to_float(v),
4829 CastTarget::Bool => cast_to_bool(v),
4830 CastTarget::Date => cast_to_date(v),
4831 CastTarget::Timestamp | CastTarget::Timestamptz => cast_to_timestamp(v),
4834 CastTarget::Interval => cast_to_interval(v),
4838 CastTarget::Json | CastTarget::Jsonb => match v {
4842 Value::Json(s) => Ok(Value::Json(s)),
4843 Value::Text(s) => Ok(Value::Json(s)),
4844 other => Err(EvalError::TypeMismatch {
4845 detail: alloc::format!(
4846 "::json / ::jsonb only accepts TEXT-shape inputs, got {:?}",
4847 other.data_type()
4848 ),
4849 }),
4850 },
4851 CastTarget::RegType | CastTarget::RegClass => match v {
4869 Value::Text(s) => {
4870 let bare = s.rsplit('.').next().unwrap_or(&s).to_string();
4875 Ok(Value::Text(bare))
4876 }
4877 Value::Int(n) => Ok(Value::Text(alloc::format!("{n}"))),
4878 Value::BigInt(n) => Ok(Value::Text(alloc::format!("{n}"))),
4879 other => Err(EvalError::TypeMismatch {
4880 detail: alloc::format!(
4881 "::regtype / ::regclass accepts TEXT (name) or integer (oid), got {:?}",
4882 other.data_type()
4883 ),
4884 }),
4885 },
4886 CastTarget::TextArray => match v {
4890 Value::TextArray(items) => Ok(Value::TextArray(items)),
4891 Value::Text(s) => decode_text_array_external(&s).map(Value::TextArray),
4892 other => Err(EvalError::TypeMismatch {
4893 detail: alloc::format!(
4894 "::TEXT[] only accepts TEXT / TEXT[] inputs, got {:?}",
4895 other.data_type()
4896 ),
4897 }),
4898 },
4899 CastTarget::IntArray => cast_to_int_array(v),
4903 CastTarget::BigIntArray => cast_to_bigint_array(v),
4904 CastTarget::TsVector => match v {
4911 Value::TsVector(items) => Ok(Value::TsVector(items)),
4912 Value::Text(s) => decode_tsvector_external(&s).map(Value::TsVector),
4913 other => Err(EvalError::TypeMismatch {
4914 detail: alloc::format!(
4915 "::tsvector only accepts TEXT / tsvector inputs, got {:?}",
4916 other.data_type()
4917 ),
4918 }),
4919 },
4920 CastTarget::TsQuery => match v {
4921 Value::TsQuery(ast) => Ok(Value::TsQuery(ast)),
4922 Value::Text(s) => decode_tsquery_external(&s).map(Value::TsQuery),
4923 other => Err(EvalError::TypeMismatch {
4924 detail: alloc::format!(
4925 "::tsquery only accepts TEXT / tsquery inputs, got {:?}",
4926 other.data_type()
4927 ),
4928 }),
4929 },
4930 CastTarget::Uuid => match v {
4935 Value::Uuid(b) => Ok(Value::Uuid(b)),
4936 Value::Text(s) => match spg_storage::parse_uuid_str(&s) {
4937 Some(b) => Ok(Value::Uuid(b)),
4938 None => Err(EvalError::TypeMismatch {
4939 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4940 }),
4941 },
4942 other => Err(EvalError::TypeMismatch {
4943 detail: alloc::format!(
4944 "::uuid only accepts TEXT / uuid inputs, got {:?}",
4945 other.data_type()
4946 ),
4947 }),
4948 },
4949 CastTarget::Bytea => match v {
4955 Value::Bytes(b) => Ok(Value::Bytes(b)),
4956 Value::Text(s) => match crate::decode_bytea_literal(&s) {
4957 Ok(b) => Ok(Value::Bytes(b)),
4958 Err(msg) => Err(EvalError::TypeMismatch {
4959 detail: alloc::format!("invalid input syntax for type bytea: {msg}"),
4960 }),
4961 },
4962 other => Err(EvalError::TypeMismatch {
4963 detail: alloc::format!(
4964 "::bytea only accepts TEXT / bytea inputs, got {:?}",
4965 other.data_type()
4966 ),
4967 }),
4968 },
4969 }
4970}
4971
4972fn cast_to_int_array(v: Value) -> Result<Value, EvalError> {
4973 match v {
4974 Value::IntArray(items) => Ok(Value::IntArray(items)),
4975 Value::BigIntArray(items) => {
4976 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4977 for item in items {
4978 match item {
4979 None => out.push(None),
4980 Some(n) => match i32::try_from(n) {
4981 Ok(x) => out.push(Some(x)),
4982 Err(_) => {
4983 return Err(EvalError::TypeMismatch {
4984 detail: alloc::format!("::INT[] element {n} overflows i32"),
4985 });
4986 }
4987 },
4988 }
4989 }
4990 Ok(Value::IntArray(out))
4991 }
4992 Value::Text(s) => decode_int_array_external(&s).map(Value::IntArray),
4993 Value::TextArray(items) => {
4994 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4995 for item in items {
4996 match item {
4997 None => out.push(None),
4998 Some(s) => match s.parse::<i32>() {
4999 Ok(n) => out.push(Some(n)),
5000 Err(_) => {
5001 return Err(EvalError::TypeMismatch {
5002 detail: alloc::format!("::INT[] cannot parse {s:?}"),
5003 });
5004 }
5005 },
5006 }
5007 }
5008 Ok(Value::IntArray(out))
5009 }
5010 other => Err(EvalError::TypeMismatch {
5011 detail: alloc::format!("::INT[] does not accept {:?}", other.data_type()),
5012 }),
5013 }
5014}
5015
5016fn cast_to_bigint_array(v: Value) -> Result<Value, EvalError> {
5017 match v {
5018 Value::BigIntArray(items) => Ok(Value::BigIntArray(items)),
5019 Value::IntArray(items) => Ok(Value::BigIntArray(
5020 items.into_iter().map(|x| x.map(i64::from)).collect(),
5021 )),
5022 Value::Text(s) => decode_bigint_array_external(&s).map(Value::BigIntArray),
5023 Value::TextArray(items) => {
5024 let mut out: Vec<Option<i64>> = Vec::with_capacity(items.len());
5025 for item in items {
5026 match item {
5027 None => out.push(None),
5028 Some(s) => match s.parse::<i64>() {
5029 Ok(n) => out.push(Some(n)),
5030 Err(_) => {
5031 return Err(EvalError::TypeMismatch {
5032 detail: alloc::format!("::BIGINT[] cannot parse {s:?}"),
5033 });
5034 }
5035 },
5036 }
5037 }
5038 Ok(Value::BigIntArray(out))
5039 }
5040 other => Err(EvalError::TypeMismatch {
5041 detail: alloc::format!("::BIGINT[] does not accept {:?}", other.data_type()),
5042 }),
5043 }
5044}
5045
5046fn decode_int_array_external(s: &str) -> Result<Vec<Option<i32>>, EvalError> {
5047 let trimmed = s.trim();
5048 let inner = trimmed
5049 .strip_prefix('{')
5050 .and_then(|x| x.strip_suffix('}'))
5051 .ok_or_else(|| EvalError::TypeMismatch {
5052 detail: alloc::format!("INT[] literal {s:?} must be enclosed in '{{...}}'"),
5053 })?;
5054 if inner.trim().is_empty() {
5055 return Ok(Vec::new());
5056 }
5057 inner
5058 .split(',')
5059 .map(|part| {
5060 let p = part.trim();
5061 if p.eq_ignore_ascii_case("NULL") {
5062 Ok(None)
5063 } else {
5064 p.parse::<i32>()
5065 .map(Some)
5066 .map_err(|_| EvalError::TypeMismatch {
5067 detail: alloc::format!("INT[] element {p:?} is not an i32"),
5068 })
5069 }
5070 })
5071 .collect()
5072}
5073
5074fn decode_bigint_array_external(s: &str) -> Result<Vec<Option<i64>>, EvalError> {
5075 let trimmed = s.trim();
5076 let inner = trimmed
5077 .strip_prefix('{')
5078 .and_then(|x| x.strip_suffix('}'))
5079 .ok_or_else(|| EvalError::TypeMismatch {
5080 detail: alloc::format!("BIGINT[] literal {s:?} must be enclosed in '{{...}}'"),
5081 })?;
5082 if inner.trim().is_empty() {
5083 return Ok(Vec::new());
5084 }
5085 inner
5086 .split(',')
5087 .map(|part| {
5088 let p = part.trim();
5089 if p.eq_ignore_ascii_case("NULL") {
5090 Ok(None)
5091 } else {
5092 p.parse::<i64>()
5093 .map(Some)
5094 .map_err(|_| EvalError::TypeMismatch {
5095 detail: alloc::format!("BIGINT[] element {p:?} is not an i64"),
5096 })
5097 }
5098 })
5099 .collect()
5100}
5101
5102fn decode_text_array_external(s: &str) -> Result<Vec<Option<String>>, EvalError> {
5107 let trimmed = s.trim();
5108 let inner = trimmed
5109 .strip_prefix('{')
5110 .and_then(|x| x.strip_suffix('}'))
5111 .ok_or_else(|| EvalError::TypeMismatch {
5112 detail: alloc::format!("TEXT[] literal {s:?} must be enclosed in '{{...}}'"),
5113 })?;
5114 let mut out: Vec<Option<String>> = Vec::new();
5115 if inner.trim().is_empty() {
5116 return Ok(out);
5117 }
5118 let bytes = inner.as_bytes();
5119 let mut i = 0;
5120 while i <= bytes.len() {
5121 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
5122 i += 1;
5123 }
5124 if i < bytes.len() && bytes[i] == b'"' {
5125 i += 1;
5126 let mut buf = String::new();
5127 while i < bytes.len() && bytes[i] != b'"' {
5128 if bytes[i] == b'\\' && i + 1 < bytes.len() {
5129 buf.push(bytes[i + 1] as char);
5130 i += 2;
5131 } else {
5132 buf.push(bytes[i] as char);
5133 i += 1;
5134 }
5135 }
5136 if i >= bytes.len() {
5137 return Err(EvalError::TypeMismatch {
5138 detail: "unterminated quoted element in TEXT[] literal".into(),
5139 });
5140 }
5141 i += 1;
5142 out.push(Some(buf));
5143 } else {
5144 let start = i;
5145 while i < bytes.len() && bytes[i] != b',' {
5146 i += 1;
5147 }
5148 let raw = inner[start..i].trim();
5149 if raw.eq_ignore_ascii_case("NULL") {
5150 out.push(None);
5151 } else {
5152 out.push(Some(raw.to_string()));
5153 }
5154 }
5155 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
5156 i += 1;
5157 }
5158 if i >= bytes.len() {
5159 break;
5160 }
5161 if bytes[i] != b',' {
5162 return Err(EvalError::TypeMismatch {
5163 detail: "expected ',' between TEXT[] elements".into(),
5164 });
5165 }
5166 i += 1;
5167 }
5168 Ok(out)
5169}
5170
5171fn cast_to_interval(v: Value) -> Result<Value, EvalError> {
5172 match v {
5173 Value::Interval { months, micros } => Ok(Value::Interval { months, micros }),
5174 Value::Text(s) => {
5175 let (months, micros) = spg_sql::parser::parse_interval_text(&s).ok_or_else(|| {
5176 EvalError::TypeMismatch {
5177 detail: alloc::format!("cannot parse {s:?} as INTERVAL"),
5178 }
5179 })?;
5180 Ok(Value::Interval { months, micros })
5181 }
5182 other => Err(EvalError::TypeMismatch {
5183 detail: alloc::format!(
5184 "::INTERVAL only accepts TEXT-shape inputs, got {:?}",
5185 other.data_type()
5186 ),
5187 }),
5188 }
5189}
5190
5191fn cast_to_date(v: Value) -> Result<Value, EvalError> {
5192 match v {
5193 Value::Date(d) => Ok(Value::Date(d)),
5194 Value::Int(n) => Ok(Value::Date(n)),
5197 Value::BigInt(n) => {
5198 i32::try_from(n)
5199 .map(Value::Date)
5200 .map_err(|_| EvalError::TypeMismatch {
5201 detail: "bigint days-since-epoch out of DATE range".into(),
5202 })
5203 }
5204 Value::Timestamp(t) => {
5206 let days = t.div_euclid(86_400_000_000);
5207 i32::try_from(days)
5208 .map(Value::Date)
5209 .map_err(|_| EvalError::TypeMismatch {
5210 detail: "timestamp out of DATE range".into(),
5211 })
5212 }
5213 Value::Text(s) => parse_date_literal(&s)
5214 .map(Value::Date)
5215 .ok_or(EvalError::TypeMismatch {
5216 detail: format!("cannot parse {s:?} as DATE (expected YYYY-MM-DD)"),
5217 }),
5218 other => Err(EvalError::TypeMismatch {
5219 detail: format!("cannot cast {:?} to DATE", other.data_type()),
5220 }),
5221 }
5222}
5223
5224fn cast_to_timestamp(v: Value) -> Result<Value, EvalError> {
5225 match v {
5226 Value::Timestamp(t) => Ok(Value::Timestamp(t)),
5227 Value::Int(n) => Ok(Value::Timestamp(i64::from(n))),
5231 Value::BigInt(n) => Ok(Value::Timestamp(n)),
5232 Value::Date(d) => Ok(Value::Timestamp(i64::from(d) * 86_400_000_000)),
5234 Value::Text(s) => {
5235 parse_timestamp_literal(&s)
5236 .map(Value::Timestamp)
5237 .ok_or(EvalError::TypeMismatch {
5238 detail: format!(
5239 "cannot parse {s:?} as TIMESTAMP \
5240 (expected YYYY-MM-DD[ HH:MM:SS[.ffffff]])"
5241 ),
5242 })
5243 }
5244 other => Err(EvalError::TypeMismatch {
5245 detail: format!("cannot cast {:?} to TIMESTAMP", other.data_type()),
5246 }),
5247 }
5248}
5249
5250fn value_to_text(v: &Value) -> String {
5251 match v {
5252 Value::SmallInt(n) => format!("{n}"),
5256 Value::Int(n) => format!("{n}"),
5257 Value::BigInt(n) => format!("{n}"),
5258 Value::Float(x) => format!("{x}"),
5259 Value::Text(s) | Value::Json(s) => s.clone(),
5261 Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
5262 Value::Vector(v) => {
5263 let cells: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
5264 format!("[{}]", cells.join(", "))
5265 }
5266 Value::Sq8Vector(q) => {
5271 let cells: Vec<String> = spg_storage::quantize::dequantize(q)
5272 .iter()
5273 .map(|x| format!("{x}"))
5274 .collect();
5275 format!("[{}]", cells.join(", "))
5276 }
5277 Value::HalfVector(h) => {
5280 let cells: Vec<String> = h.to_f32_vec().iter().map(|x| format!("{x}")).collect();
5281 format!("[{}]", cells.join(", "))
5282 }
5283 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
5284 Value::Date(d) => format_date(*d),
5285 Value::Timestamp(t) => format_timestamp(*t),
5286 Value::Interval { months, micros } => format_interval(*months, *micros),
5287 Value::Null => "NULL".into(),
5288 Value::Bytes(b) => format_bytea_hex(b),
5290 Value::TextArray(items) => format_text_array(items),
5292 Value::IntArray(items) => format_int_array(items),
5293 Value::BigIntArray(items) => format_bigint_array(items),
5294 Value::TsVector(lexs) => format_tsvector(lexs),
5296 Value::TsQuery(ast) => format_tsquery(ast),
5297 Value::Uuid(b) => spg_storage::format_uuid(b),
5300 Value::Time(us) => format_time(*us),
5302 Value::TimeTz { us, offset_secs } => format_timetz(*us, *offset_secs),
5304 Value::Year(y) => format!("{y:04}"),
5306 Value::Money(c) => format_money(*c),
5308 Value::Range { .. } => crate::format_range_text(v),
5312 Value::Hstore(pairs) => crate::format_hstore_text(pairs),
5314 Value::IntArray2D(rows) => crate::format_int_2d_text_pub(rows),
5316 Value::BigIntArray2D(rows) => crate::format_bigint_2d_text_pub(rows),
5317 Value::TextArray2D(rows) => crate::format_text_2d_text_pub(rows),
5318 _ => format!("{v:?}"),
5320 }
5321}
5322
5323pub fn format_date(days: i32) -> String {
5326 let (y, m, d) = civil_from_days(days);
5327 format!("{y:04}-{m:02}-{d:02}")
5328}
5329
5330pub fn format_timestamptz(micros: i64) -> String {
5341 let base = format_timestamp(micros);
5342 let mut s = String::with_capacity(base.len() + 3);
5343 s.push_str(&base);
5344 s.push_str("+00");
5345 s
5346}
5347
5348pub fn format_money(cents: i64) -> String {
5352 let neg = cents < 0;
5353 let abs = cents.unsigned_abs();
5354 let dollars = abs / 100;
5355 let cc = abs % 100;
5356 let dollar_str = dollars.to_string();
5358 let bytes = dollar_str.as_bytes();
5359 let mut int_part = String::with_capacity(dollar_str.len() + dollar_str.len() / 3);
5360 for (i, b) in bytes.iter().enumerate() {
5361 let from_right = bytes.len() - i;
5364 if i > 0 && from_right % 3 == 0 {
5365 int_part.push(',');
5366 }
5367 int_part.push(*b as char);
5368 }
5369 let sign = if neg { "-" } else { "" };
5370 format!("{sign}${int_part}.{cc:02}")
5371}
5372
5373pub fn format_timetz(us: i64, offset_secs: i32) -> String {
5378 let time = format_time(us);
5379 let sign = if offset_secs < 0 { '-' } else { '+' };
5380 let abs = offset_secs.unsigned_abs();
5381 let oh = abs / 3600;
5382 let om = (abs % 3600) / 60;
5383 if om == 0 {
5384 format!("{time}{sign}{oh:02}")
5385 } else {
5386 format!("{time}{sign}{oh:02}:{om:02}")
5387 }
5388}
5389
5390pub fn format_time(us: i64) -> String {
5395 let total_secs = us.div_euclid(1_000_000);
5396 let frac = us.rem_euclid(1_000_000);
5397 let hh = total_secs / 3600;
5398 let mm = (total_secs / 60) % 60;
5399 let ss = total_secs % 60;
5400 if frac == 0 {
5401 format!("{hh:02}:{mm:02}:{ss:02}")
5402 } else {
5403 let raw = format!("{frac:06}");
5404 let trimmed = raw.trim_end_matches('0');
5405 format!("{hh:02}:{mm:02}:{ss:02}.{trimmed}")
5406 }
5407}
5408
5409pub fn format_timestamp(micros: i64) -> String {
5410 const MICROS_PER_DAY: i64 = 86_400_000_000;
5411 let days = micros.div_euclid(MICROS_PER_DAY);
5414 let day_micros = micros.rem_euclid(MICROS_PER_DAY);
5415 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
5416 let (y, m, d) = civil_from_days(day_i32);
5417 let secs = day_micros / 1_000_000;
5418 let frac = day_micros % 1_000_000;
5419 let hh = secs / 3600;
5420 let mm = (secs / 60) % 60;
5421 let ss = secs % 60;
5422 if frac == 0 {
5423 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}")
5424 } else {
5425 let raw = format!("{frac:06}");
5427 let trimmed = raw.trim_end_matches('0');
5428 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}.{trimmed}")
5429 }
5430}
5431
5432#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5437fn civil_from_days(days: i32) -> (i32, u32, u32) {
5438 let z = i64::from(days) + 719_468;
5439 let era = z.div_euclid(146_097);
5440 let doe = (z - era * 146_097) as u32;
5444 let yoe = (doe.saturating_sub(doe / 1460) + doe / 36524 - doe / 146_096) / 365;
5445 let y_base = i64::from(yoe) + era * 400;
5446 let doy = doe.saturating_sub(365 * yoe + yoe / 4 - yoe / 100);
5447 let mp = (5 * doy + 2) / 153;
5448 let d = doy.saturating_sub((153 * mp + 2) / 5) + 1;
5449 let m = if mp < 10 { mp + 3 } else { mp - 9 };
5450 let y = if m <= 2 { y_base + 1 } else { y_base };
5451 (y as i32, m, d)
5452}
5453
5454#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5457pub fn days_from_civil(y: i32, m: u32, d: u32) -> i32 {
5458 let y_adj = if m <= 2 {
5459 i64::from(y) - 1
5460 } else {
5461 i64::from(y)
5462 };
5463 let era = y_adj.div_euclid(400);
5464 let yoe = (y_adj - era * 400) as u32;
5465 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d.saturating_sub(1);
5466 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
5467 let total = era * 146_097 + i64::from(doe) - 719_468;
5468 i32::try_from(total).unwrap_or(i32::MAX)
5469}
5470
5471pub fn parse_date_literal(s: &str) -> Option<i32> {
5475 let bytes = s.as_bytes();
5476 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
5477 return None;
5478 }
5479 let y: i32 = s[0..4].parse().ok()?;
5480 let m: u32 = s[5..7].parse().ok()?;
5481 let d: u32 = s[8..10].parse().ok()?;
5482 if !(1..=12).contains(&m) || !(1..=31).contains(&d) {
5483 return None;
5484 }
5485 Some(days_from_civil(y, m, d))
5486}
5487
5488pub fn parse_timestamp_literal(s: &str) -> Option<i64> {
5493 let trimmed = s.trim();
5494 let (date_part, time_part) = match trimmed.find([' ', 'T']) {
5495 Some(i) => (&trimmed[..i], Some(&trimmed[i + 1..])),
5496 None => (trimmed, None),
5497 };
5498 let days = parse_date_literal(date_part)?;
5499 let (day_micros, tz_offset_micros) = match time_part {
5500 None => (0, 0),
5501 Some(t) => parse_time_of_day_micros(t)?,
5502 };
5503 Some(i64::from(days) * 86_400_000_000 + day_micros - tz_offset_micros)
5513}
5514
5515fn parse_time_of_day_micros(t: &str) -> Option<(i64, i64)> {
5528 let t = t.trim();
5529 let (core, tz_micros) = if let Some(rest) = t.strip_suffix('Z') {
5535 (rest, 0i64)
5536 } else if let Some(rest) = t.strip_suffix(" UTC").or_else(|| t.strip_suffix("UTC")) {
5537 (rest, 0i64)
5538 } else if let Some((idx, sign_byte)) = find_offset_sign(t) {
5539 let suffix = &t[idx..];
5540 let micros = parse_tz_offset_suffix(suffix, sign_byte == b'+')?;
5541 (&t[..idx], micros)
5542 } else {
5543 (t, 0i64)
5544 };
5545 let (time, frac_str) = match core.split_once('.') {
5546 Some((a, b)) => (a, Some(b)),
5547 None => (core, None),
5548 };
5549 let bytes = time.as_bytes();
5550 if bytes.len() != 8 || bytes[2] != b':' || bytes[5] != b':' {
5551 return None;
5552 }
5553 let hh: i64 = time[0..2].parse().ok()?;
5554 let mm: i64 = time[3..5].parse().ok()?;
5555 let ss: i64 = time[6..8].parse().ok()?;
5556 if !(0..24).contains(&hh) || !(0..60).contains(&mm) || !(0..60).contains(&ss) {
5557 return None;
5558 }
5559 let frac_micros: i64 = match frac_str {
5560 None => 0,
5561 Some(f) => {
5562 if f.is_empty() || f.len() > 9 {
5564 return None;
5565 }
5566 let mut padded = String::with_capacity(6);
5567 padded.push_str(&f[..f.len().min(6)]);
5568 while padded.len() < 6 {
5569 padded.push('0');
5570 }
5571 padded.parse().ok()?
5572 }
5573 };
5574 Some((
5575 ((hh * 3600 + mm * 60 + ss) * 1_000_000) + frac_micros,
5576 tz_micros,
5577 ))
5578}
5579
5580fn find_offset_sign(t: &str) -> Option<(usize, u8)> {
5586 let bytes = t.as_bytes();
5587 if bytes.len() < 9 {
5589 return None;
5590 }
5591 for i in 8..bytes.len() {
5592 match bytes[i] {
5593 b'+' | b'-' => return Some((i, bytes[i])),
5594 _ => {}
5595 }
5596 }
5597 None
5598}
5599
5600fn parse_tz_offset_suffix(suffix: &str, is_positive: bool) -> Option<i64> {
5604 let body = &suffix[1..];
5606 let (hh, mm): (i64, i64) = if let Some((h, m)) = body.split_once(':') {
5607 (h.parse().ok()?, m.parse().ok()?)
5608 } else {
5609 match body.len() {
5610 2 => (body.parse().ok()?, 0),
5611 3 => {
5612 return None;
5616 }
5617 4 => {
5618 let h: i64 = body[0..2].parse().ok()?;
5619 let m: i64 = body[2..4].parse().ok()?;
5620 (h, m)
5621 }
5622 _ => return None,
5623 }
5624 };
5625 if !(0..=18).contains(&hh) || !(0..60).contains(&mm) {
5626 return None;
5627 }
5628 let abs = (hh * 3600 + mm * 60) * 1_000_000;
5629 Some(if is_positive { abs } else { -abs })
5630}
5631
5632pub fn format_interval(months: i32, micros: i64) -> String {
5637 const MICROS_PER_DAY: i64 = 86_400_000_000;
5638 let mut parts: Vec<String> = Vec::new();
5639 let years = months / 12;
5640 let mons = months % 12;
5641 let unit = |n: i64, singular: &'static str, plural: &'static str| -> &'static str {
5644 if n == 1 { singular } else { plural }
5645 };
5646 if years != 0 {
5647 parts.push(format!(
5648 "{years} {}",
5649 unit(i64::from(years), "year", "years")
5650 ));
5651 }
5652 if mons != 0 {
5653 parts.push(format!("{mons} {}", unit(i64::from(mons), "mon", "mons")));
5654 }
5655 let days = micros / MICROS_PER_DAY;
5656 let mut rem = micros % MICROS_PER_DAY;
5657 if days != 0 {
5658 parts.push(format!("{days} {}", unit(days, "day", "days")));
5659 }
5660 if rem != 0 {
5661 let neg = rem < 0;
5662 if neg {
5663 rem = -rem;
5664 }
5665 let secs = rem / 1_000_000;
5666 let frac = rem % 1_000_000;
5667 let hh = secs / 3600;
5668 let mm = (secs / 60) % 60;
5669 let ss = secs % 60;
5670 let sign = if neg { "-" } else { "" };
5671 if frac == 0 {
5672 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}"));
5673 } else {
5674 let raw = format!("{frac:06}");
5675 let trimmed = raw.trim_end_matches('0');
5676 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}.{trimmed}"));
5677 }
5678 }
5679 if parts.is_empty() {
5680 "0".into()
5681 } else {
5682 parts.join(" ")
5683 }
5684}
5685
5686fn add_months_to_civil(y: i32, m: u32, d: u32, months: i32) -> (i32, u32, u32) {
5689 let total_months = i64::from(y) * 12 + i64::from(m) - 1 + i64::from(months);
5690 let new_year = i32::try_from(total_months.div_euclid(12)).unwrap_or(i32::MAX);
5691 let new_month_zero = total_months.rem_euclid(12);
5692 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5693 let new_month = (new_month_zero as u32) + 1;
5694 let max_day = days_in_month(new_year, new_month);
5695 (new_year, new_month, d.min(max_day))
5696}
5697
5698const fn days_in_month(y: i32, m: u32) -> u32 {
5699 match m {
5700 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
5701 2 => {
5702 if y.rem_euclid(4) == 0 && (y.rem_euclid(100) != 0 || y.rem_euclid(400) == 0) {
5704 29
5705 } else {
5706 28
5707 }
5708 }
5709 _ => 30,
5712 }
5713}
5714
5715pub fn format_text_array(items: &[Option<String>]) -> String {
5721 let mut out = String::with_capacity(2 + items.len() * 8);
5722 out.push('{');
5723 for (i, item) in items.iter().enumerate() {
5724 if i > 0 {
5725 out.push(',');
5726 }
5727 match item {
5728 None => out.push_str("NULL"),
5729 Some(s) => {
5730 let needs_quote = s.is_empty()
5731 || s.eq_ignore_ascii_case("NULL")
5732 || s.chars()
5733 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
5734 if needs_quote {
5735 out.push('"');
5736 for c in s.chars() {
5737 if c == '"' || c == '\\' {
5738 out.push('\\');
5739 }
5740 out.push(c);
5741 }
5742 out.push('"');
5743 } else {
5744 out.push_str(s);
5745 }
5746 }
5747 }
5748 }
5749 out.push('}');
5750 out
5751}
5752
5753pub fn format_int_array(items: &[Option<i32>]) -> String {
5757 let mut out = String::with_capacity(2 + items.len() * 4);
5758 out.push('{');
5759 for (i, item) in items.iter().enumerate() {
5760 if i > 0 {
5761 out.push(',');
5762 }
5763 match item {
5764 None => out.push_str("NULL"),
5765 Some(n) => out.push_str(&n.to_string()),
5766 }
5767 }
5768 out.push('}');
5769 out
5770}
5771
5772pub fn format_bigint_array(items: &[Option<i64>]) -> String {
5775 let mut out = String::with_capacity(2 + items.len() * 6);
5776 out.push('{');
5777 for (i, item) in items.iter().enumerate() {
5778 if i > 0 {
5779 out.push(',');
5780 }
5781 match item {
5782 None => out.push_str("NULL"),
5783 Some(n) => out.push_str(&n.to_string()),
5784 }
5785 }
5786 out.push('}');
5787 out
5788}
5789
5790pub fn format_tsvector(lexs: &[TsLexeme]) -> String {
5796 let mut out = String::with_capacity(lexs.len() * 12);
5797 for (i, l) in lexs.iter().enumerate() {
5798 if i > 0 {
5799 out.push(' ');
5800 }
5801 out.push('\'');
5802 for c in l.word.chars() {
5803 if c == '\'' {
5804 out.push('\'');
5805 }
5806 out.push(c);
5807 }
5808 out.push('\'');
5809 if !l.positions.is_empty() {
5810 for (pi, p) in l.positions.iter().enumerate() {
5811 out.push(if pi == 0 { ':' } else { ',' });
5812 out.push_str(&p.to_string());
5813 }
5814 match l.weight {
5819 3 => out.push('A'),
5820 2 => out.push('B'),
5821 1 => out.push('C'),
5822 _ => {}
5823 }
5824 }
5825 }
5826 out
5827}
5828
5829pub fn format_tsquery(ast: &TsQueryAst) -> String {
5832 fn go(ast: &TsQueryAst, parent_prec: u8, out: &mut String) {
5833 let (own_prec, write_self): (u8, &dyn Fn(&mut String)) = match ast {
5835 TsQueryAst::Or(_, _) => (1, &|_| {}),
5836 TsQueryAst::And(_, _) | TsQueryAst::Phrase { .. } => (2, &|_| {}),
5837 TsQueryAst::Not(_) => (3, &|_| {}),
5838 TsQueryAst::Term { .. } => (4, &|_| {}),
5839 };
5840 let need_parens = own_prec < parent_prec;
5841 if need_parens {
5842 out.push('(');
5843 }
5844 match ast {
5845 TsQueryAst::Term { word, .. } => {
5846 out.push('\'');
5847 for c in word.chars() {
5848 if c == '\'' {
5849 out.push('\'');
5850 }
5851 out.push(c);
5852 }
5853 out.push('\'');
5854 }
5855 TsQueryAst::And(a, b) => {
5856 go(a, own_prec, out);
5857 out.push_str(" & ");
5858 go(b, own_prec, out);
5859 }
5860 TsQueryAst::Or(a, b) => {
5861 go(a, own_prec, out);
5862 out.push_str(" | ");
5863 go(b, own_prec, out);
5864 }
5865 TsQueryAst::Not(x) => {
5866 out.push('!');
5867 go(x, own_prec, out);
5868 }
5869 TsQueryAst::Phrase {
5870 left,
5871 right,
5872 distance,
5873 } => {
5874 go(left, own_prec, out);
5875 out.push_str(&alloc::format!(" <{distance}> "));
5876 go(right, own_prec, out);
5877 }
5878 }
5879 write_self(out);
5880 if need_parens {
5881 out.push(')');
5882 }
5883 }
5884 let mut out = String::new();
5885 go(ast, 0, &mut out);
5886 out
5887}
5888
5889pub fn decode_tsvector_external(s: &str) -> Result<Vec<TsLexeme>, EvalError> {
5898 let mut out: Vec<TsLexeme> = Vec::new();
5899 let mut i = 0;
5900 let bytes = s.as_bytes();
5901 while i < bytes.len() {
5902 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
5903 i += 1;
5904 }
5905 if i >= bytes.len() {
5906 break;
5907 }
5908 let word = if bytes[i] == b'\'' {
5911 i += 1;
5912 let mut w = String::new();
5913 loop {
5914 if i >= bytes.len() {
5915 return Err(EvalError::TypeMismatch {
5916 detail: "tsvector literal: unterminated quoted lexeme".into(),
5917 });
5918 }
5919 let b = bytes[i];
5920 if b == b'\'' {
5921 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
5922 w.push('\'');
5923 i += 2;
5924 } else {
5925 i += 1;
5926 break;
5927 }
5928 } else {
5929 w.push(b as char);
5930 i += 1;
5931 }
5932 }
5933 w
5934 } else {
5935 let start = i;
5937 while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b':' {
5938 i += 1;
5939 }
5940 core::str::from_utf8(&bytes[start..i])
5941 .map_err(|_| EvalError::TypeMismatch {
5942 detail: "tsvector literal: non-UTF-8 lexeme".into(),
5943 })?
5944 .to_string()
5945 };
5946 if word.is_empty() {
5947 return Err(EvalError::TypeMismatch {
5948 detail: "tsvector literal: empty lexeme".into(),
5949 });
5950 }
5951 let mut positions: Vec<u16> = Vec::new();
5954 let mut weight: u8 = 0;
5955 if i < bytes.len() && bytes[i] == b':' {
5956 i += 1;
5957 loop {
5958 let start = i;
5959 while i < bytes.len() && bytes[i].is_ascii_digit() {
5960 i += 1;
5961 }
5962 if start == i {
5963 return Err(EvalError::TypeMismatch {
5964 detail: "tsvector literal: expected digit after ':'".into(),
5965 });
5966 }
5967 let num: u16 = core::str::from_utf8(&bytes[start..i])
5968 .expect("ascii digits")
5969 .parse()
5970 .map_err(|_| EvalError::TypeMismatch {
5971 detail: alloc::format!(
5972 "tsvector literal: position {} overflows u16",
5973 core::str::from_utf8(&bytes[start..i]).unwrap_or("?")
5974 ),
5975 })?;
5976 positions.push(num);
5977 if i < bytes.len() {
5978 let w = bytes[i];
5979 if matches!(w, b'A' | b'B' | b'C' | b'D') {
5980 weight = match w {
5981 b'A' => 3,
5982 b'B' => 2,
5983 b'C' => 1,
5984 _ => 0,
5985 };
5986 i += 1;
5987 }
5988 }
5989 if i < bytes.len() && bytes[i] == b',' {
5990 i += 1;
5991 continue;
5992 }
5993 break;
5994 }
5995 }
5996 positions.sort_unstable();
5997 positions.dedup();
5998 match out.binary_search_by(|l| l.word.as_str().cmp(word.as_str())) {
6001 Ok(idx) => {
6002 for p in positions {
6003 if !out[idx].positions.contains(&p) {
6004 out[idx].positions.push(p);
6005 }
6006 }
6007 out[idx].positions.sort_unstable();
6008 if weight != 0 {
6009 out[idx].weight = weight;
6010 }
6011 }
6012 Err(idx) => {
6013 out.insert(
6014 idx,
6015 TsLexeme {
6016 word,
6017 positions,
6018 weight,
6019 },
6020 );
6021 }
6022 }
6023 }
6024 Ok(out)
6025}
6026
6027pub fn decode_tsquery_external(s: &str) -> Result<TsQueryAst, EvalError> {
6033 let mut p = TsQueryParser {
6034 bytes: s.as_bytes(),
6035 pos: 0,
6036 };
6037 p.skip_ws();
6038 if p.pos >= p.bytes.len() {
6039 return Err(EvalError::TypeMismatch {
6040 detail: "tsquery literal: empty".into(),
6041 });
6042 }
6043 let ast = p.parse_or()?;
6044 p.skip_ws();
6045 if p.pos < p.bytes.len() {
6046 return Err(EvalError::TypeMismatch {
6047 detail: alloc::format!("tsquery literal: trailing garbage at offset {}", p.pos),
6048 });
6049 }
6050 Ok(ast)
6051}
6052
6053struct TsQueryParser<'a> {
6054 bytes: &'a [u8],
6055 pos: usize,
6056}
6057
6058impl<'a> TsQueryParser<'a> {
6059 fn skip_ws(&mut self) {
6060 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_whitespace() {
6061 self.pos += 1;
6062 }
6063 }
6064 fn peek(&self) -> Option<u8> {
6065 self.bytes.get(self.pos).copied()
6066 }
6067 fn parse_or(&mut self) -> Result<TsQueryAst, EvalError> {
6068 let mut lhs = self.parse_and()?;
6069 loop {
6070 self.skip_ws();
6071 if self.peek() != Some(b'|') {
6072 return Ok(lhs);
6073 }
6074 self.pos += 1;
6075 let rhs = self.parse_and()?;
6076 lhs = TsQueryAst::Or(Box::new(lhs), Box::new(rhs));
6077 }
6078 }
6079 fn parse_and(&mut self) -> Result<TsQueryAst, EvalError> {
6080 let mut lhs = self.parse_unary()?;
6081 loop {
6082 self.skip_ws();
6083 match self.peek() {
6084 Some(b'&') => {
6085 self.pos += 1;
6086 let rhs = self.parse_unary()?;
6087 lhs = TsQueryAst::And(Box::new(lhs), Box::new(rhs));
6088 }
6089 Some(b'<') => {
6090 self.pos += 1;
6092 let start = self.pos;
6093 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
6094 self.pos += 1;
6095 }
6096 if start == self.pos || self.peek() != Some(b'>') {
6097 return Err(EvalError::TypeMismatch {
6098 detail: "tsquery literal: malformed <N> phrase operator".into(),
6099 });
6100 }
6101 let n: u16 = core::str::from_utf8(&self.bytes[start..self.pos])
6102 .expect("ascii digits")
6103 .parse()
6104 .map_err(|_| EvalError::TypeMismatch {
6105 detail: "tsquery literal: phrase distance overflows u16".into(),
6106 })?;
6107 self.pos += 1; let rhs = self.parse_unary()?;
6109 lhs = TsQueryAst::Phrase {
6110 left: Box::new(lhs),
6111 right: Box::new(rhs),
6112 distance: n,
6113 };
6114 }
6115 _ => return Ok(lhs),
6116 }
6117 }
6118 }
6119 fn parse_unary(&mut self) -> Result<TsQueryAst, EvalError> {
6120 self.skip_ws();
6121 if self.peek() == Some(b'!') {
6122 self.pos += 1;
6123 let inner = self.parse_unary()?;
6124 return Ok(TsQueryAst::Not(Box::new(inner)));
6125 }
6126 self.parse_atom()
6127 }
6128 fn parse_atom(&mut self) -> Result<TsQueryAst, EvalError> {
6129 self.skip_ws();
6130 match self.peek() {
6131 Some(b'(') => {
6132 self.pos += 1;
6133 let inner = self.parse_or()?;
6134 self.skip_ws();
6135 if self.peek() != Some(b')') {
6136 return Err(EvalError::TypeMismatch {
6137 detail: "tsquery literal: missing ')'".into(),
6138 });
6139 }
6140 self.pos += 1;
6141 Ok(inner)
6142 }
6143 Some(b'\'') => {
6144 self.pos += 1;
6145 let mut w = String::new();
6146 loop {
6147 match self.peek() {
6148 None => {
6149 return Err(EvalError::TypeMismatch {
6150 detail: "tsquery literal: unterminated quoted lexeme".into(),
6151 });
6152 }
6153 Some(b'\'') => {
6154 if self.bytes.get(self.pos + 1) == Some(&b'\'') {
6155 w.push('\'');
6156 self.pos += 2;
6157 } else {
6158 self.pos += 1;
6159 break;
6160 }
6161 }
6162 Some(b) => {
6163 w.push(b as char);
6164 self.pos += 1;
6165 }
6166 }
6167 }
6168 self.skip_weight_suffix();
6171 Ok(TsQueryAst::Term {
6172 word: w,
6173 weight_mask: 0,
6174 })
6175 }
6176 Some(b) if b.is_ascii_alphanumeric() || b == b'_' => {
6177 let start = self.pos;
6178 while self.pos < self.bytes.len() {
6179 let c = self.bytes[self.pos];
6180 if c.is_ascii_alphanumeric() || c == b'_' {
6181 self.pos += 1;
6182 } else {
6183 break;
6184 }
6185 }
6186 let w = core::str::from_utf8(&self.bytes[start..self.pos])
6187 .map_err(|_| EvalError::TypeMismatch {
6188 detail: "tsquery literal: non-UTF-8 lexeme".into(),
6189 })?
6190 .to_string();
6191 self.skip_weight_suffix();
6192 Ok(TsQueryAst::Term {
6193 word: w,
6194 weight_mask: 0,
6195 })
6196 }
6197 Some(b) => Err(EvalError::TypeMismatch {
6198 detail: alloc::format!(
6199 "tsquery literal: unexpected byte {:?} at offset {}",
6200 b as char,
6201 self.pos
6202 ),
6203 }),
6204 None => Err(EvalError::TypeMismatch {
6205 detail: "tsquery literal: expected term".into(),
6206 }),
6207 }
6208 }
6209 fn skip_weight_suffix(&mut self) {
6210 if self.peek() != Some(b':') {
6211 return;
6212 }
6213 self.pos += 1;
6214 while let Some(b) = self.peek() {
6215 if matches!(
6216 b,
6217 b'A' | b'B' | b'C' | b'D' | b'a' | b'b' | b'c' | b'd' | b'*'
6218 ) || b.is_ascii_digit()
6219 {
6220 self.pos += 1;
6221 } else {
6222 break;
6223 }
6224 }
6225 }
6226}
6227
6228pub fn format_bytea_hex(b: &[u8]) -> String {
6232 let mut out = String::with_capacity(2 + 2 * b.len());
6233 out.push_str("\\x");
6234 const HEX: &[u8; 16] = b"0123456789abcdef";
6235 for byte in b {
6236 out.push(HEX[(byte >> 4) as usize] as char);
6237 out.push(HEX[(byte & 0x0F) as usize] as char);
6238 }
6239 out
6240}
6241
6242pub fn format_numeric(scaled: i128, scale: u8) -> String {
6247 if scale == 0 {
6248 return format!("{scaled}");
6249 }
6250 let negative = scaled < 0;
6251 let mag_str = scaled.unsigned_abs().to_string();
6252 let mag_bytes = mag_str.as_bytes();
6253 let scale_u = scale as usize;
6254 let mut out = String::with_capacity(mag_str.len() + 3);
6255 if negative {
6256 out.push('-');
6257 }
6258 if mag_bytes.len() <= scale_u {
6259 out.push('0');
6260 out.push('.');
6261 for _ in mag_bytes.len()..scale_u {
6262 out.push('0');
6263 }
6264 out.push_str(&mag_str);
6265 } else {
6266 let split = mag_bytes.len() - scale_u;
6267 out.push_str(&mag_str[..split]);
6268 out.push('.');
6269 out.push_str(&mag_str[split..]);
6270 }
6271 out
6272}
6273
6274fn cast_numeric_to_int(v: Value) -> Result<Value, EvalError> {
6275 match v {
6276 Value::Int(n) => Ok(Value::Int(n)),
6277 Value::BigInt(n) => i32::try_from(n)
6278 .map(Value::Int)
6279 .map_err(|_| EvalError::TypeMismatch {
6280 detail: format!("bigint {n} does not fit in int"),
6281 }),
6282 #[allow(clippy::cast_possible_truncation)]
6283 Value::Float(x) => Ok(Value::Int(x as i32)),
6284 Value::Text(s) => {
6285 s.trim()
6286 .parse::<i32>()
6287 .map(Value::Int)
6288 .map_err(|_| EvalError::TypeMismatch {
6289 detail: format!("cannot parse {s:?} as int"),
6290 })
6291 }
6292 Value::Bool(b) => Ok(Value::Int(i32::from(b))),
6293 other => Err(EvalError::TypeMismatch {
6294 detail: format!("cannot cast {:?} to int", other.data_type()),
6295 }),
6296 }
6297}
6298
6299fn cast_numeric_to_bigint(v: Value) -> Result<Value, EvalError> {
6300 match v {
6301 Value::Int(n) => Ok(Value::BigInt(i64::from(n))),
6302 Value::BigInt(n) => Ok(Value::BigInt(n)),
6303 #[allow(clippy::cast_possible_truncation)]
6304 Value::Float(x) => Ok(Value::BigInt(x as i64)),
6305 Value::Text(s) => {
6306 s.trim()
6307 .parse::<i64>()
6308 .map(Value::BigInt)
6309 .map_err(|_| EvalError::TypeMismatch {
6310 detail: format!("cannot parse {s:?} as bigint"),
6311 })
6312 }
6313 Value::Bool(b) => Ok(Value::BigInt(i64::from(b))),
6314 other => Err(EvalError::TypeMismatch {
6315 detail: format!("cannot cast {:?} to bigint", other.data_type()),
6316 }),
6317 }
6318}
6319
6320fn cast_numeric_to_float(v: Value) -> Result<Value, EvalError> {
6321 match v {
6322 Value::Int(n) => Ok(Value::Float(f64::from(n))),
6323 #[allow(clippy::cast_precision_loss)]
6324 Value::BigInt(n) => Ok(Value::Float(n as f64)),
6325 Value::Float(x) => Ok(Value::Float(x)),
6326 Value::Text(s) => {
6327 s.trim()
6328 .parse::<f64>()
6329 .map(Value::Float)
6330 .map_err(|_| EvalError::TypeMismatch {
6331 detail: format!("cannot parse {s:?} as float"),
6332 })
6333 }
6334 other => Err(EvalError::TypeMismatch {
6335 detail: format!("cannot cast {:?} to float", other.data_type()),
6336 }),
6337 }
6338}
6339
6340fn cast_to_bool(v: Value) -> Result<Value, EvalError> {
6341 match v {
6342 Value::Bool(b) => Ok(Value::Bool(b)),
6343 Value::Int(n) => Ok(Value::Bool(n != 0)),
6344 Value::BigInt(n) => Ok(Value::Bool(n != 0)),
6345 Value::Text(s) => {
6346 let lo = s.trim().to_ascii_lowercase();
6347 match lo.as_str() {
6348 "true" | "t" | "yes" | "y" | "1" | "on" => Ok(Value::Bool(true)),
6349 "false" | "f" | "no" | "n" | "0" | "off" => Ok(Value::Bool(false)),
6350 _ => Err(EvalError::TypeMismatch {
6351 detail: format!("cannot parse {s:?} as bool"),
6352 }),
6353 }
6354 }
6355 other => Err(EvalError::TypeMismatch {
6356 detail: format!("cannot cast {:?} to bool", other.data_type()),
6357 }),
6358 }
6359}
6360
6361pub fn cast_to_vector(v: Value) -> Result<Value, EvalError> {
6364 match v {
6365 Value::Null => Ok(Value::Null),
6366 Value::Vector(v) => Ok(Value::Vector(v)),
6367 Value::Text(s) => parse_vector_text(&s)
6368 .map(Value::Vector)
6369 .ok_or(EvalError::TypeMismatch {
6370 detail: format!("cannot parse {s:?} as a vector literal"),
6371 }),
6372 other => Err(EvalError::TypeMismatch {
6373 detail: format!("::vector requires text input, got {:?}", other.data_type()),
6374 }),
6375 }
6376}
6377
6378pub fn parse_vector_text(s: &str) -> Option<Vec<f32>> {
6380 let trimmed = s.trim();
6381 let inner = trimmed.strip_prefix('[')?.strip_suffix(']')?;
6382 let trimmed_inner = inner.trim();
6383 if trimmed_inner.is_empty() {
6384 return Some(Vec::new());
6385 }
6386 let mut out = Vec::new();
6387 for part in trimmed_inner.split(',') {
6388 let f: f32 = part.trim().parse().ok()?;
6389 out.push(f);
6390 }
6391 Some(out)
6392}
6393
6394pub(crate) fn literal_to_value(l: &Literal) -> Value {
6395 match l {
6396 Literal::Integer(n) => {
6397 if let Ok(small) = i32::try_from(*n) {
6398 Value::Int(small)
6399 } else {
6400 Value::BigInt(*n)
6401 }
6402 }
6403 Literal::Float(x) => Value::Float(*x),
6404 Literal::String(s) => Value::Text(s.clone()),
6405 Literal::Vector(v) => Value::Vector(v.clone()),
6406 Literal::TextArray(items) => Value::TextArray(items.clone()),
6407 Literal::IntArray(items) => Value::IntArray(items.clone()),
6408 Literal::BigIntArray(items) => Value::BigIntArray(items.clone()),
6409 Literal::Bool(b) => Value::Bool(*b),
6410 Literal::Null => Value::Null,
6411 Literal::Interval { months, micros, .. } => Value::Interval {
6412 months: *months,
6413 micros: *micros,
6414 },
6415 }
6416}
6417
6418pub(crate) fn column_collation(e: &Expr, ctx: &EvalContext<'_>) -> Option<spg_storage::Collation> {
6424 let Expr::Column(c) = e else {
6425 return None;
6426 };
6427 let matches_composite = |s: &str| {
6433 c.qualifier.as_deref().is_some_and(|q| {
6434 s.len() == q.len() + 1 + c.name.len()
6435 && s.as_bytes()[q.len()] == b'.'
6436 && s.starts_with(q)
6437 && s.ends_with(c.name.as_str())
6438 })
6439 };
6440 if c.qualifier.is_some()
6441 && let Some(s) = ctx.columns.iter().find(|s| matches_composite(&s.name))
6442 {
6443 return Some(s.collation);
6444 }
6445 if let Some(s) = ctx.columns.iter().find(|s| s.name == c.name) {
6446 return Some(s.collation);
6447 }
6448 let ends_with_dot_name = |s: &str| {
6452 s.len() > c.name.len()
6455 && s.ends_with(c.name.as_str())
6456 && s.as_bytes()[s.len() - c.name.len() - 1] == b'.'
6457 };
6458 let mut matches = ctx.columns.iter().filter(|s| ends_with_dot_name(&s.name));
6459 let first = matches.next();
6460 let extra = matches.next();
6461 match (first, extra) {
6462 (Some(s), None) => Some(s.collation),
6463 _ => None,
6464 }
6465}
6466
6467fn collation_fold_for_compare(
6474 op: BinOp,
6475 lhs: &Expr,
6476 rhs: &Expr,
6477 l: Value,
6478 r: Value,
6479 ctx: &EvalContext<'_>,
6480) -> (Value, Value) {
6481 if !matches!(
6482 op,
6483 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq
6484 ) {
6485 return (l, r);
6486 }
6487 let lhs_col = column_collation(lhs, ctx);
6488 let rhs_col = column_collation(rhs, ctx);
6489 let ci = matches!(lhs_col, Some(spg_storage::Collation::CaseInsensitive))
6490 || matches!(rhs_col, Some(spg_storage::Collation::CaseInsensitive));
6491 if !ci {
6492 return (l, r);
6493 }
6494 let fold = |v: Value| match v {
6495 Value::Text(s) => Value::Text(s.to_ascii_lowercase()),
6496 other => other,
6497 };
6498 (fold(l), fold(r))
6499}
6500
6501#[inline]
6511fn composite_eq(schema_name: &str, qualifier: &str, name: &str) -> bool {
6512 schema_name.len() == qualifier.len() + 1 + name.len()
6513 && schema_name.as_bytes()[qualifier.len()] == b'.'
6514 && schema_name[..qualifier.len()] == *qualifier
6515 && schema_name[qualifier.len() + 1..] == *name
6516}
6517
6518pub(crate) fn find_column_pos(c: &ColumnName, ctx: &EvalContext<'_>) -> Option<usize> {
6523 if let Some(q) = &c.qualifier {
6524 if let Some(pos) = ctx
6525 .columns
6526 .iter()
6527 .position(|s| composite_eq(&s.name, q, &c.name))
6528 {
6529 return Some(pos);
6530 }
6531 }
6532 ctx.columns.iter().position(|s| s.name == c.name)
6533}
6534
6535fn resolve_column_borrowed<'r>(
6536 c: &ColumnName,
6537 row: &'r Row,
6538 ctx: &EvalContext<'_>,
6539) -> Result<Option<&'r Value>, EvalError> {
6540 if let Some(q) = &c.qualifier {
6541 if let Some(pos) = ctx
6542 .columns
6543 .iter()
6544 .position(|s| composite_eq(&s.name, q, &c.name))
6545 {
6546 return Ok(row.values.get(pos));
6547 }
6548 }
6549 if let Some(pos) = ctx.columns.iter().position(|s| s.name == c.name) {
6550 return Ok(row.values.get(pos));
6551 }
6552 Ok(None)
6553}
6554
6555fn text_prefix_chars(t: &str, n: i64) -> String {
6558 if n >= 0 {
6559 let n = usize::try_from(n).unwrap_or(usize::MAX);
6560 match t.char_indices().nth(n) {
6561 Some((byte_idx, _)) => t[..byte_idx].into(),
6562 None => t.into(),
6563 }
6564 } else {
6565 let drop_tail = usize::try_from(-n).unwrap_or(usize::MAX);
6566 let total = t.chars().count();
6567 let keep = total.saturating_sub(drop_tail);
6568 match t.char_indices().nth(keep) {
6569 Some((byte_idx, _)) => t[..byte_idx].into(),
6570 None => t.into(),
6571 }
6572 }
6573}
6574
6575fn resolve_column(c: &ColumnName, row: &Row, ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
6576 if let Some(q) = &c.qualifier {
6577 if let Some(pos) = ctx
6584 .columns
6585 .iter()
6586 .position(|s| composite_eq(&s.name, q, &c.name))
6587 {
6588 return Ok(row.values[pos].clone());
6589 }
6590 let prefix = alloc::format!("{q}.");
6597 if ctx.columns.iter().any(|sc| sc.name.starts_with(&prefix)) {
6598 return Err(EvalError::ColumnNotFound {
6599 name: alloc::format!("{q}.{name}", name = c.name),
6600 });
6601 }
6602 let expected = ctx.table_alias.ok_or_else(|| EvalError::UnknownQualifier {
6603 qualifier: q.clone(),
6604 })?;
6605 if q != expected {
6606 return Err(EvalError::UnknownQualifier {
6607 qualifier: q.clone(),
6608 });
6609 }
6610 }
6611 if let Some(pos) = ctx.columns.iter().position(|s| s.name == c.name) {
6612 return Ok(row.values[pos].clone());
6613 }
6614 let suffix = alloc::format!(".{name}", name = c.name);
6617 let mut matches = ctx
6618 .columns
6619 .iter()
6620 .enumerate()
6621 .filter(|(_, s)| s.name.ends_with(&suffix));
6622 let first = matches.next();
6623 let extra = matches.next();
6624 match (first, extra) {
6625 (Some((pos, _)), None) => Ok(row.values[pos].clone()),
6626 (Some(_), Some(_)) => Err(EvalError::TypeMismatch {
6627 detail: alloc::format!("ambiguous column reference: {}", c.name),
6628 }),
6629 _ => Err(EvalError::ColumnNotFound {
6630 name: c.name.clone(),
6631 }),
6632 }
6633}
6634
6635fn apply_unary(op: UnOp, v: Value) -> Result<Value, EvalError> {
6636 match (op, v) {
6637 (_, Value::Null) => Ok(Value::Null),
6638 (UnOp::Neg, Value::Int(n)) => {
6639 n.checked_neg()
6640 .map(Value::Int)
6641 .ok_or(EvalError::TypeMismatch {
6642 detail: "integer overflow on unary -".into(),
6643 })
6644 }
6645 (UnOp::Neg, Value::BigInt(n)) => {
6646 n.checked_neg()
6647 .map(Value::BigInt)
6648 .ok_or(EvalError::TypeMismatch {
6649 detail: "bigint overflow on unary -".into(),
6650 })
6651 }
6652 (UnOp::Neg, Value::Float(x)) => Ok(Value::Float(-x)),
6653 (UnOp::Neg, other) => Err(EvalError::TypeMismatch {
6654 detail: format!("unary - applied to {:?}", other.data_type()),
6655 }),
6656 (UnOp::BitNot, Value::SmallInt(n)) => Ok(Value::Int(!i32::from(n))),
6657 (UnOp::BitNot, Value::Int(n)) => Ok(Value::Int(!n)),
6658 (UnOp::BitNot, Value::BigInt(n)) => Ok(Value::BigInt(!n)),
6659 (UnOp::BitNot, other) => Err(EvalError::TypeMismatch {
6660 detail: format!("cannot apply ~ to {other:?}"),
6661 }),
6662 (UnOp::Not, Value::Bool(b)) => Ok(Value::Bool(!b)),
6663 (UnOp::Not, other) => Err(EvalError::TypeMismatch {
6664 detail: format!("NOT applied to {:?}", other.data_type()),
6665 }),
6666 }
6667}
6668
6669fn values_not_distinct(l: &Value, r: &Value) -> bool {
6672 match (l, r) {
6673 (Value::Null, Value::Null) => true,
6674 (Value::Null, _) | (_, Value::Null) => false,
6675 _ => l == r,
6676 }
6677}
6678
6679fn apply_binary(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6680 if let BinOp::And = op {
6683 return and_3vl(l, r);
6684 }
6685 if let BinOp::Or = op {
6686 return or_3vl(l, r);
6687 }
6688 if let BinOp::IsNotDistinctFrom = op {
6691 return Ok(Value::Bool(values_not_distinct(&l, &r)));
6692 }
6693 if let BinOp::IsDistinctFrom = op {
6694 return Ok(Value::Bool(!values_not_distinct(&l, &r)));
6695 }
6696 if l.is_null() || r.is_null() {
6698 return Ok(Value::Null);
6699 }
6700 if matches!(l, Value::Numeric { .. }) || matches!(r, Value::Numeric { .. }) {
6703 return apply_binary_numeric(op, l, r);
6704 }
6705 if let Some(result) = apply_binary_calendar(op, &l, &r)? {
6713 return Ok(result);
6714 }
6715 match op {
6716 BinOp::Add => arith(l, r, i64::checked_add, |a, b| a + b, "+"),
6717 BinOp::Sub => arith(l, r, i64::checked_sub, |a, b| a - b, "-"),
6718 BinOp::Mul => arith(l, r, i64::checked_mul, |a, b| a * b, "*"),
6719 BinOp::Div => div_op(l, r),
6720 BinOp::L2Distance => l2_distance(l, r),
6721 BinOp::InnerProduct => inner_product(l, r),
6722 BinOp::CosineDistance => cosine_distance(l, r),
6723 BinOp::Concat => Ok(text_concat(&l, &r)),
6724 BinOp::BitOr => bitop(l, r, |a, b| a | b, "|"),
6725 BinOp::BitAnd => bitop(l, r, |a, b| a & b, "&"),
6726 BinOp::JsonGet => crate::json::path_get(&l, &r, false),
6727 BinOp::JsonGetText => crate::json::path_get(&l, &r, true),
6728 BinOp::JsonGetPath => crate::json::path_walk(&l, &r, false),
6729 BinOp::JsonGetPathText => crate::json::path_walk(&l, &r, true),
6730 BinOp::JsonContains => crate::json::contains(&l, &r),
6731 BinOp::TsMatch => ts_match(l, r),
6734 BinOp::InetContainedBy
6736 | BinOp::InetContainedByEq
6737 | BinOp::InetContains
6738 | BinOp::InetContainsEq
6739 | BinOp::InetOverlap => inet_op_bool_result(op, &l, &r),
6740 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6741 compare(op, &l, &r)
6742 }
6743 BinOp::And | BinOp::Or | BinOp::IsDistinctFrom | BinOp::IsNotDistinctFrom => {
6744 unreachable!("handled above")
6745 }
6746 }
6747}
6748
6749fn apply_binary_calendar(op: BinOp, l: &Value, r: &Value) -> Result<Option<Value>, EvalError> {
6753 let int_value = |v: &Value| -> Option<i64> {
6754 match v {
6755 Value::SmallInt(n) => Some(i64::from(*n)),
6756 Value::Int(n) => Some(i64::from(*n)),
6757 Value::BigInt(n) => Some(*n),
6758 _ => None,
6759 }
6760 };
6761 match (l, r) {
6765 (Value::Date(a), Value::Date(b)) if op == BinOp::Sub => {
6766 return Ok(Some(Value::BigInt(i64::from(*a) - i64::from(*b))));
6767 }
6768 (Value::Timestamp(a), Value::Timestamp(b)) if op == BinOp::Sub => {
6769 let delta = a.checked_sub(*b).ok_or(EvalError::TypeMismatch {
6770 detail: "TIMESTAMP - TIMESTAMP overflows i64 microseconds".into(),
6771 })?;
6772 return Ok(Some(Value::BigInt(delta)));
6773 }
6774 _ => {}
6775 }
6776 if let Some(out) = apply_binary_interval(op, l, r)? {
6780 return Ok(Some(out));
6781 }
6782 match (l, r) {
6783 (Value::Date(d), other) if op == BinOp::Add => {
6784 if let Some(n) = int_value(other) {
6785 let days = i64::from(*d).saturating_add(n);
6786 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6787 detail: "DATE + integer overflows DATE range".into(),
6788 })?;
6789 return Ok(Some(Value::Date(days32)));
6790 }
6791 }
6792 (other, Value::Date(d)) if op == BinOp::Add => {
6793 if let Some(n) = int_value(other) {
6794 let days = i64::from(*d).saturating_add(n);
6795 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6796 detail: "integer + DATE overflows DATE range".into(),
6797 })?;
6798 return Ok(Some(Value::Date(days32)));
6799 }
6800 }
6801 (Value::Date(d), other) if op == BinOp::Sub => {
6802 if let Some(n) = int_value(other) {
6803 let days = i64::from(*d).saturating_sub(n);
6804 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6805 detail: "DATE - integer overflows DATE range".into(),
6806 })?;
6807 return Ok(Some(Value::Date(days32)));
6808 }
6809 }
6810 _ => {}
6811 }
6812 Ok(None)
6813}
6814
6815pub(crate) fn apply_binary_interval(
6823 op: BinOp,
6824 l: &Value,
6825 r: &Value,
6826) -> Result<Option<Value>, EvalError> {
6827 let (lhs, rhs, sign): (&Value, &Value, i64) = match (l, r, op) {
6830 (Value::Interval { .. }, _, BinOp::Add) => (r, l, 1),
6831 (_, Value::Interval { .. }, BinOp::Add) => (l, r, 1),
6832 (_, Value::Interval { .. }, BinOp::Sub) => (l, r, -1),
6833 _ => return Ok(None),
6834 };
6835 let Value::Interval {
6836 months: rhs_months,
6837 micros: rhs_us,
6838 } = rhs
6839 else {
6840 unreachable!("rhs guaranteed to be Interval by the match above");
6841 };
6842 let signed_months = i64::from(*rhs_months) * sign;
6843 let signed_micros = rhs_us.checked_mul(sign).ok_or(EvalError::TypeMismatch {
6844 detail: "INTERVAL micros overflows on negation".into(),
6845 })?;
6846 match lhs {
6847 Value::Timestamp(t) => Ok(Some(Value::Timestamp(add_interval_to_micros(
6848 *t,
6849 signed_months,
6850 signed_micros,
6851 )?))),
6852 Value::Date(d) => {
6853 let day_aligned = signed_micros.rem_euclid(86_400_000_000) == 0;
6857 if day_aligned {
6858 let micros_per_day = 86_400_000_000_i64;
6859 let days_delta = signed_micros / micros_per_day;
6860 let shifted = shift_date_by_months(*d, signed_months)?;
6861 let new_days =
6862 i64::from(shifted)
6863 .checked_add(days_delta)
6864 .ok_or(EvalError::TypeMismatch {
6865 detail: "DATE ± INTERVAL overflows DATE range".into(),
6866 })?;
6867 let days32 = i32::try_from(new_days).map_err(|_| EvalError::TypeMismatch {
6868 detail: "DATE ± INTERVAL overflows DATE range".into(),
6869 })?;
6870 Ok(Some(Value::Date(days32)))
6871 } else {
6872 let base =
6873 i64::from(*d)
6874 .checked_mul(86_400_000_000)
6875 .ok_or(EvalError::TypeMismatch {
6876 detail: "DATE → TIMESTAMP lift overflows for INTERVAL math".into(),
6877 })?;
6878 Ok(Some(Value::Timestamp(add_interval_to_micros(
6879 base,
6880 signed_months,
6881 signed_micros,
6882 )?)))
6883 }
6884 }
6885 Value::Interval {
6886 months: lhs_months,
6887 micros: lhs_us,
6888 } => {
6889 let new_months = i64::from(*lhs_months)
6890 .checked_add(signed_months)
6891 .and_then(|n| i32::try_from(n).ok())
6892 .ok_or(EvalError::TypeMismatch {
6893 detail: "INTERVAL ± INTERVAL months overflows i32".into(),
6894 })?;
6895 let new_micros = lhs_us
6896 .checked_add(signed_micros)
6897 .ok_or(EvalError::TypeMismatch {
6898 detail: "INTERVAL ± INTERVAL micros overflows i64".into(),
6899 })?;
6900 Ok(Some(Value::Interval {
6901 months: new_months,
6902 micros: new_micros,
6903 }))
6904 }
6905 _ => Err(EvalError::TypeMismatch {
6906 detail: format!(
6907 "operator {op:?} not defined for {:?} and INTERVAL",
6908 lhs.data_type()
6909 ),
6910 }),
6911 }
6912}
6913
6914fn shift_date_by_months(d: i32, months: i64) -> Result<i32, EvalError> {
6916 let (y, m, day) = civil_from_days(d);
6917 let months_i32 = i32::try_from(months).map_err(|_| EvalError::TypeMismatch {
6918 detail: "INTERVAL months delta out of i32 range".into(),
6919 })?;
6920 let (ny, nm, nd) = add_months_to_civil(y, m, day, months_i32);
6921 Ok(days_from_civil(ny, nm, nd))
6922}
6923
6924fn add_interval_to_micros(t: i64, months: i64, micros: i64) -> Result<i64, EvalError> {
6928 let mut out = t;
6929 if months != 0 {
6930 const MICROS_PER_DAY: i64 = 86_400_000_000;
6931 let days = out.div_euclid(MICROS_PER_DAY);
6932 let day_micros = out.rem_euclid(MICROS_PER_DAY);
6933 let day_i32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6934 detail: "TIMESTAMP day component out of i32 range for INTERVAL months math".into(),
6935 })?;
6936 let shifted_days = shift_date_by_months(day_i32, months)?;
6937 out = i64::from(shifted_days)
6938 .checked_mul(MICROS_PER_DAY)
6939 .and_then(|n| n.checked_add(day_micros))
6940 .ok_or(EvalError::TypeMismatch {
6941 detail: "TIMESTAMP ± INTERVAL months overflows i64 microseconds".into(),
6942 })?;
6943 }
6944 out.checked_add(micros).ok_or(EvalError::TypeMismatch {
6945 detail: "TIMESTAMP ± INTERVAL micros overflows i64".into(),
6946 })
6947}
6948
6949#[allow(clippy::needless_pass_by_value)] fn apply_binary_numeric(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6954 let float_path = matches!(l, Value::Float(_)) || matches!(r, Value::Float(_));
6958 if float_path {
6959 let af = as_f64(&l)?;
6960 let bf = as_f64(&r)?;
6961 return match op {
6962 BinOp::Add => Ok(Value::Float(af + bf)),
6963 BinOp::Sub => Ok(Value::Float(af - bf)),
6964 BinOp::Mul => Ok(Value::Float(af * bf)),
6965 BinOp::Div => {
6966 if bf == 0.0 {
6967 Err(EvalError::DivisionByZero)
6968 } else {
6969 Ok(Value::Float(af / bf))
6970 }
6971 }
6972 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6973 let ord = af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
6974 detail: "NaN in NUMERIC/Float comparison".into(),
6975 })?;
6976 Ok(Value::Bool(cmp_to_bool(op, ord)))
6977 }
6978 BinOp::Concat => Ok(text_concat(&l, &r)),
6979 other => Err(EvalError::TypeMismatch {
6980 detail: format!("operator {other:?} not defined for NUMERIC and Float"),
6981 }),
6982 };
6983 }
6984 let (a, sa) = numeric_or_widen(&l).ok_or_else(|| EvalError::TypeMismatch {
6986 detail: format!("NUMERIC op against non-numeric {:?}", l.data_type()),
6987 })?;
6988 let (b, sb) = numeric_or_widen(&r).ok_or_else(|| EvalError::TypeMismatch {
6989 detail: format!("NUMERIC op against non-numeric {:?}", r.data_type()),
6990 })?;
6991 match op {
6992 BinOp::Add | BinOp::Sub => {
6993 let target_scale = sa.max(sb);
6994 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
6995 detail: "NUMERIC overflow on rescale".into(),
6996 })?;
6997 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
6998 detail: "NUMERIC overflow on rescale".into(),
6999 })?;
7000 let r = match op {
7001 BinOp::Add => lhs.checked_add(rhs),
7002 BinOp::Sub => lhs.checked_sub(rhs),
7003 _ => unreachable!(),
7004 }
7005 .ok_or(EvalError::TypeMismatch {
7006 detail: "NUMERIC overflow on +/-".into(),
7007 })?;
7008 Ok(Value::Numeric {
7009 scaled: r,
7010 scale: target_scale,
7011 })
7012 }
7013 BinOp::Mul => {
7014 let scaled = a.checked_mul(b).ok_or(EvalError::TypeMismatch {
7015 detail: "NUMERIC overflow on *".into(),
7016 })?;
7017 Ok(Value::Numeric {
7018 scaled,
7019 scale: sa.saturating_add(sb),
7020 })
7021 }
7022 BinOp::Div => {
7023 if b == 0 {
7024 return Err(EvalError::DivisionByZero);
7025 }
7026 let target_scale = sa.max(sb);
7030 let bump = pow10_i128(target_scale.saturating_add(sb).saturating_sub(sa));
7034 let num = a.checked_mul(bump).ok_or(EvalError::TypeMismatch {
7035 detail: "NUMERIC overflow on / scaling".into(),
7036 })?;
7037 let half = if b >= 0 { b / 2 } else { -(b / 2) };
7038 let adj = if (num >= 0) == (b >= 0) {
7039 num + half
7040 } else {
7041 num - half
7042 };
7043 Ok(Value::Numeric {
7044 scaled: adj / b,
7045 scale: target_scale,
7046 })
7047 }
7048 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
7049 let target_scale = sa.max(sb);
7050 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
7051 detail: "NUMERIC overflow on rescale".into(),
7052 })?;
7053 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
7054 detail: "NUMERIC overflow on rescale".into(),
7055 })?;
7056 Ok(Value::Bool(cmp_to_bool(op, lhs.cmp(&rhs))))
7057 }
7058 BinOp::Concat => Ok(text_concat(&l, &r)),
7059 other => Err(EvalError::TypeMismatch {
7060 detail: format!("operator {other:?} not defined for NUMERIC"),
7061 }),
7062 }
7063}
7064
7065fn numeric_or_widen(v: &Value) -> Option<(i128, u8)> {
7069 match v {
7070 Value::Numeric { scaled, scale } => Some((*scaled, *scale)),
7071 Value::Int(n) => Some((i128::from(*n), 0)),
7072 Value::SmallInt(n) => Some((i128::from(*n), 0)),
7073 Value::BigInt(n) => Some((i128::from(*n), 0)),
7074 _ => None,
7075 }
7076}
7077
7078fn rescale(scaled: i128, src: u8, dst: u8) -> Option<i128> {
7079 if src == dst {
7080 return Some(scaled);
7081 }
7082 if dst > src {
7083 scaled.checked_mul(pow10_i128(dst - src))
7084 } else {
7085 let drop = pow10_i128(src - dst);
7086 let half = drop / 2;
7087 let r = if scaled >= 0 {
7088 scaled + half
7089 } else {
7090 scaled - half
7091 };
7092 Some(r / drop)
7093 }
7094}
7095
7096const fn pow10_i128(p: u8) -> i128 {
7097 let mut acc: i128 = 1;
7098 let mut i = 0;
7099 while i < p {
7100 acc *= 10;
7101 i += 1;
7102 }
7103 acc
7104}
7105
7106const fn cmp_to_bool(op: BinOp, ord: core::cmp::Ordering) -> bool {
7107 use core::cmp::Ordering::{Equal, Greater, Less};
7108 match op {
7109 BinOp::Eq => matches!(ord, Equal),
7110 BinOp::NotEq => !matches!(ord, Equal),
7111 BinOp::Lt => matches!(ord, Less),
7112 BinOp::LtEq => matches!(ord, Less | Equal),
7113 BinOp::Gt => matches!(ord, Greater),
7114 BinOp::GtEq => matches!(ord, Greater | Equal),
7115 _ => false,
7116 }
7117}
7118
7119fn tsvector_concat(l: &[spg_storage::TsLexeme], r: &[spg_storage::TsLexeme]) -> Value {
7128 let shift = l
7129 .iter()
7130 .flat_map(|x| x.positions.iter().copied())
7131 .max()
7132 .unwrap_or(0);
7133 let mut out: Vec<spg_storage::TsLexeme> = l.to_vec();
7134 for lex in r {
7135 let shifted: Vec<u16> = lex
7136 .positions
7137 .iter()
7138 .map(|p| p.saturating_add(shift))
7139 .collect();
7140 if let Some(existing) = out.iter_mut().find(|x| x.word == lex.word) {
7141 existing.positions.extend(shifted);
7142 existing.positions.sort_unstable();
7143 existing.weight = existing.weight.max(lex.weight);
7144 } else {
7145 out.push(spg_storage::TsLexeme {
7146 word: lex.word.clone(),
7147 positions: shifted,
7148 weight: lex.weight,
7149 });
7150 }
7151 }
7152 out.sort_by(|a, b| a.word.cmp(&b.word));
7153 Value::TsVector(out)
7154}
7155
7156fn text_concat(l: &Value, r: &Value) -> Value {
7157 if let (Value::TsVector(a), Value::TsVector(b)) = (l, r) {
7158 return tsvector_concat(a, b);
7159 }
7160 match (l, r) {
7165 (Value::Null, _) | (_, Value::Null) => {
7166 if matches!(
7170 l,
7171 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
7172 ) || matches!(
7173 r,
7174 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
7175 ) {
7176 return Value::Null;
7177 }
7178 }
7179 (Value::TextArray(a), Value::TextArray(b)) => {
7180 let mut out = a.clone();
7181 out.extend(b.iter().cloned());
7182 return Value::TextArray(out);
7183 }
7184 (Value::TextArray(a), Value::Text(s)) => {
7185 let mut out = a.clone();
7186 out.push(Some(s.clone()));
7187 return Value::TextArray(out);
7188 }
7189 (Value::Text(s), Value::TextArray(b)) => {
7190 let mut out: alloc::vec::Vec<Option<alloc::string::String>> =
7191 alloc::vec::Vec::with_capacity(1 + b.len());
7192 out.push(Some(s.clone()));
7193 out.extend(b.iter().cloned());
7194 return Value::TextArray(out);
7195 }
7196 (Value::IntArray(a), Value::IntArray(b)) => {
7201 let mut out = a.clone();
7202 out.extend(b.iter().copied());
7203 return Value::IntArray(out);
7204 }
7205 (Value::IntArray(a), Value::Int(n)) => {
7206 let mut out = a.clone();
7207 out.push(Some(*n));
7208 return Value::IntArray(out);
7209 }
7210 (Value::IntArray(a), Value::SmallInt(n)) => {
7211 let mut out = a.clone();
7212 out.push(Some(i32::from(*n)));
7213 return Value::IntArray(out);
7214 }
7215 (Value::Int(n), Value::IntArray(b)) => {
7216 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
7217 out.push(Some(*n));
7218 out.extend(b.iter().copied());
7219 return Value::IntArray(out);
7220 }
7221 (Value::SmallInt(n), Value::IntArray(b)) => {
7222 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
7223 out.push(Some(i32::from(*n)));
7224 out.extend(b.iter().copied());
7225 return Value::IntArray(out);
7226 }
7227 (Value::BigIntArray(a), Value::BigIntArray(b)) => {
7228 let mut out = a.clone();
7229 out.extend(b.iter().copied());
7230 return Value::BigIntArray(out);
7231 }
7232 (Value::BigIntArray(a), Value::IntArray(b)) => {
7233 let mut out = a.clone();
7234 out.extend(b.iter().map(|o| o.map(i64::from)));
7235 return Value::BigIntArray(out);
7236 }
7237 (Value::IntArray(a), Value::BigIntArray(b)) => {
7238 let mut out: alloc::vec::Vec<Option<i64>> =
7239 a.iter().map(|o| o.map(i64::from)).collect();
7240 out.extend(b.iter().copied());
7241 return Value::BigIntArray(out);
7242 }
7243 (Value::BigIntArray(a), Value::BigInt(n)) => {
7244 let mut out = a.clone();
7245 out.push(Some(*n));
7246 return Value::BigIntArray(out);
7247 }
7248 (Value::BigIntArray(a), Value::Int(n)) => {
7249 let mut out = a.clone();
7250 out.push(Some(i64::from(*n)));
7251 return Value::BigIntArray(out);
7252 }
7253 (Value::BigIntArray(a), Value::SmallInt(n)) => {
7254 let mut out = a.clone();
7255 out.push(Some(i64::from(*n)));
7256 return Value::BigIntArray(out);
7257 }
7258 (Value::BigInt(n), Value::BigIntArray(b)) => {
7259 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
7260 out.push(Some(*n));
7261 out.extend(b.iter().copied());
7262 return Value::BigIntArray(out);
7263 }
7264 (Value::Int(n), Value::BigIntArray(b)) => {
7265 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
7266 out.push(Some(i64::from(*n)));
7267 out.extend(b.iter().copied());
7268 return Value::BigIntArray(out);
7269 }
7270 (Value::SmallInt(n), Value::BigIntArray(b)) => {
7271 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
7272 out.push(Some(i64::from(*n)));
7273 out.extend(b.iter().copied());
7274 return Value::BigIntArray(out);
7275 }
7276 (Value::Bytes(a), Value::Bytes(b)) => {
7278 let mut out = a.clone();
7279 out.extend_from_slice(b);
7280 return Value::Bytes(out);
7281 }
7282 _ => {}
7283 }
7284 let a = value_to_text(l);
7285 let b = value_to_text(r);
7286 Value::Text(a + &b)
7287}
7288
7289fn inner_product(l: Value, r: Value) -> Result<Value, EvalError> {
7292 let (a, b) = unwrap_vec_pair(l, r, "<#>")?;
7293 let mut dot: f64 = 0.0;
7294 for (x, y) in a.iter().zip(b.iter()) {
7295 dot += f64::from(*x) * f64::from(*y);
7296 }
7297 Ok(Value::Float(-dot))
7298}
7299
7300fn cosine_distance(l: Value, r: Value) -> Result<Value, EvalError> {
7303 let (a, b) = unwrap_vec_pair(l, r, "<=>")?;
7304 let mut dot: f64 = 0.0;
7305 let mut na: f64 = 0.0;
7306 let mut nb: f64 = 0.0;
7307 for (x, y) in a.iter().zip(b.iter()) {
7308 let xf = f64::from(*x);
7309 let yf = f64::from(*y);
7310 dot += xf * yf;
7311 na += xf * xf;
7312 nb += yf * yf;
7313 }
7314 let denom = sqrt_newton(na) * sqrt_newton(nb);
7315 if denom == 0.0 {
7316 return Ok(Value::Float(f64::NAN));
7317 }
7318 Ok(Value::Float(1.0 - dot / denom))
7319}
7320
7321fn unwrap_vec_pair(l: Value, r: Value, op: &str) -> Result<(Vec<f32>, Vec<f32>), EvalError> {
7322 let to_f32 = |v: Value| -> Option<Vec<f32>> {
7330 match v {
7331 Value::Vector(a) => Some(a),
7332 Value::Sq8Vector(q) => Some(spg_storage::quantize::dequantize(&q)),
7333 Value::HalfVector(h) => Some(h.to_f32_vec()),
7335 _ => None,
7336 }
7337 };
7338 let l_ty = l.data_type();
7339 let r_ty = r.data_type();
7340 match (to_f32(l), to_f32(r)) {
7341 (Some(a), Some(b)) => {
7342 if a.len() != b.len() {
7343 return Err(EvalError::TypeMismatch {
7344 detail: format!("vector dim mismatch in {op}: {} vs {}", a.len(), b.len()),
7345 });
7346 }
7347 Ok((a, b))
7348 }
7349 _ => Err(EvalError::TypeMismatch {
7350 detail: format!("{op} requires two vectors, got {l_ty:?} and {r_ty:?}"),
7351 }),
7352 }
7353}
7354
7355fn bitop(
7363 l: Value,
7364 r: Value,
7365 f: impl Fn(i64, i64) -> i64,
7366 op_name: &str,
7367) -> Result<Value, EvalError> {
7368 let widen = |v: Value| -> Value {
7369 match v {
7370 Value::SmallInt(n) => Value::Int(i32::from(n)),
7371 other => other,
7372 }
7373 };
7374 match (widen(l), widen(r)) {
7375 (Value::Int(a), Value::Int(b)) => {
7376 let result = f(i64::from(a), i64::from(b));
7377 Ok(Value::Int(result as i32))
7379 }
7380 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
7381 Ok(Value::BigInt(f(i64::from(a), b)))
7382 }
7383 (Value::BigInt(a), Value::BigInt(b)) => Ok(Value::BigInt(f(a, b))),
7384 (a, b) => Err(EvalError::TypeMismatch {
7385 detail: format!("cannot apply {op_name} to {a:?} and {b:?}"),
7386 }),
7387 }
7388}
7389
7390fn arith(
7391 l: Value,
7392 r: Value,
7393 int_op: impl Fn(i64, i64) -> Option<i64>,
7394 float_op: impl Fn(f64, f64) -> f64,
7395 op_name: &str,
7396) -> Result<Value, EvalError> {
7397 let widen = |v: Value| -> Value {
7400 match v {
7401 Value::SmallInt(n) => Value::Int(i32::from(n)),
7402 other => other,
7403 }
7404 };
7405 let l = widen(l);
7406 let r = widen(r);
7407 match (l, r) {
7408 (Value::Int(a), Value::Int(b)) => {
7409 let result = int_op(i64::from(a), i64::from(b)).ok_or(EvalError::TypeMismatch {
7410 detail: format!("integer overflow on {op_name}"),
7411 })?;
7412 if let Ok(small) = i32::try_from(result) {
7413 Ok(Value::Int(small))
7414 } else {
7415 Ok(Value::BigInt(result))
7416 }
7417 }
7418 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
7419 let result = int_op(i64::from(a), b).ok_or(EvalError::TypeMismatch {
7420 detail: format!("bigint overflow on {op_name}"),
7421 })?;
7422 Ok(Value::BigInt(result))
7423 }
7424 (Value::BigInt(a), Value::BigInt(b)) => {
7425 let result = int_op(a, b).ok_or(EvalError::TypeMismatch {
7426 detail: format!("bigint overflow on {op_name}"),
7427 })?;
7428 Ok(Value::BigInt(result))
7429 }
7430 (a, b)
7431 if a.data_type() == Some(DataType::Float) || b.data_type() == Some(DataType::Float) =>
7432 {
7433 let af = as_f64(&a)?;
7434 let bf = as_f64(&b)?;
7435 Ok(Value::Float(float_op(af, bf)))
7436 }
7437 (a, b) => Err(EvalError::TypeMismatch {
7438 detail: format!(
7439 "{op_name} applied to non-numeric: {:?} vs {:?}",
7440 a.data_type(),
7441 b.data_type()
7442 ),
7443 }),
7444 }
7445}
7446
7447#[allow(clippy::many_single_char_names)] fn l2_distance(l: Value, r: Value) -> Result<Value, EvalError> {
7453 let (a, b) = unwrap_vec_pair(l, r, "<->")?;
7458 let mut sum: f64 = 0.0;
7459 for (x, y) in a.iter().zip(b.iter()) {
7460 let d = f64::from(*x) - f64::from(*y);
7461 sum += d * d;
7462 }
7463 Ok(Value::Float(sqrt_newton(sum)))
7464}
7465
7466fn sqrt_newton(x: f64) -> f64 {
7471 if x <= 0.0 {
7472 return 0.0;
7473 }
7474 let mut g = x;
7475 for _ in 0..10 {
7478 g = 0.5 * (g + x / g);
7479 }
7480 g
7481}
7482
7483fn div_op(l: Value, r: Value) -> Result<Value, EvalError> {
7484 let any_float = matches!(l.data_type(), Some(DataType::Float))
7485 || matches!(r.data_type(), Some(DataType::Float));
7486 if any_float {
7487 let a = as_f64(&l)?;
7488 let b = as_f64(&r)?;
7489 if b == 0.0 {
7490 return Err(EvalError::DivisionByZero);
7491 }
7492 return Ok(Value::Float(a / b));
7493 }
7494 arith(
7495 l,
7496 r,
7497 |a, b| {
7498 if b == 0 { None } else { Some(a / b) }
7499 },
7500 |a, b| a / b,
7501 "/",
7502 )
7503 .map_err(|e| match e {
7504 EvalError::TypeMismatch { detail } if detail.contains('/') => EvalError::DivisionByZero,
7507 other => other,
7508 })
7509}
7510
7511fn as_f64(v: &Value) -> Result<f64, EvalError> {
7512 match v {
7513 Value::SmallInt(n) => Ok(f64::from(*n)),
7514 Value::Int(n) => Ok(f64::from(*n)),
7515 #[allow(clippy::cast_precision_loss)]
7516 Value::BigInt(n) => Ok(*n as f64),
7517 Value::Float(x) => Ok(*x),
7518 #[allow(clippy::cast_precision_loss)]
7519 Value::Numeric { scaled, scale } => {
7520 let mut div = 1.0_f64;
7521 for _ in 0..*scale {
7522 div *= 10.0;
7523 }
7524 Ok((*scaled as f64) / div)
7525 }
7526 other => Err(EvalError::TypeMismatch {
7527 detail: format!("cannot convert {:?} to FLOAT", other.data_type()),
7528 }),
7529 }
7530}
7531
7532fn compare(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
7533 let ord = match (l, r) {
7534 (Value::Int(a), Value::Int(b)) => i64::from(*a).cmp(&i64::from(*b)),
7535 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
7536 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
7537 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
7538 (a, b)
7539 if matches!(a.data_type(), Some(DataType::Float))
7540 || matches!(b.data_type(), Some(DataType::Float)) =>
7541 {
7542 let af = as_f64(a)?;
7543 let bf = as_f64(b)?;
7544 af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
7545 detail: "NaN in comparison".into(),
7546 })?
7547 }
7548 (Value::Text(a), Value::Text(b)) => a.cmp(b),
7549 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
7550 (Value::Date(a), Value::Date(b)) => a.cmp(b),
7554 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
7555 (Value::Date(a), Value::Timestamp(b)) => (i64::from(*a) * 86_400_000_000).cmp(b),
7556 (Value::Timestamp(a), Value::Date(b)) => a.cmp(&(i64::from(*b) * 86_400_000_000)),
7557 (Value::Date(a), Value::Text(b)) => {
7561 let bd = parse_date_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7562 detail: format!("cannot parse {b:?} as DATE for comparison"),
7563 })?;
7564 a.cmp(&bd)
7565 }
7566 (Value::Text(a), Value::Date(b)) => {
7567 let ad = parse_date_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7568 detail: format!("cannot parse {a:?} as DATE for comparison"),
7569 })?;
7570 ad.cmp(b)
7571 }
7572 (Value::Timestamp(a), Value::Text(b)) => {
7573 let bt = parse_timestamp_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7574 detail: format!("cannot parse {b:?} as TIMESTAMP for comparison"),
7575 })?;
7576 a.cmp(&bt)
7577 }
7578 (Value::Text(a), Value::Timestamp(b)) => {
7579 let at = parse_timestamp_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7580 detail: format!("cannot parse {a:?} as TIMESTAMP for comparison"),
7581 })?;
7582 at.cmp(b)
7583 }
7584 (Value::Uuid(a), Value::Uuid(b)) => a.cmp(b),
7586 (Value::Uuid(a), Value::Text(b)) => {
7592 let bu = spg_storage::parse_uuid_str(b).ok_or_else(|| EvalError::TypeMismatch {
7593 detail: format!("invalid input syntax for type uuid: {b:?}"),
7594 })?;
7595 a.cmp(&bu)
7596 }
7597 (Value::Text(a), Value::Uuid(b)) => {
7598 let au = spg_storage::parse_uuid_str(a).ok_or_else(|| EvalError::TypeMismatch {
7599 detail: format!("invalid input syntax for type uuid: {a:?}"),
7600 })?;
7601 au.cmp(b)
7602 }
7603 (a, b) => {
7604 return Err(EvalError::TypeMismatch {
7605 detail: format!(
7606 "comparison between {:?} and {:?}",
7607 a.data_type(),
7608 b.data_type()
7609 ),
7610 });
7611 }
7612 };
7613 let result = match op {
7614 BinOp::Eq => ord.is_eq(),
7615 BinOp::NotEq => !ord.is_eq(),
7616 BinOp::Lt => ord.is_lt(),
7617 BinOp::LtEq => ord.is_le(),
7618 BinOp::Gt => ord.is_gt(),
7619 BinOp::GtEq => ord.is_ge(),
7620 BinOp::And
7621 | BinOp::Or
7622 | BinOp::BitOr
7623 | BinOp::BitAnd
7624 | BinOp::Add
7625 | BinOp::Sub
7626 | BinOp::Mul
7627 | BinOp::Div
7628 | BinOp::L2Distance
7629 | BinOp::InnerProduct
7630 | BinOp::CosineDistance
7631 | BinOp::Concat
7632 | BinOp::JsonGet
7633 | BinOp::JsonGetText
7634 | BinOp::JsonGetPath
7635 | BinOp::JsonGetPathText
7636 | BinOp::JsonContains
7637 | BinOp::TsMatch
7638 | BinOp::IsDistinctFrom
7639 | BinOp::IsNotDistinctFrom
7640 | BinOp::InetContainedBy
7641 | BinOp::InetContainedByEq
7642 | BinOp::InetContains
7643 | BinOp::InetContainsEq
7644 | BinOp::InetOverlap => {
7645 unreachable!("compare() only called with comparison ops")
7646 }
7647 };
7648 Ok(Value::Bool(result))
7649}
7650
7651pub(crate) fn and_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7653 match (l, r) {
7654 (Value::Bool(false), _) | (_, Value::Bool(false)) => Ok(Value::Bool(false)),
7655 (Value::Bool(true), Value::Bool(true)) => Ok(Value::Bool(true)),
7656 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7657 (a, b) => Err(EvalError::TypeMismatch {
7658 detail: format!(
7659 "AND on non-boolean: {:?} and {:?}",
7660 a.data_type(),
7661 b.data_type()
7662 ),
7663 }),
7664 }
7665}
7666
7667fn or_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7668 match (l, r) {
7669 (Value::Bool(true), _) | (_, Value::Bool(true)) => Ok(Value::Bool(true)),
7670 (Value::Bool(false), Value::Bool(false)) => Ok(Value::Bool(false)),
7671 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7672 (a, b) => Err(EvalError::TypeMismatch {
7673 detail: format!(
7674 "OR on non-boolean: {:?} and {:?}",
7675 a.data_type(),
7676 b.data_type()
7677 ),
7678 }),
7679 }
7680}
7681
7682#[cfg(test)]
7683mod tests {
7684 use super::*;
7685 use alloc::vec;
7686 use spg_storage::{ColumnSchema, Row};
7687
7688 fn col(name: &str, ty: DataType) -> ColumnSchema {
7689 ColumnSchema::new(name, ty, true)
7690 }
7691
7692 fn ctx<'a>(cols: &'a [ColumnSchema], alias: Option<&'a str>) -> EvalContext<'a> {
7693 EvalContext::new(cols, alias)
7694 }
7695
7696 fn lit(n: i64) -> Expr {
7697 Expr::Literal(Literal::Integer(n))
7698 }
7699
7700 fn null() -> Expr {
7701 Expr::Literal(Literal::Null)
7702 }
7703
7704 fn col_ref(name: &str) -> Expr {
7705 Expr::Column(ColumnName {
7706 qualifier: None,
7707 name: name.into(),
7708 })
7709 }
7710
7711 #[test]
7712 fn literal_evaluates_to_value() {
7713 let r = Row::new(vec![]);
7714 let cs: [ColumnSchema; 0] = [];
7715 let c = ctx(&cs, None);
7716 assert_eq!(eval_expr(&lit(42), &r, &c).unwrap(), Value::Int(42));
7717 assert_eq!(
7718 eval_expr(&Expr::Literal(Literal::Float(1.5)), &r, &c).unwrap(),
7719 Value::Float(1.5)
7720 );
7721 assert_eq!(eval_expr(&null(), &r, &c).unwrap(), Value::Null);
7722 }
7723
7724 #[test]
7725 fn column_lookup_unqualified() {
7726 let cs = vec![col("a", DataType::Int), col("b", DataType::Text)];
7727 let r = Row::new(vec![Value::Int(7), Value::Text("hi".into())]);
7728 let c = ctx(&cs, None);
7729 assert_eq!(eval_expr(&col_ref("a"), &r, &c).unwrap(), Value::Int(7));
7730 assert_eq!(
7731 eval_expr(&col_ref("b"), &r, &c).unwrap(),
7732 Value::Text("hi".into())
7733 );
7734 }
7735
7736 #[test]
7737 fn column_not_found_errors() {
7738 let cs = vec![col("a", DataType::Int)];
7739 let r = Row::new(vec![Value::Int(0)]);
7740 let c = ctx(&cs, None);
7741 let err = eval_expr(&col_ref("ghost"), &r, &c).unwrap_err();
7742 assert!(matches!(err, EvalError::ColumnNotFound { ref name } if name == "ghost"));
7743 }
7744
7745 #[test]
7746 fn qualified_column_matches_alias() {
7747 let cs = vec![col("a", DataType::Int)];
7748 let r = Row::new(vec![Value::Int(5)]);
7749 let c = ctx(&cs, Some("u"));
7750 let qualified = Expr::Column(ColumnName {
7751 qualifier: Some("u".into()),
7752 name: "a".into(),
7753 });
7754 assert_eq!(eval_expr(&qualified, &r, &c).unwrap(), Value::Int(5));
7755 }
7756
7757 #[test]
7758 fn qualified_column_unknown_alias_errors() {
7759 let cs = vec![col("a", DataType::Int)];
7760 let r = Row::new(vec![Value::Int(5)]);
7761 let c = ctx(&cs, Some("u"));
7762 let wrong = Expr::Column(ColumnName {
7763 qualifier: Some("x".into()),
7764 name: "a".into(),
7765 });
7766 assert!(matches!(
7767 eval_expr(&wrong, &r, &c).unwrap_err(),
7768 EvalError::UnknownQualifier { .. }
7769 ));
7770 }
7771
7772 #[test]
7773 fn arithmetic_with_widening() {
7774 let r = Row::new(vec![]);
7775 let cs: [ColumnSchema; 0] = [];
7776 let c = ctx(&cs, None);
7777 let e = Expr::Binary {
7778 lhs: alloc::boxed::Box::new(lit(2)),
7779 op: BinOp::Add,
7780 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::Float(0.5))),
7781 };
7782 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Float(2.5));
7783 }
7784
7785 #[test]
7786 fn division_by_zero_errors() {
7787 let r = Row::new(vec![]);
7788 let cs: [ColumnSchema; 0] = [];
7789 let c = ctx(&cs, None);
7790 let e = Expr::Binary {
7791 lhs: alloc::boxed::Box::new(lit(1)),
7792 op: BinOp::Div,
7793 rhs: alloc::boxed::Box::new(lit(0)),
7794 };
7795 assert_eq!(
7796 eval_expr(&e, &r, &c).unwrap_err(),
7797 EvalError::DivisionByZero
7798 );
7799 }
7800
7801 #[test]
7802 fn comparison_returns_bool() {
7803 let r = Row::new(vec![]);
7804 let cs: [ColumnSchema; 0] = [];
7805 let c = ctx(&cs, None);
7806 let e = Expr::Binary {
7807 lhs: alloc::boxed::Box::new(lit(1)),
7808 op: BinOp::Lt,
7809 rhs: alloc::boxed::Box::new(lit(2)),
7810 };
7811 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7812 }
7813
7814 #[test]
7815 fn null_propagates_through_arithmetic() {
7816 let r = Row::new(vec![]);
7817 let cs: [ColumnSchema; 0] = [];
7818 let c = ctx(&cs, None);
7819 let e = Expr::Binary {
7820 lhs: alloc::boxed::Box::new(lit(1)),
7821 op: BinOp::Add,
7822 rhs: alloc::boxed::Box::new(null()),
7823 };
7824 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7825 }
7826
7827 #[test]
7828 fn and_three_valued_logic() {
7829 let r = Row::new(vec![]);
7830 let cs: [ColumnSchema; 0] = [];
7831 let c = ctx(&cs, None);
7832 let tt = |a: bool, b_null: bool| Expr::Binary {
7833 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7834 op: BinOp::And,
7835 rhs: alloc::boxed::Box::new(if b_null {
7836 null()
7837 } else {
7838 Expr::Literal(Literal::Bool(true))
7839 }),
7840 };
7841 assert_eq!(
7843 eval_expr(&tt(false, true), &r, &c).unwrap(),
7844 Value::Bool(false)
7845 );
7846 assert_eq!(eval_expr(&tt(true, true), &r, &c).unwrap(), Value::Null);
7848 assert_eq!(
7850 eval_expr(&tt(true, false), &r, &c).unwrap(),
7851 Value::Bool(true)
7852 );
7853 }
7854
7855 #[test]
7856 fn or_three_valued_logic() {
7857 let r = Row::new(vec![]);
7858 let cs: [ColumnSchema; 0] = [];
7859 let c = ctx(&cs, None);
7860 let or_with_null = |a: bool| Expr::Binary {
7861 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7862 op: BinOp::Or,
7863 rhs: alloc::boxed::Box::new(null()),
7864 };
7865 assert_eq!(
7867 eval_expr(&or_with_null(true), &r, &c).unwrap(),
7868 Value::Bool(true)
7869 );
7870 assert_eq!(
7872 eval_expr(&or_with_null(false), &r, &c).unwrap(),
7873 Value::Null
7874 );
7875 }
7876
7877 #[test]
7878 fn not_on_null_is_null() {
7879 let r = Row::new(vec![]);
7880 let cs: [ColumnSchema; 0] = [];
7881 let c = ctx(&cs, None);
7882 let e = Expr::Unary {
7883 op: UnOp::Not,
7884 expr: alloc::boxed::Box::new(null()),
7885 };
7886 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7887 }
7888
7889 #[test]
7890 fn text_comparison_lexicographic() {
7891 let r = Row::new(vec![]);
7892 let cs: [ColumnSchema; 0] = [];
7893 let c = ctx(&cs, None);
7894 let e = Expr::Binary {
7895 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("apple".into()))),
7896 op: BinOp::Lt,
7897 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("banana".into()))),
7898 };
7899 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7900 }
7901
7902 #[test]
7903 fn interval_format_basics() {
7904 assert_eq!(format_interval(0, 0), "0");
7905 assert_eq!(format_interval(0, 86_400_000_000), "1 day");
7906 assert_eq!(format_interval(0, -86_400_000_000), "-1 days");
7907 assert_eq!(format_interval(0, 3_600_000_000), "01:00:00");
7908 assert_eq!(
7909 format_interval(0, 86_400_000_000 + 9_000_000),
7910 "1 day 00:00:09"
7911 );
7912 assert_eq!(format_interval(14, 0), "1 year 2 mons");
7913 assert_eq!(format_interval(-1, 0), "-1 mons");
7914 }
7915
7916 #[test]
7917 fn interval_add_to_timestamp_micros_part() {
7918 let ts = i64::from(days_from_civil(2024, 1, 1)) * 86_400_000_000;
7920 let r = add_interval_to_micros(ts, 0, 3_600_000_000).unwrap();
7921 let expected = ts + 3_600_000_000;
7922 assert_eq!(r, expected);
7923 }
7924
7925 #[test]
7926 fn interval_clamp_month_end() {
7927 let d = days_from_civil(2024, 1, 31);
7929 let shifted = shift_date_by_months(d, 1).unwrap();
7930 let (y, m, day) = civil_from_days(shifted);
7931 assert_eq!((y, m, day), (2024, 2, 29));
7932 let d = days_from_civil(2023, 1, 31);
7934 let shifted = shift_date_by_months(d, 1).unwrap();
7935 let (y, m, day) = civil_from_days(shifted);
7936 assert_eq!((y, m, day), (2023, 2, 28));
7937 let d = days_from_civil(2024, 3, 31);
7939 let shifted = shift_date_by_months(d, -1).unwrap();
7940 let (y, m, day) = civil_from_days(shifted);
7941 assert_eq!((y, m, day), (2024, 2, 29));
7942 }
7943
7944 #[test]
7945 fn interval_date_plus_pure_days_stays_date() {
7946 let d = days_from_civil(2024, 6, 1);
7948 let lhs = Value::Date(d);
7949 let rhs = Value::Interval {
7950 months: 0,
7951 micros: 7 * 86_400_000_000,
7952 };
7953 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7954 .unwrap()
7955 .unwrap();
7956 let expected = days_from_civil(2024, 6, 8);
7957 assert_eq!(v, Value::Date(expected));
7958 }
7959
7960 #[test]
7961 fn interval_date_plus_sub_day_lifts_to_timestamp() {
7962 let d = days_from_civil(2024, 6, 1);
7964 let lhs = Value::Date(d);
7965 let rhs = Value::Interval {
7966 months: 0,
7967 micros: 3_600_000_000,
7968 };
7969 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7970 .unwrap()
7971 .unwrap();
7972 let expected = i64::from(d) * 86_400_000_000 + 3_600_000_000;
7973 assert_eq!(v, Value::Timestamp(expected));
7974 }
7975}