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::WindowFunction { .. } => Err(EvalError::TypeMismatch {
271 detail: "window function reached row eval — engine rewrite bug".into(),
272 }),
273 Expr::Array(items) => {
279 let mut materialised: Vec<Value> = Vec::with_capacity(items.len());
280 for elem in items {
281 materialised.push(eval_expr(elem, row, ctx)?);
282 }
283 let mut has_text = false;
284 let mut has_bigint = false;
285 let mut has_int = false;
286 for v in &materialised {
287 match v {
288 Value::Null => {}
289 Value::Int(_) | Value::SmallInt(_) => has_int = true,
290 Value::BigInt(_) => has_bigint = true,
291 Value::Text(_) | Value::Json(_) => has_text = true,
292 _ => has_text = true,
293 }
294 }
295 if has_text || (!has_int && !has_bigint) {
296 let out: Vec<Option<String>> = materialised
297 .into_iter()
298 .map(|v| match v {
299 Value::Null => None,
300 Value::Text(s) | Value::Json(s) => Some(s),
301 other => Some(value_to_text_for_array(&other)),
302 })
303 .collect();
304 return Ok(Value::TextArray(out));
305 }
306 if has_bigint {
307 let out: Vec<Option<i64>> = materialised
308 .into_iter()
309 .map(|v| match v {
310 Value::Null => None,
311 Value::Int(n) => Some(i64::from(n)),
312 Value::SmallInt(n) => Some(i64::from(n)),
313 Value::BigInt(n) => Some(n),
314 _ => unreachable!(),
315 })
316 .collect();
317 return Ok(Value::BigIntArray(out));
318 }
319 let out: Vec<Option<i32>> = materialised
320 .into_iter()
321 .map(|v| match v {
322 Value::Null => None,
323 Value::Int(n) => Some(n),
324 Value::SmallInt(n) => Some(i32::from(n)),
325 _ => unreachable!(),
326 })
327 .collect();
328 Ok(Value::IntArray(out))
329 }
330 Expr::ArraySubscript { target, index } => {
333 let target_v = eval_expr(target, row, ctx)?;
334 let idx_v = eval_expr(index, row, ctx)?;
335 if matches!(target_v, Value::Null) || matches!(idx_v, Value::Null) {
336 return Ok(Value::Null);
337 }
338 let i: i64 = match idx_v {
339 Value::Int(n) => i64::from(n),
340 Value::BigInt(n) => n,
341 Value::SmallInt(n) => i64::from(n),
342 other => {
343 return Err(EvalError::TypeMismatch {
344 detail: format!(
345 "array subscript must be integer, got {:?}",
346 other.data_type()
347 ),
348 });
349 }
350 };
351 if i < 1 {
352 return Ok(Value::Null);
353 }
354 let pos = (i - 1) as usize;
355 match target_v {
356 Value::TextArray(items) => match items.get(pos) {
357 Some(Some(s)) => Ok(Value::Text(s.clone())),
358 Some(None) | None => Ok(Value::Null),
359 },
360 Value::IntArray(items) => match items.get(pos) {
361 Some(Some(n)) => Ok(Value::Int(*n)),
362 Some(None) | None => Ok(Value::Null),
363 },
364 Value::BigIntArray(items) => match items.get(pos) {
365 Some(Some(n)) => Ok(Value::BigInt(*n)),
366 Some(None) | None => Ok(Value::Null),
367 },
368 other => Err(EvalError::TypeMismatch {
369 detail: format!(
370 "subscript target must be an array, got {:?}",
371 other.data_type()
372 ),
373 }),
374 }
375 }
376 Expr::AnyAll {
382 expr,
383 op,
384 array,
385 is_any,
386 } => {
387 let lhs = eval_expr(expr, row, ctx)?;
388 let arr = eval_expr(array, row, ctx)?;
389 if matches!(arr, Value::Null) {
390 return Ok(Value::Null);
391 }
392 let elems: Vec<Option<Value>> = match arr {
393 Value::TextArray(items) => items.into_iter().map(|o| o.map(Value::Text)).collect(),
394 Value::IntArray(items) => items.into_iter().map(|o| o.map(Value::Int)).collect(),
395 Value::BigIntArray(items) => {
396 items.into_iter().map(|o| o.map(Value::BigInt)).collect()
397 }
398 other => {
399 return Err(EvalError::TypeMismatch {
400 detail: format!(
401 "ANY/ALL right-hand side must be an array, got {:?}",
402 other.data_type()
403 ),
404 });
405 }
406 };
407 let mut saw_null = matches!(lhs, Value::Null);
408 let mut saw_match = false;
409 let mut saw_mismatch = false;
410 for elem in elems {
411 let elem_v = match elem {
412 Some(v) => v,
413 None => {
414 saw_null = true;
415 continue;
416 }
417 };
418 if matches!(lhs, Value::Null) {
419 saw_null = true;
420 continue;
421 }
422 match apply_binary(*op, lhs.clone(), elem_v) {
423 Ok(Value::Bool(true)) => saw_match = true,
424 Ok(Value::Bool(false)) => saw_mismatch = true,
425 Ok(Value::Null) => saw_null = true,
426 Ok(other) => {
427 return Err(EvalError::TypeMismatch {
428 detail: format!(
429 "ANY/ALL comparison didn't return Bool: {:?}",
430 other.data_type()
431 ),
432 });
433 }
434 Err(e) => return Err(e),
435 }
436 }
437 let result = if *is_any {
438 if saw_match {
439 Value::Bool(true)
440 } else if saw_null {
441 Value::Null
442 } else {
443 Value::Bool(false)
444 }
445 } else if saw_mismatch {
446 Value::Bool(false)
447 } else if saw_null {
448 Value::Null
449 } else {
450 Value::Bool(true)
451 };
452 Ok(result)
453 }
454 Expr::Case {
460 operand,
461 branches,
462 else_branch,
463 } => {
464 let operand_value = match operand {
465 Some(o) => Some(eval_expr(o, row, ctx)?),
466 None => None,
467 };
468 for (when_expr, then_expr) in branches {
469 let when_value = eval_expr(when_expr, row, ctx)?;
470 let matched = match &operand_value {
471 None => matches!(when_value, Value::Bool(true)),
472 Some(op_v) => matches!(
473 apply_binary(spg_sql::ast::BinOp::Eq, op_v.clone(), when_value)?,
474 Value::Bool(true)
475 ),
476 };
477 if matched {
478 return eval_expr(then_expr, row, ctx);
479 }
480 }
481 match else_branch {
482 Some(e) => eval_expr(e, row, ctx),
483 None => Ok(Value::Null),
484 }
485 }
486 }
487}
488
489fn value_to_text_for_array(v: &Value) -> String {
496 match v {
497 Value::Text(s) | Value::Json(s) => s.clone(),
498 Value::Int(n) => n.to_string(),
499 Value::BigInt(n) => n.to_string(),
500 Value::SmallInt(n) => n.to_string(),
501 Value::Bool(b) => {
502 if *b {
503 "true".into()
504 } else {
505 "false".into()
506 }
507 }
508 Value::Float(x) => format!("{x}"),
509 Value::Date(d) => format_date(*d),
510 Value::Timestamp(t) => format_timestamp(*t),
511 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
512 _ => format!("{v:?}"),
513 }
514}
515
516fn extract_field(field: spg_sql::ast::ExtractField, v: &Value) -> Result<Value, EvalError> {
520 use spg_sql::ast::ExtractField as F;
521 if matches!(v, Value::Null) {
522 return Ok(Value::Null);
523 }
524 if let Value::Interval { months, micros } = *v {
528 let years = months / 12;
529 let mons = months % 12;
530 let secs_total = micros / 1_000_000;
531 let frac = micros % 1_000_000;
532 let result = match field {
533 F::Year => i64::from(years),
534 F::Month => i64::from(mons),
535 F::Day => micros / 86_400_000_000,
536 F::Hour => (secs_total / 3600) % 24,
537 F::Minute => (secs_total / 60) % 60,
538 F::Second => secs_total % 60,
539 F::Microsecond => (secs_total % 60) * 1_000_000 + frac,
540 F::Epoch => i64::from(months) * 30 * 86_400 + secs_total,
543 };
544 return Ok(Value::BigInt(result));
545 }
546 let (days, day_micros) = match *v {
547 Value::Date(d) => (d, 0_i64),
548 Value::Timestamp(t) => {
549 let days = t.div_euclid(86_400_000_000);
550 let day_micros = t.rem_euclid(86_400_000_000);
551 (i32::try_from(days).unwrap_or(i32::MAX), day_micros)
552 }
553 _ => {
554 return Err(EvalError::TypeMismatch {
555 detail: format!(
556 "EXTRACT requires DATE / TIMESTAMP / INTERVAL, got {:?}",
557 v.data_type()
558 ),
559 });
560 }
561 };
562 let (y, m, d) = civil_components(days);
563 let secs = day_micros / 1_000_000;
564 let hh = secs / 3600;
565 let mm = (secs / 60) % 60;
566 let ss = secs % 60;
567 let frac = day_micros % 1_000_000;
568 let result = match field {
569 F::Year => i64::from(y),
570 F::Month => i64::from(m),
571 F::Day => i64::from(d),
572 F::Hour => hh,
573 F::Minute => mm,
574 F::Second => ss,
575 F::Microsecond => ss * 1_000_000 + frac,
576 F::Epoch => i64::from(days) * 86_400 + secs,
579 };
580 Ok(Value::BigInt(result))
581}
582
583fn civil_components(days: i32) -> (i32, u32, u32) {
586 civil_from_days(days)
587}
588
589fn like_match(text: &str, pattern: &str) -> bool {
594 let text: Vec<char> = text.chars().collect();
595 let pat: Vec<char> = pattern.chars().collect();
596 like_match_inner(&text, 0, &pat, 0)
597}
598
599fn like_match_inner(text: &[char], mut ti: usize, pat: &[char], mut pi: usize) -> bool {
600 while pi < pat.len() {
601 match pat[pi] {
602 '%' => {
603 while pi < pat.len() && pat[pi] == '%' {
605 pi += 1;
606 }
607 if pi == pat.len() {
608 return true;
609 }
610 for k in ti..=text.len() {
611 if like_match_inner(text, k, pat, pi) {
612 return true;
613 }
614 }
615 return false;
616 }
617 '_' => {
618 if ti >= text.len() {
619 return false;
620 }
621 ti += 1;
622 pi += 1;
623 }
624 '\\' if pi + 1 < pat.len() => {
625 let want = pat[pi + 1];
626 if ti >= text.len() || text[ti] != want {
627 return false;
628 }
629 ti += 1;
630 pi += 2;
631 }
632 c => {
633 if ti >= text.len() || text[ti] != c {
634 return false;
635 }
636 ti += 1;
637 pi += 1;
638 }
639 }
640 }
641 ti == text.len()
642}
643
644fn apply_function(name: &str, args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
647 match name.to_ascii_lowercase().as_str() {
648 "nextval" => {
650 if args.len() != 1 {
651 return Err(EvalError::TypeMismatch {
652 detail: format!("nextval() takes 1 arg, got {}", args.len()),
653 });
654 }
655 let seq_name = match &args[0] {
656 Value::Text(s) => s.clone(),
657 Value::Null => return Ok(Value::Null),
658 other => {
659 return Err(EvalError::TypeMismatch {
660 detail: format!(
661 "nextval() argument must be TEXT, got {:?}",
662 other.data_type()
663 ),
664 });
665 }
666 };
667 let resolver = ctx
668 .sequence_resolver
669 .ok_or_else(|| EvalError::TypeMismatch {
670 detail: "nextval() requires a sequence resolver (read-only context)".into(),
671 })?;
672 let v = resolver(SequenceOp::Next(seq_name))?;
673 Ok(Value::BigInt(v))
674 }
675 "currval" => {
676 if args.len() != 1 {
677 return Err(EvalError::TypeMismatch {
678 detail: format!("currval() takes 1 arg, got {}", args.len()),
679 });
680 }
681 let seq_name = match &args[0] {
682 Value::Text(s) => s.clone(),
683 Value::Null => return Ok(Value::Null),
684 other => {
685 return Err(EvalError::TypeMismatch {
686 detail: format!(
687 "currval() argument must be TEXT, got {:?}",
688 other.data_type()
689 ),
690 });
691 }
692 };
693 let resolver = ctx
694 .sequence_resolver
695 .ok_or_else(|| EvalError::TypeMismatch {
696 detail: "currval() requires a sequence resolver (read-only context)".into(),
697 })?;
698 let v = resolver(SequenceOp::Curr(seq_name))?;
699 Ok(Value::BigInt(v))
700 }
701 "setval" => {
702 if args.len() != 2 && args.len() != 3 {
703 return Err(EvalError::TypeMismatch {
704 detail: format!("setval() takes 2 or 3 args, 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 "setval() name argument must be TEXT, got {:?}",
714 other.data_type()
715 ),
716 });
717 }
718 };
719 let value = match &args[1] {
720 Value::SmallInt(n) => i64::from(*n),
721 Value::Int(n) => i64::from(*n),
722 Value::BigInt(n) => *n,
723 Value::Null => return Ok(Value::Null),
724 other => {
725 return Err(EvalError::TypeMismatch {
726 detail: format!(
727 "setval() value argument must be integer, got {:?}",
728 other.data_type()
729 ),
730 });
731 }
732 };
733 let is_called = if args.len() == 3 {
734 match &args[2] {
735 Value::Bool(b) => *b,
736 Value::Null => return Ok(Value::Null),
737 other => {
738 return Err(EvalError::TypeMismatch {
739 detail: format!(
740 "setval() is_called argument must be BOOL, got {:?}",
741 other.data_type()
742 ),
743 });
744 }
745 }
746 } else {
747 true
748 };
749 let resolver = ctx
750 .sequence_resolver
751 .ok_or_else(|| EvalError::TypeMismatch {
752 detail: "setval() requires a sequence resolver (read-only context)".into(),
753 })?;
754 let v = resolver(SequenceOp::Set {
755 name: seq_name,
756 value,
757 is_called,
758 })?;
759 Ok(Value::BigInt(v))
760 }
761 "length" | "char_length" | "character_length" => {
765 if args.len() != 1 {
766 return Err(EvalError::TypeMismatch {
767 detail: format!("length() takes 1 arg, got {}", args.len()),
768 });
769 }
770 match &args[0] {
771 Value::Null => Ok(Value::Null),
772 Value::Text(s) => {
773 let n = i32::try_from(s.chars().count()).unwrap_or(i32::MAX);
774 Ok(Value::Int(n))
775 }
776 Value::Bytes(b) => {
781 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
782 Ok(Value::Int(n))
783 }
784 other => Err(EvalError::TypeMismatch {
785 detail: format!("length() needs text or bytea, got {:?}", other.data_type()),
786 }),
787 }
788 }
789 "octet_length" => {
793 if args.len() != 1 {
794 return Err(EvalError::TypeMismatch {
795 detail: format!("octet_length() takes 1 arg, got {}", args.len()),
796 });
797 }
798 match &args[0] {
799 Value::Null => Ok(Value::Null),
800 Value::Text(s) => {
801 let n = i32::try_from(s.len()).unwrap_or(i32::MAX);
802 Ok(Value::Int(n))
803 }
804 Value::Bytes(b) => {
805 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
806 Ok(Value::Int(n))
807 }
808 other => Err(EvalError::TypeMismatch {
809 detail: format!(
810 "octet_length() needs text or bytea, got {:?}",
811 other.data_type()
812 ),
813 }),
814 }
815 }
816 "array_length" => {
823 if args.len() != 2 {
824 return Err(EvalError::TypeMismatch {
825 detail: format!("array_length() takes 2 args, got {}", args.len()),
826 });
827 }
828 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
829 return Ok(Value::Null);
830 }
831 let len = match &args[0] {
832 Value::TextArray(items) => items.len(),
833 Value::IntArray(items) => items.len(),
834 Value::BigIntArray(items) => items.len(),
835 _ => {
836 return Err(EvalError::TypeMismatch {
837 detail: format!(
838 "array_length() first arg must be an array, got {:?}",
839 args[0].data_type()
840 ),
841 });
842 }
843 };
844 let dim: i64 = match args[1] {
845 Value::Int(n) => i64::from(n),
846 Value::BigInt(n) => n,
847 Value::SmallInt(n) => i64::from(n),
848 _ => {
849 return Err(EvalError::TypeMismatch {
850 detail: format!(
851 "array_length() second arg must be integer, got {:?}",
852 args[1].data_type()
853 ),
854 });
855 }
856 };
857 if dim != 1 {
858 return Ok(Value::Null);
859 }
860 let n = i32::try_from(len).unwrap_or(i32::MAX);
861 Ok(Value::Int(n))
862 }
863 "array_position" => {
868 if args.len() != 2 {
869 return Err(EvalError::TypeMismatch {
870 detail: format!("array_position() takes 2 args, got {}", args.len()),
871 });
872 }
873 if matches!(args[0], Value::Null) {
874 return Ok(Value::Null);
875 }
876 if matches!(args[1], Value::Null) {
877 return Ok(Value::Null);
878 }
879 match (&args[0], &args[1]) {
880 (Value::TextArray(items), Value::Text(needle)) => {
881 for (idx, item) in items.iter().enumerate() {
882 if let Some(s) = item
883 && s == needle
884 {
885 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
886 }
887 }
888 Ok(Value::Null)
889 }
890 (Value::IntArray(items), needle_v)
891 if matches!(
892 needle_v,
893 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
894 ) =>
895 {
896 let needle: i64 = match *needle_v {
897 Value::Int(n) => i64::from(n),
898 Value::SmallInt(n) => i64::from(n),
899 Value::BigInt(n) => n,
900 _ => unreachable!(),
901 };
902 for (idx, item) in items.iter().enumerate() {
903 if let Some(n) = item
904 && i64::from(*n) == needle
905 {
906 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
907 }
908 }
909 Ok(Value::Null)
910 }
911 (Value::BigIntArray(items), needle_v)
912 if matches!(
913 needle_v,
914 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
915 ) =>
916 {
917 let needle: i64 = match *needle_v {
918 Value::Int(n) => i64::from(n),
919 Value::SmallInt(n) => i64::from(n),
920 Value::BigInt(n) => n,
921 _ => unreachable!(),
922 };
923 for (idx, item) in items.iter().enumerate() {
924 if let Some(n) = item
925 && *n == needle
926 {
927 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
928 }
929 }
930 Ok(Value::Null)
931 }
932 (
933 arr @ (Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_)),
934 other,
935 ) => Err(EvalError::TypeMismatch {
936 detail: format!(
937 "array_position() needle type {:?} doesn't match array {:?}",
938 other.data_type(),
939 arr.data_type()
940 ),
941 }),
942 (other, _) => Err(EvalError::TypeMismatch {
943 detail: format!(
944 "array_position() first arg must be an array, got {:?}",
945 other.data_type()
946 ),
947 }),
948 }
949 }
950 "substring" | "substr" => {
958 if !matches!(args.len(), 2 | 3) {
959 return Err(EvalError::TypeMismatch {
960 detail: format!("substring() takes 2 or 3 args, got {}", args.len()),
961 });
962 }
963 if args.iter().any(|a| matches!(a, Value::Null)) {
964 return Ok(Value::Null);
965 }
966 let start: i64 = match args[1] {
967 Value::Int(n) => i64::from(n),
968 Value::BigInt(n) => n,
969 Value::SmallInt(n) => i64::from(n),
970 _ => {
971 return Err(EvalError::TypeMismatch {
972 detail: format!(
973 "substring() start must be integer, got {:?}",
974 args[1].data_type()
975 ),
976 });
977 }
978 };
979 let length: Option<i64> = if args.len() == 3 {
980 match args[2] {
981 Value::Int(n) => Some(i64::from(n)),
982 Value::BigInt(n) => Some(n),
983 Value::SmallInt(n) => Some(i64::from(n)),
984 _ => {
985 return Err(EvalError::TypeMismatch {
986 detail: format!(
987 "substring() length must be integer, got {:?}",
988 args[2].data_type()
989 ),
990 });
991 }
992 }
993 } else {
994 None
995 };
996 let (effective_start, effective_length): (i64, Option<i64>) = match length {
999 Some(len) => {
1000 let end = start.saturating_add(len);
1001 if end <= 1 || len < 0 {
1002 return Ok(match &args[0] {
1003 Value::Text(_) => Value::Text(String::new()),
1004 Value::Bytes(_) => Value::Bytes(Vec::new()),
1005 other => {
1006 return Err(EvalError::TypeMismatch {
1007 detail: format!(
1008 "substring() needs text or bytea, got {:?}",
1009 other.data_type()
1010 ),
1011 });
1012 }
1013 });
1014 }
1015 let eff_start = start.max(1);
1016 let eff_len = end - eff_start;
1017 (eff_start, Some(eff_len.max(0)))
1018 }
1019 None => (start.max(1), None),
1020 };
1021 match &args[0] {
1022 Value::Text(s) => {
1023 let chars: Vec<char> = s.chars().collect();
1025 let skip = (effective_start - 1) as usize;
1026 if skip >= chars.len() {
1027 return Ok(Value::Text(String::new()));
1028 }
1029 let take = match effective_length {
1030 Some(n) => (n as usize).min(chars.len() - skip),
1031 None => chars.len() - skip,
1032 };
1033 Ok(Value::Text(chars[skip..skip + take].iter().collect()))
1034 }
1035 Value::Bytes(b) => {
1036 let skip = (effective_start - 1) as usize;
1037 if skip >= b.len() {
1038 return Ok(Value::Bytes(Vec::new()));
1039 }
1040 let take = match effective_length {
1041 Some(n) => (n as usize).min(b.len() - skip),
1042 None => b.len() - skip,
1043 };
1044 Ok(Value::Bytes(b[skip..skip + take].to_vec()))
1045 }
1046 other => Err(EvalError::TypeMismatch {
1047 detail: format!(
1048 "substring() needs text or bytea, got {:?}",
1049 other.data_type()
1050 ),
1051 }),
1052 }
1053 }
1054 "position" => {
1062 if args.len() != 2 {
1063 return Err(EvalError::TypeMismatch {
1064 detail: format!("position() takes 2 args, got {}", args.len()),
1065 });
1066 }
1067 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
1068 return Ok(Value::Null);
1069 }
1070 match (&args[0], &args[1]) {
1071 (Value::Text(needle), Value::Text(haystack)) => {
1072 if needle.is_empty() {
1073 return Ok(Value::Int(1));
1074 }
1075 let h_chars: Vec<char> = haystack.chars().collect();
1077 let n_chars: Vec<char> = needle.chars().collect();
1078 if n_chars.len() > h_chars.len() {
1079 return Ok(Value::Int(0));
1080 }
1081 for i in 0..=h_chars.len() - n_chars.len() {
1082 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1083 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1084 }
1085 }
1086 Ok(Value::Int(0))
1087 }
1088 (Value::Bytes(needle), Value::Bytes(haystack)) => {
1089 if needle.is_empty() {
1090 return Ok(Value::Int(1));
1091 }
1092 if needle.len() > haystack.len() {
1093 return Ok(Value::Int(0));
1094 }
1095 for i in 0..=haystack.len() - needle.len() {
1096 if &haystack[i..i + needle.len()] == needle.as_slice() {
1097 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1098 }
1099 }
1100 Ok(Value::Int(0))
1101 }
1102 (a, b) => Err(EvalError::TypeMismatch {
1103 detail: format!(
1104 "position() operands must both be text or both bytea, got {:?} and {:?}",
1105 a.data_type(),
1106 b.data_type()
1107 ),
1108 }),
1109 }
1110 }
1111 "upper" => {
1112 if args.len() != 1 {
1113 return Err(EvalError::TypeMismatch {
1114 detail: format!("upper() takes 1 arg, got {}", args.len()),
1115 });
1116 }
1117 match &args[0] {
1118 Value::Null => Ok(Value::Null),
1119 Value::Text(s) => Ok(Value::Text(s.to_uppercase())),
1120 other => Err(EvalError::TypeMismatch {
1121 detail: format!("upper() needs text, got {:?}", other.data_type()),
1122 }),
1123 }
1124 }
1125 "lower" => {
1126 if args.len() != 1 {
1127 return Err(EvalError::TypeMismatch {
1128 detail: format!("lower() takes 1 arg, got {}", args.len()),
1129 });
1130 }
1131 match &args[0] {
1132 Value::Null => Ok(Value::Null),
1133 Value::Text(s) => Ok(Value::Text(s.to_lowercase())),
1134 other => Err(EvalError::TypeMismatch {
1135 detail: format!("lower() needs text, got {:?}", other.data_type()),
1136 }),
1137 }
1138 }
1139 "abs" => {
1140 if args.len() != 1 {
1141 return Err(EvalError::TypeMismatch {
1142 detail: format!("abs() takes 1 arg, got {}", args.len()),
1143 });
1144 }
1145 match &args[0] {
1146 Value::Null => Ok(Value::Null),
1147 Value::Int(n) => Ok(Value::Int(n.wrapping_abs())),
1148 Value::BigInt(n) => Ok(Value::BigInt(n.wrapping_abs())),
1149 Value::Float(x) => Ok(Value::Float(x.abs())),
1150 other => Err(EvalError::TypeMismatch {
1151 detail: format!("abs() needs numeric, got {:?}", other.data_type()),
1152 }),
1153 }
1154 }
1155 "coalesce" => {
1156 for a in args {
1157 if !matches!(a, Value::Null) {
1158 return Ok(a.clone());
1159 }
1160 }
1161 Ok(Value::Null)
1162 }
1163 "date_trunc" => date_trunc(args),
1164 "date_part" => date_part(args),
1165 "age" => age(args),
1166 "to_char" => to_char(args),
1167 "date_format" => date_format_mysql(args),
1173 "unix_timestamp" => unix_timestamp_of(args),
1174 "from_unixtime" => from_unixtime(args),
1175 "format" => format_string(args),
1183 "concat" => {
1204 let mut out = String::new();
1205 for v in args {
1206 if matches!(v, Value::Null) {
1207 continue;
1208 }
1209 out.push_str(&value_to_format_text(v));
1210 }
1211 Ok(Value::Text(out))
1212 }
1213 "random" => {
1336 if !args.is_empty() {
1337 return Err(EvalError::TypeMismatch {
1338 detail: alloc::format!("random() takes 0 args, got {}", args.len()),
1339 });
1340 }
1341 Ok(Value::Float(prng_next_f64()))
1342 }
1343 "gen_random_uuid" | "uuid_generate_v4" => {
1350 if !args.is_empty() {
1351 return Err(EvalError::TypeMismatch {
1352 detail: alloc::format!("{name}() takes 0 args, got {}", args.len()),
1353 });
1354 }
1355 Ok(Value::Uuid(gen_random_uuid_bytes()))
1356 }
1357 "sign" => {
1358 if args.len() != 1 {
1359 return Err(EvalError::TypeMismatch {
1360 detail: alloc::format!("sign() takes 1 arg, got {}", args.len()),
1361 });
1362 }
1363 match &args[0] {
1364 Value::Null => Ok(Value::Null),
1365 Value::SmallInt(n) => Ok(Value::SmallInt(n.signum())),
1366 Value::Int(n) => Ok(Value::Int(n.signum())),
1367 Value::BigInt(n) => Ok(Value::BigInt(n.signum())),
1368 Value::Float(x) => {
1369 let s = if *x > 0.0 {
1370 1.0
1371 } else if *x < 0.0 {
1372 -1.0
1373 } else {
1374 0.0
1375 };
1376 Ok(Value::Float(s))
1377 }
1378 Value::Numeric { scaled, scale } => {
1379 let s = scaled.signum();
1380 Ok(Value::Numeric {
1381 scaled: s * pow10_i128(*scale),
1382 scale: *scale,
1383 })
1384 }
1385 other => Err(EvalError::TypeMismatch {
1386 detail: alloc::format!("sign() needs numeric, got {:?}", other.data_type()),
1387 }),
1388 }
1389 }
1390 "sqrt" => {
1391 if args.len() != 1 {
1392 return Err(EvalError::TypeMismatch {
1393 detail: alloc::format!("sqrt() takes 1 arg, got {}", args.len()),
1394 });
1395 }
1396 match &args[0] {
1397 Value::Null => Ok(Value::Null),
1398 v => {
1399 let x = value_to_f64(v).ok_or_else(|| EvalError::TypeMismatch {
1400 detail: alloc::format!("sqrt() needs numeric, got {:?}", v.data_type()),
1401 })?;
1402 if x < 0.0 {
1403 return Err(EvalError::TypeMismatch {
1404 detail: "sqrt(): negative input outside real domain".into(),
1405 });
1406 }
1407 if x == 0.0 {
1408 return Ok(Value::Float(0.0));
1409 }
1410 Ok(Value::Float(f64_sqrt(x)))
1411 }
1412 }
1413 }
1414 "power" | "pow" => {
1415 if args.len() != 2 {
1416 return Err(EvalError::TypeMismatch {
1417 detail: alloc::format!("power() takes 2 args, got {}", args.len()),
1418 });
1419 }
1420 if args.iter().any(|v| matches!(v, Value::Null)) {
1421 return Ok(Value::Null);
1422 }
1423 let x = value_to_f64(&args[0]).ok_or_else(|| EvalError::TypeMismatch {
1424 detail: "power() needs numeric x".into(),
1425 })?;
1426 let y = value_to_f64(&args[1]).ok_or_else(|| EvalError::TypeMismatch {
1427 detail: "power() needs numeric y".into(),
1428 })?;
1429 let y_int = y as i32;
1431 if (y_int as f64) == y && y.abs() < 1024.0 {
1432 let result = f64_powi(x, y_int);
1433 return Ok(Value::Float(result));
1434 }
1435 if x < 0.0 {
1439 return Err(EvalError::TypeMismatch {
1440 detail: "power(): negative base with fractional exponent yields complex result"
1441 .into(),
1442 });
1443 }
1444 if x == 0.0 && y < 0.0 {
1445 return Err(EvalError::TypeMismatch {
1446 detail: "power(): 0 raised to negative power is undefined".into(),
1447 });
1448 }
1449 if x == 0.0 {
1450 return Ok(Value::Float(0.0));
1451 }
1452 Ok(Value::Float(f64_exp(y * f64_ln(x))))
1453 }
1454 "mod" => {
1455 if args.len() != 2 {
1456 return Err(EvalError::TypeMismatch {
1457 detail: alloc::format!("mod() takes 2 args, got {}", args.len()),
1458 });
1459 }
1460 if args.iter().any(|v| matches!(v, Value::Null)) {
1461 return Ok(Value::Null);
1462 }
1463 let to_i64 = |v: &Value| -> Result<i64, EvalError> {
1464 match v {
1465 Value::SmallInt(x) => Ok(i64::from(*x)),
1466 Value::Int(x) => Ok(i64::from(*x)),
1467 Value::BigInt(x) => Ok(*x),
1468 other => Err(EvalError::TypeMismatch {
1469 detail: alloc::format!("mod() needs integer, got {:?}", other.data_type()),
1470 }),
1471 }
1472 };
1473 let y = to_i64(&args[0])?;
1474 let x = to_i64(&args[1])?;
1475 if x == 0 {
1476 return Err(EvalError::TypeMismatch {
1477 detail: "mod(): division by zero".into(),
1478 });
1479 }
1480 let result = y % x;
1483 if let Ok(small) = i16::try_from(result) {
1485 if matches!(args[0], Value::SmallInt(_)) && matches!(args[1], Value::SmallInt(_)) {
1486 return Ok(Value::SmallInt(small));
1487 }
1488 }
1489 if let Ok(int_) = i32::try_from(result) {
1490 if !matches!(args[0], Value::BigInt(_)) && !matches!(args[1], Value::BigInt(_)) {
1491 return Ok(Value::Int(int_));
1492 }
1493 }
1494 Ok(Value::BigInt(result))
1495 }
1496 "greatest" | "least" => {
1497 if args.is_empty() {
1498 return Err(EvalError::TypeMismatch {
1499 detail: alloc::format!(
1500 "{lc}() takes at least 1 arg",
1501 lc = if name.eq_ignore_ascii_case("greatest") {
1502 "greatest"
1503 } else {
1504 "least"
1505 }
1506 ),
1507 });
1508 }
1509 let non_null: alloc::vec::Vec<&Value> =
1510 args.iter().filter(|v| !matches!(v, Value::Null)).collect();
1511 if non_null.is_empty() {
1512 return Ok(Value::Null);
1513 }
1514 let is_greatest = name.eq_ignore_ascii_case("greatest");
1515 let mut best = non_null[0].clone();
1516 for v in &non_null[1..] {
1517 let ord = value_cmp_for_min_max(&best, v);
1518 let take = if is_greatest {
1519 ord == core::cmp::Ordering::Less
1520 } else {
1521 ord == core::cmp::Ordering::Greater
1522 };
1523 if take {
1524 best = (*v).clone();
1525 }
1526 }
1527 Ok(best)
1528 }
1529 "ifnull" => {
1533 if args.len() != 2 {
1534 return Err(EvalError::TypeMismatch {
1535 detail: alloc::format!("ifnull() takes 2 args, got {}", args.len()),
1536 });
1537 }
1538 for v in args {
1539 if !matches!(v, Value::Null) {
1540 return Ok(v.clone());
1541 }
1542 }
1543 Ok(Value::Null)
1544 }
1545 "if" => {
1549 if args.len() != 3 {
1550 return Err(EvalError::TypeMismatch {
1551 detail: alloc::format!(
1552 "if() takes 3 args (cond, then, else), got {}",
1553 args.len()
1554 ),
1555 });
1556 }
1557 let truthy = match &args[0] {
1558 Value::Null => false,
1559 Value::Bool(b) => *b,
1560 Value::SmallInt(n) => *n != 0,
1561 Value::Int(n) => *n != 0,
1562 Value::BigInt(n) => *n != 0,
1563 Value::Float(x) => *x != 0.0,
1564 Value::Text(s) => !s.is_empty() && s != "0",
1565 _ => true,
1566 };
1567 if truthy {
1568 Ok(args[1].clone())
1569 } else {
1570 Ok(args[2].clone())
1571 }
1572 }
1573 "nullif" => {
1574 if args.len() != 2 {
1575 return Err(EvalError::TypeMismatch {
1576 detail: alloc::format!("nullif() takes 2 args, got {}", args.len()),
1577 });
1578 }
1579 match (&args[0], &args[1]) {
1580 (Value::Null, _) => Ok(Value::Null),
1581 (a, Value::Null) => Ok(a.clone()),
1582 (a, b) => {
1583 if values_equal_for_nullif(a, b) {
1587 Ok(Value::Null)
1588 } else {
1589 Ok(a.clone())
1590 }
1591 }
1592 }
1593 }
1594 "trunc" => {
1595 match args.len() {
1596 1 => match &args[0] {
1597 Value::Null => Ok(Value::Null),
1598 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1599 Value::Float(x) => Ok(Value::Float(f64_trunc(*x))),
1600 Value::Numeric { scaled, scale } => {
1601 let factor = pow10_i128(*scale);
1602 let q = scaled / factor;
1604 Ok(Value::Numeric {
1605 scaled: q * factor,
1606 scale: *scale,
1607 })
1608 }
1609 other => Err(EvalError::TypeMismatch {
1610 detail: alloc::format!(
1611 "trunc() needs numeric, got {:?}",
1612 other.data_type()
1613 ),
1614 }),
1615 },
1616 2 => {
1617 if args.iter().any(|v| matches!(v, Value::Null)) {
1618 return Ok(Value::Null);
1619 }
1620 let n = match &args[1] {
1621 Value::SmallInt(x) => i32::from(*x),
1622 Value::Int(x) => *x,
1623 Value::BigInt(x) => {
1624 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1625 detail: "trunc(): scale must fit in i32".into(),
1626 })?
1627 }
1628 other => {
1629 return Err(EvalError::TypeMismatch {
1630 detail: alloc::format!(
1631 "trunc(): scale must be integer, got {:?}",
1632 other.data_type()
1633 ),
1634 });
1635 }
1636 };
1637 let x = match &args[0] {
1638 Value::SmallInt(v) => f64::from(*v),
1639 Value::Int(v) => f64::from(*v),
1640 Value::BigInt(v) => *v as f64,
1641 Value::Float(v) => *v,
1642 Value::Numeric { scaled, scale } => {
1643 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1644 }
1645 other => {
1646 return Err(EvalError::TypeMismatch {
1647 detail: alloc::format!(
1648 "trunc() needs numeric x, got {:?}",
1649 other.data_type()
1650 ),
1651 });
1652 }
1653 };
1654 let result = if n >= 0 {
1655 let factor = f64_powi(10.0, n);
1656 f64_trunc(x * factor) / factor
1657 } else {
1658 let factor = f64_powi(10.0, -n);
1659 f64_trunc(x / factor) * factor
1660 };
1661 Ok(Value::Float(result))
1662 }
1663 _ => Err(EvalError::TypeMismatch {
1664 detail: alloc::format!("trunc() takes 1 or 2 args, got {}", args.len()),
1665 }),
1666 }
1667 }
1668 "round" => {
1669 match args.len() {
1670 1 => match &args[0] {
1671 Value::Null => Ok(Value::Null),
1672 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1673 Value::Float(x) => Ok(Value::Float(f64_round_half_away(*x))),
1674 Value::Numeric { scaled, scale } => {
1675 let factor = pow10_i128(*scale);
1676 let q = scaled.div_euclid(factor);
1677 let r = scaled.rem_euclid(factor);
1678 let result = if 2 * r >= factor { q + 1 } else { q };
1680 Ok(Value::Numeric {
1681 scaled: result * factor,
1682 scale: *scale,
1683 })
1684 }
1685 other => Err(EvalError::TypeMismatch {
1686 detail: alloc::format!(
1687 "round() needs numeric, got {:?}",
1688 other.data_type()
1689 ),
1690 }),
1691 },
1692 2 => {
1693 if args.iter().any(|v| matches!(v, Value::Null)) {
1694 return Ok(Value::Null);
1695 }
1696 let n = match &args[1] {
1697 Value::SmallInt(x) => i32::from(*x),
1698 Value::Int(x) => *x,
1699 Value::BigInt(x) => {
1700 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1701 detail: "round(): scale must fit in i32".into(),
1702 })?
1703 }
1704 other => {
1705 return Err(EvalError::TypeMismatch {
1706 detail: alloc::format!(
1707 "round(): scale must be integer, got {:?}",
1708 other.data_type()
1709 ),
1710 });
1711 }
1712 };
1713 let x = match &args[0] {
1719 Value::SmallInt(v) => f64::from(*v),
1720 Value::Int(v) => f64::from(*v),
1721 Value::BigInt(v) => *v as f64,
1722 Value::Float(v) => *v,
1723 Value::Numeric { scaled, scale } => {
1724 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1725 }
1726 other => {
1727 return Err(EvalError::TypeMismatch {
1728 detail: alloc::format!(
1729 "round() needs numeric x, got {:?}",
1730 other.data_type()
1731 ),
1732 });
1733 }
1734 };
1735 let result = if n >= 0 {
1740 let factor = f64_powi(10.0, n);
1741 f64_round_half_away(x * factor) / factor
1742 } else {
1743 let factor = f64_powi(10.0, -n);
1744 f64_round_half_away(x / factor) * factor
1745 };
1746 Ok(Value::Float(result))
1747 }
1748 _ => Err(EvalError::TypeMismatch {
1749 detail: alloc::format!("round() takes 1 or 2 args, got {}", args.len()),
1750 }),
1751 }
1752 }
1753 "ceil" | "ceiling" => {
1754 if args.len() != 1 {
1755 return Err(EvalError::TypeMismatch {
1756 detail: alloc::format!("ceil() takes 1 arg, got {}", args.len()),
1757 });
1758 }
1759 match &args[0] {
1760 Value::Null => Ok(Value::Null),
1761 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1762 Value::Float(x) => Ok(Value::Float(f64_ceil(*x))),
1763 Value::Numeric { scaled, scale } => {
1764 let factor = pow10_i128(*scale);
1765 let q = scaled.div_euclid(factor);
1766 let r = scaled.rem_euclid(factor);
1767 let result = if r == 0 { q } else { q + 1 };
1768 Ok(Value::Numeric {
1769 scaled: result * factor,
1770 scale: *scale,
1771 })
1772 }
1773 other => Err(EvalError::TypeMismatch {
1774 detail: alloc::format!("ceil() needs numeric, got {:?}", other.data_type()),
1775 }),
1776 }
1777 }
1778 "floor" => {
1779 if args.len() != 1 {
1780 return Err(EvalError::TypeMismatch {
1781 detail: alloc::format!("floor() takes 1 arg, got {}", args.len()),
1782 });
1783 }
1784 match &args[0] {
1785 Value::Null => Ok(Value::Null),
1786 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1787 Value::Float(x) => Ok(Value::Float(f64_floor(*x))),
1788 Value::Numeric { scaled, scale } => {
1789 let factor = pow10_i128(*scale);
1790 let q = scaled.div_euclid(factor);
1791 Ok(Value::Numeric {
1795 scaled: q * factor,
1796 scale: *scale,
1797 })
1798 }
1799 other => Err(EvalError::TypeMismatch {
1800 detail: alloc::format!("floor() needs numeric, got {:?}", other.data_type()),
1801 }),
1802 }
1803 }
1804 "left" => string_left_right(args, true, "left"),
1805 "right" => string_left_right(args, false, "right"),
1806 "strpos" => {
1807 if args.len() != 2 {
1808 return Err(EvalError::TypeMismatch {
1809 detail: alloc::format!(
1810 "strpos() takes 2 args (haystack, needle), got {}",
1811 args.len()
1812 ),
1813 });
1814 }
1815 if args.iter().any(|v| matches!(v, Value::Null)) {
1816 return Ok(Value::Null);
1817 }
1818 let haystack = value_to_format_text(&args[0]);
1819 let needle = value_to_format_text(&args[1]);
1820 if needle.is_empty() {
1821 return Ok(Value::Int(1));
1822 }
1823 let h_chars: Vec<char> = haystack.chars().collect();
1824 let n_chars: Vec<char> = needle.chars().collect();
1825 if n_chars.len() > h_chars.len() {
1826 return Ok(Value::Int(0));
1827 }
1828 for i in 0..=h_chars.len() - n_chars.len() {
1829 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1830 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1831 }
1832 }
1833 Ok(Value::Int(0))
1834 }
1835 "lpad" => string_pad(args, true, "lpad"),
1836 "rpad" => string_pad(args, false, "rpad"),
1837 "repeat" => {
1838 if args.len() != 2 {
1839 return Err(EvalError::TypeMismatch {
1840 detail: alloc::format!("repeat() takes 2 args, got {}", args.len()),
1841 });
1842 }
1843 if args.iter().any(|v| matches!(v, Value::Null)) {
1844 return Ok(Value::Null);
1845 }
1846 let s = value_to_format_text(&args[0]);
1847 let n = match &args[1] {
1848 Value::SmallInt(x) => i64::from(*x),
1849 Value::Int(x) => i64::from(*x),
1850 Value::BigInt(x) => *x,
1851 other => {
1852 return Err(EvalError::TypeMismatch {
1853 detail: alloc::format!(
1854 "repeat(): n must be integer, got {:?}",
1855 other.data_type()
1856 ),
1857 });
1858 }
1859 };
1860 if n <= 0 {
1861 return Ok(Value::Text(String::new()));
1862 }
1863 const MAX_REPEAT_BYTES: usize = 64 * 1024 * 1024;
1867 let needed =
1868 s.len()
1869 .checked_mul(n as usize)
1870 .ok_or_else(|| EvalError::TypeMismatch {
1871 detail: "repeat(): result size overflows usize".into(),
1872 })?;
1873 if needed > MAX_REPEAT_BYTES {
1874 return Err(EvalError::TypeMismatch {
1875 detail: alloc::format!(
1876 "repeat(): result would exceed {MAX_REPEAT_BYTES} bytes"
1877 ),
1878 });
1879 }
1880 Ok(Value::Text(s.repeat(n as usize)))
1881 }
1882 "split_part" => {
1883 if args.len() != 3 {
1884 return Err(EvalError::TypeMismatch {
1885 detail: alloc::format!(
1886 "split_part() takes 3 args (string, delim, n), got {}",
1887 args.len()
1888 ),
1889 });
1890 }
1891 if args.iter().any(|v| matches!(v, Value::Null)) {
1892 return Ok(Value::Null);
1893 }
1894 let s = value_to_format_text(&args[0]);
1895 let delim = value_to_format_text(&args[1]);
1896 if delim.is_empty() {
1897 return Err(EvalError::TypeMismatch {
1898 detail: "split_part(): delimiter cannot be empty".into(),
1899 });
1900 }
1901 let n = match &args[2] {
1902 Value::SmallInt(x) => i64::from(*x),
1903 Value::Int(x) => i64::from(*x),
1904 Value::BigInt(x) => *x,
1905 other => {
1906 return Err(EvalError::TypeMismatch {
1907 detail: alloc::format!(
1908 "split_part(): n must be integer, got {:?}",
1909 other.data_type()
1910 ),
1911 });
1912 }
1913 };
1914 if n == 0 {
1915 return Err(EvalError::TypeMismatch {
1916 detail: "split_part(): n must be nonzero (PG: 1-indexed)".into(),
1917 });
1918 }
1919 let parts: alloc::vec::Vec<&str> = s.split(&delim[..]).collect();
1920 let total = parts.len() as i64;
1921 let idx = if n > 0 {
1922 n - 1
1923 } else {
1924 total + n
1926 };
1927 if idx < 0 || idx >= total {
1928 return Ok(Value::Text(String::new()));
1929 }
1930 Ok(Value::Text(parts[idx as usize].to_string()))
1931 }
1932 "translate" => {
1939 if args.len() != 3 {
1940 return Err(EvalError::TypeMismatch {
1941 detail: alloc::format!("translate() takes 3 args, got {}", args.len()),
1942 });
1943 }
1944 if args.iter().any(|v| matches!(v, Value::Null)) {
1945 return Ok(Value::Null);
1946 }
1947 let s = value_to_format_text(&args[0]);
1948 let from = value_to_format_text(&args[1]);
1949 let to = value_to_format_text(&args[2]);
1950 let from_chars: Vec<char> = from.chars().collect();
1951 let to_chars: Vec<char> = to.chars().collect();
1952 let mut map: alloc::collections::BTreeMap<char, Option<char>> =
1954 alloc::collections::BTreeMap::new();
1955 for (i, &fc) in from_chars.iter().enumerate() {
1956 if map.contains_key(&fc) {
1957 continue;
1958 }
1959 let replacement = to_chars.get(i).copied();
1960 map.insert(fc, replacement);
1961 }
1962 let mut out = String::with_capacity(s.len());
1963 for c in s.chars() {
1964 match map.get(&c) {
1965 Some(Some(r)) => out.push(*r),
1966 Some(None) => {} None => out.push(c),
1968 }
1969 }
1970 Ok(Value::Text(out))
1971 }
1972 "replace" => {
1973 if args.len() != 3 {
1974 return Err(EvalError::TypeMismatch {
1975 detail: alloc::format!(
1976 "replace() takes 3 args (string, from, to), got {}",
1977 args.len()
1978 ),
1979 });
1980 }
1981 if args.iter().any(|v| matches!(v, Value::Null)) {
1982 return Ok(Value::Null);
1983 }
1984 let s = value_to_format_text(&args[0]);
1985 let from = value_to_format_text(&args[1]);
1986 let to = value_to_format_text(&args[2]);
1987 if from.is_empty() {
1988 return Ok(Value::Text(s));
1989 }
1990 Ok(Value::Text(s.replace(&from[..], &to)))
1995 }
1996 "trim" | "btrim" => string_trim(args, TrimSide::Both, "trim"),
1997 "ltrim" => string_trim(args, TrimSide::Left, "ltrim"),
1998 "rtrim" => string_trim(args, TrimSide::Right, "rtrim"),
1999 "concat_ws" => {
2000 if args.is_empty() {
2001 return Err(EvalError::TypeMismatch {
2002 detail: "concat_ws() requires at least 1 arg (the separator)".into(),
2003 });
2004 }
2005 let sep = match &args[0] {
2007 Value::Null => return Ok(Value::Null),
2008 v => value_to_format_text(v),
2009 };
2010 let mut out = String::new();
2011 let mut first = true;
2012 for v in &args[1..] {
2013 if matches!(v, Value::Null) {
2014 continue;
2015 }
2016 if first {
2017 first = false;
2018 } else {
2019 out.push_str(&sep);
2020 }
2021 out.push_str(&value_to_format_text(v));
2022 }
2023 Ok(Value::Text(out))
2024 }
2025 "regexp_matches" => regexp_matches(args),
2027 "regexp_replace" => regexp_replace(args),
2028 "regexp_split_to_array" => regexp_split_to_array(args),
2029 "to_json" | "to_jsonb" => {
2033 if args.len() != 1 {
2034 return Err(EvalError::TypeMismatch {
2035 detail: alloc::format!("to_json() takes 1 arg, got {}", args.len()),
2036 });
2037 }
2038 if let Value::Json(s) = &args[0] {
2040 return Ok(Value::Json(s.clone()));
2041 }
2042 Ok(Value::Json(crate::json::value_to_json_text(&args[0])))
2043 }
2044 "json_build_object" | "jsonb_build_object" => crate::json::build_object(args),
2045 "json_build_array" | "jsonb_build_array" => crate::json::build_array(args),
2046 "jsonb_set" | "json_set" => crate::json::set(args),
2047 "jsonb_insert" | "json_insert" => crate::json::insert(args),
2048 "jsonb_path_query" | "json_path_query" => {
2050 if args.len() != 2 {
2051 return Err(EvalError::TypeMismatch {
2052 detail: alloc::format!("jsonb_path_query() takes 2 args, got {}", args.len()),
2053 });
2054 }
2055 crate::json::path_query(&args[0], &args[1])
2056 }
2057 "jsonb_path_query_first" | "json_path_query_first" => {
2058 if args.len() != 2 {
2059 return Err(EvalError::TypeMismatch {
2060 detail: alloc::format!(
2061 "jsonb_path_query_first() takes 2 args, got {}",
2062 args.len()
2063 ),
2064 });
2065 }
2066 crate::json::path_query_first(&args[0], &args[1])
2067 }
2068 "jsonb_path_query_array" | "json_path_query_array" => {
2069 if args.len() != 2 {
2070 return Err(EvalError::TypeMismatch {
2071 detail: alloc::format!(
2072 "jsonb_path_query_array() takes 2 args, got {}",
2073 args.len()
2074 ),
2075 });
2076 }
2077 crate::json::path_query_array(&args[0], &args[1])
2078 }
2079 "host" => inet_host(args),
2081 "network" => inet_network(args),
2082 "masklen" => inet_masklen(args),
2083 "encode" => encode_text(args),
2085 "decode" => decode_text(args),
2086 "error_on_null" => error_on_null(args),
2087 "to_tsvector" => fts_to_tsvector(args, ctx),
2092 "setweight" => fts_setweight(args),
2096 "string_to_array" => fn_string_to_array(args),
2100 "plainto_tsquery" => fts_plainto_tsquery(args, ctx),
2101 "phraseto_tsquery" => fts_phraseto_tsquery(args, ctx),
2102 "websearch_to_tsquery" => fts_websearch_to_tsquery(args, ctx),
2103 "to_tsquery" => fts_to_tsquery(args, ctx),
2104 "ts_rank" => fts_ts_rank(args),
2107 "ts_rank_cd" => fts_ts_rank_cd(args),
2108 "set_config" => Ok(args.get(1).cloned().unwrap_or(Value::Null)),
2113 "current_setting" => Ok(Value::Text(String::new())),
2114 "pg_get_serial_sequence" | "pg_get_constraintdef" | "pg_get_indexdef" => Ok(Value::Null),
2120 "version" => Ok(Value::Text("PostgreSQL 16 (SPG-compat)".into())),
2121 "current_database" | "database" => Ok(Value::Text("spg".into())),
2129 "current_schema" => Ok(Value::Text("public".into())),
2130 "current_user" | "session_user" | "user" => Ok(Value::Text("admin".into())),
2131 "pg_typeof" => {
2138 if args.len() != 1 {
2139 return Err(EvalError::TypeMismatch {
2140 detail: format!("pg_typeof() takes 1 arg, got {}", args.len()),
2141 });
2142 }
2143 Ok(Value::Text(pg_typeof_name(&args[0]).into()))
2144 }
2145 "lastval" => Ok(Value::Null),
2150 "similarity" => {
2155 if args.len() != 2 {
2156 return Err(EvalError::TypeMismatch {
2157 detail: format!("similarity() takes 2 args, got {}", args.len()),
2158 });
2159 }
2160 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2161 return Ok(Value::Null);
2162 }
2163 let a = match &args[0] {
2164 Value::Text(s) => s.as_str(),
2165 other => {
2166 return Err(EvalError::TypeMismatch {
2167 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2168 });
2169 }
2170 };
2171 let b = match &args[1] {
2172 Value::Text(s) => s.as_str(),
2173 other => {
2174 return Err(EvalError::TypeMismatch {
2175 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2176 });
2177 }
2178 };
2179 Ok(Value::Float(spg_storage::trgm::similarity(a, b)))
2182 }
2183 "show_trgm" => {
2184 if args.len() != 1 {
2185 return Err(EvalError::TypeMismatch {
2186 detail: format!("show_trgm() takes 1 arg, got {}", args.len()),
2187 });
2188 }
2189 if matches!(args[0], Value::Null) {
2190 return Ok(Value::Null);
2191 }
2192 let s = match &args[0] {
2193 Value::Text(s) => s.as_str(),
2194 other => {
2195 return Err(EvalError::TypeMismatch {
2196 detail: format!("show_trgm() needs text, got {:?}", other.data_type()),
2197 });
2198 }
2199 };
2200 let trigrams: Vec<Option<String>> = spg_storage::trgm::extract_trigrams(s)
2204 .into_iter()
2205 .map(Some)
2206 .collect();
2207 Ok(Value::TextArray(trigrams))
2208 }
2209 other => Err(EvalError::TypeMismatch {
2210 detail: format!("unknown function `{other}`"),
2211 }),
2212 }
2213}
2214
2215fn fts_ts_rank(args: &[Value]) -> Result<Value, EvalError> {
2220 let (vec, query) = parse_rank_args("ts_rank", args)?;
2221 match (vec, query) {
2222 (None, _) | (_, None) => Ok(Value::Null),
2223 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank(&v, &q)))),
2224 }
2225}
2226
2227fn fts_ts_rank_cd(args: &[Value]) -> Result<Value, EvalError> {
2228 let (vec, query) = parse_rank_args("ts_rank_cd", args)?;
2229 match (vec, query) {
2230 (None, _) | (_, None) => Ok(Value::Null),
2231 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank_cd(&v, &q)))),
2232 }
2233}
2234
2235fn parse_rank_args(
2236 name: &str,
2237 args: &[Value],
2238) -> Result<
2239 (
2240 Option<Vec<spg_storage::TsLexeme>>,
2241 Option<spg_storage::TsQueryAst>,
2242 ),
2243 EvalError,
2244> {
2245 if args.len() != 2 {
2246 return Err(EvalError::TypeMismatch {
2247 detail: format!(
2248 "{name}() takes 2 args in v7.12.2 (weights array + normalisation flag are v7.12.x carve-out), got {}",
2249 args.len()
2250 ),
2251 });
2252 }
2253 let vec = match &args[0] {
2254 Value::Null => None,
2255 Value::TsVector(v) => Some(v.clone()),
2256 other => {
2257 return Err(EvalError::TypeMismatch {
2258 detail: format!(
2259 "{name}() first arg must be tsvector, got {:?}",
2260 other.data_type()
2261 ),
2262 });
2263 }
2264 };
2265 let query = match &args[1] {
2266 Value::Null => None,
2267 Value::TsQuery(q) => Some(q.clone()),
2268 other => {
2269 return Err(EvalError::TypeMismatch {
2270 detail: format!(
2271 "{name}() second arg must be tsquery, got {:?}",
2272 other.data_type()
2273 ),
2274 });
2275 }
2276 };
2277 Ok((vec, query))
2278}
2279
2280fn ts_match(l: Value, r: Value) -> Result<Value, EvalError> {
2285 let (vec, query) = match (l, r) {
2286 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
2287 (Value::TsVector(v), Value::TsQuery(q)) => (v, q),
2288 (Value::TsQuery(q), Value::TsVector(v)) => (v, q),
2289 (l, r) => {
2290 return Err(EvalError::TypeMismatch {
2291 detail: format!(
2292 "@@ requires (tsvector, tsquery), got ({:?}, {:?})",
2293 l.data_type(),
2294 r.data_type()
2295 ),
2296 });
2297 }
2298 };
2299 Ok(Value::Bool(crate::fts::ts_query_matches(&vec, &query)))
2300}
2301
2302fn fts_to_tsvector(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2307 let (config, text) = parse_fts_args("to_tsvector", args, ctx)?;
2308 match text {
2309 None => Ok(Value::Null),
2310 Some(t) => Ok(Value::TsVector(crate::fts::to_tsvector(config, &t))),
2311 }
2312}
2313
2314fn fts_setweight(args: &[Value]) -> Result<Value, EvalError> {
2317 let [vec_arg, weight_arg] = args else {
2318 return Err(EvalError::TypeMismatch {
2319 detail: alloc::format!("setweight expects 2 arguments, got {}", args.len()),
2320 });
2321 };
2322 if matches!(vec_arg, Value::Null) || matches!(weight_arg, Value::Null) {
2323 return Ok(Value::Null);
2324 }
2325 let Value::TsVector(lexemes) = vec_arg else {
2326 return Err(EvalError::TypeMismatch {
2327 detail: alloc::format!(
2328 "setweight expects a tsvector, got {:?}",
2329 vec_arg.data_type()
2330 ),
2331 });
2332 };
2333 let Value::Text(w) = weight_arg else {
2334 return Err(EvalError::TypeMismatch {
2335 detail: alloc::format!(
2336 "setweight expects a weight letter, got {:?}",
2337 weight_arg.data_type()
2338 ),
2339 });
2340 };
2341 let weight = match w.to_ascii_uppercase().as_str() {
2342 "A" => 3,
2343 "B" => 2,
2344 "C" => 1,
2345 "D" => 0,
2346 other => {
2347 return Err(EvalError::TypeMismatch {
2348 detail: alloc::format!("unrecognized weight: {other:?} (expected A, B, C or D)"),
2349 });
2350 }
2351 };
2352 let mut out = lexemes.clone();
2353 for lex in &mut out {
2354 lex.weight = weight;
2355 }
2356 Ok(Value::TsVector(out))
2357}
2358
2359fn fn_string_to_array(args: &[Value]) -> Result<Value, EvalError> {
2361 let [text_arg, delim_arg] = args else {
2362 return Err(EvalError::TypeMismatch {
2363 detail: alloc::format!("string_to_array expects 2 arguments, got {}", args.len()),
2364 });
2365 };
2366 let text = match text_arg {
2367 Value::Null => return Ok(Value::Null),
2368 Value::Text(t) => t,
2369 other => {
2370 return Err(EvalError::TypeMismatch {
2371 detail: alloc::format!("string_to_array expects text, got {:?}", other.data_type()),
2372 });
2373 }
2374 };
2375 if text.is_empty() {
2377 return Ok(Value::TextArray(Vec::new()));
2378 }
2379 let parts: Vec<Option<String>> = match delim_arg {
2380 Value::Null => text.chars().map(|c| Some(c.to_string())).collect(),
2382 Value::Text(d) if d.is_empty() => alloc::vec![Some(text.clone())],
2383 Value::Text(d) => text
2384 .split(d.as_str())
2385 .map(|p| Some(p.to_string()))
2386 .collect(),
2387 other => {
2388 return Err(EvalError::TypeMismatch {
2389 detail: alloc::format!(
2390 "string_to_array delimiter must be text, got {:?}",
2391 other.data_type()
2392 ),
2393 });
2394 }
2395 };
2396 Ok(Value::TextArray(parts))
2397}
2398
2399fn fts_plainto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2400 let (config, text) = parse_fts_args("plainto_tsquery", args, ctx)?;
2401 match text {
2402 None => Ok(Value::Null),
2403 Some(t) => Ok(Value::TsQuery(crate::fts::plainto_tsquery(config, &t))),
2404 }
2405}
2406
2407fn fts_phraseto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2408 let (config, text) = parse_fts_args("phraseto_tsquery", args, ctx)?;
2409 match text {
2410 None => Ok(Value::Null),
2411 Some(t) => Ok(Value::TsQuery(crate::fts::phraseto_tsquery(config, &t))),
2412 }
2413}
2414
2415fn fts_websearch_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2416 let (config, text) = parse_fts_args("websearch_to_tsquery", args, ctx)?;
2417 match text {
2418 None => Ok(Value::Null),
2419 Some(t) => Ok(Value::TsQuery(crate::fts::websearch_to_tsquery(config, &t))),
2420 }
2421}
2422
2423fn fts_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2424 let (config, text) = parse_fts_args("to_tsquery", args, ctx)?;
2425 match text {
2426 None => Ok(Value::Null),
2427 Some(t) => Ok(Value::TsQuery(crate::fts::to_tsquery(config, &t)?)),
2428 }
2429}
2430
2431fn parse_fts_args(
2436 name: &str,
2437 args: &[Value],
2438 ctx: &EvalContext<'_>,
2439) -> Result<(crate::fts::TsConfig, Option<String>), EvalError> {
2440 let (config_arg, text_arg) = match args {
2441 [t] => (None, t),
2442 [c, t] => (Some(c), t),
2443 _ => {
2444 return Err(EvalError::TypeMismatch {
2445 detail: format!("{name}() takes 1 or 2 args, got {}", args.len()),
2446 });
2447 }
2448 };
2449 let config = match config_arg {
2450 None => match ctx.default_text_search_config {
2451 Some(name_str) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2452 EvalError::TypeMismatch {
2453 detail: format!(
2454 "text search config not implemented: {name_str:?} (supported: simple, english)"
2455 ),
2456 }
2457 })?,
2458 None => crate::fts::TsConfig::Simple,
2459 },
2460 Some(Value::Null) => return Ok((crate::fts::TsConfig::Simple, None)),
2461 Some(Value::Text(name_str)) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2462 EvalError::TypeMismatch {
2463 detail: format!(
2464 "text search config not implemented: {name_str:?} (supported: simple, english)"
2465 ),
2466 }
2467 })?,
2468 Some(other) => {
2469 return Err(EvalError::TypeMismatch {
2470 detail: format!(
2471 "{name}() config arg must be text, got {:?}",
2472 other.data_type()
2473 ),
2474 });
2475 }
2476 };
2477 let text = match text_arg {
2478 Value::Null => None,
2479 Value::Text(s) => Some(s.clone()),
2480 other => {
2481 return Err(EvalError::TypeMismatch {
2482 detail: format!(
2483 "{name}() text arg must be text, got {:?}",
2484 other.data_type()
2485 ),
2486 });
2487 }
2488 };
2489 Ok((config, text))
2490}
2491
2492fn encode_text(args: &[Value]) -> Result<Value, EvalError> {
2498 if args.len() != 2 {
2499 return Err(EvalError::TypeMismatch {
2500 detail: format!("encode() takes 2 args, got {}", args.len()),
2501 });
2502 }
2503 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2504 return Ok(Value::Null);
2505 }
2506 let bytes: &[u8] = match &args[0] {
2507 Value::Text(s) => s.as_bytes(),
2508 other => {
2509 return Err(EvalError::TypeMismatch {
2510 detail: format!("encode() expects text bytes, got {:?}", other.data_type()),
2511 });
2512 }
2513 };
2514 let fmt = match &args[1] {
2515 Value::Text(s) => s.to_ascii_lowercase(),
2516 other => {
2517 return Err(EvalError::TypeMismatch {
2518 detail: format!("encode() format must be text, got {:?}", other.data_type()),
2519 });
2520 }
2521 };
2522 let out = match fmt.as_str() {
2523 "base64" => b64_encode(bytes, B64_STD),
2524 "base64url" => b64_encode(bytes, B64_URL),
2525 "base32hex" => b32hex_encode(bytes),
2526 "hex" => hex_encode(bytes),
2527 other => {
2528 return Err(EvalError::TypeMismatch {
2529 detail: format!("encode(): unknown format `{other}`"),
2530 });
2531 }
2532 };
2533 Ok(Value::Text(out))
2534}
2535
2536fn decode_text(args: &[Value]) -> Result<Value, EvalError> {
2540 if args.len() != 2 {
2541 return Err(EvalError::TypeMismatch {
2542 detail: format!("decode() takes 2 args, got {}", args.len()),
2543 });
2544 }
2545 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2546 return Ok(Value::Null);
2547 }
2548 let text = match &args[0] {
2549 Value::Text(s) => s.as_str(),
2550 other => {
2551 return Err(EvalError::TypeMismatch {
2552 detail: format!("decode() expects text, got {:?}", other.data_type()),
2553 });
2554 }
2555 };
2556 let fmt = match &args[1] {
2557 Value::Text(s) => s.to_ascii_lowercase(),
2558 other => {
2559 return Err(EvalError::TypeMismatch {
2560 detail: format!("decode() format must be text, got {:?}", other.data_type()),
2561 });
2562 }
2563 };
2564 let bytes = match fmt.as_str() {
2565 "base64" => b64_decode(text, B64_STD)?,
2566 "base64url" => b64_decode(text, B64_URL)?,
2567 "base32hex" => b32hex_decode(text)?,
2568 "hex" => hex_decode(text)?,
2569 other => {
2570 return Err(EvalError::TypeMismatch {
2571 detail: format!("decode(): unknown format `{other}`"),
2572 });
2573 }
2574 };
2575 let s = String::from_utf8(bytes).map_err(|_| EvalError::TypeMismatch {
2576 detail: "decode(): result bytes are not valid UTF-8 (SPG stores raw bytes as Text)".into(),
2577 })?;
2578 Ok(Value::Text(s))
2579}
2580
2581fn error_on_null(args: &[Value]) -> Result<Value, EvalError> {
2585 if args.len() != 1 {
2586 return Err(EvalError::TypeMismatch {
2587 detail: format!("error_on_null() takes 1 arg, got {}", args.len()),
2588 });
2589 }
2590 if matches!(args[0], Value::Null) {
2591 return Err(EvalError::TypeMismatch {
2592 detail: "error_on_null(): argument is NULL".into(),
2593 });
2594 }
2595 Ok(args[0].clone())
2596}
2597
2598const B64_STD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2601const B64_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2602const B32HEX_ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHIJKLMNOPQRSTUV";
2603
2604fn b64_encode(bytes: &[u8], alpha: &[u8; 64]) -> String {
2605 let mut out = String::with_capacity((bytes.len() + 2) / 3 * 4);
2606 let mut i = 0;
2607 while i + 3 <= bytes.len() {
2608 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8) | (bytes[i + 2] as u32);
2609 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2610 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2611 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2612 out.push(alpha[(n & 0x3f) as usize] as char);
2613 i += 3;
2614 }
2615 let rem = bytes.len() - i;
2616 if rem == 1 {
2617 let n = (bytes[i] as u32) << 16;
2618 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2619 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2620 out.push('=');
2621 out.push('=');
2622 } else if rem == 2 {
2623 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8);
2624 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2625 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2626 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2627 out.push('=');
2628 }
2629 out
2630}
2631
2632fn b64_decode(text: &str, alpha: &[u8; 64]) -> Result<Vec<u8>, EvalError> {
2633 let mut lookup = [255u8; 256];
2634 for (i, &c) in alpha.iter().enumerate() {
2635 lookup[c as usize] = i as u8;
2636 }
2637 let mut out = Vec::with_capacity(text.len() * 3 / 4);
2638 let mut buf: u32 = 0;
2639 let mut bits: u32 = 0;
2640 for c in text.bytes() {
2641 if c == b'=' {
2642 break;
2643 }
2644 if c == b'\n' || c == b'\r' || c == b' ' {
2645 continue;
2646 }
2647 let v = lookup[c as usize];
2648 if v == 255 {
2649 return Err(EvalError::TypeMismatch {
2650 detail: format!("decode(base64): invalid char {:?}", c as char),
2651 });
2652 }
2653 buf = (buf << 6) | v as u32;
2654 bits += 6;
2655 if bits >= 8 {
2656 bits -= 8;
2657 out.push(((buf >> bits) & 0xff) as u8);
2658 }
2659 }
2660 Ok(out)
2661}
2662
2663fn b32hex_encode(bytes: &[u8]) -> String {
2664 let mut out = String::with_capacity((bytes.len() * 8 + 4) / 5);
2665 let mut buf: u64 = 0;
2666 let mut bits: u32 = 0;
2667 for &b in bytes {
2668 buf = (buf << 8) | b as u64;
2669 bits += 8;
2670 while bits >= 5 {
2671 bits -= 5;
2672 out.push(B32HEX_ALPHABET[((buf >> bits) & 0x1f) as usize] as char);
2673 }
2674 }
2675 if bits > 0 {
2676 out.push(B32HEX_ALPHABET[((buf << (5 - bits)) & 0x1f) as usize] as char);
2677 }
2678 while out.len() % 8 != 0 {
2680 out.push('=');
2681 }
2682 out
2683}
2684
2685fn b32hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2686 let mut lookup = [255u8; 256];
2687 for (i, &c) in B32HEX_ALPHABET.iter().enumerate() {
2688 lookup[c as usize] = i as u8;
2689 let lower = (c as char).to_ascii_lowercase() as u8;
2691 lookup[lower as usize] = i as u8;
2692 }
2693 let mut out = Vec::with_capacity(text.len() * 5 / 8);
2694 let mut buf: u64 = 0;
2695 let mut bits: u32 = 0;
2696 for c in text.bytes() {
2697 if c == b'=' {
2698 break;
2699 }
2700 if c == b'\n' || c == b'\r' || c == b' ' {
2701 continue;
2702 }
2703 let v = lookup[c as usize];
2704 if v == 255 {
2705 return Err(EvalError::TypeMismatch {
2706 detail: format!("decode(base32hex): invalid char {:?}", c as char),
2707 });
2708 }
2709 buf = (buf << 5) | v as u64;
2710 bits += 5;
2711 if bits >= 8 {
2712 bits -= 8;
2713 out.push(((buf >> bits) & 0xff) as u8);
2714 }
2715 }
2716 Ok(out)
2717}
2718
2719fn hex_encode(bytes: &[u8]) -> String {
2720 const HEX: &[u8; 16] = b"0123456789abcdef";
2721 let mut out = String::with_capacity(bytes.len() * 2);
2722 for &b in bytes {
2723 out.push(HEX[(b >> 4) as usize] as char);
2724 out.push(HEX[(b & 0xf) as usize] as char);
2725 }
2726 out
2727}
2728
2729fn hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2730 let trimmed = text.trim();
2731 if trimmed.len() % 2 != 0 {
2732 return Err(EvalError::TypeMismatch {
2733 detail: "decode(hex): input length must be even".into(),
2734 });
2735 }
2736 let mut out = Vec::with_capacity(trimmed.len() / 2);
2737 let mut hi: u8 = 0;
2738 for (i, c) in trimmed.bytes().enumerate() {
2739 let v = match c {
2740 b'0'..=b'9' => c - b'0',
2741 b'a'..=b'f' => c - b'a' + 10,
2742 b'A'..=b'F' => c - b'A' + 10,
2743 _ => {
2744 return Err(EvalError::TypeMismatch {
2745 detail: format!("decode(hex): invalid char {:?}", c as char),
2746 });
2747 }
2748 };
2749 if i % 2 == 0 {
2750 hi = v;
2751 } else {
2752 out.push((hi << 4) | v);
2753 }
2754 }
2755 Ok(out)
2756}
2757
2758fn date_part(args: &[Value]) -> Result<Value, EvalError> {
2763 use spg_sql::ast::ExtractField as F;
2764 if args.len() != 2 {
2765 return Err(EvalError::TypeMismatch {
2766 detail: format!("date_part() takes 2 args, got {}", args.len()),
2767 });
2768 }
2769 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
2770 return Ok(Value::Null);
2771 }
2772 let Value::Text(field_name) = &args[0] else {
2773 return Err(EvalError::TypeMismatch {
2774 detail: format!(
2775 "date_part() needs a text field, got {:?}",
2776 args[0].data_type()
2777 ),
2778 });
2779 };
2780 let field = match field_name.to_ascii_lowercase().as_str() {
2781 "year" => F::Year,
2782 "month" => F::Month,
2783 "day" => F::Day,
2784 "hour" => F::Hour,
2785 "minute" => F::Minute,
2786 "second" => F::Second,
2787 "microsecond" | "microseconds" => F::Microsecond,
2788 "epoch" => F::Epoch,
2789 other => {
2790 return Err(EvalError::TypeMismatch {
2791 detail: format!(
2792 "unknown date_part field {other:?}; \
2793 supported: year, month, day, hour, minute, second, microsecond"
2794 ),
2795 });
2796 }
2797 };
2798 extract_field(field, &args[1])
2799}
2800
2801fn age(args: &[Value]) -> Result<Value, EvalError> {
2811 if args.is_empty() || args.len() > 2 {
2812 return Err(EvalError::TypeMismatch {
2813 detail: format!("age() takes 1 or 2 args, got {}", args.len()),
2814 });
2815 }
2816 if args.iter().any(|v| matches!(v, Value::Null)) {
2817 return Ok(Value::Null);
2818 }
2819 let to_micros = |v: &Value| -> Result<i64, EvalError> {
2822 match v {
2823 Value::Timestamp(t) => Ok(*t),
2824 Value::Date(d) => Ok(i64::from(*d) * 86_400_000_000),
2825 other => Err(EvalError::TypeMismatch {
2826 detail: format!("age() needs DATE or TIMESTAMP, got {:?}", other.data_type()),
2827 }),
2828 }
2829 };
2830 if args.len() == 1 {
2831 return Err(EvalError::TypeMismatch {
2832 detail: "single-arg age() is unsupported in v2.12 \
2833 (use age(CURRENT_DATE, t) explicitly)"
2834 .into(),
2835 });
2836 }
2837 let a = to_micros(&args[0])?;
2838 let b = to_micros(&args[1])?;
2839 let delta = a.checked_sub(b).ok_or(EvalError::TypeMismatch {
2840 detail: "age() subtraction overflows i64 microseconds".into(),
2841 })?;
2842 Ok(Value::Interval {
2843 months: 0,
2844 micros: delta,
2845 })
2846}
2847
2848fn inet_host(args: &[Value]) -> Result<Value, EvalError> {
2862 let s = match args {
2863 [Value::Text(s)] => s.clone(),
2864 [Value::Null] => return Ok(Value::Null),
2865 _ => {
2866 return Err(EvalError::TypeMismatch {
2867 detail: alloc::format!("host() takes one TEXT arg, got {} args", args.len()),
2868 });
2869 }
2870 };
2871 let host = s.split('/').next().unwrap_or("").to_string();
2872 Ok(Value::Text(host))
2873}
2874
2875fn inet_network(args: &[Value]) -> Result<Value, EvalError> {
2876 let s = match args {
2877 [Value::Text(s)] => s.clone(),
2878 [Value::Null] => return Ok(Value::Null),
2879 _ => {
2880 return Err(EvalError::TypeMismatch {
2881 detail: alloc::format!("network() takes one TEXT arg, got {} args", args.len()),
2882 });
2883 }
2884 };
2885 let mut split = s.splitn(2, '/');
2889 let host = split.next().unwrap_or("").to_string();
2890 let mask: u32 = split.next().and_then(|m| m.parse().ok()).unwrap_or(32);
2891 if !host.contains('.') {
2892 return Ok(Value::Text(s));
2894 }
2895 let octets: Vec<&str> = host.split('.').collect();
2896 if octets.len() != 4 {
2897 return Ok(Value::Text(s));
2898 }
2899 let keep_bytes = ((mask + 7) / 8) as usize;
2900 let mut out = alloc::string::String::new();
2901 for (i, oct) in octets.iter().enumerate() {
2902 if i > 0 {
2903 out.push('.');
2904 }
2905 if i < keep_bytes {
2906 out.push_str(oct);
2907 } else {
2908 out.push('0');
2909 }
2910 }
2911 out.push('/');
2912 out.push_str(&mask.to_string());
2913 Ok(Value::Text(out))
2914}
2915
2916fn inet_masklen(args: &[Value]) -> Result<Value, EvalError> {
2917 let s = match args {
2918 [Value::Text(s)] => s.clone(),
2919 [Value::Null] => return Ok(Value::Null),
2920 _ => {
2921 return Err(EvalError::TypeMismatch {
2922 detail: alloc::format!("masklen() takes one TEXT arg, got {} args", args.len()),
2923 });
2924 }
2925 };
2926 let mask: i32 = s
2927 .split_once('/')
2928 .and_then(|(_, m)| m.parse().ok())
2929 .unwrap_or(32);
2930 Ok(Value::Int(mask))
2931}
2932
2933struct InetNet {
2952 bytes: [u8; 16],
2953 family_bytes: u8,
2955 prefix_bits: u8,
2957}
2958
2959fn parse_inet_text(s: &str) -> Option<InetNet> {
2960 let mut split = s.splitn(2, '/');
2961 let host = split.next()?;
2962 let mask_str = split.next();
2963 if host.contains(':') {
2964 let bytes = parse_ipv6(host)?;
2965 let prefix_bits = match mask_str {
2966 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 128)?,
2967 None => 128,
2968 };
2969 let mut out = [0u8; 16];
2970 out.copy_from_slice(&bytes);
2971 Some(InetNet {
2972 bytes: out,
2973 family_bytes: 16,
2974 prefix_bits,
2975 })
2976 } else {
2977 let bytes = parse_ipv4(host)?;
2978 let prefix_bits = match mask_str {
2979 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 32)?,
2980 None => 32,
2981 };
2982 let mut out = [0u8; 16];
2983 out[..4].copy_from_slice(&bytes);
2984 Some(InetNet {
2985 bytes: out,
2986 family_bytes: 4,
2987 prefix_bits,
2988 })
2989 }
2990}
2991
2992fn parse_ipv4(s: &str) -> Option<[u8; 4]> {
2993 let parts: Vec<&str> = s.split('.').collect();
2994 if parts.len() != 4 {
2995 return None;
2996 }
2997 let mut out = [0u8; 4];
2998 for (i, p) in parts.iter().enumerate() {
2999 out[i] = p.parse::<u8>().ok()?;
3000 }
3001 Some(out)
3002}
3003
3004fn parse_ipv6(s: &str) -> Option<[u8; 16]> {
3005 let (head, tail) = match s.find("::") {
3007 Some(idx) => (&s[..idx], Some(&s[idx + 2..])),
3008 None => (s, None),
3009 };
3010 let head_groups: Vec<&str> = if head.is_empty() {
3011 Vec::new()
3012 } else {
3013 head.split(':').collect()
3014 };
3015 let tail_groups: Vec<&str> = match tail {
3016 Some(t) if !t.is_empty() => t.split(':').collect(),
3017 _ => Vec::new(),
3018 };
3019 let head_len = head_groups.len();
3020 let tail_len = tail_groups.len();
3021 if tail.is_none() {
3023 if head_len != 8 {
3024 return None;
3025 }
3026 } else if head_len + tail_len > 7 {
3027 return None;
3028 }
3029 let mut words = [0u16; 8];
3030 for (i, g) in head_groups.iter().enumerate() {
3031 words[i] = u16::from_str_radix(g, 16).ok()?;
3032 }
3033 let tail_start = 8 - tail_len;
3034 for (i, g) in tail_groups.iter().enumerate() {
3035 words[tail_start + i] = u16::from_str_radix(g, 16).ok()?;
3036 }
3037 let mut out = [0u8; 16];
3038 for (i, w) in words.iter().enumerate() {
3039 out[i * 2] = (w >> 8) as u8;
3040 out[i * 2 + 1] = (w & 0xff) as u8;
3041 }
3042 Some(out)
3043}
3044
3045fn network_prefix_eq(a: &InetNet, b: &InetNet, prefix_bits: u8) -> bool {
3048 let full_bytes = (prefix_bits / 8) as usize;
3049 if a.bytes[..full_bytes] != b.bytes[..full_bytes] {
3050 return false;
3051 }
3052 let extra = prefix_bits % 8;
3053 if extra == 0 {
3054 return true;
3055 }
3056 let mask: u8 = 0xff << (8 - extra);
3057 (a.bytes[full_bytes] & mask) == (b.bytes[full_bytes] & mask)
3058}
3059
3060fn inet_contained_eq(a: &InetNet, b: &InetNet) -> bool {
3062 if a.family_bytes != b.family_bytes {
3063 return false;
3064 }
3065 if a.prefix_bits < b.prefix_bits {
3066 return false;
3067 }
3068 network_prefix_eq(a, b, b.prefix_bits)
3069}
3070
3071fn inet_networks_equal(a: &InetNet, b: &InetNet) -> bool {
3074 if a.family_bytes != b.family_bytes {
3075 return false;
3076 }
3077 if a.prefix_bits != b.prefix_bits {
3078 return false;
3079 }
3080 network_prefix_eq(a, b, a.prefix_bits)
3081}
3082
3083fn inet_op_bool_result(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
3084 if matches!(l, Value::Null) || matches!(r, Value::Null) {
3085 return Ok(Value::Null);
3086 }
3087 let (lt, rt) = match (l, r) {
3088 (Value::Text(a), Value::Text(b)) => (a, b),
3089 _ => {
3090 return Err(EvalError::TypeMismatch {
3091 detail: format!(
3092 "inet operator requires TEXT/INET operands, got {:?} and {:?}",
3093 l.data_type(),
3094 r.data_type()
3095 ),
3096 });
3097 }
3098 };
3099 let a = parse_inet_text(lt).ok_or_else(|| EvalError::TypeMismatch {
3100 detail: format!("invalid inet text: {:?}", lt),
3101 })?;
3102 let b = parse_inet_text(rt).ok_or_else(|| EvalError::TypeMismatch {
3103 detail: format!("invalid inet text: {:?}", rt),
3104 })?;
3105 let result = match op {
3106 BinOp::InetContainedByEq => inet_contained_eq(&a, &b),
3107 BinOp::InetContainedBy => inet_contained_eq(&a, &b) && !inet_networks_equal(&a, &b),
3108 BinOp::InetContainsEq => inet_contained_eq(&b, &a),
3109 BinOp::InetContains => inet_contained_eq(&b, &a) && !inet_networks_equal(&a, &b),
3110 BinOp::InetOverlap => inet_contained_eq(&a, &b) || inet_contained_eq(&b, &a),
3111 _ => unreachable!("inet_op_bool_result called with non-inet op"),
3112 };
3113 Ok(Value::Bool(result))
3114}
3115
3116#[derive(Debug, Clone)]
3147enum ReNode {
3148 Literal(char),
3151 AnyChar,
3153 Class {
3155 members: Vec<ClassMember>,
3156 negated: bool,
3157 },
3158 Start,
3160 End,
3162 Quant {
3164 inner: Box<ReNode>,
3165 min: usize,
3166 max: Option<usize>,
3167 },
3168 Concat(Vec<ReNode>),
3170 Alt(Vec<ReNode>),
3172}
3173
3174#[derive(Debug, Clone)]
3175enum ClassMember {
3176 Single(char),
3177 Range(char, char),
3178}
3179
3180fn re_compile(pat: &str) -> Result<ReNode, EvalError> {
3181 let chars: Vec<char> = pat.chars().collect();
3182 let mut p = 0;
3183 let n = re_parse_alt(&chars, &mut p)?;
3184 if p != chars.len() {
3185 return Err(EvalError::TypeMismatch {
3186 detail: alloc::format!("regex compile: trailing chars at pos {p} in {pat:?}"),
3187 });
3188 }
3189 Ok(n)
3190}
3191
3192fn re_parse_alt(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3193 let mut branches = alloc::vec![re_parse_concat(chars, p)?];
3194 while *p < chars.len() && chars[*p] == '|' {
3195 *p += 1;
3196 branches.push(re_parse_concat(chars, p)?);
3197 }
3198 if branches.len() == 1 {
3199 Ok(branches.pop().unwrap())
3200 } else {
3201 Ok(ReNode::Alt(branches))
3202 }
3203}
3204
3205fn re_parse_concat(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3206 let mut items: Vec<ReNode> = Vec::new();
3207 while *p < chars.len() {
3208 let c = chars[*p];
3209 if c == '|' || c == ')' {
3210 break;
3211 }
3212 let atom = re_parse_atom(chars, p)?;
3213 let quantified = if *p < chars.len() {
3215 match chars[*p] {
3216 '*' => {
3217 *p += 1;
3218 if *p < chars.len() && chars[*p] == '?' {
3222 *p += 1;
3223 }
3224 ReNode::Quant {
3225 inner: Box::new(atom),
3226 min: 0,
3227 max: None,
3228 }
3229 }
3230 '+' => {
3231 *p += 1;
3232 if *p < chars.len() && chars[*p] == '?' {
3233 *p += 1;
3234 }
3235 ReNode::Quant {
3236 inner: Box::new(atom),
3237 min: 1,
3238 max: None,
3239 }
3240 }
3241 '?' => {
3242 *p += 1;
3243 ReNode::Quant {
3244 inner: Box::new(atom),
3245 min: 0,
3246 max: Some(1),
3247 }
3248 }
3249 _ => atom,
3250 }
3251 } else {
3252 atom
3253 };
3254 items.push(quantified);
3255 }
3256 if items.len() == 1 {
3257 Ok(items.pop().unwrap())
3258 } else {
3259 Ok(ReNode::Concat(items))
3260 }
3261}
3262
3263fn re_parse_atom(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3264 let c = chars[*p];
3265 match c {
3266 '(' => {
3267 *p += 1;
3268 let inner = re_parse_alt(chars, p)?;
3269 if *p >= chars.len() || chars[*p] != ')' {
3270 return Err(EvalError::TypeMismatch {
3271 detail: "regex compile: unmatched '('".into(),
3272 });
3273 }
3274 *p += 1;
3275 Ok(inner)
3276 }
3277 '[' => {
3278 *p += 1;
3279 let mut negated = false;
3280 if *p < chars.len() && chars[*p] == '^' {
3281 negated = true;
3282 *p += 1;
3283 }
3284 let mut members: Vec<ClassMember> = Vec::new();
3285 while *p < chars.len() && chars[*p] != ']' {
3286 let start = chars[*p];
3287 *p += 1;
3288 if *p + 1 < chars.len() && chars[*p] == '-' && chars[*p + 1] != ']' {
3289 let end = chars[*p + 1];
3290 *p += 2;
3291 members.push(ClassMember::Range(start, end));
3292 } else {
3293 members.push(ClassMember::Single(start));
3294 }
3295 }
3296 if *p >= chars.len() {
3297 return Err(EvalError::TypeMismatch {
3298 detail: "regex compile: unmatched '['".into(),
3299 });
3300 }
3301 *p += 1; Ok(ReNode::Class { members, negated })
3303 }
3304 '.' => {
3305 *p += 1;
3306 Ok(ReNode::AnyChar)
3307 }
3308 '^' => {
3309 *p += 1;
3310 Ok(ReNode::Start)
3311 }
3312 '$' => {
3313 *p += 1;
3314 Ok(ReNode::End)
3315 }
3316 '\\' => {
3317 *p += 1;
3318 if *p >= chars.len() {
3319 return Err(EvalError::TypeMismatch {
3320 detail: "regex compile: dangling backslash".into(),
3321 });
3322 }
3323 let esc = chars[*p];
3324 *p += 1;
3325 match esc {
3326 'd' => Ok(ReNode::Class {
3327 members: alloc::vec![ClassMember::Range('0', '9')],
3328 negated: false,
3329 }),
3330 'D' => Ok(ReNode::Class {
3331 members: alloc::vec![ClassMember::Range('0', '9')],
3332 negated: true,
3333 }),
3334 'w' => Ok(ReNode::Class {
3335 members: alloc::vec![
3336 ClassMember::Range('a', 'z'),
3337 ClassMember::Range('A', 'Z'),
3338 ClassMember::Range('0', '9'),
3339 ClassMember::Single('_'),
3340 ],
3341 negated: false,
3342 }),
3343 'W' => Ok(ReNode::Class {
3344 members: alloc::vec![
3345 ClassMember::Range('a', 'z'),
3346 ClassMember::Range('A', 'Z'),
3347 ClassMember::Range('0', '9'),
3348 ClassMember::Single('_'),
3349 ],
3350 negated: true,
3351 }),
3352 's' => Ok(ReNode::Class {
3353 members: alloc::vec![
3354 ClassMember::Single(' '),
3355 ClassMember::Single('\t'),
3356 ClassMember::Single('\n'),
3357 ClassMember::Single('\r'),
3358 ],
3359 negated: false,
3360 }),
3361 'S' => Ok(ReNode::Class {
3362 members: alloc::vec![
3363 ClassMember::Single(' '),
3364 ClassMember::Single('\t'),
3365 ClassMember::Single('\n'),
3366 ClassMember::Single('\r'),
3367 ],
3368 negated: true,
3369 }),
3370 other => Ok(ReNode::Literal(other)),
3371 }
3372 }
3373 other => {
3374 *p += 1;
3375 Ok(ReNode::Literal(other))
3376 }
3377 }
3378}
3379
3380fn class_matches(member: &ClassMember, c: char) -> bool {
3381 match member {
3382 ClassMember::Single(s) => *s == c,
3383 ClassMember::Range(a, b) => c >= *a && c <= *b,
3384 }
3385}
3386
3387fn re_match_at(node: &ReNode, s: &[char], pos: usize) -> Option<usize> {
3392 match node {
3393 ReNode::Literal(c) => {
3394 if s.get(pos).copied() == Some(*c) {
3395 Some(pos + 1)
3396 } else {
3397 None
3398 }
3399 }
3400 ReNode::AnyChar => {
3401 if pos < s.len() && s[pos] != '\n' {
3402 Some(pos + 1)
3403 } else {
3404 None
3405 }
3406 }
3407 ReNode::Class { members, negated } => {
3408 let c = *s.get(pos)?;
3409 let hit = members.iter().any(|m| class_matches(m, c));
3410 if hit ^ negated { Some(pos + 1) } else { None }
3411 }
3412 ReNode::Start => {
3413 if pos == 0 {
3414 Some(pos)
3415 } else {
3416 None
3417 }
3418 }
3419 ReNode::End => {
3420 if pos == s.len() {
3421 Some(pos)
3422 } else {
3423 None
3424 }
3425 }
3426 ReNode::Concat(items) => {
3427 let mut p = pos;
3428 for it in items {
3429 p = re_match_at(it, s, p)?;
3430 }
3431 Some(p)
3432 }
3433 ReNode::Alt(branches) => {
3434 for b in branches {
3435 if let Some(p) = re_match_at(b, s, pos) {
3436 return Some(p);
3437 }
3438 }
3439 None
3440 }
3441 ReNode::Quant { inner, min, max } => {
3442 let mut count = 0usize;
3447 let mut p = pos;
3448 loop {
3449 if let Some(cap) = max {
3450 if count >= *cap {
3451 break;
3452 }
3453 }
3454 match re_match_at(inner, s, p) {
3455 Some(np) if np > p => {
3456 p = np;
3457 count += 1;
3458 }
3459 _ => break,
3460 }
3461 }
3462 if count < *min {
3463 return None;
3464 }
3465 Some(p)
3466 }
3467 }
3468}
3469
3470fn re_find(node: &ReNode, s: &[char], from: usize) -> Option<(usize, usize)> {
3473 let mut start = from;
3474 loop {
3475 if let Some(end) = re_match_at(node, s, start) {
3476 return Some((start, end));
3477 }
3478 if start >= s.len() {
3479 return None;
3480 }
3481 start += 1;
3482 }
3483}
3484
3485fn regexp_matches(args: &[Value]) -> Result<Value, EvalError> {
3491 let (text, pat, all_matches) = match args.len() {
3492 2 => (text_arg(&args[0])?, text_arg(&args[1])?, false),
3493 3 => {
3494 let flags = text_arg(&args[2])?.unwrap_or_default();
3495 (
3496 text_arg(&args[0])?,
3497 text_arg(&args[1])?,
3498 flags.contains('g'),
3499 )
3500 }
3501 n => {
3502 return Err(EvalError::TypeMismatch {
3503 detail: alloc::format!("regexp_matches() takes 2 or 3 args, got {n}"),
3504 });
3505 }
3506 };
3507 let Some(text) = text else {
3508 return Ok(Value::Null);
3509 };
3510 let Some(pat) = pat else {
3511 return Ok(Value::Null);
3512 };
3513 let node = re_compile(&pat)?;
3514 let chars: Vec<char> = text.chars().collect();
3515 let mut out: Vec<Option<String>> = Vec::new();
3516 let mut from = 0usize;
3517 while let Some((s_pos, e_pos)) = re_find(&node, &chars, from) {
3518 out.push(Some(chars[s_pos..e_pos].iter().collect()));
3519 if !all_matches {
3520 break;
3521 }
3522 from = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3524 if from > chars.len() {
3525 break;
3526 }
3527 }
3528 Ok(Value::TextArray(out))
3529}
3530
3531fn regexp_replace(args: &[Value]) -> Result<Value, EvalError> {
3535 let (text, pat, repl, flags) = match args.len() {
3536 3 => (
3537 text_arg(&args[0])?,
3538 text_arg(&args[1])?,
3539 text_arg(&args[2])?,
3540 String::new(),
3541 ),
3542 4 => (
3543 text_arg(&args[0])?,
3544 text_arg(&args[1])?,
3545 text_arg(&args[2])?,
3546 text_arg(&args[3])?.unwrap_or_default(),
3547 ),
3548 n => {
3549 return Err(EvalError::TypeMismatch {
3550 detail: alloc::format!("regexp_replace() takes 3 or 4 args, got {n}"),
3551 });
3552 }
3553 };
3554 let Some(text) = text else {
3555 return Ok(Value::Null);
3556 };
3557 let Some(pat) = pat else {
3558 return Ok(Value::Null);
3559 };
3560 let Some(repl) = repl else {
3561 return Ok(Value::Null);
3562 };
3563 let global = flags.contains('g');
3564 let node = re_compile(&pat)?;
3565 let chars: Vec<char> = text.chars().collect();
3566 let mut out = String::with_capacity(text.len());
3567 let mut from = 0usize;
3568 loop {
3569 match re_find(&node, &chars, from) {
3570 Some((s_pos, e_pos)) => {
3571 out.extend(chars[from..s_pos].iter());
3572 out.push_str(&repl);
3573 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3574 from = step;
3575 if !global {
3576 if from <= chars.len() {
3577 out.extend(chars[from..].iter());
3578 }
3579 return Ok(Value::Text(out));
3580 }
3581 if from > chars.len() {
3582 break;
3583 }
3584 }
3585 None => {
3586 out.extend(chars[from..].iter());
3587 break;
3588 }
3589 }
3590 }
3591 Ok(Value::Text(out))
3592}
3593
3594fn regexp_split_to_array(args: &[Value]) -> Result<Value, EvalError> {
3597 if args.len() != 2 {
3598 return Err(EvalError::TypeMismatch {
3599 detail: alloc::format!("regexp_split_to_array() takes 2 args, got {}", args.len()),
3600 });
3601 }
3602 let text = text_arg(&args[0])?;
3603 let pat = text_arg(&args[1])?;
3604 let Some(text) = text else {
3605 return Ok(Value::Null);
3606 };
3607 let Some(pat) = pat else {
3608 return Ok(Value::Null);
3609 };
3610 let node = re_compile(&pat)?;
3611 let chars: Vec<char> = text.chars().collect();
3612 let mut out: Vec<Option<String>> = Vec::new();
3613 let mut piece_start = 0usize;
3614 let mut from = 0usize;
3615 loop {
3616 match re_find(&node, &chars, from) {
3617 Some((s_pos, e_pos)) => {
3618 let piece: String = chars[piece_start..s_pos].iter().collect();
3619 out.push(Some(piece));
3620 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3621 from = step;
3622 piece_start = step;
3623 if from > chars.len() {
3624 break;
3625 }
3626 }
3627 None => {
3628 let tail: String = chars[piece_start..].iter().collect();
3629 out.push(Some(tail));
3630 break;
3631 }
3632 }
3633 }
3634 Ok(Value::TextArray(out))
3635}
3636
3637fn text_arg(v: &Value) -> Result<Option<String>, EvalError> {
3640 match v {
3641 Value::Text(s) => Ok(Some(s.clone())),
3642 Value::Null => Ok(None),
3643 other => Err(EvalError::TypeMismatch {
3644 detail: alloc::format!(
3645 "regex function expects TEXT arg, got {:?}",
3646 other.data_type()
3647 ),
3648 }),
3649 }
3650}
3651
3652#[derive(Debug, Clone, Copy)]
3654enum TrimSide {
3655 Left,
3656 Right,
3657 Both,
3658}
3659
3660fn string_left_right(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
3664 if args.len() != 2 {
3665 return Err(EvalError::TypeMismatch {
3666 detail: alloc::format!("{fn_name}() takes 2 args, got {}", args.len()),
3667 });
3668 }
3669 if args.iter().any(|v| matches!(v, Value::Null)) {
3670 return Ok(Value::Null);
3671 }
3672 let s = value_to_format_text(&args[0]);
3673 let n = match &args[1] {
3674 Value::SmallInt(x) => i64::from(*x),
3675 Value::Int(x) => i64::from(*x),
3676 Value::BigInt(x) => *x,
3677 other => {
3678 return Err(EvalError::TypeMismatch {
3679 detail: alloc::format!(
3680 "{fn_name}(): n must be integer, got {:?}",
3681 other.data_type()
3682 ),
3683 });
3684 }
3685 };
3686 let chars: Vec<char> = s.chars().collect();
3687 let len = chars.len() as i64;
3688 if n == 0 {
3689 return Ok(Value::Text(String::new()));
3690 }
3691 let (start, end) = if is_left {
3692 if n > 0 {
3693 (0usize, (n.min(len)) as usize)
3694 } else {
3695 let drop = (-n).min(len);
3697 (0usize, (len - drop) as usize)
3698 }
3699 } else if n > 0 {
3700 let start = (len - n).max(0);
3702 (start as usize, len as usize)
3703 } else {
3704 let drop = (-n).min(len);
3706 (drop as usize, len as usize)
3707 };
3708 if start >= end {
3709 return Ok(Value::Text(String::new()));
3710 }
3711 Ok(Value::Text(chars[start..end].iter().collect()))
3712}
3713
3714fn value_cmp_for_min_max(a: &Value, b: &Value) -> core::cmp::Ordering {
3718 use core::cmp::Ordering;
3719 let a_int = match a {
3721 Value::SmallInt(x) => Some(i64::from(*x)),
3722 Value::Int(x) => Some(i64::from(*x)),
3723 Value::BigInt(x) => Some(*x),
3724 _ => None,
3725 };
3726 let b_int = match b {
3727 Value::SmallInt(x) => Some(i64::from(*x)),
3728 Value::Int(x) => Some(i64::from(*x)),
3729 Value::BigInt(x) => Some(*x),
3730 _ => None,
3731 };
3732 if let (Some(av), Some(bv)) = (a_int, b_int) {
3733 return av.cmp(&bv);
3734 }
3735 let a_f = value_to_f64(a);
3737 let b_f = value_to_f64(b);
3738 if let (Some(av), Some(bv)) = (a_f, b_f) {
3739 return av.partial_cmp(&bv).unwrap_or(Ordering::Equal);
3740 }
3741 match (a, b) {
3743 (Value::Text(av), Value::Text(bv)) => av.cmp(bv),
3744 (Value::Bytes(av), Value::Bytes(bv)) => av.cmp(bv),
3745 _ => Ordering::Equal,
3746 }
3747}
3748
3749fn value_to_f64(v: &Value) -> Option<f64> {
3750 match v {
3751 Value::Float(x) => Some(*x),
3752 Value::SmallInt(x) => Some(f64::from(*x)),
3753 Value::Int(x) => Some(f64::from(*x)),
3754 Value::BigInt(x) => Some(*x as f64),
3755 Value::Numeric { scaled, scale } => {
3756 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3757 }
3758 _ => None,
3759 }
3760}
3761
3762fn values_equal_for_nullif(a: &Value, b: &Value) -> bool {
3767 if a == b {
3769 return true;
3770 }
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(a), Some(b)) = (a_int, b_int) {
3785 return a == b;
3786 }
3787 let a_f = match a {
3789 Value::Float(x) => Some(*x),
3790 Value::SmallInt(x) => Some(f64::from(*x)),
3791 Value::Int(x) => Some(f64::from(*x)),
3792 Value::BigInt(x) => Some(*x as f64),
3793 Value::Numeric { scaled, scale } => {
3794 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3795 }
3796 _ => None,
3797 };
3798 let b_f = match b {
3799 Value::Float(x) => Some(*x),
3800 Value::SmallInt(x) => Some(f64::from(*x)),
3801 Value::Int(x) => Some(f64::from(*x)),
3802 Value::BigInt(x) => Some(*x as f64),
3803 Value::Numeric { scaled, scale } => {
3804 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3805 }
3806 _ => None,
3807 };
3808 if let (Some(a), Some(b)) = (a_f, b_f) {
3809 return a == b;
3810 }
3811 false
3812}
3813
3814fn f64_trunc(x: f64) -> f64 {
3819 if x.is_nan() || x.is_infinite() {
3820 return x;
3821 }
3822 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3823 return x;
3824 }
3825 (x as i64) as f64
3826}
3827
3828static PRNG_STATE: core::sync::atomic::AtomicU64 =
3833 core::sync::atomic::AtomicU64::new(0x2545_F491_4F6C_DD1D);
3834
3835fn prng_next_u64() -> u64 {
3841 use core::sync::atomic::Ordering;
3842 let mut x = PRNG_STATE.load(Ordering::Relaxed);
3843 loop {
3844 if x == 0 {
3845 x = 0x2545_F491_4F6C_DD1D;
3846 }
3847 let mut next = x;
3848 next ^= next << 13;
3849 next ^= next >> 7;
3850 next ^= next << 17;
3851 match PRNG_STATE.compare_exchange_weak(x, next, Ordering::Relaxed, Ordering::Relaxed) {
3852 Ok(_) => return next,
3853 Err(seen) => x = seen,
3854 }
3855 }
3856}
3857
3858fn prng_next_f64() -> f64 {
3860 let mantissa = prng_next_u64() >> 11;
3862 let denom = (1u64 << 53) as f64;
3863 mantissa as f64 / denom
3864}
3865
3866pub fn gen_random_uuid_bytes() -> [u8; 16] {
3873 let mut out = [0u8; 16];
3874 let hi = prng_next_u64().to_be_bytes();
3875 let lo = prng_next_u64().to_be_bytes();
3876 out[..8].copy_from_slice(&hi);
3877 out[8..].copy_from_slice(&lo);
3878 out[6] = (out[6] & 0x0f) | 0x40;
3880 out[8] = (out[8] & 0x3f) | 0x80;
3882 out
3883}
3884
3885fn f64_sqrt(x: f64) -> f64 {
3890 if x == 0.0 || x.is_nan() {
3891 return x;
3892 }
3893 if x.is_infinite() {
3894 return x;
3895 }
3896 let bits = x.to_bits();
3900 let exp = ((bits >> 52) & 0x7ff) as i64 - 1023;
3901 let new_exp = (exp / 2) + 1023;
3902 let mut guess = f64::from_bits(((new_exp as u64) & 0x7ff) << 52);
3903 for _ in 0..8 {
3905 guess = 0.5 * (guess + x / guess);
3906 }
3907 guess
3908}
3909
3910fn f64_exp(x: f64) -> f64 {
3915 if x.is_nan() {
3916 return x;
3917 }
3918 if x > 709.0 {
3919 return f64::INFINITY;
3920 }
3921 if x < -745.0 {
3922 return 0.0;
3923 }
3924 const LN2: f64 = 0.6931471805599453;
3926 let k = f64_round_half_away(x / LN2) as i32;
3927 let r = x - (k as f64) * LN2;
3928 let mut term = 1.0;
3930 let mut sum = 1.0;
3931 for n in 1..=20 {
3932 term *= r / (n as f64);
3933 sum += term;
3934 if term.abs() < 1e-18 {
3935 break;
3936 }
3937 }
3938 f64_powi(2.0, k) * sum
3940}
3941
3942fn f64_ln(x: f64) -> f64 {
3945 if x <= 0.0 {
3946 return f64::NAN;
3947 }
3948 if x == 1.0 {
3949 return 0.0;
3950 }
3951 const LN2: f64 = 0.6931471805599453;
3953 let mut k = 0i32;
3954 let mut m = x;
3955 while m >= 2.0 {
3956 m *= 0.5;
3957 k += 1;
3958 }
3959 while m < 1.0 {
3960 m *= 2.0;
3961 k -= 1;
3962 }
3963 let u = (m - 1.0) / (m + 1.0);
3966 let u2 = u * u;
3967 let mut term = u;
3968 let mut sum = u;
3969 for k_iter in 1..50 {
3970 term *= u2;
3971 let denom = (2 * k_iter + 1) as f64;
3972 sum += term / denom;
3973 if (term / denom).abs() < 1e-18 {
3974 break;
3975 }
3976 }
3977 2.0 * sum + (k as f64) * LN2
3978}
3979
3980fn f64_powi(base: f64, exp: i32) -> f64 {
3984 if exp == 0 {
3985 return 1.0;
3986 }
3987 let mut result = 1.0;
3988 let mut b = if exp > 0 { base } else { 1.0 / base };
3989 let mut e = exp.unsigned_abs();
3990 while e > 0 {
3991 if e & 1 == 1 {
3992 result *= b;
3993 }
3994 e >>= 1;
3995 if e > 0 {
3996 b *= b;
3997 }
3998 }
3999 result
4000}
4001
4002fn f64_round_half_away(x: f64) -> f64 {
4005 if x.is_nan() || x.is_infinite() {
4006 return x;
4007 }
4008 if x >= 0.0 {
4009 f64_floor(x + 0.5)
4010 } else {
4011 f64_ceil(x - 0.5)
4012 }
4013}
4014
4015fn f64_ceil(x: f64) -> f64 {
4020 if x.is_nan() || x.is_infinite() {
4021 return x;
4022 }
4023 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
4024 return x;
4025 }
4026 let trunc = (x as i64) as f64;
4027 if x > 0.0 && x != trunc {
4028 trunc + 1.0
4029 } else {
4030 trunc
4031 }
4032}
4033
4034fn f64_floor(x: f64) -> f64 {
4042 if x.is_nan() || x.is_infinite() {
4043 return x;
4044 }
4045 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
4048 return x;
4049 }
4050 let trunc = (x as i64) as f64;
4051 if x < 0.0 && x != trunc {
4052 trunc - 1.0
4053 } else {
4054 trunc
4055 }
4056}
4057
4058fn string_pad(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
4066 if args.len() != 2 && args.len() != 3 {
4067 return Err(EvalError::TypeMismatch {
4068 detail: alloc::format!("{fn_name}() takes 2 or 3 args, got {}", args.len()),
4069 });
4070 }
4071 if args.iter().any(|v| matches!(v, Value::Null)) {
4072 return Ok(Value::Null);
4073 }
4074 let s = value_to_format_text(&args[0]);
4075 let target = match &args[1] {
4076 Value::SmallInt(x) => i64::from(*x),
4077 Value::Int(x) => i64::from(*x),
4078 Value::BigInt(x) => *x,
4079 other => {
4080 return Err(EvalError::TypeMismatch {
4081 detail: alloc::format!(
4082 "{fn_name}(): length must be integer, got {:?}",
4083 other.data_type()
4084 ),
4085 });
4086 }
4087 };
4088 let fill = if args.len() == 3 {
4089 value_to_format_text(&args[2])
4090 } else {
4091 String::from(" ")
4092 };
4093 if target <= 0 {
4094 return Ok(Value::Text(String::new()));
4095 }
4096 let target = target as usize;
4097 let s_chars: Vec<char> = s.chars().collect();
4098 if s_chars.len() >= target {
4099 return Ok(Value::Text(s_chars[..target].iter().collect()));
4102 }
4103 if fill.is_empty() {
4104 return Ok(Value::Text(s));
4105 }
4106 let pad_needed = target - s_chars.len();
4107 let fill_chars: Vec<char> = fill.chars().collect();
4108 let mut padding = String::with_capacity(pad_needed * 4);
4109 for i in 0..pad_needed {
4110 padding.push(fill_chars[i % fill_chars.len()]);
4111 }
4112 if is_left {
4113 Ok(Value::Text(padding + &s))
4114 } else {
4115 Ok(Value::Text(s + &padding))
4116 }
4117}
4118
4119fn string_trim(args: &[Value], side: TrimSide, fn_name: &str) -> Result<Value, EvalError> {
4125 let (input, chars_str) = match args {
4126 [v] => (v.clone(), String::from(" ")),
4127 [v, c] => (v.clone(), {
4128 if matches!(c, Value::Null) {
4130 return Ok(Value::Null);
4131 }
4132 value_to_format_text(c)
4133 }),
4134 _ => {
4135 return Err(EvalError::TypeMismatch {
4136 detail: alloc::format!("{fn_name}() takes 1 or 2 args, got {}", args.len()),
4137 });
4138 }
4139 };
4140 if matches!(input, Value::Null) {
4141 return Ok(Value::Null);
4142 }
4143 let s = value_to_format_text(&input);
4144 let charset: alloc::collections::BTreeSet<char> = chars_str.chars().collect();
4145 let chars: Vec<char> = s.chars().collect();
4146 let mut start = 0usize;
4147 let mut end = chars.len();
4148 if matches!(side, TrimSide::Left | TrimSide::Both) {
4149 while start < end && charset.contains(&chars[start]) {
4150 start += 1;
4151 }
4152 }
4153 if matches!(side, TrimSide::Right | TrimSide::Both) {
4154 while end > start && charset.contains(&chars[end - 1]) {
4155 end -= 1;
4156 }
4157 }
4158 Ok(Value::Text(chars[start..end].iter().collect()))
4159}
4160
4161fn format_string(args: &[Value]) -> Result<Value, EvalError> {
4172 if args.is_empty() {
4173 return Err(EvalError::TypeMismatch {
4174 detail: "format() takes at least 1 arg (format string)".into(),
4175 });
4176 }
4177 let fmt = match &args[0] {
4178 Value::Text(s) => s.clone(),
4179 Value::Null => return Ok(Value::Null),
4180 other => {
4181 return Err(EvalError::TypeMismatch {
4182 detail: format!(
4183 "format(): first arg must be text, got {:?}",
4184 other.data_type()
4185 ),
4186 });
4187 }
4188 };
4189 let arg_values = &args[1..];
4190 let mut out = String::new();
4191 let mut chars = fmt.chars().peekable();
4192 let mut implicit_cursor: usize = 0;
4196 while let Some(c) = chars.next() {
4197 if c != '%' {
4198 out.push(c);
4199 continue;
4200 }
4201 let mut explicit_pos: Option<usize> = None;
4203 let mut digit_buf = String::new();
4205 while let Some(&d) = chars.peek() {
4206 if d.is_ascii_digit() {
4207 digit_buf.push(d);
4208 chars.next();
4209 } else {
4210 break;
4211 }
4212 }
4213 if !digit_buf.is_empty() && matches!(chars.peek(), Some(&'$')) {
4214 chars.next(); explicit_pos =
4216 Some(
4217 digit_buf
4218 .parse::<usize>()
4219 .map_err(|_| EvalError::TypeMismatch {
4220 detail: format!("format(): invalid arg position {digit_buf:?}"),
4221 })?,
4222 );
4223 digit_buf.clear();
4224 }
4225 let spec = match chars.next() {
4227 Some(c) => c,
4228 None => {
4229 return Err(EvalError::TypeMismatch {
4230 detail: "format(): trailing `%` with no specifier".into(),
4231 });
4232 }
4233 };
4234 let _ = digit_buf;
4241 if spec == '%' {
4242 out.push('%');
4243 continue;
4244 }
4245 let arg_index = match explicit_pos {
4246 Some(p) => p.saturating_sub(1),
4247 None => {
4248 let i = implicit_cursor;
4249 implicit_cursor += 1;
4250 i
4251 }
4252 };
4253 let arg = arg_values.get(arg_index).cloned().unwrap_or(Value::Null);
4254 match spec {
4255 's' => match arg {
4256 Value::Null => {} v => out.push_str(&value_to_format_text(&v)),
4258 },
4259 'I' => match arg {
4260 Value::Null => {
4261 return Err(EvalError::TypeMismatch {
4262 detail: "format(): NULL is not a valid identifier (%I)".into(),
4263 });
4264 }
4265 v => {
4266 let s = value_to_format_text(&v);
4267 out.push('"');
4268 for ch in s.chars() {
4269 if ch == '"' {
4270 out.push('"');
4271 out.push('"');
4272 } else {
4273 out.push(ch);
4274 }
4275 }
4276 out.push('"');
4277 }
4278 },
4279 'L' => match arg {
4280 Value::Null => out.push_str("NULL"),
4281 v => {
4282 let s = value_to_format_text(&v);
4283 out.push('\'');
4284 for ch in s.chars() {
4285 if ch == '\'' {
4286 out.push('\'');
4287 out.push('\'');
4288 } else {
4289 out.push(ch);
4290 }
4291 }
4292 out.push('\'');
4293 }
4294 },
4295 other => {
4296 return Err(EvalError::TypeMismatch {
4297 detail: format!(
4298 "format(): unknown specifier '%{other}' \
4299 (v7.17 supports %s %I %L %%)"
4300 ),
4301 });
4302 }
4303 }
4304 }
4305 Ok(Value::Text(out))
4306}
4307
4308fn pg_typeof_name(v: &Value) -> &'static str {
4314 match v {
4315 Value::SmallInt(_) => "smallint",
4316 Value::Int(_) => "integer",
4317 Value::BigInt(_) => "bigint",
4318 Value::Float(_) => "double precision",
4319 Value::Text(_) => "text",
4320 Value::Bool(_) => "boolean",
4321 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => "vector",
4322 Value::Numeric { .. } => "numeric",
4323 Value::Date(_) => "date",
4324 Value::Timestamp(_) => "timestamp without time zone",
4325 Value::Interval { .. } => "interval",
4326 Value::Json(_) => {
4327 "json"
4338 }
4339 Value::Bytes(_) => "bytea",
4340 Value::TextArray(_) => "text[]",
4341 Value::IntArray(_) => "integer[]",
4342 Value::BigIntArray(_) => "bigint[]",
4343 Value::TsVector(_) => "tsvector",
4344 Value::TsQuery(_) => "tsquery",
4345 Value::Uuid(_) => "uuid",
4346 Value::Null => "unknown",
4347 _ => "unknown",
4350 }
4351}
4352
4353fn value_to_format_text(v: &Value) -> String {
4354 match v {
4355 Value::Text(s) | Value::Json(s) => s.clone(),
4356 Value::SmallInt(n) => n.to_string(),
4357 Value::Int(n) => n.to_string(),
4358 Value::BigInt(n) => n.to_string(),
4359 Value::Float(x) => format!("{x}"),
4360 Value::Bool(b) => {
4361 if *b {
4362 "t".into()
4363 } else {
4364 "f".into()
4365 }
4366 }
4367 Value::Null => String::new(),
4368 other => format!("{other:?}"),
4369 }
4370}
4371
4372fn to_char(args: &[Value]) -> Result<Value, EvalError> {
4373 use core::fmt::Write as _;
4374 if args.len() != 2 {
4375 return Err(EvalError::TypeMismatch {
4376 detail: format!("to_char() takes 2 args, got {}", args.len()),
4377 });
4378 }
4379 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4380 return Ok(Value::Null);
4381 }
4382 let Value::Text(fmt) = &args[1] else {
4383 return Err(EvalError::TypeMismatch {
4384 detail: format!(
4385 "to_char() needs a text format, got {:?}",
4386 args[1].data_type()
4387 ),
4388 });
4389 };
4390 let (days, day_micros) = match &args[0] {
4391 Value::Date(d) => (*d, 0_i64),
4392 Value::Timestamp(t) => {
4393 let days = t.div_euclid(86_400_000_000);
4394 (
4395 i32::try_from(days).unwrap_or(i32::MAX),
4396 t.rem_euclid(86_400_000_000),
4397 )
4398 }
4399 other => {
4400 return Err(EvalError::TypeMismatch {
4401 detail: format!(
4402 "to_char() needs DATE or TIMESTAMP, got {:?}",
4403 other.data_type()
4404 ),
4405 });
4406 }
4407 };
4408 let (y, mo, d) = civil_from_days(days);
4409 let secs = day_micros / 1_000_000;
4410 let frac = day_micros % 1_000_000;
4411 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4415 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4416 let ss = u32::try_from(secs % 60).unwrap_or(0);
4417 let hh12 = match hh24 % 12 {
4418 0 => 12,
4419 x => x,
4420 };
4421 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4422 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);
4426 let bytes = fmt.as_bytes();
4427 let mut i = 0;
4428 while i < bytes.len() {
4430 let rest = &bytes[i..];
4432 if rest.starts_with(b"YYYY") {
4433 let _ = write!(out, "{y:04}");
4434 i += 4;
4435 } else if rest.starts_with(b"YY") {
4436 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4437 let yy = (y.rem_euclid(100)) as u32;
4438 let _ = write!(out, "{yy:02}");
4439 i += 2;
4440 } else if rest.starts_with(b"Month") {
4441 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4442 i += 5;
4443 } else if rest.starts_with(b"Mon") {
4444 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4445 i += 3;
4446 } else if rest.starts_with(b"MM") {
4447 let _ = write!(out, "{mo:02}");
4448 i += 2;
4449 } else if rest.starts_with(b"DD") {
4450 let _ = write!(out, "{d:02}");
4451 i += 2;
4452 } else if rest.starts_with(b"HH24") {
4453 let _ = write!(out, "{hh24:02}");
4454 i += 4;
4455 } else if rest.starts_with(b"HH12") {
4456 let _ = write!(out, "{hh12:02}");
4457 i += 4;
4458 } else if rest.starts_with(b"MI") {
4459 let _ = write!(out, "{mi:02}");
4460 i += 2;
4461 } else if rest.starts_with(b"SS") {
4462 let _ = write!(out, "{ss:02}");
4463 i += 2;
4464 } else if rest.starts_with(b"MS") {
4465 let _ = write!(out, "{ms:03}");
4466 i += 2;
4467 } else if rest.starts_with(b"US") {
4468 let _ = write!(out, "{us:06}");
4469 i += 2;
4470 } else if rest.starts_with(b"AM") || rest.starts_with(b"PM") {
4471 out.push_str(ampm);
4472 i += 2;
4473 } else {
4474 out.push(bytes[i] as char);
4476 i += 1;
4477 }
4478 }
4479 Ok(Value::Text(out))
4480}
4481
4482const MONTH_FULL: [&str; 12] = [
4483 "January",
4484 "February",
4485 "March",
4486 "April",
4487 "May",
4488 "June",
4489 "July",
4490 "August",
4491 "September",
4492 "October",
4493 "November",
4494 "December",
4495];
4496const MONTH_ABBR: [&str; 12] = [
4497 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
4498];
4499
4500fn date_format_mysql(args: &[Value]) -> Result<Value, EvalError> {
4519 use core::fmt::Write as _;
4520 if args.len() != 2 {
4521 return Err(EvalError::TypeMismatch {
4522 detail: format!("date_format() takes 2 args, got {}", args.len()),
4523 });
4524 }
4525 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4526 return Ok(Value::Null);
4527 }
4528 let Value::Text(fmt) = &args[1] else {
4529 return Err(EvalError::TypeMismatch {
4530 detail: format!(
4531 "date_format() needs a text format, got {:?}",
4532 args[1].data_type()
4533 ),
4534 });
4535 };
4536 let (days, day_micros) = match &args[0] {
4537 Value::Date(d) => (*d, 0_i64),
4538 Value::Timestamp(t) => {
4539 let days = t.div_euclid(86_400_000_000);
4540 (
4541 i32::try_from(days).unwrap_or(i32::MAX),
4542 t.rem_euclid(86_400_000_000),
4543 )
4544 }
4545 other => {
4546 return Err(EvalError::TypeMismatch {
4547 detail: format!(
4548 "date_format() needs DATE or TIMESTAMP, got {:?}",
4549 other.data_type()
4550 ),
4551 });
4552 }
4553 };
4554 let (y, mo, d) = civil_from_days(days);
4555 let secs = day_micros / 1_000_000;
4556 let frac = day_micros % 1_000_000;
4557 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4558 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4559 let ss = u32::try_from(secs % 60).unwrap_or(0);
4560 let hh12 = match hh24 % 12 {
4561 0 => 12,
4562 x => x,
4563 };
4564 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4565 let us = u32::try_from(frac).unwrap_or(0);
4566
4567 let mut out = String::with_capacity(fmt.len() + 8);
4568 let bytes = fmt.as_bytes();
4569 let mut i = 0;
4570 while i < bytes.len() {
4571 if bytes[i] != b'%' {
4572 out.push(bytes[i] as char);
4573 i += 1;
4574 continue;
4575 }
4576 if i + 1 >= bytes.len() {
4577 out.push('%');
4579 i += 1;
4580 continue;
4581 }
4582 let token = bytes[i + 1];
4583 match token {
4584 b'Y' => {
4585 let _ = write!(out, "{y:04}");
4586 }
4587 b'y' => {
4588 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4589 let yy = (y.rem_euclid(100)) as u32;
4590 let _ = write!(out, "{yy:02}");
4591 }
4592 b'm' => {
4593 let _ = write!(out, "{mo:02}");
4594 }
4595 b'c' => {
4596 let _ = write!(out, "{mo}");
4597 }
4598 b'd' => {
4599 let _ = write!(out, "{d:02}");
4600 }
4601 b'e' => {
4602 let _ = write!(out, "{d}");
4603 }
4604 b'H' => {
4605 let _ = write!(out, "{hh24:02}");
4606 }
4607 b'h' | b'I' => {
4608 let _ = write!(out, "{hh12:02}");
4609 }
4610 b'i' => {
4611 let _ = write!(out, "{mi:02}");
4614 }
4615 b's' | b'S' => {
4616 let _ = write!(out, "{ss:02}");
4617 }
4618 b'f' => {
4619 let _ = write!(out, "{us:06}");
4620 }
4621 b'p' => {
4622 out.push_str(ampm);
4623 }
4624 b'M' => {
4625 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4626 }
4627 b'b' => {
4628 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4629 }
4630 b'%' => {
4631 out.push('%');
4632 }
4633 other => {
4634 out.push(other as char);
4637 }
4638 }
4639 i += 2;
4640 }
4641 Ok(Value::Text(out))
4642}
4643
4644fn unix_timestamp_of(args: &[Value]) -> Result<Value, EvalError> {
4651 if args.len() != 1 {
4652 return Err(EvalError::TypeMismatch {
4653 detail: format!("unix_timestamp() takes 0 or 1 arg, got {}", args.len()),
4654 });
4655 }
4656 match &args[0] {
4657 Value::Null => Ok(Value::Null),
4658 Value::Timestamp(t) => Ok(Value::BigInt(t.div_euclid(1_000_000))),
4659 Value::Date(d) => Ok(Value::BigInt(i64::from(*d) * 86_400)),
4660 other => Err(EvalError::TypeMismatch {
4661 detail: format!(
4662 "unix_timestamp() needs DATE or TIMESTAMP, got {:?}",
4663 other.data_type()
4664 ),
4665 }),
4666 }
4667}
4668
4669fn from_unixtime(args: &[Value]) -> Result<Value, EvalError> {
4673 if !(1..=2).contains(&args.len()) {
4674 return Err(EvalError::TypeMismatch {
4675 detail: format!("from_unixtime() takes 1 or 2 args, got {}", args.len()),
4676 });
4677 }
4678 if args.iter().any(|v| matches!(v, Value::Null)) {
4679 return Ok(Value::Null);
4680 }
4681 let secs: i64 = match &args[0] {
4682 Value::SmallInt(n) => i64::from(*n),
4683 Value::Int(n) => i64::from(*n),
4684 Value::BigInt(n) => *n,
4685 Value::Float(x) => *x as i64,
4686 Value::Numeric { scaled, scale } => {
4687 let denom = 10_i128.pow(u32::from(*scale));
4688 i64::try_from(scaled.div_euclid(denom)).unwrap_or(i64::MAX)
4689 }
4690 other => {
4691 return Err(EvalError::TypeMismatch {
4692 detail: format!(
4693 "from_unixtime() needs a numeric epoch second count, got {:?}",
4694 other.data_type()
4695 ),
4696 });
4697 }
4698 };
4699 let ts = Value::Timestamp(secs.saturating_mul(1_000_000));
4700 if args.len() == 1 {
4701 Ok(ts)
4702 } else {
4703 date_format_mysql(&[ts, args[1].clone()])
4704 }
4705}
4706
4707fn date_trunc(args: &[Value]) -> Result<Value, EvalError> {
4712 if args.len() != 2 {
4713 return Err(EvalError::TypeMismatch {
4714 detail: format!("date_trunc() takes 2 args, got {}", args.len()),
4715 });
4716 }
4717 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4718 return Ok(Value::Null);
4719 }
4720 let Value::Text(unit) = &args[0] else {
4721 return Err(EvalError::TypeMismatch {
4722 detail: format!(
4723 "date_trunc() needs a text unit, got {:?}",
4724 args[0].data_type()
4725 ),
4726 });
4727 };
4728 let micros = match &args[1] {
4731 Value::Timestamp(t) => *t,
4732 Value::Date(d) => i64::from(*d) * 86_400_000_000,
4733 other => {
4734 return Err(EvalError::TypeMismatch {
4735 detail: format!(
4736 "date_trunc() needs DATE or TIMESTAMP, got {:?}",
4737 other.data_type()
4738 ),
4739 });
4740 }
4741 };
4742 let unit_lc = unit.to_ascii_lowercase();
4743 let days = micros.div_euclid(86_400_000_000);
4744 let day_micros = micros.rem_euclid(86_400_000_000);
4745 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
4746 let (y, m, _) = civil_from_days(day_i32);
4747 let truncated = match unit_lc.as_str() {
4748 "year" => i64::from(days_from_civil(y, 1, 1)) * 86_400_000_000,
4749 "month" => i64::from(days_from_civil(y, m, 1)) * 86_400_000_000,
4750 "day" => days * 86_400_000_000,
4751 "hour" => days * 86_400_000_000 + (day_micros / 3_600_000_000) * 3_600_000_000,
4752 "minute" => days * 86_400_000_000 + (day_micros / 60_000_000) * 60_000_000,
4753 "second" => days * 86_400_000_000 + (day_micros / 1_000_000) * 1_000_000,
4754 other => {
4755 return Err(EvalError::TypeMismatch {
4756 detail: format!(
4757 "unknown date_trunc unit {other:?}; \
4758 supported: year, month, day, hour, minute, second"
4759 ),
4760 });
4761 }
4762 };
4763 Ok(Value::Timestamp(truncated))
4764}
4765
4766pub fn cast_value(v: Value, target: CastTarget) -> Result<Value, EvalError> {
4768 if matches!(v, Value::Null) {
4769 return Ok(Value::Null);
4770 }
4771 match target {
4772 CastTarget::Vector => cast_to_vector(v),
4773 CastTarget::Text => Ok(Value::Text(value_to_text(&v))),
4774 CastTarget::Int => cast_numeric_to_int(v),
4775 CastTarget::BigInt => cast_numeric_to_bigint(v),
4776 CastTarget::Float => cast_numeric_to_float(v),
4777 CastTarget::Bool => cast_to_bool(v),
4778 CastTarget::Date => cast_to_date(v),
4779 CastTarget::Timestamp | CastTarget::Timestamptz => cast_to_timestamp(v),
4782 CastTarget::Interval => cast_to_interval(v),
4786 CastTarget::Json | CastTarget::Jsonb => match v {
4790 Value::Json(s) => Ok(Value::Json(s)),
4791 Value::Text(s) => Ok(Value::Json(s)),
4792 other => Err(EvalError::TypeMismatch {
4793 detail: alloc::format!(
4794 "::json / ::jsonb only accepts TEXT-shape inputs, got {:?}",
4795 other.data_type()
4796 ),
4797 }),
4798 },
4799 CastTarget::RegType | CastTarget::RegClass => match v {
4817 Value::Text(s) => {
4818 let bare = s.rsplit('.').next().unwrap_or(&s).to_string();
4823 Ok(Value::Text(bare))
4824 }
4825 Value::Int(n) => Ok(Value::Text(alloc::format!("{n}"))),
4826 Value::BigInt(n) => Ok(Value::Text(alloc::format!("{n}"))),
4827 other => Err(EvalError::TypeMismatch {
4828 detail: alloc::format!(
4829 "::regtype / ::regclass accepts TEXT (name) or integer (oid), got {:?}",
4830 other.data_type()
4831 ),
4832 }),
4833 },
4834 CastTarget::TextArray => match v {
4838 Value::TextArray(items) => Ok(Value::TextArray(items)),
4839 Value::Text(s) => decode_text_array_external(&s).map(Value::TextArray),
4840 other => Err(EvalError::TypeMismatch {
4841 detail: alloc::format!(
4842 "::TEXT[] only accepts TEXT / TEXT[] inputs, got {:?}",
4843 other.data_type()
4844 ),
4845 }),
4846 },
4847 CastTarget::IntArray => cast_to_int_array(v),
4851 CastTarget::BigIntArray => cast_to_bigint_array(v),
4852 CastTarget::TsVector => match v {
4859 Value::TsVector(items) => Ok(Value::TsVector(items)),
4860 Value::Text(s) => decode_tsvector_external(&s).map(Value::TsVector),
4861 other => Err(EvalError::TypeMismatch {
4862 detail: alloc::format!(
4863 "::tsvector only accepts TEXT / tsvector inputs, got {:?}",
4864 other.data_type()
4865 ),
4866 }),
4867 },
4868 CastTarget::TsQuery => match v {
4869 Value::TsQuery(ast) => Ok(Value::TsQuery(ast)),
4870 Value::Text(s) => decode_tsquery_external(&s).map(Value::TsQuery),
4871 other => Err(EvalError::TypeMismatch {
4872 detail: alloc::format!(
4873 "::tsquery only accepts TEXT / tsquery inputs, got {:?}",
4874 other.data_type()
4875 ),
4876 }),
4877 },
4878 CastTarget::Uuid => match v {
4883 Value::Uuid(b) => Ok(Value::Uuid(b)),
4884 Value::Text(s) => match spg_storage::parse_uuid_str(&s) {
4885 Some(b) => Ok(Value::Uuid(b)),
4886 None => Err(EvalError::TypeMismatch {
4887 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4888 }),
4889 },
4890 other => Err(EvalError::TypeMismatch {
4891 detail: alloc::format!(
4892 "::uuid only accepts TEXT / uuid inputs, got {:?}",
4893 other.data_type()
4894 ),
4895 }),
4896 },
4897 CastTarget::Bytea => match v {
4903 Value::Bytes(b) => Ok(Value::Bytes(b)),
4904 Value::Text(s) => match crate::decode_bytea_literal(&s) {
4905 Ok(b) => Ok(Value::Bytes(b)),
4906 Err(msg) => Err(EvalError::TypeMismatch {
4907 detail: alloc::format!("invalid input syntax for type bytea: {msg}"),
4908 }),
4909 },
4910 other => Err(EvalError::TypeMismatch {
4911 detail: alloc::format!(
4912 "::bytea only accepts TEXT / bytea inputs, got {:?}",
4913 other.data_type()
4914 ),
4915 }),
4916 },
4917 }
4918}
4919
4920fn cast_to_int_array(v: Value) -> Result<Value, EvalError> {
4921 match v {
4922 Value::IntArray(items) => Ok(Value::IntArray(items)),
4923 Value::BigIntArray(items) => {
4924 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4925 for item in items {
4926 match item {
4927 None => out.push(None),
4928 Some(n) => match i32::try_from(n) {
4929 Ok(x) => out.push(Some(x)),
4930 Err(_) => {
4931 return Err(EvalError::TypeMismatch {
4932 detail: alloc::format!("::INT[] element {n} overflows i32"),
4933 });
4934 }
4935 },
4936 }
4937 }
4938 Ok(Value::IntArray(out))
4939 }
4940 Value::Text(s) => decode_int_array_external(&s).map(Value::IntArray),
4941 Value::TextArray(items) => {
4942 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4943 for item in items {
4944 match item {
4945 None => out.push(None),
4946 Some(s) => match s.parse::<i32>() {
4947 Ok(n) => out.push(Some(n)),
4948 Err(_) => {
4949 return Err(EvalError::TypeMismatch {
4950 detail: alloc::format!("::INT[] cannot parse {s:?}"),
4951 });
4952 }
4953 },
4954 }
4955 }
4956 Ok(Value::IntArray(out))
4957 }
4958 other => Err(EvalError::TypeMismatch {
4959 detail: alloc::format!("::INT[] does not accept {:?}", other.data_type()),
4960 }),
4961 }
4962}
4963
4964fn cast_to_bigint_array(v: Value) -> Result<Value, EvalError> {
4965 match v {
4966 Value::BigIntArray(items) => Ok(Value::BigIntArray(items)),
4967 Value::IntArray(items) => Ok(Value::BigIntArray(
4968 items.into_iter().map(|x| x.map(i64::from)).collect(),
4969 )),
4970 Value::Text(s) => decode_bigint_array_external(&s).map(Value::BigIntArray),
4971 Value::TextArray(items) => {
4972 let mut out: Vec<Option<i64>> = Vec::with_capacity(items.len());
4973 for item in items {
4974 match item {
4975 None => out.push(None),
4976 Some(s) => match s.parse::<i64>() {
4977 Ok(n) => out.push(Some(n)),
4978 Err(_) => {
4979 return Err(EvalError::TypeMismatch {
4980 detail: alloc::format!("::BIGINT[] cannot parse {s:?}"),
4981 });
4982 }
4983 },
4984 }
4985 }
4986 Ok(Value::BigIntArray(out))
4987 }
4988 other => Err(EvalError::TypeMismatch {
4989 detail: alloc::format!("::BIGINT[] does not accept {:?}", other.data_type()),
4990 }),
4991 }
4992}
4993
4994fn decode_int_array_external(s: &str) -> Result<Vec<Option<i32>>, EvalError> {
4995 let trimmed = s.trim();
4996 let inner = trimmed
4997 .strip_prefix('{')
4998 .and_then(|x| x.strip_suffix('}'))
4999 .ok_or_else(|| EvalError::TypeMismatch {
5000 detail: alloc::format!("INT[] literal {s:?} must be enclosed in '{{...}}'"),
5001 })?;
5002 if inner.trim().is_empty() {
5003 return Ok(Vec::new());
5004 }
5005 inner
5006 .split(',')
5007 .map(|part| {
5008 let p = part.trim();
5009 if p.eq_ignore_ascii_case("NULL") {
5010 Ok(None)
5011 } else {
5012 p.parse::<i32>()
5013 .map(Some)
5014 .map_err(|_| EvalError::TypeMismatch {
5015 detail: alloc::format!("INT[] element {p:?} is not an i32"),
5016 })
5017 }
5018 })
5019 .collect()
5020}
5021
5022fn decode_bigint_array_external(s: &str) -> Result<Vec<Option<i64>>, EvalError> {
5023 let trimmed = s.trim();
5024 let inner = trimmed
5025 .strip_prefix('{')
5026 .and_then(|x| x.strip_suffix('}'))
5027 .ok_or_else(|| EvalError::TypeMismatch {
5028 detail: alloc::format!("BIGINT[] literal {s:?} must be enclosed in '{{...}}'"),
5029 })?;
5030 if inner.trim().is_empty() {
5031 return Ok(Vec::new());
5032 }
5033 inner
5034 .split(',')
5035 .map(|part| {
5036 let p = part.trim();
5037 if p.eq_ignore_ascii_case("NULL") {
5038 Ok(None)
5039 } else {
5040 p.parse::<i64>()
5041 .map(Some)
5042 .map_err(|_| EvalError::TypeMismatch {
5043 detail: alloc::format!("BIGINT[] element {p:?} is not an i64"),
5044 })
5045 }
5046 })
5047 .collect()
5048}
5049
5050fn decode_text_array_external(s: &str) -> Result<Vec<Option<String>>, EvalError> {
5055 let trimmed = s.trim();
5056 let inner = trimmed
5057 .strip_prefix('{')
5058 .and_then(|x| x.strip_suffix('}'))
5059 .ok_or_else(|| EvalError::TypeMismatch {
5060 detail: alloc::format!("TEXT[] literal {s:?} must be enclosed in '{{...}}'"),
5061 })?;
5062 let mut out: Vec<Option<String>> = Vec::new();
5063 if inner.trim().is_empty() {
5064 return Ok(out);
5065 }
5066 let bytes = inner.as_bytes();
5067 let mut i = 0;
5068 while i <= bytes.len() {
5069 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
5070 i += 1;
5071 }
5072 if i < bytes.len() && bytes[i] == b'"' {
5073 i += 1;
5074 let mut buf = String::new();
5075 while i < bytes.len() && bytes[i] != b'"' {
5076 if bytes[i] == b'\\' && i + 1 < bytes.len() {
5077 buf.push(bytes[i + 1] as char);
5078 i += 2;
5079 } else {
5080 buf.push(bytes[i] as char);
5081 i += 1;
5082 }
5083 }
5084 if i >= bytes.len() {
5085 return Err(EvalError::TypeMismatch {
5086 detail: "unterminated quoted element in TEXT[] literal".into(),
5087 });
5088 }
5089 i += 1;
5090 out.push(Some(buf));
5091 } else {
5092 let start = i;
5093 while i < bytes.len() && bytes[i] != b',' {
5094 i += 1;
5095 }
5096 let raw = inner[start..i].trim();
5097 if raw.eq_ignore_ascii_case("NULL") {
5098 out.push(None);
5099 } else {
5100 out.push(Some(raw.to_string()));
5101 }
5102 }
5103 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
5104 i += 1;
5105 }
5106 if i >= bytes.len() {
5107 break;
5108 }
5109 if bytes[i] != b',' {
5110 return Err(EvalError::TypeMismatch {
5111 detail: "expected ',' between TEXT[] elements".into(),
5112 });
5113 }
5114 i += 1;
5115 }
5116 Ok(out)
5117}
5118
5119fn cast_to_interval(v: Value) -> Result<Value, EvalError> {
5120 match v {
5121 Value::Interval { months, micros } => Ok(Value::Interval { months, micros }),
5122 Value::Text(s) => {
5123 let (months, micros) = spg_sql::parser::parse_interval_text(&s).ok_or_else(|| {
5124 EvalError::TypeMismatch {
5125 detail: alloc::format!("cannot parse {s:?} as INTERVAL"),
5126 }
5127 })?;
5128 Ok(Value::Interval { months, micros })
5129 }
5130 other => Err(EvalError::TypeMismatch {
5131 detail: alloc::format!(
5132 "::INTERVAL only accepts TEXT-shape inputs, got {:?}",
5133 other.data_type()
5134 ),
5135 }),
5136 }
5137}
5138
5139fn cast_to_date(v: Value) -> Result<Value, EvalError> {
5140 match v {
5141 Value::Date(d) => Ok(Value::Date(d)),
5142 Value::Int(n) => Ok(Value::Date(n)),
5145 Value::BigInt(n) => {
5146 i32::try_from(n)
5147 .map(Value::Date)
5148 .map_err(|_| EvalError::TypeMismatch {
5149 detail: "bigint days-since-epoch out of DATE range".into(),
5150 })
5151 }
5152 Value::Timestamp(t) => {
5154 let days = t.div_euclid(86_400_000_000);
5155 i32::try_from(days)
5156 .map(Value::Date)
5157 .map_err(|_| EvalError::TypeMismatch {
5158 detail: "timestamp out of DATE range".into(),
5159 })
5160 }
5161 Value::Text(s) => parse_date_literal(&s)
5162 .map(Value::Date)
5163 .ok_or(EvalError::TypeMismatch {
5164 detail: format!("cannot parse {s:?} as DATE (expected YYYY-MM-DD)"),
5165 }),
5166 other => Err(EvalError::TypeMismatch {
5167 detail: format!("cannot cast {:?} to DATE", other.data_type()),
5168 }),
5169 }
5170}
5171
5172fn cast_to_timestamp(v: Value) -> Result<Value, EvalError> {
5173 match v {
5174 Value::Timestamp(t) => Ok(Value::Timestamp(t)),
5175 Value::Int(n) => Ok(Value::Timestamp(i64::from(n))),
5179 Value::BigInt(n) => Ok(Value::Timestamp(n)),
5180 Value::Date(d) => Ok(Value::Timestamp(i64::from(d) * 86_400_000_000)),
5182 Value::Text(s) => {
5183 parse_timestamp_literal(&s)
5184 .map(Value::Timestamp)
5185 .ok_or(EvalError::TypeMismatch {
5186 detail: format!(
5187 "cannot parse {s:?} as TIMESTAMP \
5188 (expected YYYY-MM-DD[ HH:MM:SS[.ffffff]])"
5189 ),
5190 })
5191 }
5192 other => Err(EvalError::TypeMismatch {
5193 detail: format!("cannot cast {:?} to TIMESTAMP", other.data_type()),
5194 }),
5195 }
5196}
5197
5198fn value_to_text(v: &Value) -> String {
5199 match v {
5200 Value::SmallInt(n) => format!("{n}"),
5204 Value::Int(n) => format!("{n}"),
5205 Value::BigInt(n) => format!("{n}"),
5206 Value::Float(x) => format!("{x}"),
5207 Value::Text(s) | Value::Json(s) => s.clone(),
5209 Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
5210 Value::Vector(v) => {
5211 let cells: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
5212 format!("[{}]", cells.join(", "))
5213 }
5214 Value::Sq8Vector(q) => {
5219 let cells: Vec<String> = spg_storage::quantize::dequantize(q)
5220 .iter()
5221 .map(|x| format!("{x}"))
5222 .collect();
5223 format!("[{}]", cells.join(", "))
5224 }
5225 Value::HalfVector(h) => {
5228 let cells: Vec<String> = h.to_f32_vec().iter().map(|x| format!("{x}")).collect();
5229 format!("[{}]", cells.join(", "))
5230 }
5231 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
5232 Value::Date(d) => format_date(*d),
5233 Value::Timestamp(t) => format_timestamp(*t),
5234 Value::Interval { months, micros } => format_interval(*months, *micros),
5235 Value::Null => "NULL".into(),
5236 Value::Bytes(b) => format_bytea_hex(b),
5238 Value::TextArray(items) => format_text_array(items),
5240 Value::IntArray(items) => format_int_array(items),
5241 Value::BigIntArray(items) => format_bigint_array(items),
5242 Value::TsVector(lexs) => format_tsvector(lexs),
5244 Value::TsQuery(ast) => format_tsquery(ast),
5245 Value::Uuid(b) => spg_storage::format_uuid(b),
5248 Value::Time(us) => format_time(*us),
5250 Value::TimeTz { us, offset_secs } => format_timetz(*us, *offset_secs),
5252 Value::Year(y) => format!("{y:04}"),
5254 Value::Money(c) => format_money(*c),
5256 Value::Range { .. } => crate::format_range_text(v),
5260 Value::Hstore(pairs) => crate::format_hstore_text(pairs),
5262 Value::IntArray2D(rows) => crate::format_int_2d_text_pub(rows),
5264 Value::BigIntArray2D(rows) => crate::format_bigint_2d_text_pub(rows),
5265 Value::TextArray2D(rows) => crate::format_text_2d_text_pub(rows),
5266 _ => format!("{v:?}"),
5268 }
5269}
5270
5271pub fn format_date(days: i32) -> String {
5274 let (y, m, d) = civil_from_days(days);
5275 format!("{y:04}-{m:02}-{d:02}")
5276}
5277
5278pub fn format_timestamptz(micros: i64) -> String {
5289 let base = format_timestamp(micros);
5290 let mut s = String::with_capacity(base.len() + 3);
5291 s.push_str(&base);
5292 s.push_str("+00");
5293 s
5294}
5295
5296pub fn format_money(cents: i64) -> String {
5300 let neg = cents < 0;
5301 let abs = cents.unsigned_abs();
5302 let dollars = abs / 100;
5303 let cc = abs % 100;
5304 let dollar_str = dollars.to_string();
5306 let bytes = dollar_str.as_bytes();
5307 let mut int_part = String::with_capacity(dollar_str.len() + dollar_str.len() / 3);
5308 for (i, b) in bytes.iter().enumerate() {
5309 let from_right = bytes.len() - i;
5312 if i > 0 && from_right % 3 == 0 {
5313 int_part.push(',');
5314 }
5315 int_part.push(*b as char);
5316 }
5317 let sign = if neg { "-" } else { "" };
5318 format!("{sign}${int_part}.{cc:02}")
5319}
5320
5321pub fn format_timetz(us: i64, offset_secs: i32) -> String {
5326 let time = format_time(us);
5327 let sign = if offset_secs < 0 { '-' } else { '+' };
5328 let abs = offset_secs.unsigned_abs();
5329 let oh = abs / 3600;
5330 let om = (abs % 3600) / 60;
5331 if om == 0 {
5332 format!("{time}{sign}{oh:02}")
5333 } else {
5334 format!("{time}{sign}{oh:02}:{om:02}")
5335 }
5336}
5337
5338pub fn format_time(us: i64) -> String {
5343 let total_secs = us.div_euclid(1_000_000);
5344 let frac = us.rem_euclid(1_000_000);
5345 let hh = total_secs / 3600;
5346 let mm = (total_secs / 60) % 60;
5347 let ss = total_secs % 60;
5348 if frac == 0 {
5349 format!("{hh:02}:{mm:02}:{ss:02}")
5350 } else {
5351 let raw = format!("{frac:06}");
5352 let trimmed = raw.trim_end_matches('0');
5353 format!("{hh:02}:{mm:02}:{ss:02}.{trimmed}")
5354 }
5355}
5356
5357pub fn format_timestamp(micros: i64) -> String {
5358 const MICROS_PER_DAY: i64 = 86_400_000_000;
5359 let days = micros.div_euclid(MICROS_PER_DAY);
5362 let day_micros = micros.rem_euclid(MICROS_PER_DAY);
5363 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
5364 let (y, m, d) = civil_from_days(day_i32);
5365 let secs = day_micros / 1_000_000;
5366 let frac = day_micros % 1_000_000;
5367 let hh = secs / 3600;
5368 let mm = (secs / 60) % 60;
5369 let ss = secs % 60;
5370 if frac == 0 {
5371 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}")
5372 } else {
5373 let raw = format!("{frac:06}");
5375 let trimmed = raw.trim_end_matches('0');
5376 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}.{trimmed}")
5377 }
5378}
5379
5380#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5385fn civil_from_days(days: i32) -> (i32, u32, u32) {
5386 let z = i64::from(days) + 719_468;
5387 let era = z.div_euclid(146_097);
5388 let doe = (z - era * 146_097) as u32;
5392 let yoe = (doe.saturating_sub(doe / 1460) + doe / 36524 - doe / 146_096) / 365;
5393 let y_base = i64::from(yoe) + era * 400;
5394 let doy = doe.saturating_sub(365 * yoe + yoe / 4 - yoe / 100);
5395 let mp = (5 * doy + 2) / 153;
5396 let d = doy.saturating_sub((153 * mp + 2) / 5) + 1;
5397 let m = if mp < 10 { mp + 3 } else { mp - 9 };
5398 let y = if m <= 2 { y_base + 1 } else { y_base };
5399 (y as i32, m, d)
5400}
5401
5402#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5405pub fn days_from_civil(y: i32, m: u32, d: u32) -> i32 {
5406 let y_adj = if m <= 2 {
5407 i64::from(y) - 1
5408 } else {
5409 i64::from(y)
5410 };
5411 let era = y_adj.div_euclid(400);
5412 let yoe = (y_adj - era * 400) as u32;
5413 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d.saturating_sub(1);
5414 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
5415 let total = era * 146_097 + i64::from(doe) - 719_468;
5416 i32::try_from(total).unwrap_or(i32::MAX)
5417}
5418
5419pub fn parse_date_literal(s: &str) -> Option<i32> {
5423 let bytes = s.as_bytes();
5424 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
5425 return None;
5426 }
5427 let y: i32 = s[0..4].parse().ok()?;
5428 let m: u32 = s[5..7].parse().ok()?;
5429 let d: u32 = s[8..10].parse().ok()?;
5430 if !(1..=12).contains(&m) || !(1..=31).contains(&d) {
5431 return None;
5432 }
5433 Some(days_from_civil(y, m, d))
5434}
5435
5436pub fn parse_timestamp_literal(s: &str) -> Option<i64> {
5441 let trimmed = s.trim();
5442 let (date_part, time_part) = match trimmed.find([' ', 'T']) {
5443 Some(i) => (&trimmed[..i], Some(&trimmed[i + 1..])),
5444 None => (trimmed, None),
5445 };
5446 let days = parse_date_literal(date_part)?;
5447 let (day_micros, tz_offset_micros) = match time_part {
5448 None => (0, 0),
5449 Some(t) => parse_time_of_day_micros(t)?,
5450 };
5451 Some(i64::from(days) * 86_400_000_000 + day_micros - tz_offset_micros)
5461}
5462
5463fn parse_time_of_day_micros(t: &str) -> Option<(i64, i64)> {
5476 let t = t.trim();
5477 let (core, tz_micros) = if let Some(rest) = t.strip_suffix('Z') {
5483 (rest, 0i64)
5484 } else if let Some(rest) = t.strip_suffix(" UTC").or_else(|| t.strip_suffix("UTC")) {
5485 (rest, 0i64)
5486 } else if let Some((idx, sign_byte)) = find_offset_sign(t) {
5487 let suffix = &t[idx..];
5488 let micros = parse_tz_offset_suffix(suffix, sign_byte == b'+')?;
5489 (&t[..idx], micros)
5490 } else {
5491 (t, 0i64)
5492 };
5493 let (time, frac_str) = match core.split_once('.') {
5494 Some((a, b)) => (a, Some(b)),
5495 None => (core, None),
5496 };
5497 let bytes = time.as_bytes();
5498 if bytes.len() != 8 || bytes[2] != b':' || bytes[5] != b':' {
5499 return None;
5500 }
5501 let hh: i64 = time[0..2].parse().ok()?;
5502 let mm: i64 = time[3..5].parse().ok()?;
5503 let ss: i64 = time[6..8].parse().ok()?;
5504 if !(0..24).contains(&hh) || !(0..60).contains(&mm) || !(0..60).contains(&ss) {
5505 return None;
5506 }
5507 let frac_micros: i64 = match frac_str {
5508 None => 0,
5509 Some(f) => {
5510 if f.is_empty() || f.len() > 9 {
5512 return None;
5513 }
5514 let mut padded = String::with_capacity(6);
5515 padded.push_str(&f[..f.len().min(6)]);
5516 while padded.len() < 6 {
5517 padded.push('0');
5518 }
5519 padded.parse().ok()?
5520 }
5521 };
5522 Some((
5523 ((hh * 3600 + mm * 60 + ss) * 1_000_000) + frac_micros,
5524 tz_micros,
5525 ))
5526}
5527
5528fn find_offset_sign(t: &str) -> Option<(usize, u8)> {
5534 let bytes = t.as_bytes();
5535 if bytes.len() < 9 {
5537 return None;
5538 }
5539 for i in 8..bytes.len() {
5540 match bytes[i] {
5541 b'+' | b'-' => return Some((i, bytes[i])),
5542 _ => {}
5543 }
5544 }
5545 None
5546}
5547
5548fn parse_tz_offset_suffix(suffix: &str, is_positive: bool) -> Option<i64> {
5552 let body = &suffix[1..];
5554 let (hh, mm): (i64, i64) = if let Some((h, m)) = body.split_once(':') {
5555 (h.parse().ok()?, m.parse().ok()?)
5556 } else {
5557 match body.len() {
5558 2 => (body.parse().ok()?, 0),
5559 3 => {
5560 return None;
5564 }
5565 4 => {
5566 let h: i64 = body[0..2].parse().ok()?;
5567 let m: i64 = body[2..4].parse().ok()?;
5568 (h, m)
5569 }
5570 _ => return None,
5571 }
5572 };
5573 if !(0..=18).contains(&hh) || !(0..60).contains(&mm) {
5574 return None;
5575 }
5576 let abs = (hh * 3600 + mm * 60) * 1_000_000;
5577 Some(if is_positive { abs } else { -abs })
5578}
5579
5580pub fn format_interval(months: i32, micros: i64) -> String {
5585 const MICROS_PER_DAY: i64 = 86_400_000_000;
5586 let mut parts: Vec<String> = Vec::new();
5587 let years = months / 12;
5588 let mons = months % 12;
5589 let unit = |n: i64, singular: &'static str, plural: &'static str| -> &'static str {
5592 if n == 1 { singular } else { plural }
5593 };
5594 if years != 0 {
5595 parts.push(format!(
5596 "{years} {}",
5597 unit(i64::from(years), "year", "years")
5598 ));
5599 }
5600 if mons != 0 {
5601 parts.push(format!("{mons} {}", unit(i64::from(mons), "mon", "mons")));
5602 }
5603 let days = micros / MICROS_PER_DAY;
5604 let mut rem = micros % MICROS_PER_DAY;
5605 if days != 0 {
5606 parts.push(format!("{days} {}", unit(days, "day", "days")));
5607 }
5608 if rem != 0 {
5609 let neg = rem < 0;
5610 if neg {
5611 rem = -rem;
5612 }
5613 let secs = rem / 1_000_000;
5614 let frac = rem % 1_000_000;
5615 let hh = secs / 3600;
5616 let mm = (secs / 60) % 60;
5617 let ss = secs % 60;
5618 let sign = if neg { "-" } else { "" };
5619 if frac == 0 {
5620 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}"));
5621 } else {
5622 let raw = format!("{frac:06}");
5623 let trimmed = raw.trim_end_matches('0');
5624 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}.{trimmed}"));
5625 }
5626 }
5627 if parts.is_empty() {
5628 "0".into()
5629 } else {
5630 parts.join(" ")
5631 }
5632}
5633
5634fn add_months_to_civil(y: i32, m: u32, d: u32, months: i32) -> (i32, u32, u32) {
5637 let total_months = i64::from(y) * 12 + i64::from(m) - 1 + i64::from(months);
5638 let new_year = i32::try_from(total_months.div_euclid(12)).unwrap_or(i32::MAX);
5639 let new_month_zero = total_months.rem_euclid(12);
5640 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5641 let new_month = (new_month_zero as u32) + 1;
5642 let max_day = days_in_month(new_year, new_month);
5643 (new_year, new_month, d.min(max_day))
5644}
5645
5646const fn days_in_month(y: i32, m: u32) -> u32 {
5647 match m {
5648 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
5649 2 => {
5650 if y.rem_euclid(4) == 0 && (y.rem_euclid(100) != 0 || y.rem_euclid(400) == 0) {
5652 29
5653 } else {
5654 28
5655 }
5656 }
5657 _ => 30,
5660 }
5661}
5662
5663pub fn format_text_array(items: &[Option<String>]) -> String {
5669 let mut out = String::with_capacity(2 + items.len() * 8);
5670 out.push('{');
5671 for (i, item) in items.iter().enumerate() {
5672 if i > 0 {
5673 out.push(',');
5674 }
5675 match item {
5676 None => out.push_str("NULL"),
5677 Some(s) => {
5678 let needs_quote = s.is_empty()
5679 || s.eq_ignore_ascii_case("NULL")
5680 || s.chars()
5681 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
5682 if needs_quote {
5683 out.push('"');
5684 for c in s.chars() {
5685 if c == '"' || c == '\\' {
5686 out.push('\\');
5687 }
5688 out.push(c);
5689 }
5690 out.push('"');
5691 } else {
5692 out.push_str(s);
5693 }
5694 }
5695 }
5696 }
5697 out.push('}');
5698 out
5699}
5700
5701pub fn format_int_array(items: &[Option<i32>]) -> String {
5705 let mut out = String::with_capacity(2 + items.len() * 4);
5706 out.push('{');
5707 for (i, item) in items.iter().enumerate() {
5708 if i > 0 {
5709 out.push(',');
5710 }
5711 match item {
5712 None => out.push_str("NULL"),
5713 Some(n) => out.push_str(&n.to_string()),
5714 }
5715 }
5716 out.push('}');
5717 out
5718}
5719
5720pub fn format_bigint_array(items: &[Option<i64>]) -> String {
5723 let mut out = String::with_capacity(2 + items.len() * 6);
5724 out.push('{');
5725 for (i, item) in items.iter().enumerate() {
5726 if i > 0 {
5727 out.push(',');
5728 }
5729 match item {
5730 None => out.push_str("NULL"),
5731 Some(n) => out.push_str(&n.to_string()),
5732 }
5733 }
5734 out.push('}');
5735 out
5736}
5737
5738pub fn format_tsvector(lexs: &[TsLexeme]) -> String {
5744 let mut out = String::with_capacity(lexs.len() * 12);
5745 for (i, l) in lexs.iter().enumerate() {
5746 if i > 0 {
5747 out.push(' ');
5748 }
5749 out.push('\'');
5750 for c in l.word.chars() {
5751 if c == '\'' {
5752 out.push('\'');
5753 }
5754 out.push(c);
5755 }
5756 out.push('\'');
5757 if !l.positions.is_empty() {
5758 for (pi, p) in l.positions.iter().enumerate() {
5759 out.push(if pi == 0 { ':' } else { ',' });
5760 out.push_str(&p.to_string());
5761 }
5762 match l.weight {
5767 3 => out.push('A'),
5768 2 => out.push('B'),
5769 1 => out.push('C'),
5770 _ => {}
5771 }
5772 }
5773 }
5774 out
5775}
5776
5777pub fn format_tsquery(ast: &TsQueryAst) -> String {
5780 fn go(ast: &TsQueryAst, parent_prec: u8, out: &mut String) {
5781 let (own_prec, write_self): (u8, &dyn Fn(&mut String)) = match ast {
5783 TsQueryAst::Or(_, _) => (1, &|_| {}),
5784 TsQueryAst::And(_, _) | TsQueryAst::Phrase { .. } => (2, &|_| {}),
5785 TsQueryAst::Not(_) => (3, &|_| {}),
5786 TsQueryAst::Term { .. } => (4, &|_| {}),
5787 };
5788 let need_parens = own_prec < parent_prec;
5789 if need_parens {
5790 out.push('(');
5791 }
5792 match ast {
5793 TsQueryAst::Term { word, .. } => {
5794 out.push('\'');
5795 for c in word.chars() {
5796 if c == '\'' {
5797 out.push('\'');
5798 }
5799 out.push(c);
5800 }
5801 out.push('\'');
5802 }
5803 TsQueryAst::And(a, b) => {
5804 go(a, own_prec, out);
5805 out.push_str(" & ");
5806 go(b, own_prec, out);
5807 }
5808 TsQueryAst::Or(a, b) => {
5809 go(a, own_prec, out);
5810 out.push_str(" | ");
5811 go(b, own_prec, out);
5812 }
5813 TsQueryAst::Not(x) => {
5814 out.push('!');
5815 go(x, own_prec, out);
5816 }
5817 TsQueryAst::Phrase {
5818 left,
5819 right,
5820 distance,
5821 } => {
5822 go(left, own_prec, out);
5823 out.push_str(&alloc::format!(" <{distance}> "));
5824 go(right, own_prec, out);
5825 }
5826 }
5827 write_self(out);
5828 if need_parens {
5829 out.push(')');
5830 }
5831 }
5832 let mut out = String::new();
5833 go(ast, 0, &mut out);
5834 out
5835}
5836
5837pub fn decode_tsvector_external(s: &str) -> Result<Vec<TsLexeme>, EvalError> {
5846 let mut out: Vec<TsLexeme> = Vec::new();
5847 let mut i = 0;
5848 let bytes = s.as_bytes();
5849 while i < bytes.len() {
5850 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
5851 i += 1;
5852 }
5853 if i >= bytes.len() {
5854 break;
5855 }
5856 let word = if bytes[i] == b'\'' {
5859 i += 1;
5860 let mut w = String::new();
5861 loop {
5862 if i >= bytes.len() {
5863 return Err(EvalError::TypeMismatch {
5864 detail: "tsvector literal: unterminated quoted lexeme".into(),
5865 });
5866 }
5867 let b = bytes[i];
5868 if b == b'\'' {
5869 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
5870 w.push('\'');
5871 i += 2;
5872 } else {
5873 i += 1;
5874 break;
5875 }
5876 } else {
5877 w.push(b as char);
5878 i += 1;
5879 }
5880 }
5881 w
5882 } else {
5883 let start = i;
5885 while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b':' {
5886 i += 1;
5887 }
5888 core::str::from_utf8(&bytes[start..i])
5889 .map_err(|_| EvalError::TypeMismatch {
5890 detail: "tsvector literal: non-UTF-8 lexeme".into(),
5891 })?
5892 .to_string()
5893 };
5894 if word.is_empty() {
5895 return Err(EvalError::TypeMismatch {
5896 detail: "tsvector literal: empty lexeme".into(),
5897 });
5898 }
5899 let mut positions: Vec<u16> = Vec::new();
5902 let mut weight: u8 = 0;
5903 if i < bytes.len() && bytes[i] == b':' {
5904 i += 1;
5905 loop {
5906 let start = i;
5907 while i < bytes.len() && bytes[i].is_ascii_digit() {
5908 i += 1;
5909 }
5910 if start == i {
5911 return Err(EvalError::TypeMismatch {
5912 detail: "tsvector literal: expected digit after ':'".into(),
5913 });
5914 }
5915 let num: u16 = core::str::from_utf8(&bytes[start..i])
5916 .expect("ascii digits")
5917 .parse()
5918 .map_err(|_| EvalError::TypeMismatch {
5919 detail: alloc::format!(
5920 "tsvector literal: position {} overflows u16",
5921 core::str::from_utf8(&bytes[start..i]).unwrap_or("?")
5922 ),
5923 })?;
5924 positions.push(num);
5925 if i < bytes.len() {
5926 let w = bytes[i];
5927 if matches!(w, b'A' | b'B' | b'C' | b'D') {
5928 weight = match w {
5929 b'A' => 3,
5930 b'B' => 2,
5931 b'C' => 1,
5932 _ => 0,
5933 };
5934 i += 1;
5935 }
5936 }
5937 if i < bytes.len() && bytes[i] == b',' {
5938 i += 1;
5939 continue;
5940 }
5941 break;
5942 }
5943 }
5944 positions.sort_unstable();
5945 positions.dedup();
5946 match out.binary_search_by(|l| l.word.as_str().cmp(word.as_str())) {
5949 Ok(idx) => {
5950 for p in positions {
5951 if !out[idx].positions.contains(&p) {
5952 out[idx].positions.push(p);
5953 }
5954 }
5955 out[idx].positions.sort_unstable();
5956 if weight != 0 {
5957 out[idx].weight = weight;
5958 }
5959 }
5960 Err(idx) => {
5961 out.insert(
5962 idx,
5963 TsLexeme {
5964 word,
5965 positions,
5966 weight,
5967 },
5968 );
5969 }
5970 }
5971 }
5972 Ok(out)
5973}
5974
5975pub fn decode_tsquery_external(s: &str) -> Result<TsQueryAst, EvalError> {
5981 let mut p = TsQueryParser {
5982 bytes: s.as_bytes(),
5983 pos: 0,
5984 };
5985 p.skip_ws();
5986 if p.pos >= p.bytes.len() {
5987 return Err(EvalError::TypeMismatch {
5988 detail: "tsquery literal: empty".into(),
5989 });
5990 }
5991 let ast = p.parse_or()?;
5992 p.skip_ws();
5993 if p.pos < p.bytes.len() {
5994 return Err(EvalError::TypeMismatch {
5995 detail: alloc::format!("tsquery literal: trailing garbage at offset {}", p.pos),
5996 });
5997 }
5998 Ok(ast)
5999}
6000
6001struct TsQueryParser<'a> {
6002 bytes: &'a [u8],
6003 pos: usize,
6004}
6005
6006impl<'a> TsQueryParser<'a> {
6007 fn skip_ws(&mut self) {
6008 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_whitespace() {
6009 self.pos += 1;
6010 }
6011 }
6012 fn peek(&self) -> Option<u8> {
6013 self.bytes.get(self.pos).copied()
6014 }
6015 fn parse_or(&mut self) -> Result<TsQueryAst, EvalError> {
6016 let mut lhs = self.parse_and()?;
6017 loop {
6018 self.skip_ws();
6019 if self.peek() != Some(b'|') {
6020 return Ok(lhs);
6021 }
6022 self.pos += 1;
6023 let rhs = self.parse_and()?;
6024 lhs = TsQueryAst::Or(Box::new(lhs), Box::new(rhs));
6025 }
6026 }
6027 fn parse_and(&mut self) -> Result<TsQueryAst, EvalError> {
6028 let mut lhs = self.parse_unary()?;
6029 loop {
6030 self.skip_ws();
6031 match self.peek() {
6032 Some(b'&') => {
6033 self.pos += 1;
6034 let rhs = self.parse_unary()?;
6035 lhs = TsQueryAst::And(Box::new(lhs), Box::new(rhs));
6036 }
6037 Some(b'<') => {
6038 self.pos += 1;
6040 let start = self.pos;
6041 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
6042 self.pos += 1;
6043 }
6044 if start == self.pos || self.peek() != Some(b'>') {
6045 return Err(EvalError::TypeMismatch {
6046 detail: "tsquery literal: malformed <N> phrase operator".into(),
6047 });
6048 }
6049 let n: u16 = core::str::from_utf8(&self.bytes[start..self.pos])
6050 .expect("ascii digits")
6051 .parse()
6052 .map_err(|_| EvalError::TypeMismatch {
6053 detail: "tsquery literal: phrase distance overflows u16".into(),
6054 })?;
6055 self.pos += 1; let rhs = self.parse_unary()?;
6057 lhs = TsQueryAst::Phrase {
6058 left: Box::new(lhs),
6059 right: Box::new(rhs),
6060 distance: n,
6061 };
6062 }
6063 _ => return Ok(lhs),
6064 }
6065 }
6066 }
6067 fn parse_unary(&mut self) -> Result<TsQueryAst, EvalError> {
6068 self.skip_ws();
6069 if self.peek() == Some(b'!') {
6070 self.pos += 1;
6071 let inner = self.parse_unary()?;
6072 return Ok(TsQueryAst::Not(Box::new(inner)));
6073 }
6074 self.parse_atom()
6075 }
6076 fn parse_atom(&mut self) -> Result<TsQueryAst, EvalError> {
6077 self.skip_ws();
6078 match self.peek() {
6079 Some(b'(') => {
6080 self.pos += 1;
6081 let inner = self.parse_or()?;
6082 self.skip_ws();
6083 if self.peek() != Some(b')') {
6084 return Err(EvalError::TypeMismatch {
6085 detail: "tsquery literal: missing ')'".into(),
6086 });
6087 }
6088 self.pos += 1;
6089 Ok(inner)
6090 }
6091 Some(b'\'') => {
6092 self.pos += 1;
6093 let mut w = String::new();
6094 loop {
6095 match self.peek() {
6096 None => {
6097 return Err(EvalError::TypeMismatch {
6098 detail: "tsquery literal: unterminated quoted lexeme".into(),
6099 });
6100 }
6101 Some(b'\'') => {
6102 if self.bytes.get(self.pos + 1) == Some(&b'\'') {
6103 w.push('\'');
6104 self.pos += 2;
6105 } else {
6106 self.pos += 1;
6107 break;
6108 }
6109 }
6110 Some(b) => {
6111 w.push(b as char);
6112 self.pos += 1;
6113 }
6114 }
6115 }
6116 self.skip_weight_suffix();
6119 Ok(TsQueryAst::Term {
6120 word: w,
6121 weight_mask: 0,
6122 })
6123 }
6124 Some(b) if b.is_ascii_alphanumeric() || b == b'_' => {
6125 let start = self.pos;
6126 while self.pos < self.bytes.len() {
6127 let c = self.bytes[self.pos];
6128 if c.is_ascii_alphanumeric() || c == b'_' {
6129 self.pos += 1;
6130 } else {
6131 break;
6132 }
6133 }
6134 let w = core::str::from_utf8(&self.bytes[start..self.pos])
6135 .map_err(|_| EvalError::TypeMismatch {
6136 detail: "tsquery literal: non-UTF-8 lexeme".into(),
6137 })?
6138 .to_string();
6139 self.skip_weight_suffix();
6140 Ok(TsQueryAst::Term {
6141 word: w,
6142 weight_mask: 0,
6143 })
6144 }
6145 Some(b) => Err(EvalError::TypeMismatch {
6146 detail: alloc::format!(
6147 "tsquery literal: unexpected byte {:?} at offset {}",
6148 b as char,
6149 self.pos
6150 ),
6151 }),
6152 None => Err(EvalError::TypeMismatch {
6153 detail: "tsquery literal: expected term".into(),
6154 }),
6155 }
6156 }
6157 fn skip_weight_suffix(&mut self) {
6158 if self.peek() != Some(b':') {
6159 return;
6160 }
6161 self.pos += 1;
6162 while let Some(b) = self.peek() {
6163 if matches!(
6164 b,
6165 b'A' | b'B' | b'C' | b'D' | b'a' | b'b' | b'c' | b'd' | b'*'
6166 ) || b.is_ascii_digit()
6167 {
6168 self.pos += 1;
6169 } else {
6170 break;
6171 }
6172 }
6173 }
6174}
6175
6176pub fn format_bytea_hex(b: &[u8]) -> String {
6180 let mut out = String::with_capacity(2 + 2 * b.len());
6181 out.push_str("\\x");
6182 const HEX: &[u8; 16] = b"0123456789abcdef";
6183 for byte in b {
6184 out.push(HEX[(byte >> 4) as usize] as char);
6185 out.push(HEX[(byte & 0x0F) as usize] as char);
6186 }
6187 out
6188}
6189
6190pub fn format_numeric(scaled: i128, scale: u8) -> String {
6195 if scale == 0 {
6196 return format!("{scaled}");
6197 }
6198 let negative = scaled < 0;
6199 let mag_str = scaled.unsigned_abs().to_string();
6200 let mag_bytes = mag_str.as_bytes();
6201 let scale_u = scale as usize;
6202 let mut out = String::with_capacity(mag_str.len() + 3);
6203 if negative {
6204 out.push('-');
6205 }
6206 if mag_bytes.len() <= scale_u {
6207 out.push('0');
6208 out.push('.');
6209 for _ in mag_bytes.len()..scale_u {
6210 out.push('0');
6211 }
6212 out.push_str(&mag_str);
6213 } else {
6214 let split = mag_bytes.len() - scale_u;
6215 out.push_str(&mag_str[..split]);
6216 out.push('.');
6217 out.push_str(&mag_str[split..]);
6218 }
6219 out
6220}
6221
6222fn cast_numeric_to_int(v: Value) -> Result<Value, EvalError> {
6223 match v {
6224 Value::Int(n) => Ok(Value::Int(n)),
6225 Value::BigInt(n) => i32::try_from(n)
6226 .map(Value::Int)
6227 .map_err(|_| EvalError::TypeMismatch {
6228 detail: format!("bigint {n} does not fit in int"),
6229 }),
6230 #[allow(clippy::cast_possible_truncation)]
6231 Value::Float(x) => Ok(Value::Int(x as i32)),
6232 Value::Text(s) => {
6233 s.trim()
6234 .parse::<i32>()
6235 .map(Value::Int)
6236 .map_err(|_| EvalError::TypeMismatch {
6237 detail: format!("cannot parse {s:?} as int"),
6238 })
6239 }
6240 Value::Bool(b) => Ok(Value::Int(i32::from(b))),
6241 other => Err(EvalError::TypeMismatch {
6242 detail: format!("cannot cast {:?} to int", other.data_type()),
6243 }),
6244 }
6245}
6246
6247fn cast_numeric_to_bigint(v: Value) -> Result<Value, EvalError> {
6248 match v {
6249 Value::Int(n) => Ok(Value::BigInt(i64::from(n))),
6250 Value::BigInt(n) => Ok(Value::BigInt(n)),
6251 #[allow(clippy::cast_possible_truncation)]
6252 Value::Float(x) => Ok(Value::BigInt(x as i64)),
6253 Value::Text(s) => {
6254 s.trim()
6255 .parse::<i64>()
6256 .map(Value::BigInt)
6257 .map_err(|_| EvalError::TypeMismatch {
6258 detail: format!("cannot parse {s:?} as bigint"),
6259 })
6260 }
6261 Value::Bool(b) => Ok(Value::BigInt(i64::from(b))),
6262 other => Err(EvalError::TypeMismatch {
6263 detail: format!("cannot cast {:?} to bigint", other.data_type()),
6264 }),
6265 }
6266}
6267
6268fn cast_numeric_to_float(v: Value) -> Result<Value, EvalError> {
6269 match v {
6270 Value::Int(n) => Ok(Value::Float(f64::from(n))),
6271 #[allow(clippy::cast_precision_loss)]
6272 Value::BigInt(n) => Ok(Value::Float(n as f64)),
6273 Value::Float(x) => Ok(Value::Float(x)),
6274 Value::Text(s) => {
6275 s.trim()
6276 .parse::<f64>()
6277 .map(Value::Float)
6278 .map_err(|_| EvalError::TypeMismatch {
6279 detail: format!("cannot parse {s:?} as float"),
6280 })
6281 }
6282 other => Err(EvalError::TypeMismatch {
6283 detail: format!("cannot cast {:?} to float", other.data_type()),
6284 }),
6285 }
6286}
6287
6288fn cast_to_bool(v: Value) -> Result<Value, EvalError> {
6289 match v {
6290 Value::Bool(b) => Ok(Value::Bool(b)),
6291 Value::Int(n) => Ok(Value::Bool(n != 0)),
6292 Value::BigInt(n) => Ok(Value::Bool(n != 0)),
6293 Value::Text(s) => {
6294 let lo = s.trim().to_ascii_lowercase();
6295 match lo.as_str() {
6296 "true" | "t" | "yes" | "y" | "1" | "on" => Ok(Value::Bool(true)),
6297 "false" | "f" | "no" | "n" | "0" | "off" => Ok(Value::Bool(false)),
6298 _ => Err(EvalError::TypeMismatch {
6299 detail: format!("cannot parse {s:?} as bool"),
6300 }),
6301 }
6302 }
6303 other => Err(EvalError::TypeMismatch {
6304 detail: format!("cannot cast {:?} to bool", other.data_type()),
6305 }),
6306 }
6307}
6308
6309pub fn cast_to_vector(v: Value) -> Result<Value, EvalError> {
6312 match v {
6313 Value::Null => Ok(Value::Null),
6314 Value::Vector(v) => Ok(Value::Vector(v)),
6315 Value::Text(s) => parse_vector_text(&s)
6316 .map(Value::Vector)
6317 .ok_or(EvalError::TypeMismatch {
6318 detail: format!("cannot parse {s:?} as a vector literal"),
6319 }),
6320 other => Err(EvalError::TypeMismatch {
6321 detail: format!("::vector requires text input, got {:?}", other.data_type()),
6322 }),
6323 }
6324}
6325
6326pub fn parse_vector_text(s: &str) -> Option<Vec<f32>> {
6328 let trimmed = s.trim();
6329 let inner = trimmed.strip_prefix('[')?.strip_suffix(']')?;
6330 let trimmed_inner = inner.trim();
6331 if trimmed_inner.is_empty() {
6332 return Some(Vec::new());
6333 }
6334 let mut out = Vec::new();
6335 for part in trimmed_inner.split(',') {
6336 let f: f32 = part.trim().parse().ok()?;
6337 out.push(f);
6338 }
6339 Some(out)
6340}
6341
6342pub(crate) fn literal_to_value(l: &Literal) -> Value {
6343 match l {
6344 Literal::Integer(n) => {
6345 if let Ok(small) = i32::try_from(*n) {
6346 Value::Int(small)
6347 } else {
6348 Value::BigInt(*n)
6349 }
6350 }
6351 Literal::Float(x) => Value::Float(*x),
6352 Literal::String(s) => Value::Text(s.clone()),
6353 Literal::Vector(v) => Value::Vector(v.clone()),
6354 Literal::TextArray(items) => Value::TextArray(items.clone()),
6355 Literal::IntArray(items) => Value::IntArray(items.clone()),
6356 Literal::BigIntArray(items) => Value::BigIntArray(items.clone()),
6357 Literal::Bool(b) => Value::Bool(*b),
6358 Literal::Null => Value::Null,
6359 Literal::Interval { months, micros, .. } => Value::Interval {
6360 months: *months,
6361 micros: *micros,
6362 },
6363 }
6364}
6365
6366pub(crate) fn column_collation(e: &Expr, ctx: &EvalContext<'_>) -> Option<spg_storage::Collation> {
6372 let Expr::Column(c) = e else {
6373 return None;
6374 };
6375 if let Some(q) = &c.qualifier {
6376 let composite = alloc::format!("{q}.{name}", name = c.name);
6377 if let Some(s) = ctx.columns.iter().find(|s| s.name == composite) {
6378 return Some(s.collation);
6379 }
6380 }
6381 if let Some(s) = ctx.columns.iter().find(|s| s.name == c.name) {
6382 return Some(s.collation);
6383 }
6384 let suffix = alloc::format!(".{name}", name = c.name);
6388 let mut matches = ctx.columns.iter().filter(|s| s.name.ends_with(&suffix));
6389 let first = matches.next();
6390 let extra = matches.next();
6391 match (first, extra) {
6392 (Some(s), None) => Some(s.collation),
6393 _ => None,
6394 }
6395}
6396
6397fn collation_fold_for_compare(
6404 op: BinOp,
6405 lhs: &Expr,
6406 rhs: &Expr,
6407 l: Value,
6408 r: Value,
6409 ctx: &EvalContext<'_>,
6410) -> (Value, Value) {
6411 if !matches!(
6412 op,
6413 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq
6414 ) {
6415 return (l, r);
6416 }
6417 let lhs_col = column_collation(lhs, ctx);
6418 let rhs_col = column_collation(rhs, ctx);
6419 let ci = matches!(lhs_col, Some(spg_storage::Collation::CaseInsensitive))
6420 || matches!(rhs_col, Some(spg_storage::Collation::CaseInsensitive));
6421 if !ci {
6422 return (l, r);
6423 }
6424 let fold = |v: Value| match v {
6425 Value::Text(s) => Value::Text(s.to_ascii_lowercase()),
6426 other => other,
6427 };
6428 (fold(l), fold(r))
6429}
6430
6431#[inline]
6441fn composite_eq(schema_name: &str, qualifier: &str, name: &str) -> bool {
6442 schema_name.len() == qualifier.len() + 1 + name.len()
6443 && schema_name.as_bytes()[qualifier.len()] == b'.'
6444 && schema_name[..qualifier.len()] == *qualifier
6445 && schema_name[qualifier.len() + 1..] == *name
6446}
6447
6448pub(crate) fn find_column_pos(c: &ColumnName, ctx: &EvalContext<'_>) -> Option<usize> {
6453 if let Some(q) = &c.qualifier {
6454 if let Some(pos) = ctx
6455 .columns
6456 .iter()
6457 .position(|s| composite_eq(&s.name, q, &c.name))
6458 {
6459 return Some(pos);
6460 }
6461 }
6462 ctx.columns.iter().position(|s| s.name == c.name)
6463}
6464
6465fn resolve_column_borrowed<'r>(
6466 c: &ColumnName,
6467 row: &'r Row,
6468 ctx: &EvalContext<'_>,
6469) -> Result<Option<&'r Value>, EvalError> {
6470 if let Some(q) = &c.qualifier {
6471 if let Some(pos) = ctx
6472 .columns
6473 .iter()
6474 .position(|s| composite_eq(&s.name, q, &c.name))
6475 {
6476 return Ok(row.values.get(pos));
6477 }
6478 }
6479 if let Some(pos) = ctx.columns.iter().position(|s| s.name == c.name) {
6480 return Ok(row.values.get(pos));
6481 }
6482 Ok(None)
6483}
6484
6485fn text_prefix_chars(t: &str, n: i64) -> String {
6488 if n >= 0 {
6489 let n = usize::try_from(n).unwrap_or(usize::MAX);
6490 match t.char_indices().nth(n) {
6491 Some((byte_idx, _)) => t[..byte_idx].into(),
6492 None => t.into(),
6493 }
6494 } else {
6495 let drop_tail = usize::try_from(-n).unwrap_or(usize::MAX);
6496 let total = t.chars().count();
6497 let keep = total.saturating_sub(drop_tail);
6498 match t.char_indices().nth(keep) {
6499 Some((byte_idx, _)) => t[..byte_idx].into(),
6500 None => t.into(),
6501 }
6502 }
6503}
6504
6505fn resolve_column(c: &ColumnName, row: &Row, ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
6506 if let Some(q) = &c.qualifier {
6507 if let Some(pos) = ctx
6514 .columns
6515 .iter()
6516 .position(|s| composite_eq(&s.name, q, &c.name))
6517 {
6518 return Ok(row.values[pos].clone());
6519 }
6520 let prefix = alloc::format!("{q}.");
6527 if ctx.columns.iter().any(|sc| sc.name.starts_with(&prefix)) {
6528 return Err(EvalError::ColumnNotFound {
6529 name: alloc::format!("{q}.{name}", name = c.name),
6530 });
6531 }
6532 let expected = ctx.table_alias.ok_or_else(|| EvalError::UnknownQualifier {
6533 qualifier: q.clone(),
6534 })?;
6535 if q != expected {
6536 return Err(EvalError::UnknownQualifier {
6537 qualifier: q.clone(),
6538 });
6539 }
6540 }
6541 if let Some(pos) = ctx.columns.iter().position(|s| s.name == c.name) {
6542 return Ok(row.values[pos].clone());
6543 }
6544 let suffix = alloc::format!(".{name}", name = c.name);
6547 let mut matches = ctx
6548 .columns
6549 .iter()
6550 .enumerate()
6551 .filter(|(_, s)| s.name.ends_with(&suffix));
6552 let first = matches.next();
6553 let extra = matches.next();
6554 match (first, extra) {
6555 (Some((pos, _)), None) => Ok(row.values[pos].clone()),
6556 (Some(_), Some(_)) => Err(EvalError::TypeMismatch {
6557 detail: alloc::format!("ambiguous column reference: {}", c.name),
6558 }),
6559 _ => Err(EvalError::ColumnNotFound {
6560 name: c.name.clone(),
6561 }),
6562 }
6563}
6564
6565fn apply_unary(op: UnOp, v: Value) -> Result<Value, EvalError> {
6566 match (op, v) {
6567 (_, Value::Null) => Ok(Value::Null),
6568 (UnOp::Neg, Value::Int(n)) => {
6569 n.checked_neg()
6570 .map(Value::Int)
6571 .ok_or(EvalError::TypeMismatch {
6572 detail: "integer overflow on unary -".into(),
6573 })
6574 }
6575 (UnOp::Neg, Value::BigInt(n)) => {
6576 n.checked_neg()
6577 .map(Value::BigInt)
6578 .ok_or(EvalError::TypeMismatch {
6579 detail: "bigint overflow on unary -".into(),
6580 })
6581 }
6582 (UnOp::Neg, Value::Float(x)) => Ok(Value::Float(-x)),
6583 (UnOp::Neg, other) => Err(EvalError::TypeMismatch {
6584 detail: format!("unary - applied to {:?}", other.data_type()),
6585 }),
6586 (UnOp::BitNot, Value::SmallInt(n)) => Ok(Value::Int(!i32::from(n))),
6587 (UnOp::BitNot, Value::Int(n)) => Ok(Value::Int(!n)),
6588 (UnOp::BitNot, Value::BigInt(n)) => Ok(Value::BigInt(!n)),
6589 (UnOp::BitNot, other) => Err(EvalError::TypeMismatch {
6590 detail: format!("cannot apply ~ to {other:?}"),
6591 }),
6592 (UnOp::Not, Value::Bool(b)) => Ok(Value::Bool(!b)),
6593 (UnOp::Not, other) => Err(EvalError::TypeMismatch {
6594 detail: format!("NOT applied to {:?}", other.data_type()),
6595 }),
6596 }
6597}
6598
6599fn values_not_distinct(l: &Value, r: &Value) -> bool {
6602 match (l, r) {
6603 (Value::Null, Value::Null) => true,
6604 (Value::Null, _) | (_, Value::Null) => false,
6605 _ => l == r,
6606 }
6607}
6608
6609fn apply_binary(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6610 if let BinOp::And = op {
6613 return and_3vl(l, r);
6614 }
6615 if let BinOp::Or = op {
6616 return or_3vl(l, r);
6617 }
6618 if let BinOp::IsNotDistinctFrom = op {
6621 return Ok(Value::Bool(values_not_distinct(&l, &r)));
6622 }
6623 if let BinOp::IsDistinctFrom = op {
6624 return Ok(Value::Bool(!values_not_distinct(&l, &r)));
6625 }
6626 if l.is_null() || r.is_null() {
6628 return Ok(Value::Null);
6629 }
6630 if matches!(l, Value::Numeric { .. }) || matches!(r, Value::Numeric { .. }) {
6633 return apply_binary_numeric(op, l, r);
6634 }
6635 if let Some(result) = apply_binary_calendar(op, &l, &r)? {
6643 return Ok(result);
6644 }
6645 match op {
6646 BinOp::Add => arith(l, r, i64::checked_add, |a, b| a + b, "+"),
6647 BinOp::Sub => arith(l, r, i64::checked_sub, |a, b| a - b, "-"),
6648 BinOp::Mul => arith(l, r, i64::checked_mul, |a, b| a * b, "*"),
6649 BinOp::Div => div_op(l, r),
6650 BinOp::L2Distance => l2_distance(l, r),
6651 BinOp::InnerProduct => inner_product(l, r),
6652 BinOp::CosineDistance => cosine_distance(l, r),
6653 BinOp::Concat => Ok(text_concat(&l, &r)),
6654 BinOp::BitOr => bitop(l, r, |a, b| a | b, "|"),
6655 BinOp::BitAnd => bitop(l, r, |a, b| a & b, "&"),
6656 BinOp::JsonGet => crate::json::path_get(&l, &r, false),
6657 BinOp::JsonGetText => crate::json::path_get(&l, &r, true),
6658 BinOp::JsonGetPath => crate::json::path_walk(&l, &r, false),
6659 BinOp::JsonGetPathText => crate::json::path_walk(&l, &r, true),
6660 BinOp::JsonContains => crate::json::contains(&l, &r),
6661 BinOp::TsMatch => ts_match(l, r),
6664 BinOp::InetContainedBy
6666 | BinOp::InetContainedByEq
6667 | BinOp::InetContains
6668 | BinOp::InetContainsEq
6669 | BinOp::InetOverlap => inet_op_bool_result(op, &l, &r),
6670 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6671 compare(op, &l, &r)
6672 }
6673 BinOp::And | BinOp::Or | BinOp::IsDistinctFrom | BinOp::IsNotDistinctFrom => {
6674 unreachable!("handled above")
6675 }
6676 }
6677}
6678
6679fn apply_binary_calendar(op: BinOp, l: &Value, r: &Value) -> Result<Option<Value>, EvalError> {
6683 let int_value = |v: &Value| -> Option<i64> {
6684 match v {
6685 Value::SmallInt(n) => Some(i64::from(*n)),
6686 Value::Int(n) => Some(i64::from(*n)),
6687 Value::BigInt(n) => Some(*n),
6688 _ => None,
6689 }
6690 };
6691 match (l, r) {
6695 (Value::Date(a), Value::Date(b)) if op == BinOp::Sub => {
6696 return Ok(Some(Value::BigInt(i64::from(*a) - i64::from(*b))));
6697 }
6698 (Value::Timestamp(a), Value::Timestamp(b)) if op == BinOp::Sub => {
6699 let delta = a.checked_sub(*b).ok_or(EvalError::TypeMismatch {
6700 detail: "TIMESTAMP - TIMESTAMP overflows i64 microseconds".into(),
6701 })?;
6702 return Ok(Some(Value::BigInt(delta)));
6703 }
6704 _ => {}
6705 }
6706 if let Some(out) = apply_binary_interval(op, l, r)? {
6710 return Ok(Some(out));
6711 }
6712 match (l, r) {
6713 (Value::Date(d), other) if op == BinOp::Add => {
6714 if let Some(n) = int_value(other) {
6715 let days = i64::from(*d).saturating_add(n);
6716 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6717 detail: "DATE + integer overflows DATE range".into(),
6718 })?;
6719 return Ok(Some(Value::Date(days32)));
6720 }
6721 }
6722 (other, Value::Date(d)) if op == BinOp::Add => {
6723 if let Some(n) = int_value(other) {
6724 let days = i64::from(*d).saturating_add(n);
6725 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6726 detail: "integer + DATE overflows DATE range".into(),
6727 })?;
6728 return Ok(Some(Value::Date(days32)));
6729 }
6730 }
6731 (Value::Date(d), other) if op == BinOp::Sub => {
6732 if let Some(n) = int_value(other) {
6733 let days = i64::from(*d).saturating_sub(n);
6734 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6735 detail: "DATE - integer overflows DATE range".into(),
6736 })?;
6737 return Ok(Some(Value::Date(days32)));
6738 }
6739 }
6740 _ => {}
6741 }
6742 Ok(None)
6743}
6744
6745pub(crate) fn apply_binary_interval(
6753 op: BinOp,
6754 l: &Value,
6755 r: &Value,
6756) -> Result<Option<Value>, EvalError> {
6757 let (lhs, rhs, sign): (&Value, &Value, i64) = match (l, r, op) {
6760 (Value::Interval { .. }, _, BinOp::Add) => (r, l, 1),
6761 (_, Value::Interval { .. }, BinOp::Add) => (l, r, 1),
6762 (_, Value::Interval { .. }, BinOp::Sub) => (l, r, -1),
6763 _ => return Ok(None),
6764 };
6765 let Value::Interval {
6766 months: rhs_months,
6767 micros: rhs_us,
6768 } = rhs
6769 else {
6770 unreachable!("rhs guaranteed to be Interval by the match above");
6771 };
6772 let signed_months = i64::from(*rhs_months) * sign;
6773 let signed_micros = rhs_us.checked_mul(sign).ok_or(EvalError::TypeMismatch {
6774 detail: "INTERVAL micros overflows on negation".into(),
6775 })?;
6776 match lhs {
6777 Value::Timestamp(t) => Ok(Some(Value::Timestamp(add_interval_to_micros(
6778 *t,
6779 signed_months,
6780 signed_micros,
6781 )?))),
6782 Value::Date(d) => {
6783 let day_aligned = signed_micros.rem_euclid(86_400_000_000) == 0;
6787 if day_aligned {
6788 let micros_per_day = 86_400_000_000_i64;
6789 let days_delta = signed_micros / micros_per_day;
6790 let shifted = shift_date_by_months(*d, signed_months)?;
6791 let new_days =
6792 i64::from(shifted)
6793 .checked_add(days_delta)
6794 .ok_or(EvalError::TypeMismatch {
6795 detail: "DATE ± INTERVAL overflows DATE range".into(),
6796 })?;
6797 let days32 = i32::try_from(new_days).map_err(|_| EvalError::TypeMismatch {
6798 detail: "DATE ± INTERVAL overflows DATE range".into(),
6799 })?;
6800 Ok(Some(Value::Date(days32)))
6801 } else {
6802 let base =
6803 i64::from(*d)
6804 .checked_mul(86_400_000_000)
6805 .ok_or(EvalError::TypeMismatch {
6806 detail: "DATE → TIMESTAMP lift overflows for INTERVAL math".into(),
6807 })?;
6808 Ok(Some(Value::Timestamp(add_interval_to_micros(
6809 base,
6810 signed_months,
6811 signed_micros,
6812 )?)))
6813 }
6814 }
6815 Value::Interval {
6816 months: lhs_months,
6817 micros: lhs_us,
6818 } => {
6819 let new_months = i64::from(*lhs_months)
6820 .checked_add(signed_months)
6821 .and_then(|n| i32::try_from(n).ok())
6822 .ok_or(EvalError::TypeMismatch {
6823 detail: "INTERVAL ± INTERVAL months overflows i32".into(),
6824 })?;
6825 let new_micros = lhs_us
6826 .checked_add(signed_micros)
6827 .ok_or(EvalError::TypeMismatch {
6828 detail: "INTERVAL ± INTERVAL micros overflows i64".into(),
6829 })?;
6830 Ok(Some(Value::Interval {
6831 months: new_months,
6832 micros: new_micros,
6833 }))
6834 }
6835 _ => Err(EvalError::TypeMismatch {
6836 detail: format!(
6837 "operator {op:?} not defined for {:?} and INTERVAL",
6838 lhs.data_type()
6839 ),
6840 }),
6841 }
6842}
6843
6844fn shift_date_by_months(d: i32, months: i64) -> Result<i32, EvalError> {
6846 let (y, m, day) = civil_from_days(d);
6847 let months_i32 = i32::try_from(months).map_err(|_| EvalError::TypeMismatch {
6848 detail: "INTERVAL months delta out of i32 range".into(),
6849 })?;
6850 let (ny, nm, nd) = add_months_to_civil(y, m, day, months_i32);
6851 Ok(days_from_civil(ny, nm, nd))
6852}
6853
6854fn add_interval_to_micros(t: i64, months: i64, micros: i64) -> Result<i64, EvalError> {
6858 let mut out = t;
6859 if months != 0 {
6860 const MICROS_PER_DAY: i64 = 86_400_000_000;
6861 let days = out.div_euclid(MICROS_PER_DAY);
6862 let day_micros = out.rem_euclid(MICROS_PER_DAY);
6863 let day_i32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6864 detail: "TIMESTAMP day component out of i32 range for INTERVAL months math".into(),
6865 })?;
6866 let shifted_days = shift_date_by_months(day_i32, months)?;
6867 out = i64::from(shifted_days)
6868 .checked_mul(MICROS_PER_DAY)
6869 .and_then(|n| n.checked_add(day_micros))
6870 .ok_or(EvalError::TypeMismatch {
6871 detail: "TIMESTAMP ± INTERVAL months overflows i64 microseconds".into(),
6872 })?;
6873 }
6874 out.checked_add(micros).ok_or(EvalError::TypeMismatch {
6875 detail: "TIMESTAMP ± INTERVAL micros overflows i64".into(),
6876 })
6877}
6878
6879#[allow(clippy::needless_pass_by_value)] fn apply_binary_numeric(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6884 let float_path = matches!(l, Value::Float(_)) || matches!(r, Value::Float(_));
6888 if float_path {
6889 let af = as_f64(&l)?;
6890 let bf = as_f64(&r)?;
6891 return match op {
6892 BinOp::Add => Ok(Value::Float(af + bf)),
6893 BinOp::Sub => Ok(Value::Float(af - bf)),
6894 BinOp::Mul => Ok(Value::Float(af * bf)),
6895 BinOp::Div => {
6896 if bf == 0.0 {
6897 Err(EvalError::DivisionByZero)
6898 } else {
6899 Ok(Value::Float(af / bf))
6900 }
6901 }
6902 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6903 let ord = af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
6904 detail: "NaN in NUMERIC/Float comparison".into(),
6905 })?;
6906 Ok(Value::Bool(cmp_to_bool(op, ord)))
6907 }
6908 BinOp::Concat => Ok(text_concat(&l, &r)),
6909 other => Err(EvalError::TypeMismatch {
6910 detail: format!("operator {other:?} not defined for NUMERIC and Float"),
6911 }),
6912 };
6913 }
6914 let (a, sa) = numeric_or_widen(&l).ok_or_else(|| EvalError::TypeMismatch {
6916 detail: format!("NUMERIC op against non-numeric {:?}", l.data_type()),
6917 })?;
6918 let (b, sb) = numeric_or_widen(&r).ok_or_else(|| EvalError::TypeMismatch {
6919 detail: format!("NUMERIC op against non-numeric {:?}", r.data_type()),
6920 })?;
6921 match op {
6922 BinOp::Add | BinOp::Sub => {
6923 let target_scale = sa.max(sb);
6924 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
6925 detail: "NUMERIC overflow on rescale".into(),
6926 })?;
6927 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
6928 detail: "NUMERIC overflow on rescale".into(),
6929 })?;
6930 let r = match op {
6931 BinOp::Add => lhs.checked_add(rhs),
6932 BinOp::Sub => lhs.checked_sub(rhs),
6933 _ => unreachable!(),
6934 }
6935 .ok_or(EvalError::TypeMismatch {
6936 detail: "NUMERIC overflow on +/-".into(),
6937 })?;
6938 Ok(Value::Numeric {
6939 scaled: r,
6940 scale: target_scale,
6941 })
6942 }
6943 BinOp::Mul => {
6944 let scaled = a.checked_mul(b).ok_or(EvalError::TypeMismatch {
6945 detail: "NUMERIC overflow on *".into(),
6946 })?;
6947 Ok(Value::Numeric {
6948 scaled,
6949 scale: sa.saturating_add(sb),
6950 })
6951 }
6952 BinOp::Div => {
6953 if b == 0 {
6954 return Err(EvalError::DivisionByZero);
6955 }
6956 let target_scale = sa.max(sb);
6960 let bump = pow10_i128(target_scale.saturating_add(sb).saturating_sub(sa));
6964 let num = a.checked_mul(bump).ok_or(EvalError::TypeMismatch {
6965 detail: "NUMERIC overflow on / scaling".into(),
6966 })?;
6967 let half = if b >= 0 { b / 2 } else { -(b / 2) };
6968 let adj = if (num >= 0) == (b >= 0) {
6969 num + half
6970 } else {
6971 num - half
6972 };
6973 Ok(Value::Numeric {
6974 scaled: adj / b,
6975 scale: target_scale,
6976 })
6977 }
6978 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6979 let target_scale = sa.max(sb);
6980 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
6981 detail: "NUMERIC overflow on rescale".into(),
6982 })?;
6983 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
6984 detail: "NUMERIC overflow on rescale".into(),
6985 })?;
6986 Ok(Value::Bool(cmp_to_bool(op, lhs.cmp(&rhs))))
6987 }
6988 BinOp::Concat => Ok(text_concat(&l, &r)),
6989 other => Err(EvalError::TypeMismatch {
6990 detail: format!("operator {other:?} not defined for NUMERIC"),
6991 }),
6992 }
6993}
6994
6995fn numeric_or_widen(v: &Value) -> Option<(i128, u8)> {
6999 match v {
7000 Value::Numeric { scaled, scale } => Some((*scaled, *scale)),
7001 Value::Int(n) => Some((i128::from(*n), 0)),
7002 Value::SmallInt(n) => Some((i128::from(*n), 0)),
7003 Value::BigInt(n) => Some((i128::from(*n), 0)),
7004 _ => None,
7005 }
7006}
7007
7008fn rescale(scaled: i128, src: u8, dst: u8) -> Option<i128> {
7009 if src == dst {
7010 return Some(scaled);
7011 }
7012 if dst > src {
7013 scaled.checked_mul(pow10_i128(dst - src))
7014 } else {
7015 let drop = pow10_i128(src - dst);
7016 let half = drop / 2;
7017 let r = if scaled >= 0 {
7018 scaled + half
7019 } else {
7020 scaled - half
7021 };
7022 Some(r / drop)
7023 }
7024}
7025
7026const fn pow10_i128(p: u8) -> i128 {
7027 let mut acc: i128 = 1;
7028 let mut i = 0;
7029 while i < p {
7030 acc *= 10;
7031 i += 1;
7032 }
7033 acc
7034}
7035
7036const fn cmp_to_bool(op: BinOp, ord: core::cmp::Ordering) -> bool {
7037 use core::cmp::Ordering::{Equal, Greater, Less};
7038 match op {
7039 BinOp::Eq => matches!(ord, Equal),
7040 BinOp::NotEq => !matches!(ord, Equal),
7041 BinOp::Lt => matches!(ord, Less),
7042 BinOp::LtEq => matches!(ord, Less | Equal),
7043 BinOp::Gt => matches!(ord, Greater),
7044 BinOp::GtEq => matches!(ord, Greater | Equal),
7045 _ => false,
7046 }
7047}
7048
7049fn tsvector_concat(l: &[spg_storage::TsLexeme], r: &[spg_storage::TsLexeme]) -> Value {
7058 let shift = l
7059 .iter()
7060 .flat_map(|x| x.positions.iter().copied())
7061 .max()
7062 .unwrap_or(0);
7063 let mut out: Vec<spg_storage::TsLexeme> = l.to_vec();
7064 for lex in r {
7065 let shifted: Vec<u16> = lex
7066 .positions
7067 .iter()
7068 .map(|p| p.saturating_add(shift))
7069 .collect();
7070 if let Some(existing) = out.iter_mut().find(|x| x.word == lex.word) {
7071 existing.positions.extend(shifted);
7072 existing.positions.sort_unstable();
7073 existing.weight = existing.weight.max(lex.weight);
7074 } else {
7075 out.push(spg_storage::TsLexeme {
7076 word: lex.word.clone(),
7077 positions: shifted,
7078 weight: lex.weight,
7079 });
7080 }
7081 }
7082 out.sort_by(|a, b| a.word.cmp(&b.word));
7083 Value::TsVector(out)
7084}
7085
7086fn text_concat(l: &Value, r: &Value) -> Value {
7087 if let (Value::TsVector(a), Value::TsVector(b)) = (l, r) {
7088 return tsvector_concat(a, b);
7089 }
7090 match (l, r) {
7095 (Value::Null, _) | (_, Value::Null) => {
7096 if matches!(
7100 l,
7101 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
7102 ) || matches!(
7103 r,
7104 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
7105 ) {
7106 return Value::Null;
7107 }
7108 }
7109 (Value::TextArray(a), Value::TextArray(b)) => {
7110 let mut out = a.clone();
7111 out.extend(b.iter().cloned());
7112 return Value::TextArray(out);
7113 }
7114 (Value::TextArray(a), Value::Text(s)) => {
7115 let mut out = a.clone();
7116 out.push(Some(s.clone()));
7117 return Value::TextArray(out);
7118 }
7119 (Value::Text(s), Value::TextArray(b)) => {
7120 let mut out: alloc::vec::Vec<Option<alloc::string::String>> =
7121 alloc::vec::Vec::with_capacity(1 + b.len());
7122 out.push(Some(s.clone()));
7123 out.extend(b.iter().cloned());
7124 return Value::TextArray(out);
7125 }
7126 (Value::IntArray(a), Value::IntArray(b)) => {
7131 let mut out = a.clone();
7132 out.extend(b.iter().copied());
7133 return Value::IntArray(out);
7134 }
7135 (Value::IntArray(a), Value::Int(n)) => {
7136 let mut out = a.clone();
7137 out.push(Some(*n));
7138 return Value::IntArray(out);
7139 }
7140 (Value::IntArray(a), Value::SmallInt(n)) => {
7141 let mut out = a.clone();
7142 out.push(Some(i32::from(*n)));
7143 return Value::IntArray(out);
7144 }
7145 (Value::Int(n), Value::IntArray(b)) => {
7146 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
7147 out.push(Some(*n));
7148 out.extend(b.iter().copied());
7149 return Value::IntArray(out);
7150 }
7151 (Value::SmallInt(n), Value::IntArray(b)) => {
7152 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
7153 out.push(Some(i32::from(*n)));
7154 out.extend(b.iter().copied());
7155 return Value::IntArray(out);
7156 }
7157 (Value::BigIntArray(a), Value::BigIntArray(b)) => {
7158 let mut out = a.clone();
7159 out.extend(b.iter().copied());
7160 return Value::BigIntArray(out);
7161 }
7162 (Value::BigIntArray(a), Value::IntArray(b)) => {
7163 let mut out = a.clone();
7164 out.extend(b.iter().map(|o| o.map(i64::from)));
7165 return Value::BigIntArray(out);
7166 }
7167 (Value::IntArray(a), Value::BigIntArray(b)) => {
7168 let mut out: alloc::vec::Vec<Option<i64>> =
7169 a.iter().map(|o| o.map(i64::from)).collect();
7170 out.extend(b.iter().copied());
7171 return Value::BigIntArray(out);
7172 }
7173 (Value::BigIntArray(a), Value::BigInt(n)) => {
7174 let mut out = a.clone();
7175 out.push(Some(*n));
7176 return Value::BigIntArray(out);
7177 }
7178 (Value::BigIntArray(a), Value::Int(n)) => {
7179 let mut out = a.clone();
7180 out.push(Some(i64::from(*n)));
7181 return Value::BigIntArray(out);
7182 }
7183 (Value::BigIntArray(a), Value::SmallInt(n)) => {
7184 let mut out = a.clone();
7185 out.push(Some(i64::from(*n)));
7186 return Value::BigIntArray(out);
7187 }
7188 (Value::BigInt(n), Value::BigIntArray(b)) => {
7189 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
7190 out.push(Some(*n));
7191 out.extend(b.iter().copied());
7192 return Value::BigIntArray(out);
7193 }
7194 (Value::Int(n), Value::BigIntArray(b)) => {
7195 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
7196 out.push(Some(i64::from(*n)));
7197 out.extend(b.iter().copied());
7198 return Value::BigIntArray(out);
7199 }
7200 (Value::SmallInt(n), Value::BigIntArray(b)) => {
7201 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
7202 out.push(Some(i64::from(*n)));
7203 out.extend(b.iter().copied());
7204 return Value::BigIntArray(out);
7205 }
7206 (Value::Bytes(a), Value::Bytes(b)) => {
7208 let mut out = a.clone();
7209 out.extend_from_slice(b);
7210 return Value::Bytes(out);
7211 }
7212 _ => {}
7213 }
7214 let a = value_to_text(l);
7215 let b = value_to_text(r);
7216 Value::Text(a + &b)
7217}
7218
7219fn inner_product(l: Value, r: Value) -> Result<Value, EvalError> {
7222 let (a, b) = unwrap_vec_pair(l, r, "<#>")?;
7223 let mut dot: f64 = 0.0;
7224 for (x, y) in a.iter().zip(b.iter()) {
7225 dot += f64::from(*x) * f64::from(*y);
7226 }
7227 Ok(Value::Float(-dot))
7228}
7229
7230fn cosine_distance(l: Value, r: Value) -> Result<Value, EvalError> {
7233 let (a, b) = unwrap_vec_pair(l, r, "<=>")?;
7234 let mut dot: f64 = 0.0;
7235 let mut na: f64 = 0.0;
7236 let mut nb: f64 = 0.0;
7237 for (x, y) in a.iter().zip(b.iter()) {
7238 let xf = f64::from(*x);
7239 let yf = f64::from(*y);
7240 dot += xf * yf;
7241 na += xf * xf;
7242 nb += yf * yf;
7243 }
7244 let denom = sqrt_newton(na) * sqrt_newton(nb);
7245 if denom == 0.0 {
7246 return Ok(Value::Float(f64::NAN));
7247 }
7248 Ok(Value::Float(1.0 - dot / denom))
7249}
7250
7251fn unwrap_vec_pair(l: Value, r: Value, op: &str) -> Result<(Vec<f32>, Vec<f32>), EvalError> {
7252 let to_f32 = |v: Value| -> Option<Vec<f32>> {
7260 match v {
7261 Value::Vector(a) => Some(a),
7262 Value::Sq8Vector(q) => Some(spg_storage::quantize::dequantize(&q)),
7263 Value::HalfVector(h) => Some(h.to_f32_vec()),
7265 _ => None,
7266 }
7267 };
7268 let l_ty = l.data_type();
7269 let r_ty = r.data_type();
7270 match (to_f32(l), to_f32(r)) {
7271 (Some(a), Some(b)) => {
7272 if a.len() != b.len() {
7273 return Err(EvalError::TypeMismatch {
7274 detail: format!("vector dim mismatch in {op}: {} vs {}", a.len(), b.len()),
7275 });
7276 }
7277 Ok((a, b))
7278 }
7279 _ => Err(EvalError::TypeMismatch {
7280 detail: format!("{op} requires two vectors, got {l_ty:?} and {r_ty:?}"),
7281 }),
7282 }
7283}
7284
7285fn bitop(
7293 l: Value,
7294 r: Value,
7295 f: impl Fn(i64, i64) -> i64,
7296 op_name: &str,
7297) -> Result<Value, EvalError> {
7298 let widen = |v: Value| -> Value {
7299 match v {
7300 Value::SmallInt(n) => Value::Int(i32::from(n)),
7301 other => other,
7302 }
7303 };
7304 match (widen(l), widen(r)) {
7305 (Value::Int(a), Value::Int(b)) => {
7306 let result = f(i64::from(a), i64::from(b));
7307 Ok(Value::Int(result as i32))
7309 }
7310 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
7311 Ok(Value::BigInt(f(i64::from(a), b)))
7312 }
7313 (Value::BigInt(a), Value::BigInt(b)) => Ok(Value::BigInt(f(a, b))),
7314 (a, b) => Err(EvalError::TypeMismatch {
7315 detail: format!("cannot apply {op_name} to {a:?} and {b:?}"),
7316 }),
7317 }
7318}
7319
7320fn arith(
7321 l: Value,
7322 r: Value,
7323 int_op: impl Fn(i64, i64) -> Option<i64>,
7324 float_op: impl Fn(f64, f64) -> f64,
7325 op_name: &str,
7326) -> Result<Value, EvalError> {
7327 let widen = |v: Value| -> Value {
7330 match v {
7331 Value::SmallInt(n) => Value::Int(i32::from(n)),
7332 other => other,
7333 }
7334 };
7335 let l = widen(l);
7336 let r = widen(r);
7337 match (l, r) {
7338 (Value::Int(a), Value::Int(b)) => {
7339 let result = int_op(i64::from(a), i64::from(b)).ok_or(EvalError::TypeMismatch {
7340 detail: format!("integer overflow on {op_name}"),
7341 })?;
7342 if let Ok(small) = i32::try_from(result) {
7343 Ok(Value::Int(small))
7344 } else {
7345 Ok(Value::BigInt(result))
7346 }
7347 }
7348 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
7349 let result = int_op(i64::from(a), b).ok_or(EvalError::TypeMismatch {
7350 detail: format!("bigint overflow on {op_name}"),
7351 })?;
7352 Ok(Value::BigInt(result))
7353 }
7354 (Value::BigInt(a), Value::BigInt(b)) => {
7355 let result = int_op(a, b).ok_or(EvalError::TypeMismatch {
7356 detail: format!("bigint overflow on {op_name}"),
7357 })?;
7358 Ok(Value::BigInt(result))
7359 }
7360 (a, b)
7361 if a.data_type() == Some(DataType::Float) || b.data_type() == Some(DataType::Float) =>
7362 {
7363 let af = as_f64(&a)?;
7364 let bf = as_f64(&b)?;
7365 Ok(Value::Float(float_op(af, bf)))
7366 }
7367 (a, b) => Err(EvalError::TypeMismatch {
7368 detail: format!(
7369 "{op_name} applied to non-numeric: {:?} vs {:?}",
7370 a.data_type(),
7371 b.data_type()
7372 ),
7373 }),
7374 }
7375}
7376
7377#[allow(clippy::many_single_char_names)] fn l2_distance(l: Value, r: Value) -> Result<Value, EvalError> {
7383 let (a, b) = unwrap_vec_pair(l, r, "<->")?;
7388 let mut sum: f64 = 0.0;
7389 for (x, y) in a.iter().zip(b.iter()) {
7390 let d = f64::from(*x) - f64::from(*y);
7391 sum += d * d;
7392 }
7393 Ok(Value::Float(sqrt_newton(sum)))
7394}
7395
7396fn sqrt_newton(x: f64) -> f64 {
7401 if x <= 0.0 {
7402 return 0.0;
7403 }
7404 let mut g = x;
7405 for _ in 0..10 {
7408 g = 0.5 * (g + x / g);
7409 }
7410 g
7411}
7412
7413fn div_op(l: Value, r: Value) -> Result<Value, EvalError> {
7414 let any_float = matches!(l.data_type(), Some(DataType::Float))
7415 || matches!(r.data_type(), Some(DataType::Float));
7416 if any_float {
7417 let a = as_f64(&l)?;
7418 let b = as_f64(&r)?;
7419 if b == 0.0 {
7420 return Err(EvalError::DivisionByZero);
7421 }
7422 return Ok(Value::Float(a / b));
7423 }
7424 arith(
7425 l,
7426 r,
7427 |a, b| {
7428 if b == 0 { None } else { Some(a / b) }
7429 },
7430 |a, b| a / b,
7431 "/",
7432 )
7433 .map_err(|e| match e {
7434 EvalError::TypeMismatch { detail } if detail.contains('/') => EvalError::DivisionByZero,
7437 other => other,
7438 })
7439}
7440
7441fn as_f64(v: &Value) -> Result<f64, EvalError> {
7442 match v {
7443 Value::SmallInt(n) => Ok(f64::from(*n)),
7444 Value::Int(n) => Ok(f64::from(*n)),
7445 #[allow(clippy::cast_precision_loss)]
7446 Value::BigInt(n) => Ok(*n as f64),
7447 Value::Float(x) => Ok(*x),
7448 #[allow(clippy::cast_precision_loss)]
7449 Value::Numeric { scaled, scale } => {
7450 let mut div = 1.0_f64;
7451 for _ in 0..*scale {
7452 div *= 10.0;
7453 }
7454 Ok((*scaled as f64) / div)
7455 }
7456 other => Err(EvalError::TypeMismatch {
7457 detail: format!("cannot convert {:?} to FLOAT", other.data_type()),
7458 }),
7459 }
7460}
7461
7462fn compare(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
7463 let ord = match (l, r) {
7464 (Value::Int(a), Value::Int(b)) => i64::from(*a).cmp(&i64::from(*b)),
7465 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
7466 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
7467 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
7468 (a, b)
7469 if matches!(a.data_type(), Some(DataType::Float))
7470 || matches!(b.data_type(), Some(DataType::Float)) =>
7471 {
7472 let af = as_f64(a)?;
7473 let bf = as_f64(b)?;
7474 af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
7475 detail: "NaN in comparison".into(),
7476 })?
7477 }
7478 (Value::Text(a), Value::Text(b)) => a.cmp(b),
7479 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
7480 (Value::Date(a), Value::Date(b)) => a.cmp(b),
7484 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
7485 (Value::Date(a), Value::Timestamp(b)) => (i64::from(*a) * 86_400_000_000).cmp(b),
7486 (Value::Timestamp(a), Value::Date(b)) => a.cmp(&(i64::from(*b) * 86_400_000_000)),
7487 (Value::Date(a), Value::Text(b)) => {
7491 let bd = parse_date_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7492 detail: format!("cannot parse {b:?} as DATE for comparison"),
7493 })?;
7494 a.cmp(&bd)
7495 }
7496 (Value::Text(a), Value::Date(b)) => {
7497 let ad = parse_date_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7498 detail: format!("cannot parse {a:?} as DATE for comparison"),
7499 })?;
7500 ad.cmp(b)
7501 }
7502 (Value::Timestamp(a), Value::Text(b)) => {
7503 let bt = parse_timestamp_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7504 detail: format!("cannot parse {b:?} as TIMESTAMP for comparison"),
7505 })?;
7506 a.cmp(&bt)
7507 }
7508 (Value::Text(a), Value::Timestamp(b)) => {
7509 let at = parse_timestamp_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7510 detail: format!("cannot parse {a:?} as TIMESTAMP for comparison"),
7511 })?;
7512 at.cmp(b)
7513 }
7514 (Value::Uuid(a), Value::Uuid(b)) => a.cmp(b),
7516 (Value::Uuid(a), Value::Text(b)) => {
7522 let bu = spg_storage::parse_uuid_str(b).ok_or_else(|| EvalError::TypeMismatch {
7523 detail: format!("invalid input syntax for type uuid: {b:?}"),
7524 })?;
7525 a.cmp(&bu)
7526 }
7527 (Value::Text(a), Value::Uuid(b)) => {
7528 let au = spg_storage::parse_uuid_str(a).ok_or_else(|| EvalError::TypeMismatch {
7529 detail: format!("invalid input syntax for type uuid: {a:?}"),
7530 })?;
7531 au.cmp(b)
7532 }
7533 (a, b) => {
7534 return Err(EvalError::TypeMismatch {
7535 detail: format!(
7536 "comparison between {:?} and {:?}",
7537 a.data_type(),
7538 b.data_type()
7539 ),
7540 });
7541 }
7542 };
7543 let result = match op {
7544 BinOp::Eq => ord.is_eq(),
7545 BinOp::NotEq => !ord.is_eq(),
7546 BinOp::Lt => ord.is_lt(),
7547 BinOp::LtEq => ord.is_le(),
7548 BinOp::Gt => ord.is_gt(),
7549 BinOp::GtEq => ord.is_ge(),
7550 BinOp::And
7551 | BinOp::Or
7552 | BinOp::BitOr
7553 | BinOp::BitAnd
7554 | BinOp::Add
7555 | BinOp::Sub
7556 | BinOp::Mul
7557 | BinOp::Div
7558 | BinOp::L2Distance
7559 | BinOp::InnerProduct
7560 | BinOp::CosineDistance
7561 | BinOp::Concat
7562 | BinOp::JsonGet
7563 | BinOp::JsonGetText
7564 | BinOp::JsonGetPath
7565 | BinOp::JsonGetPathText
7566 | BinOp::JsonContains
7567 | BinOp::TsMatch
7568 | BinOp::IsDistinctFrom
7569 | BinOp::IsNotDistinctFrom
7570 | BinOp::InetContainedBy
7571 | BinOp::InetContainedByEq
7572 | BinOp::InetContains
7573 | BinOp::InetContainsEq
7574 | BinOp::InetOverlap => {
7575 unreachable!("compare() only called with comparison ops")
7576 }
7577 };
7578 Ok(Value::Bool(result))
7579}
7580
7581fn and_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7583 match (l, r) {
7584 (Value::Bool(false), _) | (_, Value::Bool(false)) => Ok(Value::Bool(false)),
7585 (Value::Bool(true), Value::Bool(true)) => Ok(Value::Bool(true)),
7586 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7587 (a, b) => Err(EvalError::TypeMismatch {
7588 detail: format!(
7589 "AND on non-boolean: {:?} and {:?}",
7590 a.data_type(),
7591 b.data_type()
7592 ),
7593 }),
7594 }
7595}
7596
7597fn or_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7598 match (l, r) {
7599 (Value::Bool(true), _) | (_, Value::Bool(true)) => Ok(Value::Bool(true)),
7600 (Value::Bool(false), Value::Bool(false)) => Ok(Value::Bool(false)),
7601 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7602 (a, b) => Err(EvalError::TypeMismatch {
7603 detail: format!(
7604 "OR on non-boolean: {:?} and {:?}",
7605 a.data_type(),
7606 b.data_type()
7607 ),
7608 }),
7609 }
7610}
7611
7612#[cfg(test)]
7613mod tests {
7614 use super::*;
7615 use alloc::vec;
7616 use spg_storage::{ColumnSchema, Row};
7617
7618 fn col(name: &str, ty: DataType) -> ColumnSchema {
7619 ColumnSchema::new(name, ty, true)
7620 }
7621
7622 fn ctx<'a>(cols: &'a [ColumnSchema], alias: Option<&'a str>) -> EvalContext<'a> {
7623 EvalContext::new(cols, alias)
7624 }
7625
7626 fn lit(n: i64) -> Expr {
7627 Expr::Literal(Literal::Integer(n))
7628 }
7629
7630 fn null() -> Expr {
7631 Expr::Literal(Literal::Null)
7632 }
7633
7634 fn col_ref(name: &str) -> Expr {
7635 Expr::Column(ColumnName {
7636 qualifier: None,
7637 name: name.into(),
7638 })
7639 }
7640
7641 #[test]
7642 fn literal_evaluates_to_value() {
7643 let r = Row::new(vec![]);
7644 let cs: [ColumnSchema; 0] = [];
7645 let c = ctx(&cs, None);
7646 assert_eq!(eval_expr(&lit(42), &r, &c).unwrap(), Value::Int(42));
7647 assert_eq!(
7648 eval_expr(&Expr::Literal(Literal::Float(1.5)), &r, &c).unwrap(),
7649 Value::Float(1.5)
7650 );
7651 assert_eq!(eval_expr(&null(), &r, &c).unwrap(), Value::Null);
7652 }
7653
7654 #[test]
7655 fn column_lookup_unqualified() {
7656 let cs = vec![col("a", DataType::Int), col("b", DataType::Text)];
7657 let r = Row::new(vec![Value::Int(7), Value::Text("hi".into())]);
7658 let c = ctx(&cs, None);
7659 assert_eq!(eval_expr(&col_ref("a"), &r, &c).unwrap(), Value::Int(7));
7660 assert_eq!(
7661 eval_expr(&col_ref("b"), &r, &c).unwrap(),
7662 Value::Text("hi".into())
7663 );
7664 }
7665
7666 #[test]
7667 fn column_not_found_errors() {
7668 let cs = vec![col("a", DataType::Int)];
7669 let r = Row::new(vec![Value::Int(0)]);
7670 let c = ctx(&cs, None);
7671 let err = eval_expr(&col_ref("ghost"), &r, &c).unwrap_err();
7672 assert!(matches!(err, EvalError::ColumnNotFound { ref name } if name == "ghost"));
7673 }
7674
7675 #[test]
7676 fn qualified_column_matches_alias() {
7677 let cs = vec![col("a", DataType::Int)];
7678 let r = Row::new(vec![Value::Int(5)]);
7679 let c = ctx(&cs, Some("u"));
7680 let qualified = Expr::Column(ColumnName {
7681 qualifier: Some("u".into()),
7682 name: "a".into(),
7683 });
7684 assert_eq!(eval_expr(&qualified, &r, &c).unwrap(), Value::Int(5));
7685 }
7686
7687 #[test]
7688 fn qualified_column_unknown_alias_errors() {
7689 let cs = vec![col("a", DataType::Int)];
7690 let r = Row::new(vec![Value::Int(5)]);
7691 let c = ctx(&cs, Some("u"));
7692 let wrong = Expr::Column(ColumnName {
7693 qualifier: Some("x".into()),
7694 name: "a".into(),
7695 });
7696 assert!(matches!(
7697 eval_expr(&wrong, &r, &c).unwrap_err(),
7698 EvalError::UnknownQualifier { .. }
7699 ));
7700 }
7701
7702 #[test]
7703 fn arithmetic_with_widening() {
7704 let r = Row::new(vec![]);
7705 let cs: [ColumnSchema; 0] = [];
7706 let c = ctx(&cs, None);
7707 let e = Expr::Binary {
7708 lhs: alloc::boxed::Box::new(lit(2)),
7709 op: BinOp::Add,
7710 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::Float(0.5))),
7711 };
7712 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Float(2.5));
7713 }
7714
7715 #[test]
7716 fn division_by_zero_errors() {
7717 let r = Row::new(vec![]);
7718 let cs: [ColumnSchema; 0] = [];
7719 let c = ctx(&cs, None);
7720 let e = Expr::Binary {
7721 lhs: alloc::boxed::Box::new(lit(1)),
7722 op: BinOp::Div,
7723 rhs: alloc::boxed::Box::new(lit(0)),
7724 };
7725 assert_eq!(
7726 eval_expr(&e, &r, &c).unwrap_err(),
7727 EvalError::DivisionByZero
7728 );
7729 }
7730
7731 #[test]
7732 fn comparison_returns_bool() {
7733 let r = Row::new(vec![]);
7734 let cs: [ColumnSchema; 0] = [];
7735 let c = ctx(&cs, None);
7736 let e = Expr::Binary {
7737 lhs: alloc::boxed::Box::new(lit(1)),
7738 op: BinOp::Lt,
7739 rhs: alloc::boxed::Box::new(lit(2)),
7740 };
7741 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7742 }
7743
7744 #[test]
7745 fn null_propagates_through_arithmetic() {
7746 let r = Row::new(vec![]);
7747 let cs: [ColumnSchema; 0] = [];
7748 let c = ctx(&cs, None);
7749 let e = Expr::Binary {
7750 lhs: alloc::boxed::Box::new(lit(1)),
7751 op: BinOp::Add,
7752 rhs: alloc::boxed::Box::new(null()),
7753 };
7754 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7755 }
7756
7757 #[test]
7758 fn and_three_valued_logic() {
7759 let r = Row::new(vec![]);
7760 let cs: [ColumnSchema; 0] = [];
7761 let c = ctx(&cs, None);
7762 let tt = |a: bool, b_null: bool| Expr::Binary {
7763 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7764 op: BinOp::And,
7765 rhs: alloc::boxed::Box::new(if b_null {
7766 null()
7767 } else {
7768 Expr::Literal(Literal::Bool(true))
7769 }),
7770 };
7771 assert_eq!(
7773 eval_expr(&tt(false, true), &r, &c).unwrap(),
7774 Value::Bool(false)
7775 );
7776 assert_eq!(eval_expr(&tt(true, true), &r, &c).unwrap(), Value::Null);
7778 assert_eq!(
7780 eval_expr(&tt(true, false), &r, &c).unwrap(),
7781 Value::Bool(true)
7782 );
7783 }
7784
7785 #[test]
7786 fn or_three_valued_logic() {
7787 let r = Row::new(vec![]);
7788 let cs: [ColumnSchema; 0] = [];
7789 let c = ctx(&cs, None);
7790 let or_with_null = |a: bool| Expr::Binary {
7791 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7792 op: BinOp::Or,
7793 rhs: alloc::boxed::Box::new(null()),
7794 };
7795 assert_eq!(
7797 eval_expr(&or_with_null(true), &r, &c).unwrap(),
7798 Value::Bool(true)
7799 );
7800 assert_eq!(
7802 eval_expr(&or_with_null(false), &r, &c).unwrap(),
7803 Value::Null
7804 );
7805 }
7806
7807 #[test]
7808 fn not_on_null_is_null() {
7809 let r = Row::new(vec![]);
7810 let cs: [ColumnSchema; 0] = [];
7811 let c = ctx(&cs, None);
7812 let e = Expr::Unary {
7813 op: UnOp::Not,
7814 expr: alloc::boxed::Box::new(null()),
7815 };
7816 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7817 }
7818
7819 #[test]
7820 fn text_comparison_lexicographic() {
7821 let r = Row::new(vec![]);
7822 let cs: [ColumnSchema; 0] = [];
7823 let c = ctx(&cs, None);
7824 let e = Expr::Binary {
7825 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("apple".into()))),
7826 op: BinOp::Lt,
7827 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("banana".into()))),
7828 };
7829 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7830 }
7831
7832 #[test]
7833 fn interval_format_basics() {
7834 assert_eq!(format_interval(0, 0), "0");
7835 assert_eq!(format_interval(0, 86_400_000_000), "1 day");
7836 assert_eq!(format_interval(0, -86_400_000_000), "-1 days");
7837 assert_eq!(format_interval(0, 3_600_000_000), "01:00:00");
7838 assert_eq!(
7839 format_interval(0, 86_400_000_000 + 9_000_000),
7840 "1 day 00:00:09"
7841 );
7842 assert_eq!(format_interval(14, 0), "1 year 2 mons");
7843 assert_eq!(format_interval(-1, 0), "-1 mons");
7844 }
7845
7846 #[test]
7847 fn interval_add_to_timestamp_micros_part() {
7848 let ts = i64::from(days_from_civil(2024, 1, 1)) * 86_400_000_000;
7850 let r = add_interval_to_micros(ts, 0, 3_600_000_000).unwrap();
7851 let expected = ts + 3_600_000_000;
7852 assert_eq!(r, expected);
7853 }
7854
7855 #[test]
7856 fn interval_clamp_month_end() {
7857 let d = days_from_civil(2024, 1, 31);
7859 let shifted = shift_date_by_months(d, 1).unwrap();
7860 let (y, m, day) = civil_from_days(shifted);
7861 assert_eq!((y, m, day), (2024, 2, 29));
7862 let d = days_from_civil(2023, 1, 31);
7864 let shifted = shift_date_by_months(d, 1).unwrap();
7865 let (y, m, day) = civil_from_days(shifted);
7866 assert_eq!((y, m, day), (2023, 2, 28));
7867 let d = days_from_civil(2024, 3, 31);
7869 let shifted = shift_date_by_months(d, -1).unwrap();
7870 let (y, m, day) = civil_from_days(shifted);
7871 assert_eq!((y, m, day), (2024, 2, 29));
7872 }
7873
7874 #[test]
7875 fn interval_date_plus_pure_days_stays_date() {
7876 let d = days_from_civil(2024, 6, 1);
7878 let lhs = Value::Date(d);
7879 let rhs = Value::Interval {
7880 months: 0,
7881 micros: 7 * 86_400_000_000,
7882 };
7883 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7884 .unwrap()
7885 .unwrap();
7886 let expected = days_from_civil(2024, 6, 8);
7887 assert_eq!(v, Value::Date(expected));
7888 }
7889
7890 #[test]
7891 fn interval_date_plus_sub_day_lifts_to_timestamp() {
7892 let d = days_from_civil(2024, 6, 1);
7894 let lhs = Value::Date(d);
7895 let rhs = Value::Interval {
7896 months: 0,
7897 micros: 3_600_000_000,
7898 };
7899 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7900 .unwrap()
7901 .unwrap();
7902 let expected = i64::from(d) * 86_400_000_000 + 3_600_000_000;
7903 assert_eq!(v, Value::Timestamp(expected));
7904 }
7905}