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 let evaluated: Result<Vec<Value>, _> =
195 args.iter().map(|a| eval_expr(a, row, ctx)).collect();
196 apply_function(name, &evaluated?, ctx)
197 }
198 Expr::Like {
199 expr,
200 pattern,
201 negated,
202 } => {
203 let v = eval_expr(expr, row, ctx)?;
204 let p = eval_expr(pattern, row, ctx)?;
205 let (text, pat) = match (v, p) {
207 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
208 (Value::Text(a), Value::Text(b)) => (a, b),
209 (Value::Text(_), other) | (other, _) => {
210 return Err(EvalError::TypeMismatch {
211 detail: format!("LIKE requires text operands, got {:?}", other.data_type()),
212 });
213 }
214 };
215 let m = like_match(&text, &pat);
216 Ok(Value::Bool(if *negated { !m } else { m }))
217 }
218 Expr::Extract { field, source } => {
219 let v = eval_expr(source, row, ctx)?;
220 extract_field(*field, &v)
221 }
222 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
226 Err(EvalError::TypeMismatch {
227 detail: "subquery reached row eval — engine resolver bug".into(),
228 })
229 }
230 Expr::WindowFunction { .. } => Err(EvalError::TypeMismatch {
235 detail: "window function reached row eval — engine rewrite bug".into(),
236 }),
237 Expr::Array(items) => {
243 let mut materialised: Vec<Value> = Vec::with_capacity(items.len());
244 for elem in items {
245 materialised.push(eval_expr(elem, row, ctx)?);
246 }
247 let mut has_text = false;
248 let mut has_bigint = false;
249 let mut has_int = false;
250 for v in &materialised {
251 match v {
252 Value::Null => {}
253 Value::Int(_) | Value::SmallInt(_) => has_int = true,
254 Value::BigInt(_) => has_bigint = true,
255 Value::Text(_) | Value::Json(_) => has_text = true,
256 _ => has_text = true,
257 }
258 }
259 if has_text || (!has_int && !has_bigint) {
260 let out: Vec<Option<String>> = materialised
261 .into_iter()
262 .map(|v| match v {
263 Value::Null => None,
264 Value::Text(s) | Value::Json(s) => Some(s),
265 other => Some(value_to_text_for_array(&other)),
266 })
267 .collect();
268 return Ok(Value::TextArray(out));
269 }
270 if has_bigint {
271 let out: Vec<Option<i64>> = materialised
272 .into_iter()
273 .map(|v| match v {
274 Value::Null => None,
275 Value::Int(n) => Some(i64::from(n)),
276 Value::SmallInt(n) => Some(i64::from(n)),
277 Value::BigInt(n) => Some(n),
278 _ => unreachable!(),
279 })
280 .collect();
281 return Ok(Value::BigIntArray(out));
282 }
283 let out: Vec<Option<i32>> = materialised
284 .into_iter()
285 .map(|v| match v {
286 Value::Null => None,
287 Value::Int(n) => Some(n),
288 Value::SmallInt(n) => Some(i32::from(n)),
289 _ => unreachable!(),
290 })
291 .collect();
292 Ok(Value::IntArray(out))
293 }
294 Expr::ArraySubscript { target, index } => {
297 let target_v = eval_expr(target, row, ctx)?;
298 let idx_v = eval_expr(index, row, ctx)?;
299 if matches!(target_v, Value::Null) || matches!(idx_v, Value::Null) {
300 return Ok(Value::Null);
301 }
302 let i: i64 = match idx_v {
303 Value::Int(n) => i64::from(n),
304 Value::BigInt(n) => n,
305 Value::SmallInt(n) => i64::from(n),
306 other => {
307 return Err(EvalError::TypeMismatch {
308 detail: format!(
309 "array subscript must be integer, got {:?}",
310 other.data_type()
311 ),
312 });
313 }
314 };
315 if i < 1 {
316 return Ok(Value::Null);
317 }
318 let pos = (i - 1) as usize;
319 match target_v {
320 Value::TextArray(items) => match items.get(pos) {
321 Some(Some(s)) => Ok(Value::Text(s.clone())),
322 Some(None) | None => Ok(Value::Null),
323 },
324 Value::IntArray(items) => match items.get(pos) {
325 Some(Some(n)) => Ok(Value::Int(*n)),
326 Some(None) | None => Ok(Value::Null),
327 },
328 Value::BigIntArray(items) => match items.get(pos) {
329 Some(Some(n)) => Ok(Value::BigInt(*n)),
330 Some(None) | None => Ok(Value::Null),
331 },
332 other => Err(EvalError::TypeMismatch {
333 detail: format!(
334 "subscript target must be an array, got {:?}",
335 other.data_type()
336 ),
337 }),
338 }
339 }
340 Expr::AnyAll {
346 expr,
347 op,
348 array,
349 is_any,
350 } => {
351 let lhs = eval_expr(expr, row, ctx)?;
352 let arr = eval_expr(array, row, ctx)?;
353 if matches!(arr, Value::Null) {
354 return Ok(Value::Null);
355 }
356 let elems: Vec<Option<Value>> = match arr {
357 Value::TextArray(items) => items.into_iter().map(|o| o.map(Value::Text)).collect(),
358 Value::IntArray(items) => items.into_iter().map(|o| o.map(Value::Int)).collect(),
359 Value::BigIntArray(items) => {
360 items.into_iter().map(|o| o.map(Value::BigInt)).collect()
361 }
362 other => {
363 return Err(EvalError::TypeMismatch {
364 detail: format!(
365 "ANY/ALL right-hand side must be an array, got {:?}",
366 other.data_type()
367 ),
368 });
369 }
370 };
371 let mut saw_null = matches!(lhs, Value::Null);
372 let mut saw_match = false;
373 let mut saw_mismatch = false;
374 for elem in elems {
375 let elem_v = match elem {
376 Some(v) => v,
377 None => {
378 saw_null = true;
379 continue;
380 }
381 };
382 if matches!(lhs, Value::Null) {
383 saw_null = true;
384 continue;
385 }
386 match apply_binary(*op, lhs.clone(), elem_v) {
387 Ok(Value::Bool(true)) => saw_match = true,
388 Ok(Value::Bool(false)) => saw_mismatch = true,
389 Ok(Value::Null) => saw_null = true,
390 Ok(other) => {
391 return Err(EvalError::TypeMismatch {
392 detail: format!(
393 "ANY/ALL comparison didn't return Bool: {:?}",
394 other.data_type()
395 ),
396 });
397 }
398 Err(e) => return Err(e),
399 }
400 }
401 let result = if *is_any {
402 if saw_match {
403 Value::Bool(true)
404 } else if saw_null {
405 Value::Null
406 } else {
407 Value::Bool(false)
408 }
409 } else if saw_mismatch {
410 Value::Bool(false)
411 } else if saw_null {
412 Value::Null
413 } else {
414 Value::Bool(true)
415 };
416 Ok(result)
417 }
418 Expr::Case {
424 operand,
425 branches,
426 else_branch,
427 } => {
428 let operand_value = match operand {
429 Some(o) => Some(eval_expr(o, row, ctx)?),
430 None => None,
431 };
432 for (when_expr, then_expr) in branches {
433 let when_value = eval_expr(when_expr, row, ctx)?;
434 let matched = match &operand_value {
435 None => matches!(when_value, Value::Bool(true)),
436 Some(op_v) => matches!(
437 apply_binary(spg_sql::ast::BinOp::Eq, op_v.clone(), when_value)?,
438 Value::Bool(true)
439 ),
440 };
441 if matched {
442 return eval_expr(then_expr, row, ctx);
443 }
444 }
445 match else_branch {
446 Some(e) => eval_expr(e, row, ctx),
447 None => Ok(Value::Null),
448 }
449 }
450 }
451}
452
453fn value_to_text_for_array(v: &Value) -> String {
460 match v {
461 Value::Text(s) | Value::Json(s) => s.clone(),
462 Value::Int(n) => n.to_string(),
463 Value::BigInt(n) => n.to_string(),
464 Value::SmallInt(n) => n.to_string(),
465 Value::Bool(b) => {
466 if *b {
467 "true".into()
468 } else {
469 "false".into()
470 }
471 }
472 Value::Float(x) => format!("{x}"),
473 Value::Date(d) => format_date(*d),
474 Value::Timestamp(t) => format_timestamp(*t),
475 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
476 _ => format!("{v:?}"),
477 }
478}
479
480fn extract_field(field: spg_sql::ast::ExtractField, v: &Value) -> Result<Value, EvalError> {
484 use spg_sql::ast::ExtractField as F;
485 if matches!(v, Value::Null) {
486 return Ok(Value::Null);
487 }
488 if let Value::Interval { months, micros } = *v {
492 let years = months / 12;
493 let mons = months % 12;
494 let secs_total = micros / 1_000_000;
495 let frac = micros % 1_000_000;
496 let result = match field {
497 F::Year => i64::from(years),
498 F::Month => i64::from(mons),
499 F::Day => micros / 86_400_000_000,
500 F::Hour => (secs_total / 3600) % 24,
501 F::Minute => (secs_total / 60) % 60,
502 F::Second => secs_total % 60,
503 F::Microsecond => (secs_total % 60) * 1_000_000 + frac,
504 F::Epoch => i64::from(months) * 30 * 86_400 + secs_total,
507 };
508 return Ok(Value::BigInt(result));
509 }
510 let (days, day_micros) = match *v {
511 Value::Date(d) => (d, 0_i64),
512 Value::Timestamp(t) => {
513 let days = t.div_euclid(86_400_000_000);
514 let day_micros = t.rem_euclid(86_400_000_000);
515 (i32::try_from(days).unwrap_or(i32::MAX), day_micros)
516 }
517 _ => {
518 return Err(EvalError::TypeMismatch {
519 detail: format!(
520 "EXTRACT requires DATE / TIMESTAMP / INTERVAL, got {:?}",
521 v.data_type()
522 ),
523 });
524 }
525 };
526 let (y, m, d) = civil_components(days);
527 let secs = day_micros / 1_000_000;
528 let hh = secs / 3600;
529 let mm = (secs / 60) % 60;
530 let ss = secs % 60;
531 let frac = day_micros % 1_000_000;
532 let result = match field {
533 F::Year => i64::from(y),
534 F::Month => i64::from(m),
535 F::Day => i64::from(d),
536 F::Hour => hh,
537 F::Minute => mm,
538 F::Second => ss,
539 F::Microsecond => ss * 1_000_000 + frac,
540 F::Epoch => i64::from(days) * 86_400 + secs,
543 };
544 Ok(Value::BigInt(result))
545}
546
547fn civil_components(days: i32) -> (i32, u32, u32) {
550 civil_from_days(days)
551}
552
553fn like_match(text: &str, pattern: &str) -> bool {
558 let text: Vec<char> = text.chars().collect();
559 let pat: Vec<char> = pattern.chars().collect();
560 like_match_inner(&text, 0, &pat, 0)
561}
562
563fn like_match_inner(text: &[char], mut ti: usize, pat: &[char], mut pi: usize) -> bool {
564 while pi < pat.len() {
565 match pat[pi] {
566 '%' => {
567 while pi < pat.len() && pat[pi] == '%' {
569 pi += 1;
570 }
571 if pi == pat.len() {
572 return true;
573 }
574 for k in ti..=text.len() {
575 if like_match_inner(text, k, pat, pi) {
576 return true;
577 }
578 }
579 return false;
580 }
581 '_' => {
582 if ti >= text.len() {
583 return false;
584 }
585 ti += 1;
586 pi += 1;
587 }
588 '\\' if pi + 1 < pat.len() => {
589 let want = pat[pi + 1];
590 if ti >= text.len() || text[ti] != want {
591 return false;
592 }
593 ti += 1;
594 pi += 2;
595 }
596 c => {
597 if ti >= text.len() || text[ti] != c {
598 return false;
599 }
600 ti += 1;
601 pi += 1;
602 }
603 }
604 }
605 ti == text.len()
606}
607
608fn apply_function(name: &str, args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
611 match name.to_ascii_lowercase().as_str() {
612 "nextval" => {
614 if args.len() != 1 {
615 return Err(EvalError::TypeMismatch {
616 detail: format!("nextval() takes 1 arg, got {}", args.len()),
617 });
618 }
619 let seq_name = match &args[0] {
620 Value::Text(s) => s.clone(),
621 Value::Null => return Ok(Value::Null),
622 other => {
623 return Err(EvalError::TypeMismatch {
624 detail: format!(
625 "nextval() argument must be TEXT, got {:?}",
626 other.data_type()
627 ),
628 });
629 }
630 };
631 let resolver = ctx
632 .sequence_resolver
633 .ok_or_else(|| EvalError::TypeMismatch {
634 detail: "nextval() requires a sequence resolver (read-only context)".into(),
635 })?;
636 let v = resolver(SequenceOp::Next(seq_name))?;
637 Ok(Value::BigInt(v))
638 }
639 "currval" => {
640 if args.len() != 1 {
641 return Err(EvalError::TypeMismatch {
642 detail: format!("currval() takes 1 arg, got {}", args.len()),
643 });
644 }
645 let seq_name = match &args[0] {
646 Value::Text(s) => s.clone(),
647 Value::Null => return Ok(Value::Null),
648 other => {
649 return Err(EvalError::TypeMismatch {
650 detail: format!(
651 "currval() argument must be TEXT, got {:?}",
652 other.data_type()
653 ),
654 });
655 }
656 };
657 let resolver = ctx
658 .sequence_resolver
659 .ok_or_else(|| EvalError::TypeMismatch {
660 detail: "currval() requires a sequence resolver (read-only context)".into(),
661 })?;
662 let v = resolver(SequenceOp::Curr(seq_name))?;
663 Ok(Value::BigInt(v))
664 }
665 "setval" => {
666 if args.len() != 2 && args.len() != 3 {
667 return Err(EvalError::TypeMismatch {
668 detail: format!("setval() takes 2 or 3 args, got {}", args.len()),
669 });
670 }
671 let seq_name = match &args[0] {
672 Value::Text(s) => s.clone(),
673 Value::Null => return Ok(Value::Null),
674 other => {
675 return Err(EvalError::TypeMismatch {
676 detail: format!(
677 "setval() name argument must be TEXT, got {:?}",
678 other.data_type()
679 ),
680 });
681 }
682 };
683 let value = match &args[1] {
684 Value::SmallInt(n) => i64::from(*n),
685 Value::Int(n) => i64::from(*n),
686 Value::BigInt(n) => *n,
687 Value::Null => return Ok(Value::Null),
688 other => {
689 return Err(EvalError::TypeMismatch {
690 detail: format!(
691 "setval() value argument must be integer, got {:?}",
692 other.data_type()
693 ),
694 });
695 }
696 };
697 let is_called = if args.len() == 3 {
698 match &args[2] {
699 Value::Bool(b) => *b,
700 Value::Null => return Ok(Value::Null),
701 other => {
702 return Err(EvalError::TypeMismatch {
703 detail: format!(
704 "setval() is_called argument must be BOOL, got {:?}",
705 other.data_type()
706 ),
707 });
708 }
709 }
710 } else {
711 true
712 };
713 let resolver = ctx
714 .sequence_resolver
715 .ok_or_else(|| EvalError::TypeMismatch {
716 detail: "setval() requires a sequence resolver (read-only context)".into(),
717 })?;
718 let v = resolver(SequenceOp::Set {
719 name: seq_name,
720 value,
721 is_called,
722 })?;
723 Ok(Value::BigInt(v))
724 }
725 "length" | "char_length" | "character_length" => {
729 if args.len() != 1 {
730 return Err(EvalError::TypeMismatch {
731 detail: format!("length() takes 1 arg, got {}", args.len()),
732 });
733 }
734 match &args[0] {
735 Value::Null => Ok(Value::Null),
736 Value::Text(s) => {
737 let n = i32::try_from(s.chars().count()).unwrap_or(i32::MAX);
738 Ok(Value::Int(n))
739 }
740 Value::Bytes(b) => {
745 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
746 Ok(Value::Int(n))
747 }
748 other => Err(EvalError::TypeMismatch {
749 detail: format!("length() needs text or bytea, got {:?}", other.data_type()),
750 }),
751 }
752 }
753 "octet_length" => {
757 if args.len() != 1 {
758 return Err(EvalError::TypeMismatch {
759 detail: format!("octet_length() takes 1 arg, got {}", args.len()),
760 });
761 }
762 match &args[0] {
763 Value::Null => Ok(Value::Null),
764 Value::Text(s) => {
765 let n = i32::try_from(s.len()).unwrap_or(i32::MAX);
766 Ok(Value::Int(n))
767 }
768 Value::Bytes(b) => {
769 let n = i32::try_from(b.len()).unwrap_or(i32::MAX);
770 Ok(Value::Int(n))
771 }
772 other => Err(EvalError::TypeMismatch {
773 detail: format!(
774 "octet_length() needs text or bytea, got {:?}",
775 other.data_type()
776 ),
777 }),
778 }
779 }
780 "array_length" => {
787 if args.len() != 2 {
788 return Err(EvalError::TypeMismatch {
789 detail: format!("array_length() takes 2 args, got {}", args.len()),
790 });
791 }
792 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
793 return Ok(Value::Null);
794 }
795 let len = match &args[0] {
796 Value::TextArray(items) => items.len(),
797 Value::IntArray(items) => items.len(),
798 Value::BigIntArray(items) => items.len(),
799 _ => {
800 return Err(EvalError::TypeMismatch {
801 detail: format!(
802 "array_length() first arg must be an array, got {:?}",
803 args[0].data_type()
804 ),
805 });
806 }
807 };
808 let dim: i64 = match args[1] {
809 Value::Int(n) => i64::from(n),
810 Value::BigInt(n) => n,
811 Value::SmallInt(n) => i64::from(n),
812 _ => {
813 return Err(EvalError::TypeMismatch {
814 detail: format!(
815 "array_length() second arg must be integer, got {:?}",
816 args[1].data_type()
817 ),
818 });
819 }
820 };
821 if dim != 1 {
822 return Ok(Value::Null);
823 }
824 let n = i32::try_from(len).unwrap_or(i32::MAX);
825 Ok(Value::Int(n))
826 }
827 "array_position" => {
832 if args.len() != 2 {
833 return Err(EvalError::TypeMismatch {
834 detail: format!("array_position() takes 2 args, got {}", args.len()),
835 });
836 }
837 if matches!(args[0], Value::Null) {
838 return Ok(Value::Null);
839 }
840 if matches!(args[1], Value::Null) {
841 return Ok(Value::Null);
842 }
843 match (&args[0], &args[1]) {
844 (Value::TextArray(items), Value::Text(needle)) => {
845 for (idx, item) in items.iter().enumerate() {
846 if let Some(s) = item
847 && s == needle
848 {
849 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
850 }
851 }
852 Ok(Value::Null)
853 }
854 (Value::IntArray(items), needle_v)
855 if matches!(
856 needle_v,
857 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
858 ) =>
859 {
860 let needle: i64 = match *needle_v {
861 Value::Int(n) => i64::from(n),
862 Value::SmallInt(n) => i64::from(n),
863 Value::BigInt(n) => n,
864 _ => unreachable!(),
865 };
866 for (idx, item) in items.iter().enumerate() {
867 if let Some(n) = item
868 && i64::from(*n) == needle
869 {
870 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
871 }
872 }
873 Ok(Value::Null)
874 }
875 (Value::BigIntArray(items), needle_v)
876 if matches!(
877 needle_v,
878 Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)
879 ) =>
880 {
881 let needle: i64 = match *needle_v {
882 Value::Int(n) => i64::from(n),
883 Value::SmallInt(n) => i64::from(n),
884 Value::BigInt(n) => n,
885 _ => unreachable!(),
886 };
887 for (idx, item) in items.iter().enumerate() {
888 if let Some(n) = item
889 && *n == needle
890 {
891 return Ok(Value::Int(i32::try_from(idx + 1).unwrap_or(i32::MAX)));
892 }
893 }
894 Ok(Value::Null)
895 }
896 (
897 arr @ (Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_)),
898 other,
899 ) => Err(EvalError::TypeMismatch {
900 detail: format!(
901 "array_position() needle type {:?} doesn't match array {:?}",
902 other.data_type(),
903 arr.data_type()
904 ),
905 }),
906 (other, _) => Err(EvalError::TypeMismatch {
907 detail: format!(
908 "array_position() first arg must be an array, got {:?}",
909 other.data_type()
910 ),
911 }),
912 }
913 }
914 "substring" | "substr" => {
922 if !matches!(args.len(), 2 | 3) {
923 return Err(EvalError::TypeMismatch {
924 detail: format!("substring() takes 2 or 3 args, got {}", args.len()),
925 });
926 }
927 if args.iter().any(|a| matches!(a, Value::Null)) {
928 return Ok(Value::Null);
929 }
930 let start: i64 = match args[1] {
931 Value::Int(n) => i64::from(n),
932 Value::BigInt(n) => n,
933 Value::SmallInt(n) => i64::from(n),
934 _ => {
935 return Err(EvalError::TypeMismatch {
936 detail: format!(
937 "substring() start must be integer, got {:?}",
938 args[1].data_type()
939 ),
940 });
941 }
942 };
943 let length: Option<i64> = if args.len() == 3 {
944 match args[2] {
945 Value::Int(n) => Some(i64::from(n)),
946 Value::BigInt(n) => Some(n),
947 Value::SmallInt(n) => Some(i64::from(n)),
948 _ => {
949 return Err(EvalError::TypeMismatch {
950 detail: format!(
951 "substring() length must be integer, got {:?}",
952 args[2].data_type()
953 ),
954 });
955 }
956 }
957 } else {
958 None
959 };
960 let (effective_start, effective_length): (i64, Option<i64>) = match length {
963 Some(len) => {
964 let end = start.saturating_add(len);
965 if end <= 1 || len < 0 {
966 return Ok(match &args[0] {
967 Value::Text(_) => Value::Text(String::new()),
968 Value::Bytes(_) => Value::Bytes(Vec::new()),
969 other => {
970 return Err(EvalError::TypeMismatch {
971 detail: format!(
972 "substring() needs text or bytea, got {:?}",
973 other.data_type()
974 ),
975 });
976 }
977 });
978 }
979 let eff_start = start.max(1);
980 let eff_len = end - eff_start;
981 (eff_start, Some(eff_len.max(0)))
982 }
983 None => (start.max(1), None),
984 };
985 match &args[0] {
986 Value::Text(s) => {
987 let chars: Vec<char> = s.chars().collect();
989 let skip = (effective_start - 1) as usize;
990 if skip >= chars.len() {
991 return Ok(Value::Text(String::new()));
992 }
993 let take = match effective_length {
994 Some(n) => (n as usize).min(chars.len() - skip),
995 None => chars.len() - skip,
996 };
997 Ok(Value::Text(chars[skip..skip + take].iter().collect()))
998 }
999 Value::Bytes(b) => {
1000 let skip = (effective_start - 1) as usize;
1001 if skip >= b.len() {
1002 return Ok(Value::Bytes(Vec::new()));
1003 }
1004 let take = match effective_length {
1005 Some(n) => (n as usize).min(b.len() - skip),
1006 None => b.len() - skip,
1007 };
1008 Ok(Value::Bytes(b[skip..skip + take].to_vec()))
1009 }
1010 other => Err(EvalError::TypeMismatch {
1011 detail: format!(
1012 "substring() needs text or bytea, got {:?}",
1013 other.data_type()
1014 ),
1015 }),
1016 }
1017 }
1018 "position" => {
1026 if args.len() != 2 {
1027 return Err(EvalError::TypeMismatch {
1028 detail: format!("position() takes 2 args, got {}", args.len()),
1029 });
1030 }
1031 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
1032 return Ok(Value::Null);
1033 }
1034 match (&args[0], &args[1]) {
1035 (Value::Text(needle), Value::Text(haystack)) => {
1036 if needle.is_empty() {
1037 return Ok(Value::Int(1));
1038 }
1039 let h_chars: Vec<char> = haystack.chars().collect();
1041 let n_chars: Vec<char> = needle.chars().collect();
1042 if n_chars.len() > h_chars.len() {
1043 return Ok(Value::Int(0));
1044 }
1045 for i in 0..=h_chars.len() - n_chars.len() {
1046 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1047 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1048 }
1049 }
1050 Ok(Value::Int(0))
1051 }
1052 (Value::Bytes(needle), Value::Bytes(haystack)) => {
1053 if needle.is_empty() {
1054 return Ok(Value::Int(1));
1055 }
1056 if needle.len() > haystack.len() {
1057 return Ok(Value::Int(0));
1058 }
1059 for i in 0..=haystack.len() - needle.len() {
1060 if &haystack[i..i + needle.len()] == needle.as_slice() {
1061 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1062 }
1063 }
1064 Ok(Value::Int(0))
1065 }
1066 (a, b) => Err(EvalError::TypeMismatch {
1067 detail: format!(
1068 "position() operands must both be text or both bytea, got {:?} and {:?}",
1069 a.data_type(),
1070 b.data_type()
1071 ),
1072 }),
1073 }
1074 }
1075 "upper" => {
1076 if args.len() != 1 {
1077 return Err(EvalError::TypeMismatch {
1078 detail: format!("upper() takes 1 arg, got {}", args.len()),
1079 });
1080 }
1081 match &args[0] {
1082 Value::Null => Ok(Value::Null),
1083 Value::Text(s) => Ok(Value::Text(s.to_uppercase())),
1084 other => Err(EvalError::TypeMismatch {
1085 detail: format!("upper() needs text, got {:?}", other.data_type()),
1086 }),
1087 }
1088 }
1089 "lower" => {
1090 if args.len() != 1 {
1091 return Err(EvalError::TypeMismatch {
1092 detail: format!("lower() takes 1 arg, got {}", args.len()),
1093 });
1094 }
1095 match &args[0] {
1096 Value::Null => Ok(Value::Null),
1097 Value::Text(s) => Ok(Value::Text(s.to_lowercase())),
1098 other => Err(EvalError::TypeMismatch {
1099 detail: format!("lower() needs text, got {:?}", other.data_type()),
1100 }),
1101 }
1102 }
1103 "abs" => {
1104 if args.len() != 1 {
1105 return Err(EvalError::TypeMismatch {
1106 detail: format!("abs() takes 1 arg, got {}", args.len()),
1107 });
1108 }
1109 match &args[0] {
1110 Value::Null => Ok(Value::Null),
1111 Value::Int(n) => Ok(Value::Int(n.wrapping_abs())),
1112 Value::BigInt(n) => Ok(Value::BigInt(n.wrapping_abs())),
1113 Value::Float(x) => Ok(Value::Float(x.abs())),
1114 other => Err(EvalError::TypeMismatch {
1115 detail: format!("abs() needs numeric, got {:?}", other.data_type()),
1116 }),
1117 }
1118 }
1119 "coalesce" => {
1120 for a in args {
1121 if !matches!(a, Value::Null) {
1122 return Ok(a.clone());
1123 }
1124 }
1125 Ok(Value::Null)
1126 }
1127 "date_trunc" => date_trunc(args),
1128 "date_part" => date_part(args),
1129 "age" => age(args),
1130 "to_char" => to_char(args),
1131 "date_format" => date_format_mysql(args),
1137 "unix_timestamp" => unix_timestamp_of(args),
1138 "from_unixtime" => from_unixtime(args),
1139 "format" => format_string(args),
1147 "concat" => {
1168 let mut out = String::new();
1169 for v in args {
1170 if matches!(v, Value::Null) {
1171 continue;
1172 }
1173 out.push_str(&value_to_format_text(v));
1174 }
1175 Ok(Value::Text(out))
1176 }
1177 "random" => {
1300 if !args.is_empty() {
1301 return Err(EvalError::TypeMismatch {
1302 detail: alloc::format!("random() takes 0 args, got {}", args.len()),
1303 });
1304 }
1305 Ok(Value::Float(prng_next_f64()))
1306 }
1307 "gen_random_uuid" | "uuid_generate_v4" => {
1314 if !args.is_empty() {
1315 return Err(EvalError::TypeMismatch {
1316 detail: alloc::format!("{name}() takes 0 args, got {}", args.len()),
1317 });
1318 }
1319 Ok(Value::Uuid(gen_random_uuid_bytes()))
1320 }
1321 "sign" => {
1322 if args.len() != 1 {
1323 return Err(EvalError::TypeMismatch {
1324 detail: alloc::format!("sign() takes 1 arg, got {}", args.len()),
1325 });
1326 }
1327 match &args[0] {
1328 Value::Null => Ok(Value::Null),
1329 Value::SmallInt(n) => Ok(Value::SmallInt(n.signum())),
1330 Value::Int(n) => Ok(Value::Int(n.signum())),
1331 Value::BigInt(n) => Ok(Value::BigInt(n.signum())),
1332 Value::Float(x) => {
1333 let s = if *x > 0.0 {
1334 1.0
1335 } else if *x < 0.0 {
1336 -1.0
1337 } else {
1338 0.0
1339 };
1340 Ok(Value::Float(s))
1341 }
1342 Value::Numeric { scaled, scale } => {
1343 let s = scaled.signum();
1344 Ok(Value::Numeric {
1345 scaled: s * pow10_i128(*scale),
1346 scale: *scale,
1347 })
1348 }
1349 other => Err(EvalError::TypeMismatch {
1350 detail: alloc::format!("sign() needs numeric, got {:?}", other.data_type()),
1351 }),
1352 }
1353 }
1354 "sqrt" => {
1355 if args.len() != 1 {
1356 return Err(EvalError::TypeMismatch {
1357 detail: alloc::format!("sqrt() takes 1 arg, got {}", args.len()),
1358 });
1359 }
1360 match &args[0] {
1361 Value::Null => Ok(Value::Null),
1362 v => {
1363 let x = value_to_f64(v).ok_or_else(|| EvalError::TypeMismatch {
1364 detail: alloc::format!("sqrt() needs numeric, got {:?}", v.data_type()),
1365 })?;
1366 if x < 0.0 {
1367 return Err(EvalError::TypeMismatch {
1368 detail: "sqrt(): negative input outside real domain".into(),
1369 });
1370 }
1371 if x == 0.0 {
1372 return Ok(Value::Float(0.0));
1373 }
1374 Ok(Value::Float(f64_sqrt(x)))
1375 }
1376 }
1377 }
1378 "power" | "pow" => {
1379 if args.len() != 2 {
1380 return Err(EvalError::TypeMismatch {
1381 detail: alloc::format!("power() takes 2 args, got {}", args.len()),
1382 });
1383 }
1384 if args.iter().any(|v| matches!(v, Value::Null)) {
1385 return Ok(Value::Null);
1386 }
1387 let x = value_to_f64(&args[0]).ok_or_else(|| EvalError::TypeMismatch {
1388 detail: "power() needs numeric x".into(),
1389 })?;
1390 let y = value_to_f64(&args[1]).ok_or_else(|| EvalError::TypeMismatch {
1391 detail: "power() needs numeric y".into(),
1392 })?;
1393 let y_int = y as i32;
1395 if (y_int as f64) == y && y.abs() < 1024.0 {
1396 let result = f64_powi(x, y_int);
1397 return Ok(Value::Float(result));
1398 }
1399 if x < 0.0 {
1403 return Err(EvalError::TypeMismatch {
1404 detail: "power(): negative base with fractional exponent yields complex result"
1405 .into(),
1406 });
1407 }
1408 if x == 0.0 && y < 0.0 {
1409 return Err(EvalError::TypeMismatch {
1410 detail: "power(): 0 raised to negative power is undefined".into(),
1411 });
1412 }
1413 if x == 0.0 {
1414 return Ok(Value::Float(0.0));
1415 }
1416 Ok(Value::Float(f64_exp(y * f64_ln(x))))
1417 }
1418 "mod" => {
1419 if args.len() != 2 {
1420 return Err(EvalError::TypeMismatch {
1421 detail: alloc::format!("mod() takes 2 args, got {}", args.len()),
1422 });
1423 }
1424 if args.iter().any(|v| matches!(v, Value::Null)) {
1425 return Ok(Value::Null);
1426 }
1427 let to_i64 = |v: &Value| -> Result<i64, EvalError> {
1428 match v {
1429 Value::SmallInt(x) => Ok(i64::from(*x)),
1430 Value::Int(x) => Ok(i64::from(*x)),
1431 Value::BigInt(x) => Ok(*x),
1432 other => Err(EvalError::TypeMismatch {
1433 detail: alloc::format!("mod() needs integer, got {:?}", other.data_type()),
1434 }),
1435 }
1436 };
1437 let y = to_i64(&args[0])?;
1438 let x = to_i64(&args[1])?;
1439 if x == 0 {
1440 return Err(EvalError::TypeMismatch {
1441 detail: "mod(): division by zero".into(),
1442 });
1443 }
1444 let result = y % x;
1447 if let Ok(small) = i16::try_from(result) {
1449 if matches!(args[0], Value::SmallInt(_)) && matches!(args[1], Value::SmallInt(_)) {
1450 return Ok(Value::SmallInt(small));
1451 }
1452 }
1453 if let Ok(int_) = i32::try_from(result) {
1454 if !matches!(args[0], Value::BigInt(_)) && !matches!(args[1], Value::BigInt(_)) {
1455 return Ok(Value::Int(int_));
1456 }
1457 }
1458 Ok(Value::BigInt(result))
1459 }
1460 "greatest" | "least" => {
1461 if args.is_empty() {
1462 return Err(EvalError::TypeMismatch {
1463 detail: alloc::format!(
1464 "{lc}() takes at least 1 arg",
1465 lc = if name.eq_ignore_ascii_case("greatest") {
1466 "greatest"
1467 } else {
1468 "least"
1469 }
1470 ),
1471 });
1472 }
1473 let non_null: alloc::vec::Vec<&Value> =
1474 args.iter().filter(|v| !matches!(v, Value::Null)).collect();
1475 if non_null.is_empty() {
1476 return Ok(Value::Null);
1477 }
1478 let is_greatest = name.eq_ignore_ascii_case("greatest");
1479 let mut best = non_null[0].clone();
1480 for v in &non_null[1..] {
1481 let ord = value_cmp_for_min_max(&best, v);
1482 let take = if is_greatest {
1483 ord == core::cmp::Ordering::Less
1484 } else {
1485 ord == core::cmp::Ordering::Greater
1486 };
1487 if take {
1488 best = (*v).clone();
1489 }
1490 }
1491 Ok(best)
1492 }
1493 "ifnull" => {
1497 if args.len() != 2 {
1498 return Err(EvalError::TypeMismatch {
1499 detail: alloc::format!("ifnull() takes 2 args, got {}", args.len()),
1500 });
1501 }
1502 for v in args {
1503 if !matches!(v, Value::Null) {
1504 return Ok(v.clone());
1505 }
1506 }
1507 Ok(Value::Null)
1508 }
1509 "if" => {
1513 if args.len() != 3 {
1514 return Err(EvalError::TypeMismatch {
1515 detail: alloc::format!(
1516 "if() takes 3 args (cond, then, else), got {}",
1517 args.len()
1518 ),
1519 });
1520 }
1521 let truthy = match &args[0] {
1522 Value::Null => false,
1523 Value::Bool(b) => *b,
1524 Value::SmallInt(n) => *n != 0,
1525 Value::Int(n) => *n != 0,
1526 Value::BigInt(n) => *n != 0,
1527 Value::Float(x) => *x != 0.0,
1528 Value::Text(s) => !s.is_empty() && s != "0",
1529 _ => true,
1530 };
1531 if truthy {
1532 Ok(args[1].clone())
1533 } else {
1534 Ok(args[2].clone())
1535 }
1536 }
1537 "nullif" => {
1538 if args.len() != 2 {
1539 return Err(EvalError::TypeMismatch {
1540 detail: alloc::format!("nullif() takes 2 args, got {}", args.len()),
1541 });
1542 }
1543 match (&args[0], &args[1]) {
1544 (Value::Null, _) => Ok(Value::Null),
1545 (a, Value::Null) => Ok(a.clone()),
1546 (a, b) => {
1547 if values_equal_for_nullif(a, b) {
1551 Ok(Value::Null)
1552 } else {
1553 Ok(a.clone())
1554 }
1555 }
1556 }
1557 }
1558 "trunc" => {
1559 match args.len() {
1560 1 => match &args[0] {
1561 Value::Null => Ok(Value::Null),
1562 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1563 Value::Float(x) => Ok(Value::Float(f64_trunc(*x))),
1564 Value::Numeric { scaled, scale } => {
1565 let factor = pow10_i128(*scale);
1566 let q = scaled / factor;
1568 Ok(Value::Numeric {
1569 scaled: q * factor,
1570 scale: *scale,
1571 })
1572 }
1573 other => Err(EvalError::TypeMismatch {
1574 detail: alloc::format!(
1575 "trunc() needs numeric, got {:?}",
1576 other.data_type()
1577 ),
1578 }),
1579 },
1580 2 => {
1581 if args.iter().any(|v| matches!(v, Value::Null)) {
1582 return Ok(Value::Null);
1583 }
1584 let n = match &args[1] {
1585 Value::SmallInt(x) => i32::from(*x),
1586 Value::Int(x) => *x,
1587 Value::BigInt(x) => {
1588 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1589 detail: "trunc(): scale must fit in i32".into(),
1590 })?
1591 }
1592 other => {
1593 return Err(EvalError::TypeMismatch {
1594 detail: alloc::format!(
1595 "trunc(): scale must be integer, got {:?}",
1596 other.data_type()
1597 ),
1598 });
1599 }
1600 };
1601 let x = match &args[0] {
1602 Value::SmallInt(v) => f64::from(*v),
1603 Value::Int(v) => f64::from(*v),
1604 Value::BigInt(v) => *v as f64,
1605 Value::Float(v) => *v,
1606 Value::Numeric { scaled, scale } => {
1607 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1608 }
1609 other => {
1610 return Err(EvalError::TypeMismatch {
1611 detail: alloc::format!(
1612 "trunc() needs numeric x, got {:?}",
1613 other.data_type()
1614 ),
1615 });
1616 }
1617 };
1618 let result = if n >= 0 {
1619 let factor = f64_powi(10.0, n);
1620 f64_trunc(x * factor) / factor
1621 } else {
1622 let factor = f64_powi(10.0, -n);
1623 f64_trunc(x / factor) * factor
1624 };
1625 Ok(Value::Float(result))
1626 }
1627 _ => Err(EvalError::TypeMismatch {
1628 detail: alloc::format!("trunc() takes 1 or 2 args, got {}", args.len()),
1629 }),
1630 }
1631 }
1632 "round" => {
1633 match args.len() {
1634 1 => match &args[0] {
1635 Value::Null => Ok(Value::Null),
1636 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1637 Value::Float(x) => Ok(Value::Float(f64_round_half_away(*x))),
1638 Value::Numeric { scaled, scale } => {
1639 let factor = pow10_i128(*scale);
1640 let q = scaled.div_euclid(factor);
1641 let r = scaled.rem_euclid(factor);
1642 let result = if 2 * r >= factor { q + 1 } else { q };
1644 Ok(Value::Numeric {
1645 scaled: result * factor,
1646 scale: *scale,
1647 })
1648 }
1649 other => Err(EvalError::TypeMismatch {
1650 detail: alloc::format!(
1651 "round() needs numeric, got {:?}",
1652 other.data_type()
1653 ),
1654 }),
1655 },
1656 2 => {
1657 if args.iter().any(|v| matches!(v, Value::Null)) {
1658 return Ok(Value::Null);
1659 }
1660 let n = match &args[1] {
1661 Value::SmallInt(x) => i32::from(*x),
1662 Value::Int(x) => *x,
1663 Value::BigInt(x) => {
1664 i32::try_from(*x).map_err(|_| EvalError::TypeMismatch {
1665 detail: "round(): scale must fit in i32".into(),
1666 })?
1667 }
1668 other => {
1669 return Err(EvalError::TypeMismatch {
1670 detail: alloc::format!(
1671 "round(): scale must be integer, got {:?}",
1672 other.data_type()
1673 ),
1674 });
1675 }
1676 };
1677 let x = match &args[0] {
1683 Value::SmallInt(v) => f64::from(*v),
1684 Value::Int(v) => f64::from(*v),
1685 Value::BigInt(v) => *v as f64,
1686 Value::Float(v) => *v,
1687 Value::Numeric { scaled, scale } => {
1688 (*scaled as f64) / f64_powi(10.0, i32::from(*scale))
1689 }
1690 other => {
1691 return Err(EvalError::TypeMismatch {
1692 detail: alloc::format!(
1693 "round() needs numeric x, got {:?}",
1694 other.data_type()
1695 ),
1696 });
1697 }
1698 };
1699 let result = if n >= 0 {
1704 let factor = f64_powi(10.0, n);
1705 f64_round_half_away(x * factor) / factor
1706 } else {
1707 let factor = f64_powi(10.0, -n);
1708 f64_round_half_away(x / factor) * factor
1709 };
1710 Ok(Value::Float(result))
1711 }
1712 _ => Err(EvalError::TypeMismatch {
1713 detail: alloc::format!("round() takes 1 or 2 args, got {}", args.len()),
1714 }),
1715 }
1716 }
1717 "ceil" | "ceiling" => {
1718 if args.len() != 1 {
1719 return Err(EvalError::TypeMismatch {
1720 detail: alloc::format!("ceil() takes 1 arg, got {}", args.len()),
1721 });
1722 }
1723 match &args[0] {
1724 Value::Null => Ok(Value::Null),
1725 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1726 Value::Float(x) => Ok(Value::Float(f64_ceil(*x))),
1727 Value::Numeric { scaled, scale } => {
1728 let factor = pow10_i128(*scale);
1729 let q = scaled.div_euclid(factor);
1730 let r = scaled.rem_euclid(factor);
1731 let result = if r == 0 { q } else { q + 1 };
1732 Ok(Value::Numeric {
1733 scaled: result * factor,
1734 scale: *scale,
1735 })
1736 }
1737 other => Err(EvalError::TypeMismatch {
1738 detail: alloc::format!("ceil() needs numeric, got {:?}", other.data_type()),
1739 }),
1740 }
1741 }
1742 "floor" => {
1743 if args.len() != 1 {
1744 return Err(EvalError::TypeMismatch {
1745 detail: alloc::format!("floor() takes 1 arg, got {}", args.len()),
1746 });
1747 }
1748 match &args[0] {
1749 Value::Null => Ok(Value::Null),
1750 Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_) => Ok(args[0].clone()),
1751 Value::Float(x) => Ok(Value::Float(f64_floor(*x))),
1752 Value::Numeric { scaled, scale } => {
1753 let factor = pow10_i128(*scale);
1754 let q = scaled.div_euclid(factor);
1755 Ok(Value::Numeric {
1759 scaled: q * factor,
1760 scale: *scale,
1761 })
1762 }
1763 other => Err(EvalError::TypeMismatch {
1764 detail: alloc::format!("floor() needs numeric, got {:?}", other.data_type()),
1765 }),
1766 }
1767 }
1768 "left" => string_left_right(args, true, "left"),
1769 "right" => string_left_right(args, false, "right"),
1770 "strpos" => {
1771 if args.len() != 2 {
1772 return Err(EvalError::TypeMismatch {
1773 detail: alloc::format!(
1774 "strpos() takes 2 args (haystack, needle), got {}",
1775 args.len()
1776 ),
1777 });
1778 }
1779 if args.iter().any(|v| matches!(v, Value::Null)) {
1780 return Ok(Value::Null);
1781 }
1782 let haystack = value_to_format_text(&args[0]);
1783 let needle = value_to_format_text(&args[1]);
1784 if needle.is_empty() {
1785 return Ok(Value::Int(1));
1786 }
1787 let h_chars: Vec<char> = haystack.chars().collect();
1788 let n_chars: Vec<char> = needle.chars().collect();
1789 if n_chars.len() > h_chars.len() {
1790 return Ok(Value::Int(0));
1791 }
1792 for i in 0..=h_chars.len() - n_chars.len() {
1793 if h_chars[i..i + n_chars.len()] == n_chars[..] {
1794 return Ok(Value::Int(i32::try_from(i + 1).unwrap_or(i32::MAX)));
1795 }
1796 }
1797 Ok(Value::Int(0))
1798 }
1799 "lpad" => string_pad(args, true, "lpad"),
1800 "rpad" => string_pad(args, false, "rpad"),
1801 "repeat" => {
1802 if args.len() != 2 {
1803 return Err(EvalError::TypeMismatch {
1804 detail: alloc::format!("repeat() takes 2 args, got {}", args.len()),
1805 });
1806 }
1807 if args.iter().any(|v| matches!(v, Value::Null)) {
1808 return Ok(Value::Null);
1809 }
1810 let s = value_to_format_text(&args[0]);
1811 let n = match &args[1] {
1812 Value::SmallInt(x) => i64::from(*x),
1813 Value::Int(x) => i64::from(*x),
1814 Value::BigInt(x) => *x,
1815 other => {
1816 return Err(EvalError::TypeMismatch {
1817 detail: alloc::format!(
1818 "repeat(): n must be integer, got {:?}",
1819 other.data_type()
1820 ),
1821 });
1822 }
1823 };
1824 if n <= 0 {
1825 return Ok(Value::Text(String::new()));
1826 }
1827 const MAX_REPEAT_BYTES: usize = 64 * 1024 * 1024;
1831 let needed =
1832 s.len()
1833 .checked_mul(n as usize)
1834 .ok_or_else(|| EvalError::TypeMismatch {
1835 detail: "repeat(): result size overflows usize".into(),
1836 })?;
1837 if needed > MAX_REPEAT_BYTES {
1838 return Err(EvalError::TypeMismatch {
1839 detail: alloc::format!(
1840 "repeat(): result would exceed {MAX_REPEAT_BYTES} bytes"
1841 ),
1842 });
1843 }
1844 Ok(Value::Text(s.repeat(n as usize)))
1845 }
1846 "split_part" => {
1847 if args.len() != 3 {
1848 return Err(EvalError::TypeMismatch {
1849 detail: alloc::format!(
1850 "split_part() takes 3 args (string, delim, n), got {}",
1851 args.len()
1852 ),
1853 });
1854 }
1855 if args.iter().any(|v| matches!(v, Value::Null)) {
1856 return Ok(Value::Null);
1857 }
1858 let s = value_to_format_text(&args[0]);
1859 let delim = value_to_format_text(&args[1]);
1860 if delim.is_empty() {
1861 return Err(EvalError::TypeMismatch {
1862 detail: "split_part(): delimiter cannot be empty".into(),
1863 });
1864 }
1865 let n = match &args[2] {
1866 Value::SmallInt(x) => i64::from(*x),
1867 Value::Int(x) => i64::from(*x),
1868 Value::BigInt(x) => *x,
1869 other => {
1870 return Err(EvalError::TypeMismatch {
1871 detail: alloc::format!(
1872 "split_part(): n must be integer, got {:?}",
1873 other.data_type()
1874 ),
1875 });
1876 }
1877 };
1878 if n == 0 {
1879 return Err(EvalError::TypeMismatch {
1880 detail: "split_part(): n must be nonzero (PG: 1-indexed)".into(),
1881 });
1882 }
1883 let parts: alloc::vec::Vec<&str> = s.split(&delim[..]).collect();
1884 let total = parts.len() as i64;
1885 let idx = if n > 0 {
1886 n - 1
1887 } else {
1888 total + n
1890 };
1891 if idx < 0 || idx >= total {
1892 return Ok(Value::Text(String::new()));
1893 }
1894 Ok(Value::Text(parts[idx as usize].to_string()))
1895 }
1896 "translate" => {
1903 if args.len() != 3 {
1904 return Err(EvalError::TypeMismatch {
1905 detail: alloc::format!("translate() takes 3 args, got {}", args.len()),
1906 });
1907 }
1908 if args.iter().any(|v| matches!(v, Value::Null)) {
1909 return Ok(Value::Null);
1910 }
1911 let s = value_to_format_text(&args[0]);
1912 let from = value_to_format_text(&args[1]);
1913 let to = value_to_format_text(&args[2]);
1914 let from_chars: Vec<char> = from.chars().collect();
1915 let to_chars: Vec<char> = to.chars().collect();
1916 let mut map: alloc::collections::BTreeMap<char, Option<char>> =
1918 alloc::collections::BTreeMap::new();
1919 for (i, &fc) in from_chars.iter().enumerate() {
1920 if map.contains_key(&fc) {
1921 continue;
1922 }
1923 let replacement = to_chars.get(i).copied();
1924 map.insert(fc, replacement);
1925 }
1926 let mut out = String::with_capacity(s.len());
1927 for c in s.chars() {
1928 match map.get(&c) {
1929 Some(Some(r)) => out.push(*r),
1930 Some(None) => {} None => out.push(c),
1932 }
1933 }
1934 Ok(Value::Text(out))
1935 }
1936 "replace" => {
1937 if args.len() != 3 {
1938 return Err(EvalError::TypeMismatch {
1939 detail: alloc::format!(
1940 "replace() takes 3 args (string, from, to), got {}",
1941 args.len()
1942 ),
1943 });
1944 }
1945 if args.iter().any(|v| matches!(v, Value::Null)) {
1946 return Ok(Value::Null);
1947 }
1948 let s = value_to_format_text(&args[0]);
1949 let from = value_to_format_text(&args[1]);
1950 let to = value_to_format_text(&args[2]);
1951 if from.is_empty() {
1952 return Ok(Value::Text(s));
1953 }
1954 Ok(Value::Text(s.replace(&from[..], &to)))
1959 }
1960 "trim" | "btrim" => string_trim(args, TrimSide::Both, "trim"),
1961 "ltrim" => string_trim(args, TrimSide::Left, "ltrim"),
1962 "rtrim" => string_trim(args, TrimSide::Right, "rtrim"),
1963 "concat_ws" => {
1964 if args.is_empty() {
1965 return Err(EvalError::TypeMismatch {
1966 detail: "concat_ws() requires at least 1 arg (the separator)".into(),
1967 });
1968 }
1969 let sep = match &args[0] {
1971 Value::Null => return Ok(Value::Null),
1972 v => value_to_format_text(v),
1973 };
1974 let mut out = String::new();
1975 let mut first = true;
1976 for v in &args[1..] {
1977 if matches!(v, Value::Null) {
1978 continue;
1979 }
1980 if first {
1981 first = false;
1982 } else {
1983 out.push_str(&sep);
1984 }
1985 out.push_str(&value_to_format_text(v));
1986 }
1987 Ok(Value::Text(out))
1988 }
1989 "regexp_matches" => regexp_matches(args),
1991 "regexp_replace" => regexp_replace(args),
1992 "regexp_split_to_array" => regexp_split_to_array(args),
1993 "to_json" | "to_jsonb" => {
1997 if args.len() != 1 {
1998 return Err(EvalError::TypeMismatch {
1999 detail: alloc::format!("to_json() takes 1 arg, got {}", args.len()),
2000 });
2001 }
2002 if let Value::Json(s) = &args[0] {
2004 return Ok(Value::Json(s.clone()));
2005 }
2006 Ok(Value::Json(crate::json::value_to_json_text(&args[0])))
2007 }
2008 "json_build_object" | "jsonb_build_object" => crate::json::build_object(args),
2009 "json_build_array" | "jsonb_build_array" => crate::json::build_array(args),
2010 "jsonb_set" | "json_set" => crate::json::set(args),
2011 "jsonb_insert" | "json_insert" => crate::json::insert(args),
2012 "jsonb_path_query" | "json_path_query" => {
2014 if args.len() != 2 {
2015 return Err(EvalError::TypeMismatch {
2016 detail: alloc::format!("jsonb_path_query() takes 2 args, got {}", args.len()),
2017 });
2018 }
2019 crate::json::path_query(&args[0], &args[1])
2020 }
2021 "jsonb_path_query_first" | "json_path_query_first" => {
2022 if args.len() != 2 {
2023 return Err(EvalError::TypeMismatch {
2024 detail: alloc::format!(
2025 "jsonb_path_query_first() takes 2 args, got {}",
2026 args.len()
2027 ),
2028 });
2029 }
2030 crate::json::path_query_first(&args[0], &args[1])
2031 }
2032 "jsonb_path_query_array" | "json_path_query_array" => {
2033 if args.len() != 2 {
2034 return Err(EvalError::TypeMismatch {
2035 detail: alloc::format!(
2036 "jsonb_path_query_array() takes 2 args, got {}",
2037 args.len()
2038 ),
2039 });
2040 }
2041 crate::json::path_query_array(&args[0], &args[1])
2042 }
2043 "host" => inet_host(args),
2045 "network" => inet_network(args),
2046 "masklen" => inet_masklen(args),
2047 "encode" => encode_text(args),
2049 "decode" => decode_text(args),
2050 "error_on_null" => error_on_null(args),
2051 "to_tsvector" => fts_to_tsvector(args, ctx),
2056 "setweight" => fts_setweight(args),
2060 "string_to_array" => fn_string_to_array(args),
2064 "plainto_tsquery" => fts_plainto_tsquery(args, ctx),
2065 "phraseto_tsquery" => fts_phraseto_tsquery(args, ctx),
2066 "websearch_to_tsquery" => fts_websearch_to_tsquery(args, ctx),
2067 "to_tsquery" => fts_to_tsquery(args, ctx),
2068 "ts_rank" => fts_ts_rank(args),
2071 "ts_rank_cd" => fts_ts_rank_cd(args),
2072 "set_config" => Ok(args.get(1).cloned().unwrap_or(Value::Null)),
2077 "current_setting" => Ok(Value::Text(String::new())),
2078 "pg_get_serial_sequence" | "pg_get_constraintdef" | "pg_get_indexdef" => Ok(Value::Null),
2084 "version" => Ok(Value::Text("PostgreSQL 16 (SPG-compat)".into())),
2085 "current_database" | "database" => Ok(Value::Text("spg".into())),
2093 "current_schema" => Ok(Value::Text("public".into())),
2094 "current_user" | "session_user" | "user" => Ok(Value::Text("admin".into())),
2095 "pg_typeof" => {
2102 if args.len() != 1 {
2103 return Err(EvalError::TypeMismatch {
2104 detail: format!("pg_typeof() takes 1 arg, got {}", args.len()),
2105 });
2106 }
2107 Ok(Value::Text(pg_typeof_name(&args[0]).into()))
2108 }
2109 "lastval" => Ok(Value::Null),
2114 "similarity" => {
2119 if args.len() != 2 {
2120 return Err(EvalError::TypeMismatch {
2121 detail: format!("similarity() takes 2 args, got {}", args.len()),
2122 });
2123 }
2124 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2125 return Ok(Value::Null);
2126 }
2127 let a = match &args[0] {
2128 Value::Text(s) => s.as_str(),
2129 other => {
2130 return Err(EvalError::TypeMismatch {
2131 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2132 });
2133 }
2134 };
2135 let b = match &args[1] {
2136 Value::Text(s) => s.as_str(),
2137 other => {
2138 return Err(EvalError::TypeMismatch {
2139 detail: format!("similarity() needs text, got {:?}", other.data_type()),
2140 });
2141 }
2142 };
2143 Ok(Value::Float(spg_storage::trgm::similarity(a, b)))
2146 }
2147 "show_trgm" => {
2148 if args.len() != 1 {
2149 return Err(EvalError::TypeMismatch {
2150 detail: format!("show_trgm() takes 1 arg, got {}", args.len()),
2151 });
2152 }
2153 if matches!(args[0], Value::Null) {
2154 return Ok(Value::Null);
2155 }
2156 let s = match &args[0] {
2157 Value::Text(s) => s.as_str(),
2158 other => {
2159 return Err(EvalError::TypeMismatch {
2160 detail: format!("show_trgm() needs text, got {:?}", other.data_type()),
2161 });
2162 }
2163 };
2164 let trigrams: Vec<Option<String>> = spg_storage::trgm::extract_trigrams(s)
2168 .into_iter()
2169 .map(Some)
2170 .collect();
2171 Ok(Value::TextArray(trigrams))
2172 }
2173 other => Err(EvalError::TypeMismatch {
2174 detail: format!("unknown function `{other}`"),
2175 }),
2176 }
2177}
2178
2179fn fts_ts_rank(args: &[Value]) -> Result<Value, EvalError> {
2184 let (vec, query) = parse_rank_args("ts_rank", args)?;
2185 match (vec, query) {
2186 (None, _) | (_, None) => Ok(Value::Null),
2187 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank(&v, &q)))),
2188 }
2189}
2190
2191fn fts_ts_rank_cd(args: &[Value]) -> Result<Value, EvalError> {
2192 let (vec, query) = parse_rank_args("ts_rank_cd", args)?;
2193 match (vec, query) {
2194 (None, _) | (_, None) => Ok(Value::Null),
2195 (Some(v), Some(q)) => Ok(Value::Float(f64::from(crate::fts::ts_rank_cd(&v, &q)))),
2196 }
2197}
2198
2199fn parse_rank_args(
2200 name: &str,
2201 args: &[Value],
2202) -> Result<
2203 (
2204 Option<Vec<spg_storage::TsLexeme>>,
2205 Option<spg_storage::TsQueryAst>,
2206 ),
2207 EvalError,
2208> {
2209 if args.len() != 2 {
2210 return Err(EvalError::TypeMismatch {
2211 detail: format!(
2212 "{name}() takes 2 args in v7.12.2 (weights array + normalisation flag are v7.12.x carve-out), got {}",
2213 args.len()
2214 ),
2215 });
2216 }
2217 let vec = match &args[0] {
2218 Value::Null => None,
2219 Value::TsVector(v) => Some(v.clone()),
2220 other => {
2221 return Err(EvalError::TypeMismatch {
2222 detail: format!(
2223 "{name}() first arg must be tsvector, got {:?}",
2224 other.data_type()
2225 ),
2226 });
2227 }
2228 };
2229 let query = match &args[1] {
2230 Value::Null => None,
2231 Value::TsQuery(q) => Some(q.clone()),
2232 other => {
2233 return Err(EvalError::TypeMismatch {
2234 detail: format!(
2235 "{name}() second arg must be tsquery, got {:?}",
2236 other.data_type()
2237 ),
2238 });
2239 }
2240 };
2241 Ok((vec, query))
2242}
2243
2244fn ts_match(l: Value, r: Value) -> Result<Value, EvalError> {
2249 let (vec, query) = match (l, r) {
2250 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
2251 (Value::TsVector(v), Value::TsQuery(q)) => (v, q),
2252 (Value::TsQuery(q), Value::TsVector(v)) => (v, q),
2253 (l, r) => {
2254 return Err(EvalError::TypeMismatch {
2255 detail: format!(
2256 "@@ requires (tsvector, tsquery), got ({:?}, {:?})",
2257 l.data_type(),
2258 r.data_type()
2259 ),
2260 });
2261 }
2262 };
2263 Ok(Value::Bool(crate::fts::ts_query_matches(&vec, &query)))
2264}
2265
2266fn fts_to_tsvector(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2271 let (config, text) = parse_fts_args("to_tsvector", args, ctx)?;
2272 match text {
2273 None => Ok(Value::Null),
2274 Some(t) => Ok(Value::TsVector(crate::fts::to_tsvector(config, &t))),
2275 }
2276}
2277
2278fn fts_setweight(args: &[Value]) -> Result<Value, EvalError> {
2281 let [vec_arg, weight_arg] = args else {
2282 return Err(EvalError::TypeMismatch {
2283 detail: alloc::format!("setweight expects 2 arguments, got {}", args.len()),
2284 });
2285 };
2286 if matches!(vec_arg, Value::Null) || matches!(weight_arg, Value::Null) {
2287 return Ok(Value::Null);
2288 }
2289 let Value::TsVector(lexemes) = vec_arg else {
2290 return Err(EvalError::TypeMismatch {
2291 detail: alloc::format!(
2292 "setweight expects a tsvector, got {:?}",
2293 vec_arg.data_type()
2294 ),
2295 });
2296 };
2297 let Value::Text(w) = weight_arg else {
2298 return Err(EvalError::TypeMismatch {
2299 detail: alloc::format!(
2300 "setweight expects a weight letter, got {:?}",
2301 weight_arg.data_type()
2302 ),
2303 });
2304 };
2305 let weight = match w.to_ascii_uppercase().as_str() {
2306 "A" => 3,
2307 "B" => 2,
2308 "C" => 1,
2309 "D" => 0,
2310 other => {
2311 return Err(EvalError::TypeMismatch {
2312 detail: alloc::format!("unrecognized weight: {other:?} (expected A, B, C or D)"),
2313 });
2314 }
2315 };
2316 let mut out = lexemes.clone();
2317 for lex in &mut out {
2318 lex.weight = weight;
2319 }
2320 Ok(Value::TsVector(out))
2321}
2322
2323fn fn_string_to_array(args: &[Value]) -> Result<Value, EvalError> {
2325 let [text_arg, delim_arg] = args else {
2326 return Err(EvalError::TypeMismatch {
2327 detail: alloc::format!("string_to_array expects 2 arguments, got {}", args.len()),
2328 });
2329 };
2330 let text = match text_arg {
2331 Value::Null => return Ok(Value::Null),
2332 Value::Text(t) => t,
2333 other => {
2334 return Err(EvalError::TypeMismatch {
2335 detail: alloc::format!("string_to_array expects text, got {:?}", other.data_type()),
2336 });
2337 }
2338 };
2339 if text.is_empty() {
2341 return Ok(Value::TextArray(Vec::new()));
2342 }
2343 let parts: Vec<Option<String>> = match delim_arg {
2344 Value::Null => text.chars().map(|c| Some(c.to_string())).collect(),
2346 Value::Text(d) if d.is_empty() => alloc::vec![Some(text.clone())],
2347 Value::Text(d) => text
2348 .split(d.as_str())
2349 .map(|p| Some(p.to_string()))
2350 .collect(),
2351 other => {
2352 return Err(EvalError::TypeMismatch {
2353 detail: alloc::format!(
2354 "string_to_array delimiter must be text, got {:?}",
2355 other.data_type()
2356 ),
2357 });
2358 }
2359 };
2360 Ok(Value::TextArray(parts))
2361}
2362
2363fn fts_plainto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2364 let (config, text) = parse_fts_args("plainto_tsquery", args, ctx)?;
2365 match text {
2366 None => Ok(Value::Null),
2367 Some(t) => Ok(Value::TsQuery(crate::fts::plainto_tsquery(config, &t))),
2368 }
2369}
2370
2371fn fts_phraseto_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2372 let (config, text) = parse_fts_args("phraseto_tsquery", args, ctx)?;
2373 match text {
2374 None => Ok(Value::Null),
2375 Some(t) => Ok(Value::TsQuery(crate::fts::phraseto_tsquery(config, &t))),
2376 }
2377}
2378
2379fn fts_websearch_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2380 let (config, text) = parse_fts_args("websearch_to_tsquery", args, ctx)?;
2381 match text {
2382 None => Ok(Value::Null),
2383 Some(t) => Ok(Value::TsQuery(crate::fts::websearch_to_tsquery(config, &t))),
2384 }
2385}
2386
2387fn fts_to_tsquery(args: &[Value], ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
2388 let (config, text) = parse_fts_args("to_tsquery", args, ctx)?;
2389 match text {
2390 None => Ok(Value::Null),
2391 Some(t) => Ok(Value::TsQuery(crate::fts::to_tsquery(config, &t)?)),
2392 }
2393}
2394
2395fn parse_fts_args(
2400 name: &str,
2401 args: &[Value],
2402 ctx: &EvalContext<'_>,
2403) -> Result<(crate::fts::TsConfig, Option<String>), EvalError> {
2404 let (config_arg, text_arg) = match args {
2405 [t] => (None, t),
2406 [c, t] => (Some(c), t),
2407 _ => {
2408 return Err(EvalError::TypeMismatch {
2409 detail: format!("{name}() takes 1 or 2 args, got {}", args.len()),
2410 });
2411 }
2412 };
2413 let config = match config_arg {
2414 None => match ctx.default_text_search_config {
2415 Some(name_str) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2416 EvalError::TypeMismatch {
2417 detail: format!(
2418 "text search config not implemented: {name_str:?} (supported: simple, english)"
2419 ),
2420 }
2421 })?,
2422 None => crate::fts::TsConfig::Simple,
2423 },
2424 Some(Value::Null) => return Ok((crate::fts::TsConfig::Simple, None)),
2425 Some(Value::Text(name_str)) => crate::fts::TsConfig::from_name(name_str).ok_or_else(|| {
2426 EvalError::TypeMismatch {
2427 detail: format!(
2428 "text search config not implemented: {name_str:?} (supported: simple, english)"
2429 ),
2430 }
2431 })?,
2432 Some(other) => {
2433 return Err(EvalError::TypeMismatch {
2434 detail: format!(
2435 "{name}() config arg must be text, got {:?}",
2436 other.data_type()
2437 ),
2438 });
2439 }
2440 };
2441 let text = match text_arg {
2442 Value::Null => None,
2443 Value::Text(s) => Some(s.clone()),
2444 other => {
2445 return Err(EvalError::TypeMismatch {
2446 detail: format!(
2447 "{name}() text arg must be text, got {:?}",
2448 other.data_type()
2449 ),
2450 });
2451 }
2452 };
2453 Ok((config, text))
2454}
2455
2456fn encode_text(args: &[Value]) -> Result<Value, EvalError> {
2462 if args.len() != 2 {
2463 return Err(EvalError::TypeMismatch {
2464 detail: format!("encode() takes 2 args, got {}", args.len()),
2465 });
2466 }
2467 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2468 return Ok(Value::Null);
2469 }
2470 let bytes: &[u8] = match &args[0] {
2471 Value::Text(s) => s.as_bytes(),
2472 other => {
2473 return Err(EvalError::TypeMismatch {
2474 detail: format!("encode() expects text bytes, got {:?}", other.data_type()),
2475 });
2476 }
2477 };
2478 let fmt = match &args[1] {
2479 Value::Text(s) => s.to_ascii_lowercase(),
2480 other => {
2481 return Err(EvalError::TypeMismatch {
2482 detail: format!("encode() format must be text, got {:?}", other.data_type()),
2483 });
2484 }
2485 };
2486 let out = match fmt.as_str() {
2487 "base64" => b64_encode(bytes, B64_STD),
2488 "base64url" => b64_encode(bytes, B64_URL),
2489 "base32hex" => b32hex_encode(bytes),
2490 "hex" => hex_encode(bytes),
2491 other => {
2492 return Err(EvalError::TypeMismatch {
2493 detail: format!("encode(): unknown format `{other}`"),
2494 });
2495 }
2496 };
2497 Ok(Value::Text(out))
2498}
2499
2500fn decode_text(args: &[Value]) -> Result<Value, EvalError> {
2504 if args.len() != 2 {
2505 return Err(EvalError::TypeMismatch {
2506 detail: format!("decode() takes 2 args, got {}", args.len()),
2507 });
2508 }
2509 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
2510 return Ok(Value::Null);
2511 }
2512 let text = match &args[0] {
2513 Value::Text(s) => s.as_str(),
2514 other => {
2515 return Err(EvalError::TypeMismatch {
2516 detail: format!("decode() expects text, got {:?}", other.data_type()),
2517 });
2518 }
2519 };
2520 let fmt = match &args[1] {
2521 Value::Text(s) => s.to_ascii_lowercase(),
2522 other => {
2523 return Err(EvalError::TypeMismatch {
2524 detail: format!("decode() format must be text, got {:?}", other.data_type()),
2525 });
2526 }
2527 };
2528 let bytes = match fmt.as_str() {
2529 "base64" => b64_decode(text, B64_STD)?,
2530 "base64url" => b64_decode(text, B64_URL)?,
2531 "base32hex" => b32hex_decode(text)?,
2532 "hex" => hex_decode(text)?,
2533 other => {
2534 return Err(EvalError::TypeMismatch {
2535 detail: format!("decode(): unknown format `{other}`"),
2536 });
2537 }
2538 };
2539 let s = String::from_utf8(bytes).map_err(|_| EvalError::TypeMismatch {
2540 detail: "decode(): result bytes are not valid UTF-8 (SPG stores raw bytes as Text)".into(),
2541 })?;
2542 Ok(Value::Text(s))
2543}
2544
2545fn error_on_null(args: &[Value]) -> Result<Value, EvalError> {
2549 if args.len() != 1 {
2550 return Err(EvalError::TypeMismatch {
2551 detail: format!("error_on_null() takes 1 arg, got {}", args.len()),
2552 });
2553 }
2554 if matches!(args[0], Value::Null) {
2555 return Err(EvalError::TypeMismatch {
2556 detail: "error_on_null(): argument is NULL".into(),
2557 });
2558 }
2559 Ok(args[0].clone())
2560}
2561
2562const B64_STD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2565const B64_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2566const B32HEX_ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHIJKLMNOPQRSTUV";
2567
2568fn b64_encode(bytes: &[u8], alpha: &[u8; 64]) -> String {
2569 let mut out = String::with_capacity((bytes.len() + 2) / 3 * 4);
2570 let mut i = 0;
2571 while i + 3 <= bytes.len() {
2572 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8) | (bytes[i + 2] as u32);
2573 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2574 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2575 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2576 out.push(alpha[(n & 0x3f) as usize] as char);
2577 i += 3;
2578 }
2579 let rem = bytes.len() - i;
2580 if rem == 1 {
2581 let n = (bytes[i] as u32) << 16;
2582 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2583 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2584 out.push('=');
2585 out.push('=');
2586 } else if rem == 2 {
2587 let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8);
2588 out.push(alpha[((n >> 18) & 0x3f) as usize] as char);
2589 out.push(alpha[((n >> 12) & 0x3f) as usize] as char);
2590 out.push(alpha[((n >> 6) & 0x3f) as usize] as char);
2591 out.push('=');
2592 }
2593 out
2594}
2595
2596fn b64_decode(text: &str, alpha: &[u8; 64]) -> Result<Vec<u8>, EvalError> {
2597 let mut lookup = [255u8; 256];
2598 for (i, &c) in alpha.iter().enumerate() {
2599 lookup[c as usize] = i as u8;
2600 }
2601 let mut out = Vec::with_capacity(text.len() * 3 / 4);
2602 let mut buf: u32 = 0;
2603 let mut bits: u32 = 0;
2604 for c in text.bytes() {
2605 if c == b'=' {
2606 break;
2607 }
2608 if c == b'\n' || c == b'\r' || c == b' ' {
2609 continue;
2610 }
2611 let v = lookup[c as usize];
2612 if v == 255 {
2613 return Err(EvalError::TypeMismatch {
2614 detail: format!("decode(base64): invalid char {:?}", c as char),
2615 });
2616 }
2617 buf = (buf << 6) | v as u32;
2618 bits += 6;
2619 if bits >= 8 {
2620 bits -= 8;
2621 out.push(((buf >> bits) & 0xff) as u8);
2622 }
2623 }
2624 Ok(out)
2625}
2626
2627fn b32hex_encode(bytes: &[u8]) -> String {
2628 let mut out = String::with_capacity((bytes.len() * 8 + 4) / 5);
2629 let mut buf: u64 = 0;
2630 let mut bits: u32 = 0;
2631 for &b in bytes {
2632 buf = (buf << 8) | b as u64;
2633 bits += 8;
2634 while bits >= 5 {
2635 bits -= 5;
2636 out.push(B32HEX_ALPHABET[((buf >> bits) & 0x1f) as usize] as char);
2637 }
2638 }
2639 if bits > 0 {
2640 out.push(B32HEX_ALPHABET[((buf << (5 - bits)) & 0x1f) as usize] as char);
2641 }
2642 while out.len() % 8 != 0 {
2644 out.push('=');
2645 }
2646 out
2647}
2648
2649fn b32hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2650 let mut lookup = [255u8; 256];
2651 for (i, &c) in B32HEX_ALPHABET.iter().enumerate() {
2652 lookup[c as usize] = i as u8;
2653 let lower = (c as char).to_ascii_lowercase() as u8;
2655 lookup[lower as usize] = i as u8;
2656 }
2657 let mut out = Vec::with_capacity(text.len() * 5 / 8);
2658 let mut buf: u64 = 0;
2659 let mut bits: u32 = 0;
2660 for c in text.bytes() {
2661 if c == b'=' {
2662 break;
2663 }
2664 if c == b'\n' || c == b'\r' || c == b' ' {
2665 continue;
2666 }
2667 let v = lookup[c as usize];
2668 if v == 255 {
2669 return Err(EvalError::TypeMismatch {
2670 detail: format!("decode(base32hex): invalid char {:?}", c as char),
2671 });
2672 }
2673 buf = (buf << 5) | v as u64;
2674 bits += 5;
2675 if bits >= 8 {
2676 bits -= 8;
2677 out.push(((buf >> bits) & 0xff) as u8);
2678 }
2679 }
2680 Ok(out)
2681}
2682
2683fn hex_encode(bytes: &[u8]) -> String {
2684 const HEX: &[u8; 16] = b"0123456789abcdef";
2685 let mut out = String::with_capacity(bytes.len() * 2);
2686 for &b in bytes {
2687 out.push(HEX[(b >> 4) as usize] as char);
2688 out.push(HEX[(b & 0xf) as usize] as char);
2689 }
2690 out
2691}
2692
2693fn hex_decode(text: &str) -> Result<Vec<u8>, EvalError> {
2694 let trimmed = text.trim();
2695 if trimmed.len() % 2 != 0 {
2696 return Err(EvalError::TypeMismatch {
2697 detail: "decode(hex): input length must be even".into(),
2698 });
2699 }
2700 let mut out = Vec::with_capacity(trimmed.len() / 2);
2701 let mut hi: u8 = 0;
2702 for (i, c) in trimmed.bytes().enumerate() {
2703 let v = match c {
2704 b'0'..=b'9' => c - b'0',
2705 b'a'..=b'f' => c - b'a' + 10,
2706 b'A'..=b'F' => c - b'A' + 10,
2707 _ => {
2708 return Err(EvalError::TypeMismatch {
2709 detail: format!("decode(hex): invalid char {:?}", c as char),
2710 });
2711 }
2712 };
2713 if i % 2 == 0 {
2714 hi = v;
2715 } else {
2716 out.push((hi << 4) | v);
2717 }
2718 }
2719 Ok(out)
2720}
2721
2722fn date_part(args: &[Value]) -> Result<Value, EvalError> {
2727 use spg_sql::ast::ExtractField as F;
2728 if args.len() != 2 {
2729 return Err(EvalError::TypeMismatch {
2730 detail: format!("date_part() takes 2 args, got {}", args.len()),
2731 });
2732 }
2733 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
2734 return Ok(Value::Null);
2735 }
2736 let Value::Text(field_name) = &args[0] else {
2737 return Err(EvalError::TypeMismatch {
2738 detail: format!(
2739 "date_part() needs a text field, got {:?}",
2740 args[0].data_type()
2741 ),
2742 });
2743 };
2744 let field = match field_name.to_ascii_lowercase().as_str() {
2745 "year" => F::Year,
2746 "month" => F::Month,
2747 "day" => F::Day,
2748 "hour" => F::Hour,
2749 "minute" => F::Minute,
2750 "second" => F::Second,
2751 "microsecond" | "microseconds" => F::Microsecond,
2752 "epoch" => F::Epoch,
2753 other => {
2754 return Err(EvalError::TypeMismatch {
2755 detail: format!(
2756 "unknown date_part field {other:?}; \
2757 supported: year, month, day, hour, minute, second, microsecond"
2758 ),
2759 });
2760 }
2761 };
2762 extract_field(field, &args[1])
2763}
2764
2765fn age(args: &[Value]) -> Result<Value, EvalError> {
2775 if args.is_empty() || args.len() > 2 {
2776 return Err(EvalError::TypeMismatch {
2777 detail: format!("age() takes 1 or 2 args, got {}", args.len()),
2778 });
2779 }
2780 if args.iter().any(|v| matches!(v, Value::Null)) {
2781 return Ok(Value::Null);
2782 }
2783 let to_micros = |v: &Value| -> Result<i64, EvalError> {
2786 match v {
2787 Value::Timestamp(t) => Ok(*t),
2788 Value::Date(d) => Ok(i64::from(*d) * 86_400_000_000),
2789 other => Err(EvalError::TypeMismatch {
2790 detail: format!("age() needs DATE or TIMESTAMP, got {:?}", other.data_type()),
2791 }),
2792 }
2793 };
2794 if args.len() == 1 {
2795 return Err(EvalError::TypeMismatch {
2796 detail: "single-arg age() is unsupported in v2.12 \
2797 (use age(CURRENT_DATE, t) explicitly)"
2798 .into(),
2799 });
2800 }
2801 let a = to_micros(&args[0])?;
2802 let b = to_micros(&args[1])?;
2803 let delta = a.checked_sub(b).ok_or(EvalError::TypeMismatch {
2804 detail: "age() subtraction overflows i64 microseconds".into(),
2805 })?;
2806 Ok(Value::Interval {
2807 months: 0,
2808 micros: delta,
2809 })
2810}
2811
2812fn inet_host(args: &[Value]) -> Result<Value, EvalError> {
2826 let s = match args {
2827 [Value::Text(s)] => s.clone(),
2828 [Value::Null] => return Ok(Value::Null),
2829 _ => {
2830 return Err(EvalError::TypeMismatch {
2831 detail: alloc::format!("host() takes one TEXT arg, got {} args", args.len()),
2832 });
2833 }
2834 };
2835 let host = s.split('/').next().unwrap_or("").to_string();
2836 Ok(Value::Text(host))
2837}
2838
2839fn inet_network(args: &[Value]) -> Result<Value, EvalError> {
2840 let s = match args {
2841 [Value::Text(s)] => s.clone(),
2842 [Value::Null] => return Ok(Value::Null),
2843 _ => {
2844 return Err(EvalError::TypeMismatch {
2845 detail: alloc::format!("network() takes one TEXT arg, got {} args", args.len()),
2846 });
2847 }
2848 };
2849 let mut split = s.splitn(2, '/');
2853 let host = split.next().unwrap_or("").to_string();
2854 let mask: u32 = split.next().and_then(|m| m.parse().ok()).unwrap_or(32);
2855 if !host.contains('.') {
2856 return Ok(Value::Text(s));
2858 }
2859 let octets: Vec<&str> = host.split('.').collect();
2860 if octets.len() != 4 {
2861 return Ok(Value::Text(s));
2862 }
2863 let keep_bytes = ((mask + 7) / 8) as usize;
2864 let mut out = alloc::string::String::new();
2865 for (i, oct) in octets.iter().enumerate() {
2866 if i > 0 {
2867 out.push('.');
2868 }
2869 if i < keep_bytes {
2870 out.push_str(oct);
2871 } else {
2872 out.push('0');
2873 }
2874 }
2875 out.push('/');
2876 out.push_str(&mask.to_string());
2877 Ok(Value::Text(out))
2878}
2879
2880fn inet_masklen(args: &[Value]) -> Result<Value, EvalError> {
2881 let s = match args {
2882 [Value::Text(s)] => s.clone(),
2883 [Value::Null] => return Ok(Value::Null),
2884 _ => {
2885 return Err(EvalError::TypeMismatch {
2886 detail: alloc::format!("masklen() takes one TEXT arg, got {} args", args.len()),
2887 });
2888 }
2889 };
2890 let mask: i32 = s
2891 .split_once('/')
2892 .and_then(|(_, m)| m.parse().ok())
2893 .unwrap_or(32);
2894 Ok(Value::Int(mask))
2895}
2896
2897struct InetNet {
2916 bytes: [u8; 16],
2917 family_bytes: u8,
2919 prefix_bits: u8,
2921}
2922
2923fn parse_inet_text(s: &str) -> Option<InetNet> {
2924 let mut split = s.splitn(2, '/');
2925 let host = split.next()?;
2926 let mask_str = split.next();
2927 if host.contains(':') {
2928 let bytes = parse_ipv6(host)?;
2929 let prefix_bits = match mask_str {
2930 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 128)?,
2931 None => 128,
2932 };
2933 let mut out = [0u8; 16];
2934 out.copy_from_slice(&bytes);
2935 Some(InetNet {
2936 bytes: out,
2937 family_bytes: 16,
2938 prefix_bits,
2939 })
2940 } else {
2941 let bytes = parse_ipv4(host)?;
2942 let prefix_bits = match mask_str {
2943 Some(m) => m.parse::<u8>().ok().filter(|&n| n <= 32)?,
2944 None => 32,
2945 };
2946 let mut out = [0u8; 16];
2947 out[..4].copy_from_slice(&bytes);
2948 Some(InetNet {
2949 bytes: out,
2950 family_bytes: 4,
2951 prefix_bits,
2952 })
2953 }
2954}
2955
2956fn parse_ipv4(s: &str) -> Option<[u8; 4]> {
2957 let parts: Vec<&str> = s.split('.').collect();
2958 if parts.len() != 4 {
2959 return None;
2960 }
2961 let mut out = [0u8; 4];
2962 for (i, p) in parts.iter().enumerate() {
2963 out[i] = p.parse::<u8>().ok()?;
2964 }
2965 Some(out)
2966}
2967
2968fn parse_ipv6(s: &str) -> Option<[u8; 16]> {
2969 let (head, tail) = match s.find("::") {
2971 Some(idx) => (&s[..idx], Some(&s[idx + 2..])),
2972 None => (s, None),
2973 };
2974 let head_groups: Vec<&str> = if head.is_empty() {
2975 Vec::new()
2976 } else {
2977 head.split(':').collect()
2978 };
2979 let tail_groups: Vec<&str> = match tail {
2980 Some(t) if !t.is_empty() => t.split(':').collect(),
2981 _ => Vec::new(),
2982 };
2983 let head_len = head_groups.len();
2984 let tail_len = tail_groups.len();
2985 if tail.is_none() {
2987 if head_len != 8 {
2988 return None;
2989 }
2990 } else if head_len + tail_len > 7 {
2991 return None;
2992 }
2993 let mut words = [0u16; 8];
2994 for (i, g) in head_groups.iter().enumerate() {
2995 words[i] = u16::from_str_radix(g, 16).ok()?;
2996 }
2997 let tail_start = 8 - tail_len;
2998 for (i, g) in tail_groups.iter().enumerate() {
2999 words[tail_start + i] = u16::from_str_radix(g, 16).ok()?;
3000 }
3001 let mut out = [0u8; 16];
3002 for (i, w) in words.iter().enumerate() {
3003 out[i * 2] = (w >> 8) as u8;
3004 out[i * 2 + 1] = (w & 0xff) as u8;
3005 }
3006 Some(out)
3007}
3008
3009fn network_prefix_eq(a: &InetNet, b: &InetNet, prefix_bits: u8) -> bool {
3012 let full_bytes = (prefix_bits / 8) as usize;
3013 if a.bytes[..full_bytes] != b.bytes[..full_bytes] {
3014 return false;
3015 }
3016 let extra = prefix_bits % 8;
3017 if extra == 0 {
3018 return true;
3019 }
3020 let mask: u8 = 0xff << (8 - extra);
3021 (a.bytes[full_bytes] & mask) == (b.bytes[full_bytes] & mask)
3022}
3023
3024fn inet_contained_eq(a: &InetNet, b: &InetNet) -> bool {
3026 if a.family_bytes != b.family_bytes {
3027 return false;
3028 }
3029 if a.prefix_bits < b.prefix_bits {
3030 return false;
3031 }
3032 network_prefix_eq(a, b, b.prefix_bits)
3033}
3034
3035fn inet_networks_equal(a: &InetNet, b: &InetNet) -> bool {
3038 if a.family_bytes != b.family_bytes {
3039 return false;
3040 }
3041 if a.prefix_bits != b.prefix_bits {
3042 return false;
3043 }
3044 network_prefix_eq(a, b, a.prefix_bits)
3045}
3046
3047fn inet_op_bool_result(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
3048 if matches!(l, Value::Null) || matches!(r, Value::Null) {
3049 return Ok(Value::Null);
3050 }
3051 let (lt, rt) = match (l, r) {
3052 (Value::Text(a), Value::Text(b)) => (a, b),
3053 _ => {
3054 return Err(EvalError::TypeMismatch {
3055 detail: format!(
3056 "inet operator requires TEXT/INET operands, got {:?} and {:?}",
3057 l.data_type(),
3058 r.data_type()
3059 ),
3060 });
3061 }
3062 };
3063 let a = parse_inet_text(lt).ok_or_else(|| EvalError::TypeMismatch {
3064 detail: format!("invalid inet text: {:?}", lt),
3065 })?;
3066 let b = parse_inet_text(rt).ok_or_else(|| EvalError::TypeMismatch {
3067 detail: format!("invalid inet text: {:?}", rt),
3068 })?;
3069 let result = match op {
3070 BinOp::InetContainedByEq => inet_contained_eq(&a, &b),
3071 BinOp::InetContainedBy => inet_contained_eq(&a, &b) && !inet_networks_equal(&a, &b),
3072 BinOp::InetContainsEq => inet_contained_eq(&b, &a),
3073 BinOp::InetContains => inet_contained_eq(&b, &a) && !inet_networks_equal(&a, &b),
3074 BinOp::InetOverlap => inet_contained_eq(&a, &b) || inet_contained_eq(&b, &a),
3075 _ => unreachable!("inet_op_bool_result called with non-inet op"),
3076 };
3077 Ok(Value::Bool(result))
3078}
3079
3080#[derive(Debug, Clone)]
3111enum ReNode {
3112 Literal(char),
3115 AnyChar,
3117 Class {
3119 members: Vec<ClassMember>,
3120 negated: bool,
3121 },
3122 Start,
3124 End,
3126 Quant {
3128 inner: Box<ReNode>,
3129 min: usize,
3130 max: Option<usize>,
3131 },
3132 Concat(Vec<ReNode>),
3134 Alt(Vec<ReNode>),
3136}
3137
3138#[derive(Debug, Clone)]
3139enum ClassMember {
3140 Single(char),
3141 Range(char, char),
3142}
3143
3144fn re_compile(pat: &str) -> Result<ReNode, EvalError> {
3145 let chars: Vec<char> = pat.chars().collect();
3146 let mut p = 0;
3147 let n = re_parse_alt(&chars, &mut p)?;
3148 if p != chars.len() {
3149 return Err(EvalError::TypeMismatch {
3150 detail: alloc::format!("regex compile: trailing chars at pos {p} in {pat:?}"),
3151 });
3152 }
3153 Ok(n)
3154}
3155
3156fn re_parse_alt(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3157 let mut branches = alloc::vec![re_parse_concat(chars, p)?];
3158 while *p < chars.len() && chars[*p] == '|' {
3159 *p += 1;
3160 branches.push(re_parse_concat(chars, p)?);
3161 }
3162 if branches.len() == 1 {
3163 Ok(branches.pop().unwrap())
3164 } else {
3165 Ok(ReNode::Alt(branches))
3166 }
3167}
3168
3169fn re_parse_concat(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3170 let mut items: Vec<ReNode> = Vec::new();
3171 while *p < chars.len() {
3172 let c = chars[*p];
3173 if c == '|' || c == ')' {
3174 break;
3175 }
3176 let atom = re_parse_atom(chars, p)?;
3177 let quantified = if *p < chars.len() {
3179 match chars[*p] {
3180 '*' => {
3181 *p += 1;
3182 if *p < chars.len() && chars[*p] == '?' {
3186 *p += 1;
3187 }
3188 ReNode::Quant {
3189 inner: Box::new(atom),
3190 min: 0,
3191 max: None,
3192 }
3193 }
3194 '+' => {
3195 *p += 1;
3196 if *p < chars.len() && chars[*p] == '?' {
3197 *p += 1;
3198 }
3199 ReNode::Quant {
3200 inner: Box::new(atom),
3201 min: 1,
3202 max: None,
3203 }
3204 }
3205 '?' => {
3206 *p += 1;
3207 ReNode::Quant {
3208 inner: Box::new(atom),
3209 min: 0,
3210 max: Some(1),
3211 }
3212 }
3213 _ => atom,
3214 }
3215 } else {
3216 atom
3217 };
3218 items.push(quantified);
3219 }
3220 if items.len() == 1 {
3221 Ok(items.pop().unwrap())
3222 } else {
3223 Ok(ReNode::Concat(items))
3224 }
3225}
3226
3227fn re_parse_atom(chars: &[char], p: &mut usize) -> Result<ReNode, EvalError> {
3228 let c = chars[*p];
3229 match c {
3230 '(' => {
3231 *p += 1;
3232 let inner = re_parse_alt(chars, p)?;
3233 if *p >= chars.len() || chars[*p] != ')' {
3234 return Err(EvalError::TypeMismatch {
3235 detail: "regex compile: unmatched '('".into(),
3236 });
3237 }
3238 *p += 1;
3239 Ok(inner)
3240 }
3241 '[' => {
3242 *p += 1;
3243 let mut negated = false;
3244 if *p < chars.len() && chars[*p] == '^' {
3245 negated = true;
3246 *p += 1;
3247 }
3248 let mut members: Vec<ClassMember> = Vec::new();
3249 while *p < chars.len() && chars[*p] != ']' {
3250 let start = chars[*p];
3251 *p += 1;
3252 if *p + 1 < chars.len() && chars[*p] == '-' && chars[*p + 1] != ']' {
3253 let end = chars[*p + 1];
3254 *p += 2;
3255 members.push(ClassMember::Range(start, end));
3256 } else {
3257 members.push(ClassMember::Single(start));
3258 }
3259 }
3260 if *p >= chars.len() {
3261 return Err(EvalError::TypeMismatch {
3262 detail: "regex compile: unmatched '['".into(),
3263 });
3264 }
3265 *p += 1; Ok(ReNode::Class { members, negated })
3267 }
3268 '.' => {
3269 *p += 1;
3270 Ok(ReNode::AnyChar)
3271 }
3272 '^' => {
3273 *p += 1;
3274 Ok(ReNode::Start)
3275 }
3276 '$' => {
3277 *p += 1;
3278 Ok(ReNode::End)
3279 }
3280 '\\' => {
3281 *p += 1;
3282 if *p >= chars.len() {
3283 return Err(EvalError::TypeMismatch {
3284 detail: "regex compile: dangling backslash".into(),
3285 });
3286 }
3287 let esc = chars[*p];
3288 *p += 1;
3289 match esc {
3290 'd' => Ok(ReNode::Class {
3291 members: alloc::vec![ClassMember::Range('0', '9')],
3292 negated: false,
3293 }),
3294 'D' => Ok(ReNode::Class {
3295 members: alloc::vec![ClassMember::Range('0', '9')],
3296 negated: true,
3297 }),
3298 'w' => Ok(ReNode::Class {
3299 members: alloc::vec![
3300 ClassMember::Range('a', 'z'),
3301 ClassMember::Range('A', 'Z'),
3302 ClassMember::Range('0', '9'),
3303 ClassMember::Single('_'),
3304 ],
3305 negated: false,
3306 }),
3307 'W' => Ok(ReNode::Class {
3308 members: alloc::vec![
3309 ClassMember::Range('a', 'z'),
3310 ClassMember::Range('A', 'Z'),
3311 ClassMember::Range('0', '9'),
3312 ClassMember::Single('_'),
3313 ],
3314 negated: true,
3315 }),
3316 's' => Ok(ReNode::Class {
3317 members: alloc::vec![
3318 ClassMember::Single(' '),
3319 ClassMember::Single('\t'),
3320 ClassMember::Single('\n'),
3321 ClassMember::Single('\r'),
3322 ],
3323 negated: false,
3324 }),
3325 'S' => Ok(ReNode::Class {
3326 members: alloc::vec![
3327 ClassMember::Single(' '),
3328 ClassMember::Single('\t'),
3329 ClassMember::Single('\n'),
3330 ClassMember::Single('\r'),
3331 ],
3332 negated: true,
3333 }),
3334 other => Ok(ReNode::Literal(other)),
3335 }
3336 }
3337 other => {
3338 *p += 1;
3339 Ok(ReNode::Literal(other))
3340 }
3341 }
3342}
3343
3344fn class_matches(member: &ClassMember, c: char) -> bool {
3345 match member {
3346 ClassMember::Single(s) => *s == c,
3347 ClassMember::Range(a, b) => c >= *a && c <= *b,
3348 }
3349}
3350
3351fn re_match_at(node: &ReNode, s: &[char], pos: usize) -> Option<usize> {
3356 match node {
3357 ReNode::Literal(c) => {
3358 if s.get(pos).copied() == Some(*c) {
3359 Some(pos + 1)
3360 } else {
3361 None
3362 }
3363 }
3364 ReNode::AnyChar => {
3365 if pos < s.len() && s[pos] != '\n' {
3366 Some(pos + 1)
3367 } else {
3368 None
3369 }
3370 }
3371 ReNode::Class { members, negated } => {
3372 let c = *s.get(pos)?;
3373 let hit = members.iter().any(|m| class_matches(m, c));
3374 if hit ^ negated { Some(pos + 1) } else { None }
3375 }
3376 ReNode::Start => {
3377 if pos == 0 {
3378 Some(pos)
3379 } else {
3380 None
3381 }
3382 }
3383 ReNode::End => {
3384 if pos == s.len() {
3385 Some(pos)
3386 } else {
3387 None
3388 }
3389 }
3390 ReNode::Concat(items) => {
3391 let mut p = pos;
3392 for it in items {
3393 p = re_match_at(it, s, p)?;
3394 }
3395 Some(p)
3396 }
3397 ReNode::Alt(branches) => {
3398 for b in branches {
3399 if let Some(p) = re_match_at(b, s, pos) {
3400 return Some(p);
3401 }
3402 }
3403 None
3404 }
3405 ReNode::Quant { inner, min, max } => {
3406 let mut count = 0usize;
3411 let mut p = pos;
3412 loop {
3413 if let Some(cap) = max {
3414 if count >= *cap {
3415 break;
3416 }
3417 }
3418 match re_match_at(inner, s, p) {
3419 Some(np) if np > p => {
3420 p = np;
3421 count += 1;
3422 }
3423 _ => break,
3424 }
3425 }
3426 if count < *min {
3427 return None;
3428 }
3429 Some(p)
3430 }
3431 }
3432}
3433
3434fn re_find(node: &ReNode, s: &[char], from: usize) -> Option<(usize, usize)> {
3437 let mut start = from;
3438 loop {
3439 if let Some(end) = re_match_at(node, s, start) {
3440 return Some((start, end));
3441 }
3442 if start >= s.len() {
3443 return None;
3444 }
3445 start += 1;
3446 }
3447}
3448
3449fn regexp_matches(args: &[Value]) -> Result<Value, EvalError> {
3455 let (text, pat, all_matches) = match args.len() {
3456 2 => (text_arg(&args[0])?, text_arg(&args[1])?, false),
3457 3 => {
3458 let flags = text_arg(&args[2])?.unwrap_or_default();
3459 (
3460 text_arg(&args[0])?,
3461 text_arg(&args[1])?,
3462 flags.contains('g'),
3463 )
3464 }
3465 n => {
3466 return Err(EvalError::TypeMismatch {
3467 detail: alloc::format!("regexp_matches() takes 2 or 3 args, got {n}"),
3468 });
3469 }
3470 };
3471 let Some(text) = text else {
3472 return Ok(Value::Null);
3473 };
3474 let Some(pat) = pat else {
3475 return Ok(Value::Null);
3476 };
3477 let node = re_compile(&pat)?;
3478 let chars: Vec<char> = text.chars().collect();
3479 let mut out: Vec<Option<String>> = Vec::new();
3480 let mut from = 0usize;
3481 while let Some((s_pos, e_pos)) = re_find(&node, &chars, from) {
3482 out.push(Some(chars[s_pos..e_pos].iter().collect()));
3483 if !all_matches {
3484 break;
3485 }
3486 from = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3488 if from > chars.len() {
3489 break;
3490 }
3491 }
3492 Ok(Value::TextArray(out))
3493}
3494
3495fn regexp_replace(args: &[Value]) -> Result<Value, EvalError> {
3499 let (text, pat, repl, flags) = match args.len() {
3500 3 => (
3501 text_arg(&args[0])?,
3502 text_arg(&args[1])?,
3503 text_arg(&args[2])?,
3504 String::new(),
3505 ),
3506 4 => (
3507 text_arg(&args[0])?,
3508 text_arg(&args[1])?,
3509 text_arg(&args[2])?,
3510 text_arg(&args[3])?.unwrap_or_default(),
3511 ),
3512 n => {
3513 return Err(EvalError::TypeMismatch {
3514 detail: alloc::format!("regexp_replace() takes 3 or 4 args, got {n}"),
3515 });
3516 }
3517 };
3518 let Some(text) = text else {
3519 return Ok(Value::Null);
3520 };
3521 let Some(pat) = pat else {
3522 return Ok(Value::Null);
3523 };
3524 let Some(repl) = repl else {
3525 return Ok(Value::Null);
3526 };
3527 let global = flags.contains('g');
3528 let node = re_compile(&pat)?;
3529 let chars: Vec<char> = text.chars().collect();
3530 let mut out = String::with_capacity(text.len());
3531 let mut from = 0usize;
3532 loop {
3533 match re_find(&node, &chars, from) {
3534 Some((s_pos, e_pos)) => {
3535 out.extend(chars[from..s_pos].iter());
3536 out.push_str(&repl);
3537 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3538 from = step;
3539 if !global {
3540 if from <= chars.len() {
3541 out.extend(chars[from..].iter());
3542 }
3543 return Ok(Value::Text(out));
3544 }
3545 if from > chars.len() {
3546 break;
3547 }
3548 }
3549 None => {
3550 out.extend(chars[from..].iter());
3551 break;
3552 }
3553 }
3554 }
3555 Ok(Value::Text(out))
3556}
3557
3558fn regexp_split_to_array(args: &[Value]) -> Result<Value, EvalError> {
3561 if args.len() != 2 {
3562 return Err(EvalError::TypeMismatch {
3563 detail: alloc::format!("regexp_split_to_array() takes 2 args, got {}", args.len()),
3564 });
3565 }
3566 let text = text_arg(&args[0])?;
3567 let pat = text_arg(&args[1])?;
3568 let Some(text) = text else {
3569 return Ok(Value::Null);
3570 };
3571 let Some(pat) = pat else {
3572 return Ok(Value::Null);
3573 };
3574 let node = re_compile(&pat)?;
3575 let chars: Vec<char> = text.chars().collect();
3576 let mut out: Vec<Option<String>> = Vec::new();
3577 let mut piece_start = 0usize;
3578 let mut from = 0usize;
3579 loop {
3580 match re_find(&node, &chars, from) {
3581 Some((s_pos, e_pos)) => {
3582 let piece: String = chars[piece_start..s_pos].iter().collect();
3583 out.push(Some(piece));
3584 let step = if e_pos > s_pos { e_pos } else { e_pos + 1 };
3585 from = step;
3586 piece_start = step;
3587 if from > chars.len() {
3588 break;
3589 }
3590 }
3591 None => {
3592 let tail: String = chars[piece_start..].iter().collect();
3593 out.push(Some(tail));
3594 break;
3595 }
3596 }
3597 }
3598 Ok(Value::TextArray(out))
3599}
3600
3601fn text_arg(v: &Value) -> Result<Option<String>, EvalError> {
3604 match v {
3605 Value::Text(s) => Ok(Some(s.clone())),
3606 Value::Null => Ok(None),
3607 other => Err(EvalError::TypeMismatch {
3608 detail: alloc::format!(
3609 "regex function expects TEXT arg, got {:?}",
3610 other.data_type()
3611 ),
3612 }),
3613 }
3614}
3615
3616#[derive(Debug, Clone, Copy)]
3618enum TrimSide {
3619 Left,
3620 Right,
3621 Both,
3622}
3623
3624fn string_left_right(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
3628 if args.len() != 2 {
3629 return Err(EvalError::TypeMismatch {
3630 detail: alloc::format!("{fn_name}() takes 2 args, got {}", args.len()),
3631 });
3632 }
3633 if args.iter().any(|v| matches!(v, Value::Null)) {
3634 return Ok(Value::Null);
3635 }
3636 let s = value_to_format_text(&args[0]);
3637 let n = match &args[1] {
3638 Value::SmallInt(x) => i64::from(*x),
3639 Value::Int(x) => i64::from(*x),
3640 Value::BigInt(x) => *x,
3641 other => {
3642 return Err(EvalError::TypeMismatch {
3643 detail: alloc::format!(
3644 "{fn_name}(): n must be integer, got {:?}",
3645 other.data_type()
3646 ),
3647 });
3648 }
3649 };
3650 let chars: Vec<char> = s.chars().collect();
3651 let len = chars.len() as i64;
3652 if n == 0 {
3653 return Ok(Value::Text(String::new()));
3654 }
3655 let (start, end) = if is_left {
3656 if n > 0 {
3657 (0usize, (n.min(len)) as usize)
3658 } else {
3659 let drop = (-n).min(len);
3661 (0usize, (len - drop) as usize)
3662 }
3663 } else if n > 0 {
3664 let start = (len - n).max(0);
3666 (start as usize, len as usize)
3667 } else {
3668 let drop = (-n).min(len);
3670 (drop as usize, len as usize)
3671 };
3672 if start >= end {
3673 return Ok(Value::Text(String::new()));
3674 }
3675 Ok(Value::Text(chars[start..end].iter().collect()))
3676}
3677
3678fn value_cmp_for_min_max(a: &Value, b: &Value) -> core::cmp::Ordering {
3682 use core::cmp::Ordering;
3683 let a_int = match a {
3685 Value::SmallInt(x) => Some(i64::from(*x)),
3686 Value::Int(x) => Some(i64::from(*x)),
3687 Value::BigInt(x) => Some(*x),
3688 _ => None,
3689 };
3690 let b_int = match b {
3691 Value::SmallInt(x) => Some(i64::from(*x)),
3692 Value::Int(x) => Some(i64::from(*x)),
3693 Value::BigInt(x) => Some(*x),
3694 _ => None,
3695 };
3696 if let (Some(av), Some(bv)) = (a_int, b_int) {
3697 return av.cmp(&bv);
3698 }
3699 let a_f = value_to_f64(a);
3701 let b_f = value_to_f64(b);
3702 if let (Some(av), Some(bv)) = (a_f, b_f) {
3703 return av.partial_cmp(&bv).unwrap_or(Ordering::Equal);
3704 }
3705 match (a, b) {
3707 (Value::Text(av), Value::Text(bv)) => av.cmp(bv),
3708 (Value::Bytes(av), Value::Bytes(bv)) => av.cmp(bv),
3709 _ => Ordering::Equal,
3710 }
3711}
3712
3713fn value_to_f64(v: &Value) -> Option<f64> {
3714 match v {
3715 Value::Float(x) => Some(*x),
3716 Value::SmallInt(x) => Some(f64::from(*x)),
3717 Value::Int(x) => Some(f64::from(*x)),
3718 Value::BigInt(x) => Some(*x as f64),
3719 Value::Numeric { scaled, scale } => {
3720 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3721 }
3722 _ => None,
3723 }
3724}
3725
3726fn values_equal_for_nullif(a: &Value, b: &Value) -> bool {
3731 if a == b {
3733 return true;
3734 }
3735 let a_int = match a {
3737 Value::SmallInt(x) => Some(i64::from(*x)),
3738 Value::Int(x) => Some(i64::from(*x)),
3739 Value::BigInt(x) => Some(*x),
3740 _ => None,
3741 };
3742 let b_int = match b {
3743 Value::SmallInt(x) => Some(i64::from(*x)),
3744 Value::Int(x) => Some(i64::from(*x)),
3745 Value::BigInt(x) => Some(*x),
3746 _ => None,
3747 };
3748 if let (Some(a), Some(b)) = (a_int, b_int) {
3749 return a == b;
3750 }
3751 let a_f = match a {
3753 Value::Float(x) => Some(*x),
3754 Value::SmallInt(x) => Some(f64::from(*x)),
3755 Value::Int(x) => Some(f64::from(*x)),
3756 Value::BigInt(x) => Some(*x as f64),
3757 Value::Numeric { scaled, scale } => {
3758 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3759 }
3760 _ => None,
3761 };
3762 let b_f = match b {
3763 Value::Float(x) => Some(*x),
3764 Value::SmallInt(x) => Some(f64::from(*x)),
3765 Value::Int(x) => Some(f64::from(*x)),
3766 Value::BigInt(x) => Some(*x as f64),
3767 Value::Numeric { scaled, scale } => {
3768 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
3769 }
3770 _ => None,
3771 };
3772 if let (Some(a), Some(b)) = (a_f, b_f) {
3773 return a == b;
3774 }
3775 false
3776}
3777
3778fn f64_trunc(x: f64) -> f64 {
3783 if x.is_nan() || x.is_infinite() {
3784 return x;
3785 }
3786 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3787 return x;
3788 }
3789 (x as i64) as f64
3790}
3791
3792static PRNG_STATE: core::sync::atomic::AtomicU64 =
3797 core::sync::atomic::AtomicU64::new(0x2545_F491_4F6C_DD1D);
3798
3799fn prng_next_u64() -> u64 {
3805 use core::sync::atomic::Ordering;
3806 let mut x = PRNG_STATE.load(Ordering::Relaxed);
3807 loop {
3808 if x == 0 {
3809 x = 0x2545_F491_4F6C_DD1D;
3810 }
3811 let mut next = x;
3812 next ^= next << 13;
3813 next ^= next >> 7;
3814 next ^= next << 17;
3815 match PRNG_STATE.compare_exchange_weak(x, next, Ordering::Relaxed, Ordering::Relaxed) {
3816 Ok(_) => return next,
3817 Err(seen) => x = seen,
3818 }
3819 }
3820}
3821
3822fn prng_next_f64() -> f64 {
3824 let mantissa = prng_next_u64() >> 11;
3826 let denom = (1u64 << 53) as f64;
3827 mantissa as f64 / denom
3828}
3829
3830pub fn gen_random_uuid_bytes() -> [u8; 16] {
3837 let mut out = [0u8; 16];
3838 let hi = prng_next_u64().to_be_bytes();
3839 let lo = prng_next_u64().to_be_bytes();
3840 out[..8].copy_from_slice(&hi);
3841 out[8..].copy_from_slice(&lo);
3842 out[6] = (out[6] & 0x0f) | 0x40;
3844 out[8] = (out[8] & 0x3f) | 0x80;
3846 out
3847}
3848
3849fn f64_sqrt(x: f64) -> f64 {
3854 if x == 0.0 || x.is_nan() {
3855 return x;
3856 }
3857 if x.is_infinite() {
3858 return x;
3859 }
3860 let bits = x.to_bits();
3864 let exp = ((bits >> 52) & 0x7ff) as i64 - 1023;
3865 let new_exp = (exp / 2) + 1023;
3866 let mut guess = f64::from_bits(((new_exp as u64) & 0x7ff) << 52);
3867 for _ in 0..8 {
3869 guess = 0.5 * (guess + x / guess);
3870 }
3871 guess
3872}
3873
3874fn f64_exp(x: f64) -> f64 {
3879 if x.is_nan() {
3880 return x;
3881 }
3882 if x > 709.0 {
3883 return f64::INFINITY;
3884 }
3885 if x < -745.0 {
3886 return 0.0;
3887 }
3888 const LN2: f64 = 0.6931471805599453;
3890 let k = f64_round_half_away(x / LN2) as i32;
3891 let r = x - (k as f64) * LN2;
3892 let mut term = 1.0;
3894 let mut sum = 1.0;
3895 for n in 1..=20 {
3896 term *= r / (n as f64);
3897 sum += term;
3898 if term.abs() < 1e-18 {
3899 break;
3900 }
3901 }
3902 f64_powi(2.0, k) * sum
3904}
3905
3906fn f64_ln(x: f64) -> f64 {
3909 if x <= 0.0 {
3910 return f64::NAN;
3911 }
3912 if x == 1.0 {
3913 return 0.0;
3914 }
3915 const LN2: f64 = 0.6931471805599453;
3917 let mut k = 0i32;
3918 let mut m = x;
3919 while m >= 2.0 {
3920 m *= 0.5;
3921 k += 1;
3922 }
3923 while m < 1.0 {
3924 m *= 2.0;
3925 k -= 1;
3926 }
3927 let u = (m - 1.0) / (m + 1.0);
3930 let u2 = u * u;
3931 let mut term = u;
3932 let mut sum = u;
3933 for k_iter in 1..50 {
3934 term *= u2;
3935 let denom = (2 * k_iter + 1) as f64;
3936 sum += term / denom;
3937 if (term / denom).abs() < 1e-18 {
3938 break;
3939 }
3940 }
3941 2.0 * sum + (k as f64) * LN2
3942}
3943
3944fn f64_powi(base: f64, exp: i32) -> f64 {
3948 if exp == 0 {
3949 return 1.0;
3950 }
3951 let mut result = 1.0;
3952 let mut b = if exp > 0 { base } else { 1.0 / base };
3953 let mut e = exp.unsigned_abs();
3954 while e > 0 {
3955 if e & 1 == 1 {
3956 result *= b;
3957 }
3958 e >>= 1;
3959 if e > 0 {
3960 b *= b;
3961 }
3962 }
3963 result
3964}
3965
3966fn f64_round_half_away(x: f64) -> f64 {
3969 if x.is_nan() || x.is_infinite() {
3970 return x;
3971 }
3972 if x >= 0.0 {
3973 f64_floor(x + 0.5)
3974 } else {
3975 f64_ceil(x - 0.5)
3976 }
3977}
3978
3979fn f64_ceil(x: f64) -> f64 {
3984 if x.is_nan() || x.is_infinite() {
3985 return x;
3986 }
3987 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
3988 return x;
3989 }
3990 let trunc = (x as i64) as f64;
3991 if x > 0.0 && x != trunc {
3992 trunc + 1.0
3993 } else {
3994 trunc
3995 }
3996}
3997
3998fn f64_floor(x: f64) -> f64 {
4006 if x.is_nan() || x.is_infinite() {
4007 return x;
4008 }
4009 if x >= 9_007_199_254_740_992.0 || x <= -9_007_199_254_740_992.0 {
4012 return x;
4013 }
4014 let trunc = (x as i64) as f64;
4015 if x < 0.0 && x != trunc {
4016 trunc - 1.0
4017 } else {
4018 trunc
4019 }
4020}
4021
4022fn string_pad(args: &[Value], is_left: bool, fn_name: &str) -> Result<Value, EvalError> {
4030 if args.len() != 2 && args.len() != 3 {
4031 return Err(EvalError::TypeMismatch {
4032 detail: alloc::format!("{fn_name}() takes 2 or 3 args, got {}", args.len()),
4033 });
4034 }
4035 if args.iter().any(|v| matches!(v, Value::Null)) {
4036 return Ok(Value::Null);
4037 }
4038 let s = value_to_format_text(&args[0]);
4039 let target = match &args[1] {
4040 Value::SmallInt(x) => i64::from(*x),
4041 Value::Int(x) => i64::from(*x),
4042 Value::BigInt(x) => *x,
4043 other => {
4044 return Err(EvalError::TypeMismatch {
4045 detail: alloc::format!(
4046 "{fn_name}(): length must be integer, got {:?}",
4047 other.data_type()
4048 ),
4049 });
4050 }
4051 };
4052 let fill = if args.len() == 3 {
4053 value_to_format_text(&args[2])
4054 } else {
4055 String::from(" ")
4056 };
4057 if target <= 0 {
4058 return Ok(Value::Text(String::new()));
4059 }
4060 let target = target as usize;
4061 let s_chars: Vec<char> = s.chars().collect();
4062 if s_chars.len() >= target {
4063 return Ok(Value::Text(s_chars[..target].iter().collect()));
4066 }
4067 if fill.is_empty() {
4068 return Ok(Value::Text(s));
4069 }
4070 let pad_needed = target - s_chars.len();
4071 let fill_chars: Vec<char> = fill.chars().collect();
4072 let mut padding = String::with_capacity(pad_needed * 4);
4073 for i in 0..pad_needed {
4074 padding.push(fill_chars[i % fill_chars.len()]);
4075 }
4076 if is_left {
4077 Ok(Value::Text(padding + &s))
4078 } else {
4079 Ok(Value::Text(s + &padding))
4080 }
4081}
4082
4083fn string_trim(args: &[Value], side: TrimSide, fn_name: &str) -> Result<Value, EvalError> {
4089 let (input, chars_str) = match args {
4090 [v] => (v.clone(), String::from(" ")),
4091 [v, c] => (v.clone(), {
4092 if matches!(c, Value::Null) {
4094 return Ok(Value::Null);
4095 }
4096 value_to_format_text(c)
4097 }),
4098 _ => {
4099 return Err(EvalError::TypeMismatch {
4100 detail: alloc::format!("{fn_name}() takes 1 or 2 args, got {}", args.len()),
4101 });
4102 }
4103 };
4104 if matches!(input, Value::Null) {
4105 return Ok(Value::Null);
4106 }
4107 let s = value_to_format_text(&input);
4108 let charset: alloc::collections::BTreeSet<char> = chars_str.chars().collect();
4109 let chars: Vec<char> = s.chars().collect();
4110 let mut start = 0usize;
4111 let mut end = chars.len();
4112 if matches!(side, TrimSide::Left | TrimSide::Both) {
4113 while start < end && charset.contains(&chars[start]) {
4114 start += 1;
4115 }
4116 }
4117 if matches!(side, TrimSide::Right | TrimSide::Both) {
4118 while end > start && charset.contains(&chars[end - 1]) {
4119 end -= 1;
4120 }
4121 }
4122 Ok(Value::Text(chars[start..end].iter().collect()))
4123}
4124
4125fn format_string(args: &[Value]) -> Result<Value, EvalError> {
4136 if args.is_empty() {
4137 return Err(EvalError::TypeMismatch {
4138 detail: "format() takes at least 1 arg (format string)".into(),
4139 });
4140 }
4141 let fmt = match &args[0] {
4142 Value::Text(s) => s.clone(),
4143 Value::Null => return Ok(Value::Null),
4144 other => {
4145 return Err(EvalError::TypeMismatch {
4146 detail: format!(
4147 "format(): first arg must be text, got {:?}",
4148 other.data_type()
4149 ),
4150 });
4151 }
4152 };
4153 let arg_values = &args[1..];
4154 let mut out = String::new();
4155 let mut chars = fmt.chars().peekable();
4156 let mut implicit_cursor: usize = 0;
4160 while let Some(c) = chars.next() {
4161 if c != '%' {
4162 out.push(c);
4163 continue;
4164 }
4165 let mut explicit_pos: Option<usize> = None;
4167 let mut digit_buf = String::new();
4169 while let Some(&d) = chars.peek() {
4170 if d.is_ascii_digit() {
4171 digit_buf.push(d);
4172 chars.next();
4173 } else {
4174 break;
4175 }
4176 }
4177 if !digit_buf.is_empty() && matches!(chars.peek(), Some(&'$')) {
4178 chars.next(); explicit_pos =
4180 Some(
4181 digit_buf
4182 .parse::<usize>()
4183 .map_err(|_| EvalError::TypeMismatch {
4184 detail: format!("format(): invalid arg position {digit_buf:?}"),
4185 })?,
4186 );
4187 digit_buf.clear();
4188 }
4189 let spec = match chars.next() {
4191 Some(c) => c,
4192 None => {
4193 return Err(EvalError::TypeMismatch {
4194 detail: "format(): trailing `%` with no specifier".into(),
4195 });
4196 }
4197 };
4198 let _ = digit_buf;
4205 if spec == '%' {
4206 out.push('%');
4207 continue;
4208 }
4209 let arg_index = match explicit_pos {
4210 Some(p) => p.saturating_sub(1),
4211 None => {
4212 let i = implicit_cursor;
4213 implicit_cursor += 1;
4214 i
4215 }
4216 };
4217 let arg = arg_values.get(arg_index).cloned().unwrap_or(Value::Null);
4218 match spec {
4219 's' => match arg {
4220 Value::Null => {} v => out.push_str(&value_to_format_text(&v)),
4222 },
4223 'I' => match arg {
4224 Value::Null => {
4225 return Err(EvalError::TypeMismatch {
4226 detail: "format(): NULL is not a valid identifier (%I)".into(),
4227 });
4228 }
4229 v => {
4230 let s = value_to_format_text(&v);
4231 out.push('"');
4232 for ch in s.chars() {
4233 if ch == '"' {
4234 out.push('"');
4235 out.push('"');
4236 } else {
4237 out.push(ch);
4238 }
4239 }
4240 out.push('"');
4241 }
4242 },
4243 'L' => match arg {
4244 Value::Null => out.push_str("NULL"),
4245 v => {
4246 let s = value_to_format_text(&v);
4247 out.push('\'');
4248 for ch in s.chars() {
4249 if ch == '\'' {
4250 out.push('\'');
4251 out.push('\'');
4252 } else {
4253 out.push(ch);
4254 }
4255 }
4256 out.push('\'');
4257 }
4258 },
4259 other => {
4260 return Err(EvalError::TypeMismatch {
4261 detail: format!(
4262 "format(): unknown specifier '%{other}' \
4263 (v7.17 supports %s %I %L %%)"
4264 ),
4265 });
4266 }
4267 }
4268 }
4269 Ok(Value::Text(out))
4270}
4271
4272fn pg_typeof_name(v: &Value) -> &'static str {
4278 match v {
4279 Value::SmallInt(_) => "smallint",
4280 Value::Int(_) => "integer",
4281 Value::BigInt(_) => "bigint",
4282 Value::Float(_) => "double precision",
4283 Value::Text(_) => "text",
4284 Value::Bool(_) => "boolean",
4285 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => "vector",
4286 Value::Numeric { .. } => "numeric",
4287 Value::Date(_) => "date",
4288 Value::Timestamp(_) => "timestamp without time zone",
4289 Value::Interval { .. } => "interval",
4290 Value::Json(_) => {
4291 "json"
4302 }
4303 Value::Bytes(_) => "bytea",
4304 Value::TextArray(_) => "text[]",
4305 Value::IntArray(_) => "integer[]",
4306 Value::BigIntArray(_) => "bigint[]",
4307 Value::TsVector(_) => "tsvector",
4308 Value::TsQuery(_) => "tsquery",
4309 Value::Uuid(_) => "uuid",
4310 Value::Null => "unknown",
4311 _ => "unknown",
4314 }
4315}
4316
4317fn value_to_format_text(v: &Value) -> String {
4318 match v {
4319 Value::Text(s) | Value::Json(s) => s.clone(),
4320 Value::SmallInt(n) => n.to_string(),
4321 Value::Int(n) => n.to_string(),
4322 Value::BigInt(n) => n.to_string(),
4323 Value::Float(x) => format!("{x}"),
4324 Value::Bool(b) => {
4325 if *b {
4326 "t".into()
4327 } else {
4328 "f".into()
4329 }
4330 }
4331 Value::Null => String::new(),
4332 other => format!("{other:?}"),
4333 }
4334}
4335
4336fn to_char(args: &[Value]) -> Result<Value, EvalError> {
4337 use core::fmt::Write as _;
4338 if args.len() != 2 {
4339 return Err(EvalError::TypeMismatch {
4340 detail: format!("to_char() takes 2 args, got {}", args.len()),
4341 });
4342 }
4343 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4344 return Ok(Value::Null);
4345 }
4346 let Value::Text(fmt) = &args[1] else {
4347 return Err(EvalError::TypeMismatch {
4348 detail: format!(
4349 "to_char() needs a text format, got {:?}",
4350 args[1].data_type()
4351 ),
4352 });
4353 };
4354 let (days, day_micros) = match &args[0] {
4355 Value::Date(d) => (*d, 0_i64),
4356 Value::Timestamp(t) => {
4357 let days = t.div_euclid(86_400_000_000);
4358 (
4359 i32::try_from(days).unwrap_or(i32::MAX),
4360 t.rem_euclid(86_400_000_000),
4361 )
4362 }
4363 other => {
4364 return Err(EvalError::TypeMismatch {
4365 detail: format!(
4366 "to_char() needs DATE or TIMESTAMP, got {:?}",
4367 other.data_type()
4368 ),
4369 });
4370 }
4371 };
4372 let (y, mo, d) = civil_from_days(days);
4373 let secs = day_micros / 1_000_000;
4374 let frac = day_micros % 1_000_000;
4375 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4379 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4380 let ss = u32::try_from(secs % 60).unwrap_or(0);
4381 let hh12 = match hh24 % 12 {
4382 0 => 12,
4383 x => x,
4384 };
4385 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4386 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);
4390 let bytes = fmt.as_bytes();
4391 let mut i = 0;
4392 while i < bytes.len() {
4394 let rest = &bytes[i..];
4396 if rest.starts_with(b"YYYY") {
4397 let _ = write!(out, "{y:04}");
4398 i += 4;
4399 } else if rest.starts_with(b"YY") {
4400 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4401 let yy = (y.rem_euclid(100)) as u32;
4402 let _ = write!(out, "{yy:02}");
4403 i += 2;
4404 } else if rest.starts_with(b"Month") {
4405 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4406 i += 5;
4407 } else if rest.starts_with(b"Mon") {
4408 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4409 i += 3;
4410 } else if rest.starts_with(b"MM") {
4411 let _ = write!(out, "{mo:02}");
4412 i += 2;
4413 } else if rest.starts_with(b"DD") {
4414 let _ = write!(out, "{d:02}");
4415 i += 2;
4416 } else if rest.starts_with(b"HH24") {
4417 let _ = write!(out, "{hh24:02}");
4418 i += 4;
4419 } else if rest.starts_with(b"HH12") {
4420 let _ = write!(out, "{hh12:02}");
4421 i += 4;
4422 } else if rest.starts_with(b"MI") {
4423 let _ = write!(out, "{mi:02}");
4424 i += 2;
4425 } else if rest.starts_with(b"SS") {
4426 let _ = write!(out, "{ss:02}");
4427 i += 2;
4428 } else if rest.starts_with(b"MS") {
4429 let _ = write!(out, "{ms:03}");
4430 i += 2;
4431 } else if rest.starts_with(b"US") {
4432 let _ = write!(out, "{us:06}");
4433 i += 2;
4434 } else if rest.starts_with(b"AM") || rest.starts_with(b"PM") {
4435 out.push_str(ampm);
4436 i += 2;
4437 } else {
4438 out.push(bytes[i] as char);
4440 i += 1;
4441 }
4442 }
4443 Ok(Value::Text(out))
4444}
4445
4446const MONTH_FULL: [&str; 12] = [
4447 "January",
4448 "February",
4449 "March",
4450 "April",
4451 "May",
4452 "June",
4453 "July",
4454 "August",
4455 "September",
4456 "October",
4457 "November",
4458 "December",
4459];
4460const MONTH_ABBR: [&str; 12] = [
4461 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
4462];
4463
4464fn date_format_mysql(args: &[Value]) -> Result<Value, EvalError> {
4483 use core::fmt::Write as _;
4484 if args.len() != 2 {
4485 return Err(EvalError::TypeMismatch {
4486 detail: format!("date_format() takes 2 args, got {}", args.len()),
4487 });
4488 }
4489 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4490 return Ok(Value::Null);
4491 }
4492 let Value::Text(fmt) = &args[1] else {
4493 return Err(EvalError::TypeMismatch {
4494 detail: format!(
4495 "date_format() needs a text format, got {:?}",
4496 args[1].data_type()
4497 ),
4498 });
4499 };
4500 let (days, day_micros) = match &args[0] {
4501 Value::Date(d) => (*d, 0_i64),
4502 Value::Timestamp(t) => {
4503 let days = t.div_euclid(86_400_000_000);
4504 (
4505 i32::try_from(days).unwrap_or(i32::MAX),
4506 t.rem_euclid(86_400_000_000),
4507 )
4508 }
4509 other => {
4510 return Err(EvalError::TypeMismatch {
4511 detail: format!(
4512 "date_format() needs DATE or TIMESTAMP, got {:?}",
4513 other.data_type()
4514 ),
4515 });
4516 }
4517 };
4518 let (y, mo, d) = civil_from_days(days);
4519 let secs = day_micros / 1_000_000;
4520 let frac = day_micros % 1_000_000;
4521 let hh24 = u32::try_from(secs / 3600).unwrap_or(0);
4522 let mi = u32::try_from((secs / 60) % 60).unwrap_or(0);
4523 let ss = u32::try_from(secs % 60).unwrap_or(0);
4524 let hh12 = match hh24 % 12 {
4525 0 => 12,
4526 x => x,
4527 };
4528 let ampm = if hh24 < 12 { "AM" } else { "PM" };
4529 let us = u32::try_from(frac).unwrap_or(0);
4530
4531 let mut out = String::with_capacity(fmt.len() + 8);
4532 let bytes = fmt.as_bytes();
4533 let mut i = 0;
4534 while i < bytes.len() {
4535 if bytes[i] != b'%' {
4536 out.push(bytes[i] as char);
4537 i += 1;
4538 continue;
4539 }
4540 if i + 1 >= bytes.len() {
4541 out.push('%');
4543 i += 1;
4544 continue;
4545 }
4546 let token = bytes[i + 1];
4547 match token {
4548 b'Y' => {
4549 let _ = write!(out, "{y:04}");
4550 }
4551 b'y' => {
4552 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
4553 let yy = (y.rem_euclid(100)) as u32;
4554 let _ = write!(out, "{yy:02}");
4555 }
4556 b'm' => {
4557 let _ = write!(out, "{mo:02}");
4558 }
4559 b'c' => {
4560 let _ = write!(out, "{mo}");
4561 }
4562 b'd' => {
4563 let _ = write!(out, "{d:02}");
4564 }
4565 b'e' => {
4566 let _ = write!(out, "{d}");
4567 }
4568 b'H' => {
4569 let _ = write!(out, "{hh24:02}");
4570 }
4571 b'h' | b'I' => {
4572 let _ = write!(out, "{hh12:02}");
4573 }
4574 b'i' => {
4575 let _ = write!(out, "{mi:02}");
4578 }
4579 b's' | b'S' => {
4580 let _ = write!(out, "{ss:02}");
4581 }
4582 b'f' => {
4583 let _ = write!(out, "{us:06}");
4584 }
4585 b'p' => {
4586 out.push_str(ampm);
4587 }
4588 b'M' => {
4589 out.push_str(MONTH_FULL[(mo - 1) as usize]);
4590 }
4591 b'b' => {
4592 out.push_str(MONTH_ABBR[(mo - 1) as usize]);
4593 }
4594 b'%' => {
4595 out.push('%');
4596 }
4597 other => {
4598 out.push(other as char);
4601 }
4602 }
4603 i += 2;
4604 }
4605 Ok(Value::Text(out))
4606}
4607
4608fn unix_timestamp_of(args: &[Value]) -> Result<Value, EvalError> {
4615 if args.len() != 1 {
4616 return Err(EvalError::TypeMismatch {
4617 detail: format!("unix_timestamp() takes 0 or 1 arg, got {}", args.len()),
4618 });
4619 }
4620 match &args[0] {
4621 Value::Null => Ok(Value::Null),
4622 Value::Timestamp(t) => Ok(Value::BigInt(t.div_euclid(1_000_000))),
4623 Value::Date(d) => Ok(Value::BigInt(i64::from(*d) * 86_400)),
4624 other => Err(EvalError::TypeMismatch {
4625 detail: format!(
4626 "unix_timestamp() needs DATE or TIMESTAMP, got {:?}",
4627 other.data_type()
4628 ),
4629 }),
4630 }
4631}
4632
4633fn from_unixtime(args: &[Value]) -> Result<Value, EvalError> {
4637 if !(1..=2).contains(&args.len()) {
4638 return Err(EvalError::TypeMismatch {
4639 detail: format!("from_unixtime() takes 1 or 2 args, got {}", args.len()),
4640 });
4641 }
4642 if args.iter().any(|v| matches!(v, Value::Null)) {
4643 return Ok(Value::Null);
4644 }
4645 let secs: i64 = match &args[0] {
4646 Value::SmallInt(n) => i64::from(*n),
4647 Value::Int(n) => i64::from(*n),
4648 Value::BigInt(n) => *n,
4649 Value::Float(x) => *x as i64,
4650 Value::Numeric { scaled, scale } => {
4651 let denom = 10_i128.pow(u32::from(*scale));
4652 i64::try_from(scaled.div_euclid(denom)).unwrap_or(i64::MAX)
4653 }
4654 other => {
4655 return Err(EvalError::TypeMismatch {
4656 detail: format!(
4657 "from_unixtime() needs a numeric epoch second count, got {:?}",
4658 other.data_type()
4659 ),
4660 });
4661 }
4662 };
4663 let ts = Value::Timestamp(secs.saturating_mul(1_000_000));
4664 if args.len() == 1 {
4665 Ok(ts)
4666 } else {
4667 date_format_mysql(&[ts, args[1].clone()])
4668 }
4669}
4670
4671fn date_trunc(args: &[Value]) -> Result<Value, EvalError> {
4676 if args.len() != 2 {
4677 return Err(EvalError::TypeMismatch {
4678 detail: format!("date_trunc() takes 2 args, got {}", args.len()),
4679 });
4680 }
4681 if matches!(&args[0], Value::Null) || matches!(&args[1], Value::Null) {
4682 return Ok(Value::Null);
4683 }
4684 let Value::Text(unit) = &args[0] else {
4685 return Err(EvalError::TypeMismatch {
4686 detail: format!(
4687 "date_trunc() needs a text unit, got {:?}",
4688 args[0].data_type()
4689 ),
4690 });
4691 };
4692 let micros = match &args[1] {
4695 Value::Timestamp(t) => *t,
4696 Value::Date(d) => i64::from(*d) * 86_400_000_000,
4697 other => {
4698 return Err(EvalError::TypeMismatch {
4699 detail: format!(
4700 "date_trunc() needs DATE or TIMESTAMP, got {:?}",
4701 other.data_type()
4702 ),
4703 });
4704 }
4705 };
4706 let unit_lc = unit.to_ascii_lowercase();
4707 let days = micros.div_euclid(86_400_000_000);
4708 let day_micros = micros.rem_euclid(86_400_000_000);
4709 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
4710 let (y, m, _) = civil_from_days(day_i32);
4711 let truncated = match unit_lc.as_str() {
4712 "year" => i64::from(days_from_civil(y, 1, 1)) * 86_400_000_000,
4713 "month" => i64::from(days_from_civil(y, m, 1)) * 86_400_000_000,
4714 "day" => days * 86_400_000_000,
4715 "hour" => days * 86_400_000_000 + (day_micros / 3_600_000_000) * 3_600_000_000,
4716 "minute" => days * 86_400_000_000 + (day_micros / 60_000_000) * 60_000_000,
4717 "second" => days * 86_400_000_000 + (day_micros / 1_000_000) * 1_000_000,
4718 other => {
4719 return Err(EvalError::TypeMismatch {
4720 detail: format!(
4721 "unknown date_trunc unit {other:?}; \
4722 supported: year, month, day, hour, minute, second"
4723 ),
4724 });
4725 }
4726 };
4727 Ok(Value::Timestamp(truncated))
4728}
4729
4730pub fn cast_value(v: Value, target: CastTarget) -> Result<Value, EvalError> {
4732 if matches!(v, Value::Null) {
4733 return Ok(Value::Null);
4734 }
4735 match target {
4736 CastTarget::Vector => cast_to_vector(v),
4737 CastTarget::Text => Ok(Value::Text(value_to_text(&v))),
4738 CastTarget::Int => cast_numeric_to_int(v),
4739 CastTarget::BigInt => cast_numeric_to_bigint(v),
4740 CastTarget::Float => cast_numeric_to_float(v),
4741 CastTarget::Bool => cast_to_bool(v),
4742 CastTarget::Date => cast_to_date(v),
4743 CastTarget::Timestamp | CastTarget::Timestamptz => cast_to_timestamp(v),
4746 CastTarget::Interval => cast_to_interval(v),
4750 CastTarget::Json | CastTarget::Jsonb => match v {
4754 Value::Json(s) => Ok(Value::Json(s)),
4755 Value::Text(s) => Ok(Value::Json(s)),
4756 other => Err(EvalError::TypeMismatch {
4757 detail: alloc::format!(
4758 "::json / ::jsonb only accepts TEXT-shape inputs, got {:?}",
4759 other.data_type()
4760 ),
4761 }),
4762 },
4763 CastTarget::RegType | CastTarget::RegClass => match v {
4781 Value::Text(s) => {
4782 let bare = s.rsplit('.').next().unwrap_or(&s).to_string();
4787 Ok(Value::Text(bare))
4788 }
4789 Value::Int(n) => Ok(Value::Text(alloc::format!("{n}"))),
4790 Value::BigInt(n) => Ok(Value::Text(alloc::format!("{n}"))),
4791 other => Err(EvalError::TypeMismatch {
4792 detail: alloc::format!(
4793 "::regtype / ::regclass accepts TEXT (name) or integer (oid), got {:?}",
4794 other.data_type()
4795 ),
4796 }),
4797 },
4798 CastTarget::TextArray => match v {
4802 Value::TextArray(items) => Ok(Value::TextArray(items)),
4803 Value::Text(s) => decode_text_array_external(&s).map(Value::TextArray),
4804 other => Err(EvalError::TypeMismatch {
4805 detail: alloc::format!(
4806 "::TEXT[] only accepts TEXT / TEXT[] inputs, got {:?}",
4807 other.data_type()
4808 ),
4809 }),
4810 },
4811 CastTarget::IntArray => cast_to_int_array(v),
4815 CastTarget::BigIntArray => cast_to_bigint_array(v),
4816 CastTarget::TsVector => match v {
4823 Value::TsVector(items) => Ok(Value::TsVector(items)),
4824 Value::Text(s) => decode_tsvector_external(&s).map(Value::TsVector),
4825 other => Err(EvalError::TypeMismatch {
4826 detail: alloc::format!(
4827 "::tsvector only accepts TEXT / tsvector inputs, got {:?}",
4828 other.data_type()
4829 ),
4830 }),
4831 },
4832 CastTarget::TsQuery => match v {
4833 Value::TsQuery(ast) => Ok(Value::TsQuery(ast)),
4834 Value::Text(s) => decode_tsquery_external(&s).map(Value::TsQuery),
4835 other => Err(EvalError::TypeMismatch {
4836 detail: alloc::format!(
4837 "::tsquery only accepts TEXT / tsquery inputs, got {:?}",
4838 other.data_type()
4839 ),
4840 }),
4841 },
4842 CastTarget::Uuid => match v {
4847 Value::Uuid(b) => Ok(Value::Uuid(b)),
4848 Value::Text(s) => match spg_storage::parse_uuid_str(&s) {
4849 Some(b) => Ok(Value::Uuid(b)),
4850 None => Err(EvalError::TypeMismatch {
4851 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4852 }),
4853 },
4854 other => Err(EvalError::TypeMismatch {
4855 detail: alloc::format!(
4856 "::uuid only accepts TEXT / uuid inputs, got {:?}",
4857 other.data_type()
4858 ),
4859 }),
4860 },
4861 CastTarget::Bytea => match v {
4867 Value::Bytes(b) => Ok(Value::Bytes(b)),
4868 Value::Text(s) => match crate::decode_bytea_literal(&s) {
4869 Ok(b) => Ok(Value::Bytes(b)),
4870 Err(msg) => Err(EvalError::TypeMismatch {
4871 detail: alloc::format!("invalid input syntax for type bytea: {msg}"),
4872 }),
4873 },
4874 other => Err(EvalError::TypeMismatch {
4875 detail: alloc::format!(
4876 "::bytea only accepts TEXT / bytea inputs, got {:?}",
4877 other.data_type()
4878 ),
4879 }),
4880 },
4881 }
4882}
4883
4884fn cast_to_int_array(v: Value) -> Result<Value, EvalError> {
4885 match v {
4886 Value::IntArray(items) => Ok(Value::IntArray(items)),
4887 Value::BigIntArray(items) => {
4888 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4889 for item in items {
4890 match item {
4891 None => out.push(None),
4892 Some(n) => match i32::try_from(n) {
4893 Ok(x) => out.push(Some(x)),
4894 Err(_) => {
4895 return Err(EvalError::TypeMismatch {
4896 detail: alloc::format!("::INT[] element {n} overflows i32"),
4897 });
4898 }
4899 },
4900 }
4901 }
4902 Ok(Value::IntArray(out))
4903 }
4904 Value::Text(s) => decode_int_array_external(&s).map(Value::IntArray),
4905 Value::TextArray(items) => {
4906 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
4907 for item in items {
4908 match item {
4909 None => out.push(None),
4910 Some(s) => match s.parse::<i32>() {
4911 Ok(n) => out.push(Some(n)),
4912 Err(_) => {
4913 return Err(EvalError::TypeMismatch {
4914 detail: alloc::format!("::INT[] cannot parse {s:?}"),
4915 });
4916 }
4917 },
4918 }
4919 }
4920 Ok(Value::IntArray(out))
4921 }
4922 other => Err(EvalError::TypeMismatch {
4923 detail: alloc::format!("::INT[] does not accept {:?}", other.data_type()),
4924 }),
4925 }
4926}
4927
4928fn cast_to_bigint_array(v: Value) -> Result<Value, EvalError> {
4929 match v {
4930 Value::BigIntArray(items) => Ok(Value::BigIntArray(items)),
4931 Value::IntArray(items) => Ok(Value::BigIntArray(
4932 items.into_iter().map(|x| x.map(i64::from)).collect(),
4933 )),
4934 Value::Text(s) => decode_bigint_array_external(&s).map(Value::BigIntArray),
4935 Value::TextArray(items) => {
4936 let mut out: Vec<Option<i64>> = Vec::with_capacity(items.len());
4937 for item in items {
4938 match item {
4939 None => out.push(None),
4940 Some(s) => match s.parse::<i64>() {
4941 Ok(n) => out.push(Some(n)),
4942 Err(_) => {
4943 return Err(EvalError::TypeMismatch {
4944 detail: alloc::format!("::BIGINT[] cannot parse {s:?}"),
4945 });
4946 }
4947 },
4948 }
4949 }
4950 Ok(Value::BigIntArray(out))
4951 }
4952 other => Err(EvalError::TypeMismatch {
4953 detail: alloc::format!("::BIGINT[] does not accept {:?}", other.data_type()),
4954 }),
4955 }
4956}
4957
4958fn decode_int_array_external(s: &str) -> Result<Vec<Option<i32>>, EvalError> {
4959 let trimmed = s.trim();
4960 let inner = trimmed
4961 .strip_prefix('{')
4962 .and_then(|x| x.strip_suffix('}'))
4963 .ok_or_else(|| EvalError::TypeMismatch {
4964 detail: alloc::format!("INT[] literal {s:?} must be enclosed in '{{...}}'"),
4965 })?;
4966 if inner.trim().is_empty() {
4967 return Ok(Vec::new());
4968 }
4969 inner
4970 .split(',')
4971 .map(|part| {
4972 let p = part.trim();
4973 if p.eq_ignore_ascii_case("NULL") {
4974 Ok(None)
4975 } else {
4976 p.parse::<i32>()
4977 .map(Some)
4978 .map_err(|_| EvalError::TypeMismatch {
4979 detail: alloc::format!("INT[] element {p:?} is not an i32"),
4980 })
4981 }
4982 })
4983 .collect()
4984}
4985
4986fn decode_bigint_array_external(s: &str) -> Result<Vec<Option<i64>>, EvalError> {
4987 let trimmed = s.trim();
4988 let inner = trimmed
4989 .strip_prefix('{')
4990 .and_then(|x| x.strip_suffix('}'))
4991 .ok_or_else(|| EvalError::TypeMismatch {
4992 detail: alloc::format!("BIGINT[] literal {s:?} must be enclosed in '{{...}}'"),
4993 })?;
4994 if inner.trim().is_empty() {
4995 return Ok(Vec::new());
4996 }
4997 inner
4998 .split(',')
4999 .map(|part| {
5000 let p = part.trim();
5001 if p.eq_ignore_ascii_case("NULL") {
5002 Ok(None)
5003 } else {
5004 p.parse::<i64>()
5005 .map(Some)
5006 .map_err(|_| EvalError::TypeMismatch {
5007 detail: alloc::format!("BIGINT[] element {p:?} is not an i64"),
5008 })
5009 }
5010 })
5011 .collect()
5012}
5013
5014fn decode_text_array_external(s: &str) -> Result<Vec<Option<String>>, EvalError> {
5019 let trimmed = s.trim();
5020 let inner = trimmed
5021 .strip_prefix('{')
5022 .and_then(|x| x.strip_suffix('}'))
5023 .ok_or_else(|| EvalError::TypeMismatch {
5024 detail: alloc::format!("TEXT[] literal {s:?} must be enclosed in '{{...}}'"),
5025 })?;
5026 let mut out: Vec<Option<String>> = Vec::new();
5027 if inner.trim().is_empty() {
5028 return Ok(out);
5029 }
5030 let bytes = inner.as_bytes();
5031 let mut i = 0;
5032 while i <= bytes.len() {
5033 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
5034 i += 1;
5035 }
5036 if i < bytes.len() && bytes[i] == b'"' {
5037 i += 1;
5038 let mut buf = String::new();
5039 while i < bytes.len() && bytes[i] != b'"' {
5040 if bytes[i] == b'\\' && i + 1 < bytes.len() {
5041 buf.push(bytes[i + 1] as char);
5042 i += 2;
5043 } else {
5044 buf.push(bytes[i] as char);
5045 i += 1;
5046 }
5047 }
5048 if i >= bytes.len() {
5049 return Err(EvalError::TypeMismatch {
5050 detail: "unterminated quoted element in TEXT[] literal".into(),
5051 });
5052 }
5053 i += 1;
5054 out.push(Some(buf));
5055 } else {
5056 let start = i;
5057 while i < bytes.len() && bytes[i] != b',' {
5058 i += 1;
5059 }
5060 let raw = inner[start..i].trim();
5061 if raw.eq_ignore_ascii_case("NULL") {
5062 out.push(None);
5063 } else {
5064 out.push(Some(raw.to_string()));
5065 }
5066 }
5067 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
5068 i += 1;
5069 }
5070 if i >= bytes.len() {
5071 break;
5072 }
5073 if bytes[i] != b',' {
5074 return Err(EvalError::TypeMismatch {
5075 detail: "expected ',' between TEXT[] elements".into(),
5076 });
5077 }
5078 i += 1;
5079 }
5080 Ok(out)
5081}
5082
5083fn cast_to_interval(v: Value) -> Result<Value, EvalError> {
5084 match v {
5085 Value::Interval { months, micros } => Ok(Value::Interval { months, micros }),
5086 Value::Text(s) => {
5087 let (months, micros) = spg_sql::parser::parse_interval_text(&s).ok_or_else(|| {
5088 EvalError::TypeMismatch {
5089 detail: alloc::format!("cannot parse {s:?} as INTERVAL"),
5090 }
5091 })?;
5092 Ok(Value::Interval { months, micros })
5093 }
5094 other => Err(EvalError::TypeMismatch {
5095 detail: alloc::format!(
5096 "::INTERVAL only accepts TEXT-shape inputs, got {:?}",
5097 other.data_type()
5098 ),
5099 }),
5100 }
5101}
5102
5103fn cast_to_date(v: Value) -> Result<Value, EvalError> {
5104 match v {
5105 Value::Date(d) => Ok(Value::Date(d)),
5106 Value::Int(n) => Ok(Value::Date(n)),
5109 Value::BigInt(n) => {
5110 i32::try_from(n)
5111 .map(Value::Date)
5112 .map_err(|_| EvalError::TypeMismatch {
5113 detail: "bigint days-since-epoch out of DATE range".into(),
5114 })
5115 }
5116 Value::Timestamp(t) => {
5118 let days = t.div_euclid(86_400_000_000);
5119 i32::try_from(days)
5120 .map(Value::Date)
5121 .map_err(|_| EvalError::TypeMismatch {
5122 detail: "timestamp out of DATE range".into(),
5123 })
5124 }
5125 Value::Text(s) => parse_date_literal(&s)
5126 .map(Value::Date)
5127 .ok_or(EvalError::TypeMismatch {
5128 detail: format!("cannot parse {s:?} as DATE (expected YYYY-MM-DD)"),
5129 }),
5130 other => Err(EvalError::TypeMismatch {
5131 detail: format!("cannot cast {:?} to DATE", other.data_type()),
5132 }),
5133 }
5134}
5135
5136fn cast_to_timestamp(v: Value) -> Result<Value, EvalError> {
5137 match v {
5138 Value::Timestamp(t) => Ok(Value::Timestamp(t)),
5139 Value::Int(n) => Ok(Value::Timestamp(i64::from(n))),
5143 Value::BigInt(n) => Ok(Value::Timestamp(n)),
5144 Value::Date(d) => Ok(Value::Timestamp(i64::from(d) * 86_400_000_000)),
5146 Value::Text(s) => {
5147 parse_timestamp_literal(&s)
5148 .map(Value::Timestamp)
5149 .ok_or(EvalError::TypeMismatch {
5150 detail: format!(
5151 "cannot parse {s:?} as TIMESTAMP \
5152 (expected YYYY-MM-DD[ HH:MM:SS[.ffffff]])"
5153 ),
5154 })
5155 }
5156 other => Err(EvalError::TypeMismatch {
5157 detail: format!("cannot cast {:?} to TIMESTAMP", other.data_type()),
5158 }),
5159 }
5160}
5161
5162fn value_to_text(v: &Value) -> String {
5163 match v {
5164 Value::SmallInt(n) => format!("{n}"),
5168 Value::Int(n) => format!("{n}"),
5169 Value::BigInt(n) => format!("{n}"),
5170 Value::Float(x) => format!("{x}"),
5171 Value::Text(s) | Value::Json(s) => s.clone(),
5173 Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
5174 Value::Vector(v) => {
5175 let cells: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
5176 format!("[{}]", cells.join(", "))
5177 }
5178 Value::Sq8Vector(q) => {
5183 let cells: Vec<String> = spg_storage::quantize::dequantize(q)
5184 .iter()
5185 .map(|x| format!("{x}"))
5186 .collect();
5187 format!("[{}]", cells.join(", "))
5188 }
5189 Value::HalfVector(h) => {
5192 let cells: Vec<String> = h.to_f32_vec().iter().map(|x| format!("{x}")).collect();
5193 format!("[{}]", cells.join(", "))
5194 }
5195 Value::Numeric { scaled, scale } => format_numeric(*scaled, *scale),
5196 Value::Date(d) => format_date(*d),
5197 Value::Timestamp(t) => format_timestamp(*t),
5198 Value::Interval { months, micros } => format_interval(*months, *micros),
5199 Value::Null => "NULL".into(),
5200 Value::Bytes(b) => format_bytea_hex(b),
5202 Value::TextArray(items) => format_text_array(items),
5204 Value::IntArray(items) => format_int_array(items),
5205 Value::BigIntArray(items) => format_bigint_array(items),
5206 Value::TsVector(lexs) => format_tsvector(lexs),
5208 Value::TsQuery(ast) => format_tsquery(ast),
5209 Value::Uuid(b) => spg_storage::format_uuid(b),
5212 Value::Time(us) => format_time(*us),
5214 Value::TimeTz { us, offset_secs } => format_timetz(*us, *offset_secs),
5216 Value::Year(y) => format!("{y:04}"),
5218 Value::Money(c) => format_money(*c),
5220 Value::Range { .. } => crate::format_range_text(v),
5224 Value::Hstore(pairs) => crate::format_hstore_text(pairs),
5226 Value::IntArray2D(rows) => crate::format_int_2d_text_pub(rows),
5228 Value::BigIntArray2D(rows) => crate::format_bigint_2d_text_pub(rows),
5229 Value::TextArray2D(rows) => crate::format_text_2d_text_pub(rows),
5230 _ => format!("{v:?}"),
5232 }
5233}
5234
5235pub fn format_date(days: i32) -> String {
5238 let (y, m, d) = civil_from_days(days);
5239 format!("{y:04}-{m:02}-{d:02}")
5240}
5241
5242pub fn format_timestamptz(micros: i64) -> String {
5253 let base = format_timestamp(micros);
5254 let mut s = String::with_capacity(base.len() + 3);
5255 s.push_str(&base);
5256 s.push_str("+00");
5257 s
5258}
5259
5260pub fn format_money(cents: i64) -> String {
5264 let neg = cents < 0;
5265 let abs = cents.unsigned_abs();
5266 let dollars = abs / 100;
5267 let cc = abs % 100;
5268 let dollar_str = dollars.to_string();
5270 let bytes = dollar_str.as_bytes();
5271 let mut int_part = String::with_capacity(dollar_str.len() + dollar_str.len() / 3);
5272 for (i, b) in bytes.iter().enumerate() {
5273 let from_right = bytes.len() - i;
5276 if i > 0 && from_right % 3 == 0 {
5277 int_part.push(',');
5278 }
5279 int_part.push(*b as char);
5280 }
5281 let sign = if neg { "-" } else { "" };
5282 format!("{sign}${int_part}.{cc:02}")
5283}
5284
5285pub fn format_timetz(us: i64, offset_secs: i32) -> String {
5290 let time = format_time(us);
5291 let sign = if offset_secs < 0 { '-' } else { '+' };
5292 let abs = offset_secs.unsigned_abs();
5293 let oh = abs / 3600;
5294 let om = (abs % 3600) / 60;
5295 if om == 0 {
5296 format!("{time}{sign}{oh:02}")
5297 } else {
5298 format!("{time}{sign}{oh:02}:{om:02}")
5299 }
5300}
5301
5302pub fn format_time(us: i64) -> String {
5307 let total_secs = us.div_euclid(1_000_000);
5308 let frac = us.rem_euclid(1_000_000);
5309 let hh = total_secs / 3600;
5310 let mm = (total_secs / 60) % 60;
5311 let ss = total_secs % 60;
5312 if frac == 0 {
5313 format!("{hh:02}:{mm:02}:{ss:02}")
5314 } else {
5315 let raw = format!("{frac:06}");
5316 let trimmed = raw.trim_end_matches('0');
5317 format!("{hh:02}:{mm:02}:{ss:02}.{trimmed}")
5318 }
5319}
5320
5321pub fn format_timestamp(micros: i64) -> String {
5322 const MICROS_PER_DAY: i64 = 86_400_000_000;
5323 let days = micros.div_euclid(MICROS_PER_DAY);
5326 let day_micros = micros.rem_euclid(MICROS_PER_DAY);
5327 let day_i32 = i32::try_from(days).unwrap_or(i32::MAX);
5328 let (y, m, d) = civil_from_days(day_i32);
5329 let secs = day_micros / 1_000_000;
5330 let frac = day_micros % 1_000_000;
5331 let hh = secs / 3600;
5332 let mm = (secs / 60) % 60;
5333 let ss = secs % 60;
5334 if frac == 0 {
5335 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}")
5336 } else {
5337 let raw = format!("{frac:06}");
5339 let trimmed = raw.trim_end_matches('0');
5340 format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}.{trimmed}")
5341 }
5342}
5343
5344#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5349fn civil_from_days(days: i32) -> (i32, u32, u32) {
5350 let z = i64::from(days) + 719_468;
5351 let era = z.div_euclid(146_097);
5352 let doe = (z - era * 146_097) as u32;
5356 let yoe = (doe.saturating_sub(doe / 1460) + doe / 36524 - doe / 146_096) / 365;
5357 let y_base = i64::from(yoe) + era * 400;
5358 let doy = doe.saturating_sub(365 * yoe + yoe / 4 - yoe / 100);
5359 let mp = (5 * doy + 2) / 153;
5360 let d = doy.saturating_sub((153 * mp + 2) / 5) + 1;
5361 let m = if mp < 10 { mp + 3 } else { mp - 9 };
5362 let y = if m <= 2 { y_base + 1 } else { y_base };
5363 (y as i32, m, d)
5364}
5365
5366#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5369pub fn days_from_civil(y: i32, m: u32, d: u32) -> i32 {
5370 let y_adj = if m <= 2 {
5371 i64::from(y) - 1
5372 } else {
5373 i64::from(y)
5374 };
5375 let era = y_adj.div_euclid(400);
5376 let yoe = (y_adj - era * 400) as u32;
5377 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d.saturating_sub(1);
5378 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
5379 let total = era * 146_097 + i64::from(doe) - 719_468;
5380 i32::try_from(total).unwrap_or(i32::MAX)
5381}
5382
5383pub fn parse_date_literal(s: &str) -> Option<i32> {
5387 let bytes = s.as_bytes();
5388 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
5389 return None;
5390 }
5391 let y: i32 = s[0..4].parse().ok()?;
5392 let m: u32 = s[5..7].parse().ok()?;
5393 let d: u32 = s[8..10].parse().ok()?;
5394 if !(1..=12).contains(&m) || !(1..=31).contains(&d) {
5395 return None;
5396 }
5397 Some(days_from_civil(y, m, d))
5398}
5399
5400pub fn parse_timestamp_literal(s: &str) -> Option<i64> {
5405 let trimmed = s.trim();
5406 let (date_part, time_part) = match trimmed.find([' ', 'T']) {
5407 Some(i) => (&trimmed[..i], Some(&trimmed[i + 1..])),
5408 None => (trimmed, None),
5409 };
5410 let days = parse_date_literal(date_part)?;
5411 let (day_micros, tz_offset_micros) = match time_part {
5412 None => (0, 0),
5413 Some(t) => parse_time_of_day_micros(t)?,
5414 };
5415 Some(i64::from(days) * 86_400_000_000 + day_micros - tz_offset_micros)
5425}
5426
5427fn parse_time_of_day_micros(t: &str) -> Option<(i64, i64)> {
5440 let t = t.trim();
5441 let (core, tz_micros) = if let Some(rest) = t.strip_suffix('Z') {
5447 (rest, 0i64)
5448 } else if let Some(rest) = t.strip_suffix(" UTC").or_else(|| t.strip_suffix("UTC")) {
5449 (rest, 0i64)
5450 } else if let Some((idx, sign_byte)) = find_offset_sign(t) {
5451 let suffix = &t[idx..];
5452 let micros = parse_tz_offset_suffix(suffix, sign_byte == b'+')?;
5453 (&t[..idx], micros)
5454 } else {
5455 (t, 0i64)
5456 };
5457 let (time, frac_str) = match core.split_once('.') {
5458 Some((a, b)) => (a, Some(b)),
5459 None => (core, None),
5460 };
5461 let bytes = time.as_bytes();
5462 if bytes.len() != 8 || bytes[2] != b':' || bytes[5] != b':' {
5463 return None;
5464 }
5465 let hh: i64 = time[0..2].parse().ok()?;
5466 let mm: i64 = time[3..5].parse().ok()?;
5467 let ss: i64 = time[6..8].parse().ok()?;
5468 if !(0..24).contains(&hh) || !(0..60).contains(&mm) || !(0..60).contains(&ss) {
5469 return None;
5470 }
5471 let frac_micros: i64 = match frac_str {
5472 None => 0,
5473 Some(f) => {
5474 if f.is_empty() || f.len() > 9 {
5476 return None;
5477 }
5478 let mut padded = String::with_capacity(6);
5479 padded.push_str(&f[..f.len().min(6)]);
5480 while padded.len() < 6 {
5481 padded.push('0');
5482 }
5483 padded.parse().ok()?
5484 }
5485 };
5486 Some((
5487 ((hh * 3600 + mm * 60 + ss) * 1_000_000) + frac_micros,
5488 tz_micros,
5489 ))
5490}
5491
5492fn find_offset_sign(t: &str) -> Option<(usize, u8)> {
5498 let bytes = t.as_bytes();
5499 if bytes.len() < 9 {
5501 return None;
5502 }
5503 for i in 8..bytes.len() {
5504 match bytes[i] {
5505 b'+' | b'-' => return Some((i, bytes[i])),
5506 _ => {}
5507 }
5508 }
5509 None
5510}
5511
5512fn parse_tz_offset_suffix(suffix: &str, is_positive: bool) -> Option<i64> {
5516 let body = &suffix[1..];
5518 let (hh, mm): (i64, i64) = if let Some((h, m)) = body.split_once(':') {
5519 (h.parse().ok()?, m.parse().ok()?)
5520 } else {
5521 match body.len() {
5522 2 => (body.parse().ok()?, 0),
5523 3 => {
5524 return None;
5528 }
5529 4 => {
5530 let h: i64 = body[0..2].parse().ok()?;
5531 let m: i64 = body[2..4].parse().ok()?;
5532 (h, m)
5533 }
5534 _ => return None,
5535 }
5536 };
5537 if !(0..=18).contains(&hh) || !(0..60).contains(&mm) {
5538 return None;
5539 }
5540 let abs = (hh * 3600 + mm * 60) * 1_000_000;
5541 Some(if is_positive { abs } else { -abs })
5542}
5543
5544pub fn format_interval(months: i32, micros: i64) -> String {
5549 const MICROS_PER_DAY: i64 = 86_400_000_000;
5550 let mut parts: Vec<String> = Vec::new();
5551 let years = months / 12;
5552 let mons = months % 12;
5553 let unit = |n: i64, singular: &'static str, plural: &'static str| -> &'static str {
5556 if n == 1 { singular } else { plural }
5557 };
5558 if years != 0 {
5559 parts.push(format!(
5560 "{years} {}",
5561 unit(i64::from(years), "year", "years")
5562 ));
5563 }
5564 if mons != 0 {
5565 parts.push(format!("{mons} {}", unit(i64::from(mons), "mon", "mons")));
5566 }
5567 let days = micros / MICROS_PER_DAY;
5568 let mut rem = micros % MICROS_PER_DAY;
5569 if days != 0 {
5570 parts.push(format!("{days} {}", unit(days, "day", "days")));
5571 }
5572 if rem != 0 {
5573 let neg = rem < 0;
5574 if neg {
5575 rem = -rem;
5576 }
5577 let secs = rem / 1_000_000;
5578 let frac = rem % 1_000_000;
5579 let hh = secs / 3600;
5580 let mm = (secs / 60) % 60;
5581 let ss = secs % 60;
5582 let sign = if neg { "-" } else { "" };
5583 if frac == 0 {
5584 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}"));
5585 } else {
5586 let raw = format!("{frac:06}");
5587 let trimmed = raw.trim_end_matches('0');
5588 parts.push(format!("{sign}{hh:02}:{mm:02}:{ss:02}.{trimmed}"));
5589 }
5590 }
5591 if parts.is_empty() {
5592 "0".into()
5593 } else {
5594 parts.join(" ")
5595 }
5596}
5597
5598fn add_months_to_civil(y: i32, m: u32, d: u32, months: i32) -> (i32, u32, u32) {
5601 let total_months = i64::from(y) * 12 + i64::from(m) - 1 + i64::from(months);
5602 let new_year = i32::try_from(total_months.div_euclid(12)).unwrap_or(i32::MAX);
5603 let new_month_zero = total_months.rem_euclid(12);
5604 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5605 let new_month = (new_month_zero as u32) + 1;
5606 let max_day = days_in_month(new_year, new_month);
5607 (new_year, new_month, d.min(max_day))
5608}
5609
5610const fn days_in_month(y: i32, m: u32) -> u32 {
5611 match m {
5612 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
5613 2 => {
5614 if y.rem_euclid(4) == 0 && (y.rem_euclid(100) != 0 || y.rem_euclid(400) == 0) {
5616 29
5617 } else {
5618 28
5619 }
5620 }
5621 _ => 30,
5624 }
5625}
5626
5627pub fn format_text_array(items: &[Option<String>]) -> String {
5633 let mut out = String::with_capacity(2 + items.len() * 8);
5634 out.push('{');
5635 for (i, item) in items.iter().enumerate() {
5636 if i > 0 {
5637 out.push(',');
5638 }
5639 match item {
5640 None => out.push_str("NULL"),
5641 Some(s) => {
5642 let needs_quote = s.is_empty()
5643 || s.eq_ignore_ascii_case("NULL")
5644 || s.chars()
5645 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
5646 if needs_quote {
5647 out.push('"');
5648 for c in s.chars() {
5649 if c == '"' || c == '\\' {
5650 out.push('\\');
5651 }
5652 out.push(c);
5653 }
5654 out.push('"');
5655 } else {
5656 out.push_str(s);
5657 }
5658 }
5659 }
5660 }
5661 out.push('}');
5662 out
5663}
5664
5665pub fn format_int_array(items: &[Option<i32>]) -> String {
5669 let mut out = String::with_capacity(2 + items.len() * 4);
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(n) => out.push_str(&n.to_string()),
5678 }
5679 }
5680 out.push('}');
5681 out
5682}
5683
5684pub fn format_bigint_array(items: &[Option<i64>]) -> String {
5687 let mut out = String::with_capacity(2 + items.len() * 6);
5688 out.push('{');
5689 for (i, item) in items.iter().enumerate() {
5690 if i > 0 {
5691 out.push(',');
5692 }
5693 match item {
5694 None => out.push_str("NULL"),
5695 Some(n) => out.push_str(&n.to_string()),
5696 }
5697 }
5698 out.push('}');
5699 out
5700}
5701
5702pub fn format_tsvector(lexs: &[TsLexeme]) -> String {
5708 let mut out = String::with_capacity(lexs.len() * 12);
5709 for (i, l) in lexs.iter().enumerate() {
5710 if i > 0 {
5711 out.push(' ');
5712 }
5713 out.push('\'');
5714 for c in l.word.chars() {
5715 if c == '\'' {
5716 out.push('\'');
5717 }
5718 out.push(c);
5719 }
5720 out.push('\'');
5721 if !l.positions.is_empty() {
5722 for (pi, p) in l.positions.iter().enumerate() {
5723 out.push(if pi == 0 { ':' } else { ',' });
5724 out.push_str(&p.to_string());
5725 }
5726 match l.weight {
5731 3 => out.push('A'),
5732 2 => out.push('B'),
5733 1 => out.push('C'),
5734 _ => {}
5735 }
5736 }
5737 }
5738 out
5739}
5740
5741pub fn format_tsquery(ast: &TsQueryAst) -> String {
5744 fn go(ast: &TsQueryAst, parent_prec: u8, out: &mut String) {
5745 let (own_prec, write_self): (u8, &dyn Fn(&mut String)) = match ast {
5747 TsQueryAst::Or(_, _) => (1, &|_| {}),
5748 TsQueryAst::And(_, _) | TsQueryAst::Phrase { .. } => (2, &|_| {}),
5749 TsQueryAst::Not(_) => (3, &|_| {}),
5750 TsQueryAst::Term { .. } => (4, &|_| {}),
5751 };
5752 let need_parens = own_prec < parent_prec;
5753 if need_parens {
5754 out.push('(');
5755 }
5756 match ast {
5757 TsQueryAst::Term { word, .. } => {
5758 out.push('\'');
5759 for c in word.chars() {
5760 if c == '\'' {
5761 out.push('\'');
5762 }
5763 out.push(c);
5764 }
5765 out.push('\'');
5766 }
5767 TsQueryAst::And(a, b) => {
5768 go(a, own_prec, out);
5769 out.push_str(" & ");
5770 go(b, own_prec, out);
5771 }
5772 TsQueryAst::Or(a, b) => {
5773 go(a, own_prec, out);
5774 out.push_str(" | ");
5775 go(b, own_prec, out);
5776 }
5777 TsQueryAst::Not(x) => {
5778 out.push('!');
5779 go(x, own_prec, out);
5780 }
5781 TsQueryAst::Phrase {
5782 left,
5783 right,
5784 distance,
5785 } => {
5786 go(left, own_prec, out);
5787 out.push_str(&alloc::format!(" <{distance}> "));
5788 go(right, own_prec, out);
5789 }
5790 }
5791 write_self(out);
5792 if need_parens {
5793 out.push(')');
5794 }
5795 }
5796 let mut out = String::new();
5797 go(ast, 0, &mut out);
5798 out
5799}
5800
5801pub fn decode_tsvector_external(s: &str) -> Result<Vec<TsLexeme>, EvalError> {
5810 let mut out: Vec<TsLexeme> = Vec::new();
5811 let mut i = 0;
5812 let bytes = s.as_bytes();
5813 while i < bytes.len() {
5814 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
5815 i += 1;
5816 }
5817 if i >= bytes.len() {
5818 break;
5819 }
5820 let word = if bytes[i] == b'\'' {
5823 i += 1;
5824 let mut w = String::new();
5825 loop {
5826 if i >= bytes.len() {
5827 return Err(EvalError::TypeMismatch {
5828 detail: "tsvector literal: unterminated quoted lexeme".into(),
5829 });
5830 }
5831 let b = bytes[i];
5832 if b == b'\'' {
5833 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
5834 w.push('\'');
5835 i += 2;
5836 } else {
5837 i += 1;
5838 break;
5839 }
5840 } else {
5841 w.push(b as char);
5842 i += 1;
5843 }
5844 }
5845 w
5846 } else {
5847 let start = i;
5849 while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b':' {
5850 i += 1;
5851 }
5852 core::str::from_utf8(&bytes[start..i])
5853 .map_err(|_| EvalError::TypeMismatch {
5854 detail: "tsvector literal: non-UTF-8 lexeme".into(),
5855 })?
5856 .to_string()
5857 };
5858 if word.is_empty() {
5859 return Err(EvalError::TypeMismatch {
5860 detail: "tsvector literal: empty lexeme".into(),
5861 });
5862 }
5863 let mut positions: Vec<u16> = Vec::new();
5866 let mut weight: u8 = 0;
5867 if i < bytes.len() && bytes[i] == b':' {
5868 i += 1;
5869 loop {
5870 let start = i;
5871 while i < bytes.len() && bytes[i].is_ascii_digit() {
5872 i += 1;
5873 }
5874 if start == i {
5875 return Err(EvalError::TypeMismatch {
5876 detail: "tsvector literal: expected digit after ':'".into(),
5877 });
5878 }
5879 let num: u16 = core::str::from_utf8(&bytes[start..i])
5880 .expect("ascii digits")
5881 .parse()
5882 .map_err(|_| EvalError::TypeMismatch {
5883 detail: alloc::format!(
5884 "tsvector literal: position {} overflows u16",
5885 core::str::from_utf8(&bytes[start..i]).unwrap_or("?")
5886 ),
5887 })?;
5888 positions.push(num);
5889 if i < bytes.len() {
5890 let w = bytes[i];
5891 if matches!(w, b'A' | b'B' | b'C' | b'D') {
5892 weight = match w {
5893 b'A' => 3,
5894 b'B' => 2,
5895 b'C' => 1,
5896 _ => 0,
5897 };
5898 i += 1;
5899 }
5900 }
5901 if i < bytes.len() && bytes[i] == b',' {
5902 i += 1;
5903 continue;
5904 }
5905 break;
5906 }
5907 }
5908 positions.sort_unstable();
5909 positions.dedup();
5910 match out.binary_search_by(|l| l.word.as_str().cmp(word.as_str())) {
5913 Ok(idx) => {
5914 for p in positions {
5915 if !out[idx].positions.contains(&p) {
5916 out[idx].positions.push(p);
5917 }
5918 }
5919 out[idx].positions.sort_unstable();
5920 if weight != 0 {
5921 out[idx].weight = weight;
5922 }
5923 }
5924 Err(idx) => {
5925 out.insert(
5926 idx,
5927 TsLexeme {
5928 word,
5929 positions,
5930 weight,
5931 },
5932 );
5933 }
5934 }
5935 }
5936 Ok(out)
5937}
5938
5939pub fn decode_tsquery_external(s: &str) -> Result<TsQueryAst, EvalError> {
5945 let mut p = TsQueryParser {
5946 bytes: s.as_bytes(),
5947 pos: 0,
5948 };
5949 p.skip_ws();
5950 if p.pos >= p.bytes.len() {
5951 return Err(EvalError::TypeMismatch {
5952 detail: "tsquery literal: empty".into(),
5953 });
5954 }
5955 let ast = p.parse_or()?;
5956 p.skip_ws();
5957 if p.pos < p.bytes.len() {
5958 return Err(EvalError::TypeMismatch {
5959 detail: alloc::format!("tsquery literal: trailing garbage at offset {}", p.pos),
5960 });
5961 }
5962 Ok(ast)
5963}
5964
5965struct TsQueryParser<'a> {
5966 bytes: &'a [u8],
5967 pos: usize,
5968}
5969
5970impl<'a> TsQueryParser<'a> {
5971 fn skip_ws(&mut self) {
5972 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_whitespace() {
5973 self.pos += 1;
5974 }
5975 }
5976 fn peek(&self) -> Option<u8> {
5977 self.bytes.get(self.pos).copied()
5978 }
5979 fn parse_or(&mut self) -> Result<TsQueryAst, EvalError> {
5980 let mut lhs = self.parse_and()?;
5981 loop {
5982 self.skip_ws();
5983 if self.peek() != Some(b'|') {
5984 return Ok(lhs);
5985 }
5986 self.pos += 1;
5987 let rhs = self.parse_and()?;
5988 lhs = TsQueryAst::Or(Box::new(lhs), Box::new(rhs));
5989 }
5990 }
5991 fn parse_and(&mut self) -> Result<TsQueryAst, EvalError> {
5992 let mut lhs = self.parse_unary()?;
5993 loop {
5994 self.skip_ws();
5995 match self.peek() {
5996 Some(b'&') => {
5997 self.pos += 1;
5998 let rhs = self.parse_unary()?;
5999 lhs = TsQueryAst::And(Box::new(lhs), Box::new(rhs));
6000 }
6001 Some(b'<') => {
6002 self.pos += 1;
6004 let start = self.pos;
6005 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
6006 self.pos += 1;
6007 }
6008 if start == self.pos || self.peek() != Some(b'>') {
6009 return Err(EvalError::TypeMismatch {
6010 detail: "tsquery literal: malformed <N> phrase operator".into(),
6011 });
6012 }
6013 let n: u16 = core::str::from_utf8(&self.bytes[start..self.pos])
6014 .expect("ascii digits")
6015 .parse()
6016 .map_err(|_| EvalError::TypeMismatch {
6017 detail: "tsquery literal: phrase distance overflows u16".into(),
6018 })?;
6019 self.pos += 1; let rhs = self.parse_unary()?;
6021 lhs = TsQueryAst::Phrase {
6022 left: Box::new(lhs),
6023 right: Box::new(rhs),
6024 distance: n,
6025 };
6026 }
6027 _ => return Ok(lhs),
6028 }
6029 }
6030 }
6031 fn parse_unary(&mut self) -> Result<TsQueryAst, EvalError> {
6032 self.skip_ws();
6033 if self.peek() == Some(b'!') {
6034 self.pos += 1;
6035 let inner = self.parse_unary()?;
6036 return Ok(TsQueryAst::Not(Box::new(inner)));
6037 }
6038 self.parse_atom()
6039 }
6040 fn parse_atom(&mut self) -> Result<TsQueryAst, EvalError> {
6041 self.skip_ws();
6042 match self.peek() {
6043 Some(b'(') => {
6044 self.pos += 1;
6045 let inner = self.parse_or()?;
6046 self.skip_ws();
6047 if self.peek() != Some(b')') {
6048 return Err(EvalError::TypeMismatch {
6049 detail: "tsquery literal: missing ')'".into(),
6050 });
6051 }
6052 self.pos += 1;
6053 Ok(inner)
6054 }
6055 Some(b'\'') => {
6056 self.pos += 1;
6057 let mut w = String::new();
6058 loop {
6059 match self.peek() {
6060 None => {
6061 return Err(EvalError::TypeMismatch {
6062 detail: "tsquery literal: unterminated quoted lexeme".into(),
6063 });
6064 }
6065 Some(b'\'') => {
6066 if self.bytes.get(self.pos + 1) == Some(&b'\'') {
6067 w.push('\'');
6068 self.pos += 2;
6069 } else {
6070 self.pos += 1;
6071 break;
6072 }
6073 }
6074 Some(b) => {
6075 w.push(b as char);
6076 self.pos += 1;
6077 }
6078 }
6079 }
6080 self.skip_weight_suffix();
6083 Ok(TsQueryAst::Term {
6084 word: w,
6085 weight_mask: 0,
6086 })
6087 }
6088 Some(b) if b.is_ascii_alphanumeric() || b == b'_' => {
6089 let start = self.pos;
6090 while self.pos < self.bytes.len() {
6091 let c = self.bytes[self.pos];
6092 if c.is_ascii_alphanumeric() || c == b'_' {
6093 self.pos += 1;
6094 } else {
6095 break;
6096 }
6097 }
6098 let w = core::str::from_utf8(&self.bytes[start..self.pos])
6099 .map_err(|_| EvalError::TypeMismatch {
6100 detail: "tsquery literal: non-UTF-8 lexeme".into(),
6101 })?
6102 .to_string();
6103 self.skip_weight_suffix();
6104 Ok(TsQueryAst::Term {
6105 word: w,
6106 weight_mask: 0,
6107 })
6108 }
6109 Some(b) => Err(EvalError::TypeMismatch {
6110 detail: alloc::format!(
6111 "tsquery literal: unexpected byte {:?} at offset {}",
6112 b as char,
6113 self.pos
6114 ),
6115 }),
6116 None => Err(EvalError::TypeMismatch {
6117 detail: "tsquery literal: expected term".into(),
6118 }),
6119 }
6120 }
6121 fn skip_weight_suffix(&mut self) {
6122 if self.peek() != Some(b':') {
6123 return;
6124 }
6125 self.pos += 1;
6126 while let Some(b) = self.peek() {
6127 if matches!(
6128 b,
6129 b'A' | b'B' | b'C' | b'D' | b'a' | b'b' | b'c' | b'd' | b'*'
6130 ) || b.is_ascii_digit()
6131 {
6132 self.pos += 1;
6133 } else {
6134 break;
6135 }
6136 }
6137 }
6138}
6139
6140pub fn format_bytea_hex(b: &[u8]) -> String {
6144 let mut out = String::with_capacity(2 + 2 * b.len());
6145 out.push_str("\\x");
6146 const HEX: &[u8; 16] = b"0123456789abcdef";
6147 for byte in b {
6148 out.push(HEX[(byte >> 4) as usize] as char);
6149 out.push(HEX[(byte & 0x0F) as usize] as char);
6150 }
6151 out
6152}
6153
6154pub fn format_numeric(scaled: i128, scale: u8) -> String {
6159 if scale == 0 {
6160 return format!("{scaled}");
6161 }
6162 let negative = scaled < 0;
6163 let mag_str = scaled.unsigned_abs().to_string();
6164 let mag_bytes = mag_str.as_bytes();
6165 let scale_u = scale as usize;
6166 let mut out = String::with_capacity(mag_str.len() + 3);
6167 if negative {
6168 out.push('-');
6169 }
6170 if mag_bytes.len() <= scale_u {
6171 out.push('0');
6172 out.push('.');
6173 for _ in mag_bytes.len()..scale_u {
6174 out.push('0');
6175 }
6176 out.push_str(&mag_str);
6177 } else {
6178 let split = mag_bytes.len() - scale_u;
6179 out.push_str(&mag_str[..split]);
6180 out.push('.');
6181 out.push_str(&mag_str[split..]);
6182 }
6183 out
6184}
6185
6186fn cast_numeric_to_int(v: Value) -> Result<Value, EvalError> {
6187 match v {
6188 Value::Int(n) => Ok(Value::Int(n)),
6189 Value::BigInt(n) => i32::try_from(n)
6190 .map(Value::Int)
6191 .map_err(|_| EvalError::TypeMismatch {
6192 detail: format!("bigint {n} does not fit in int"),
6193 }),
6194 #[allow(clippy::cast_possible_truncation)]
6195 Value::Float(x) => Ok(Value::Int(x as i32)),
6196 Value::Text(s) => {
6197 s.trim()
6198 .parse::<i32>()
6199 .map(Value::Int)
6200 .map_err(|_| EvalError::TypeMismatch {
6201 detail: format!("cannot parse {s:?} as int"),
6202 })
6203 }
6204 Value::Bool(b) => Ok(Value::Int(i32::from(b))),
6205 other => Err(EvalError::TypeMismatch {
6206 detail: format!("cannot cast {:?} to int", other.data_type()),
6207 }),
6208 }
6209}
6210
6211fn cast_numeric_to_bigint(v: Value) -> Result<Value, EvalError> {
6212 match v {
6213 Value::Int(n) => Ok(Value::BigInt(i64::from(n))),
6214 Value::BigInt(n) => Ok(Value::BigInt(n)),
6215 #[allow(clippy::cast_possible_truncation)]
6216 Value::Float(x) => Ok(Value::BigInt(x as i64)),
6217 Value::Text(s) => {
6218 s.trim()
6219 .parse::<i64>()
6220 .map(Value::BigInt)
6221 .map_err(|_| EvalError::TypeMismatch {
6222 detail: format!("cannot parse {s:?} as bigint"),
6223 })
6224 }
6225 Value::Bool(b) => Ok(Value::BigInt(i64::from(b))),
6226 other => Err(EvalError::TypeMismatch {
6227 detail: format!("cannot cast {:?} to bigint", other.data_type()),
6228 }),
6229 }
6230}
6231
6232fn cast_numeric_to_float(v: Value) -> Result<Value, EvalError> {
6233 match v {
6234 Value::Int(n) => Ok(Value::Float(f64::from(n))),
6235 #[allow(clippy::cast_precision_loss)]
6236 Value::BigInt(n) => Ok(Value::Float(n as f64)),
6237 Value::Float(x) => Ok(Value::Float(x)),
6238 Value::Text(s) => {
6239 s.trim()
6240 .parse::<f64>()
6241 .map(Value::Float)
6242 .map_err(|_| EvalError::TypeMismatch {
6243 detail: format!("cannot parse {s:?} as float"),
6244 })
6245 }
6246 other => Err(EvalError::TypeMismatch {
6247 detail: format!("cannot cast {:?} to float", other.data_type()),
6248 }),
6249 }
6250}
6251
6252fn cast_to_bool(v: Value) -> Result<Value, EvalError> {
6253 match v {
6254 Value::Bool(b) => Ok(Value::Bool(b)),
6255 Value::Int(n) => Ok(Value::Bool(n != 0)),
6256 Value::BigInt(n) => Ok(Value::Bool(n != 0)),
6257 Value::Text(s) => {
6258 let lo = s.trim().to_ascii_lowercase();
6259 match lo.as_str() {
6260 "true" | "t" | "yes" | "y" | "1" | "on" => Ok(Value::Bool(true)),
6261 "false" | "f" | "no" | "n" | "0" | "off" => Ok(Value::Bool(false)),
6262 _ => Err(EvalError::TypeMismatch {
6263 detail: format!("cannot parse {s:?} as bool"),
6264 }),
6265 }
6266 }
6267 other => Err(EvalError::TypeMismatch {
6268 detail: format!("cannot cast {:?} to bool", other.data_type()),
6269 }),
6270 }
6271}
6272
6273pub fn cast_to_vector(v: Value) -> Result<Value, EvalError> {
6276 match v {
6277 Value::Null => Ok(Value::Null),
6278 Value::Vector(v) => Ok(Value::Vector(v)),
6279 Value::Text(s) => parse_vector_text(&s)
6280 .map(Value::Vector)
6281 .ok_or(EvalError::TypeMismatch {
6282 detail: format!("cannot parse {s:?} as a vector literal"),
6283 }),
6284 other => Err(EvalError::TypeMismatch {
6285 detail: format!("::vector requires text input, got {:?}", other.data_type()),
6286 }),
6287 }
6288}
6289
6290pub fn parse_vector_text(s: &str) -> Option<Vec<f32>> {
6292 let trimmed = s.trim();
6293 let inner = trimmed.strip_prefix('[')?.strip_suffix(']')?;
6294 let trimmed_inner = inner.trim();
6295 if trimmed_inner.is_empty() {
6296 return Some(Vec::new());
6297 }
6298 let mut out = Vec::new();
6299 for part in trimmed_inner.split(',') {
6300 let f: f32 = part.trim().parse().ok()?;
6301 out.push(f);
6302 }
6303 Some(out)
6304}
6305
6306fn literal_to_value(l: &Literal) -> Value {
6307 match l {
6308 Literal::Integer(n) => {
6309 if let Ok(small) = i32::try_from(*n) {
6310 Value::Int(small)
6311 } else {
6312 Value::BigInt(*n)
6313 }
6314 }
6315 Literal::Float(x) => Value::Float(*x),
6316 Literal::String(s) => Value::Text(s.clone()),
6317 Literal::Vector(v) => Value::Vector(v.clone()),
6318 Literal::TextArray(items) => Value::TextArray(items.clone()),
6319 Literal::IntArray(items) => Value::IntArray(items.clone()),
6320 Literal::BigIntArray(items) => Value::BigIntArray(items.clone()),
6321 Literal::Bool(b) => Value::Bool(*b),
6322 Literal::Null => Value::Null,
6323 Literal::Interval { months, micros, .. } => Value::Interval {
6324 months: *months,
6325 micros: *micros,
6326 },
6327 }
6328}
6329
6330pub(crate) fn column_collation(e: &Expr, ctx: &EvalContext<'_>) -> Option<spg_storage::Collation> {
6336 let Expr::Column(c) = e else {
6337 return None;
6338 };
6339 if let Some(q) = &c.qualifier {
6340 let composite = alloc::format!("{q}.{name}", name = c.name);
6341 if let Some(s) = ctx.columns.iter().find(|s| s.name == composite) {
6342 return Some(s.collation);
6343 }
6344 }
6345 if let Some(s) = ctx.columns.iter().find(|s| s.name == c.name) {
6346 return Some(s.collation);
6347 }
6348 let suffix = alloc::format!(".{name}", name = c.name);
6352 let mut matches = ctx.columns.iter().filter(|s| s.name.ends_with(&suffix));
6353 let first = matches.next();
6354 let extra = matches.next();
6355 match (first, extra) {
6356 (Some(s), None) => Some(s.collation),
6357 _ => None,
6358 }
6359}
6360
6361fn collation_fold_for_compare(
6368 op: BinOp,
6369 lhs: &Expr,
6370 rhs: &Expr,
6371 l: Value,
6372 r: Value,
6373 ctx: &EvalContext<'_>,
6374) -> (Value, Value) {
6375 if !matches!(
6376 op,
6377 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq
6378 ) {
6379 return (l, r);
6380 }
6381 let lhs_col = column_collation(lhs, ctx);
6382 let rhs_col = column_collation(rhs, ctx);
6383 let ci = matches!(lhs_col, Some(spg_storage::Collation::CaseInsensitive))
6384 || matches!(rhs_col, Some(spg_storage::Collation::CaseInsensitive));
6385 if !ci {
6386 return (l, r);
6387 }
6388 let fold = |v: Value| match v {
6389 Value::Text(s) => Value::Text(s.to_ascii_lowercase()),
6390 other => other,
6391 };
6392 (fold(l), fold(r))
6393}
6394
6395fn resolve_column(c: &ColumnName, row: &Row, ctx: &EvalContext<'_>) -> Result<Value, EvalError> {
6396 if let Some(q) = &c.qualifier {
6397 let composite = alloc::format!("{q}.{name}", name = c.name);
6402 if let Some(pos) = ctx.columns.iter().position(|s| s.name == composite) {
6403 return Ok(row.values[pos].clone());
6404 }
6405 let expected = ctx.table_alias.ok_or_else(|| EvalError::UnknownQualifier {
6406 qualifier: q.clone(),
6407 })?;
6408 if q != expected {
6409 return Err(EvalError::UnknownQualifier {
6410 qualifier: q.clone(),
6411 });
6412 }
6413 }
6414 if let Some(pos) = ctx.columns.iter().position(|s| s.name == c.name) {
6415 return Ok(row.values[pos].clone());
6416 }
6417 let suffix = alloc::format!(".{name}", name = c.name);
6420 let mut matches = ctx
6421 .columns
6422 .iter()
6423 .enumerate()
6424 .filter(|(_, s)| s.name.ends_with(&suffix));
6425 let first = matches.next();
6426 let extra = matches.next();
6427 match (first, extra) {
6428 (Some((pos, _)), None) => Ok(row.values[pos].clone()),
6429 (Some(_), Some(_)) => Err(EvalError::TypeMismatch {
6430 detail: alloc::format!("ambiguous column reference: {}", c.name),
6431 }),
6432 _ => Err(EvalError::ColumnNotFound {
6433 name: c.name.clone(),
6434 }),
6435 }
6436}
6437
6438fn apply_unary(op: UnOp, v: Value) -> Result<Value, EvalError> {
6439 match (op, v) {
6440 (_, Value::Null) => Ok(Value::Null),
6441 (UnOp::Neg, Value::Int(n)) => {
6442 n.checked_neg()
6443 .map(Value::Int)
6444 .ok_or(EvalError::TypeMismatch {
6445 detail: "integer overflow on unary -".into(),
6446 })
6447 }
6448 (UnOp::Neg, Value::BigInt(n)) => {
6449 n.checked_neg()
6450 .map(Value::BigInt)
6451 .ok_or(EvalError::TypeMismatch {
6452 detail: "bigint overflow on unary -".into(),
6453 })
6454 }
6455 (UnOp::Neg, Value::Float(x)) => Ok(Value::Float(-x)),
6456 (UnOp::Neg, other) => Err(EvalError::TypeMismatch {
6457 detail: format!("unary - applied to {:?}", other.data_type()),
6458 }),
6459 (UnOp::BitNot, Value::SmallInt(n)) => Ok(Value::Int(!i32::from(n))),
6460 (UnOp::BitNot, Value::Int(n)) => Ok(Value::Int(!n)),
6461 (UnOp::BitNot, Value::BigInt(n)) => Ok(Value::BigInt(!n)),
6462 (UnOp::BitNot, other) => Err(EvalError::TypeMismatch {
6463 detail: format!("cannot apply ~ to {other:?}"),
6464 }),
6465 (UnOp::Not, Value::Bool(b)) => Ok(Value::Bool(!b)),
6466 (UnOp::Not, other) => Err(EvalError::TypeMismatch {
6467 detail: format!("NOT applied to {:?}", other.data_type()),
6468 }),
6469 }
6470}
6471
6472fn values_not_distinct(l: &Value, r: &Value) -> bool {
6475 match (l, r) {
6476 (Value::Null, Value::Null) => true,
6477 (Value::Null, _) | (_, Value::Null) => false,
6478 _ => l == r,
6479 }
6480}
6481
6482fn apply_binary(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6483 if let BinOp::And = op {
6486 return and_3vl(l, r);
6487 }
6488 if let BinOp::Or = op {
6489 return or_3vl(l, r);
6490 }
6491 if let BinOp::IsNotDistinctFrom = op {
6494 return Ok(Value::Bool(values_not_distinct(&l, &r)));
6495 }
6496 if let BinOp::IsDistinctFrom = op {
6497 return Ok(Value::Bool(!values_not_distinct(&l, &r)));
6498 }
6499 if l.is_null() || r.is_null() {
6501 return Ok(Value::Null);
6502 }
6503 if matches!(l, Value::Numeric { .. }) || matches!(r, Value::Numeric { .. }) {
6506 return apply_binary_numeric(op, l, r);
6507 }
6508 if let Some(result) = apply_binary_calendar(op, &l, &r)? {
6516 return Ok(result);
6517 }
6518 match op {
6519 BinOp::Add => arith(l, r, i64::checked_add, |a, b| a + b, "+"),
6520 BinOp::Sub => arith(l, r, i64::checked_sub, |a, b| a - b, "-"),
6521 BinOp::Mul => arith(l, r, i64::checked_mul, |a, b| a * b, "*"),
6522 BinOp::Div => div_op(l, r),
6523 BinOp::L2Distance => l2_distance(l, r),
6524 BinOp::InnerProduct => inner_product(l, r),
6525 BinOp::CosineDistance => cosine_distance(l, r),
6526 BinOp::Concat => Ok(text_concat(&l, &r)),
6527 BinOp::BitOr => bitop(l, r, |a, b| a | b, "|"),
6528 BinOp::BitAnd => bitop(l, r, |a, b| a & b, "&"),
6529 BinOp::JsonGet => crate::json::path_get(&l, &r, false),
6530 BinOp::JsonGetText => crate::json::path_get(&l, &r, true),
6531 BinOp::JsonGetPath => crate::json::path_walk(&l, &r, false),
6532 BinOp::JsonGetPathText => crate::json::path_walk(&l, &r, true),
6533 BinOp::JsonContains => crate::json::contains(&l, &r),
6534 BinOp::TsMatch => ts_match(l, r),
6537 BinOp::InetContainedBy
6539 | BinOp::InetContainedByEq
6540 | BinOp::InetContains
6541 | BinOp::InetContainsEq
6542 | BinOp::InetOverlap => inet_op_bool_result(op, &l, &r),
6543 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6544 compare(op, &l, &r)
6545 }
6546 BinOp::And | BinOp::Or | BinOp::IsDistinctFrom | BinOp::IsNotDistinctFrom => {
6547 unreachable!("handled above")
6548 }
6549 }
6550}
6551
6552fn apply_binary_calendar(op: BinOp, l: &Value, r: &Value) -> Result<Option<Value>, EvalError> {
6556 let int_value = |v: &Value| -> Option<i64> {
6557 match v {
6558 Value::SmallInt(n) => Some(i64::from(*n)),
6559 Value::Int(n) => Some(i64::from(*n)),
6560 Value::BigInt(n) => Some(*n),
6561 _ => None,
6562 }
6563 };
6564 match (l, r) {
6568 (Value::Date(a), Value::Date(b)) if op == BinOp::Sub => {
6569 return Ok(Some(Value::BigInt(i64::from(*a) - i64::from(*b))));
6570 }
6571 (Value::Timestamp(a), Value::Timestamp(b)) if op == BinOp::Sub => {
6572 let delta = a.checked_sub(*b).ok_or(EvalError::TypeMismatch {
6573 detail: "TIMESTAMP - TIMESTAMP overflows i64 microseconds".into(),
6574 })?;
6575 return Ok(Some(Value::BigInt(delta)));
6576 }
6577 _ => {}
6578 }
6579 if let Some(out) = apply_binary_interval(op, l, r)? {
6583 return Ok(Some(out));
6584 }
6585 match (l, r) {
6586 (Value::Date(d), other) if op == BinOp::Add => {
6587 if let Some(n) = int_value(other) {
6588 let days = i64::from(*d).saturating_add(n);
6589 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6590 detail: "DATE + integer overflows DATE range".into(),
6591 })?;
6592 return Ok(Some(Value::Date(days32)));
6593 }
6594 }
6595 (other, Value::Date(d)) if op == BinOp::Add => {
6596 if let Some(n) = int_value(other) {
6597 let days = i64::from(*d).saturating_add(n);
6598 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6599 detail: "integer + DATE overflows DATE range".into(),
6600 })?;
6601 return Ok(Some(Value::Date(days32)));
6602 }
6603 }
6604 (Value::Date(d), other) if op == BinOp::Sub => {
6605 if let Some(n) = int_value(other) {
6606 let days = i64::from(*d).saturating_sub(n);
6607 let days32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6608 detail: "DATE - integer overflows DATE range".into(),
6609 })?;
6610 return Ok(Some(Value::Date(days32)));
6611 }
6612 }
6613 _ => {}
6614 }
6615 Ok(None)
6616}
6617
6618pub(crate) fn apply_binary_interval(
6626 op: BinOp,
6627 l: &Value,
6628 r: &Value,
6629) -> Result<Option<Value>, EvalError> {
6630 let (lhs, rhs, sign): (&Value, &Value, i64) = match (l, r, op) {
6633 (Value::Interval { .. }, _, BinOp::Add) => (r, l, 1),
6634 (_, Value::Interval { .. }, BinOp::Add) => (l, r, 1),
6635 (_, Value::Interval { .. }, BinOp::Sub) => (l, r, -1),
6636 _ => return Ok(None),
6637 };
6638 let Value::Interval {
6639 months: rhs_months,
6640 micros: rhs_us,
6641 } = rhs
6642 else {
6643 unreachable!("rhs guaranteed to be Interval by the match above");
6644 };
6645 let signed_months = i64::from(*rhs_months) * sign;
6646 let signed_micros = rhs_us.checked_mul(sign).ok_or(EvalError::TypeMismatch {
6647 detail: "INTERVAL micros overflows on negation".into(),
6648 })?;
6649 match lhs {
6650 Value::Timestamp(t) => Ok(Some(Value::Timestamp(add_interval_to_micros(
6651 *t,
6652 signed_months,
6653 signed_micros,
6654 )?))),
6655 Value::Date(d) => {
6656 let day_aligned = signed_micros.rem_euclid(86_400_000_000) == 0;
6660 if day_aligned {
6661 let micros_per_day = 86_400_000_000_i64;
6662 let days_delta = signed_micros / micros_per_day;
6663 let shifted = shift_date_by_months(*d, signed_months)?;
6664 let new_days =
6665 i64::from(shifted)
6666 .checked_add(days_delta)
6667 .ok_or(EvalError::TypeMismatch {
6668 detail: "DATE ± INTERVAL overflows DATE range".into(),
6669 })?;
6670 let days32 = i32::try_from(new_days).map_err(|_| EvalError::TypeMismatch {
6671 detail: "DATE ± INTERVAL overflows DATE range".into(),
6672 })?;
6673 Ok(Some(Value::Date(days32)))
6674 } else {
6675 let base =
6676 i64::from(*d)
6677 .checked_mul(86_400_000_000)
6678 .ok_or(EvalError::TypeMismatch {
6679 detail: "DATE → TIMESTAMP lift overflows for INTERVAL math".into(),
6680 })?;
6681 Ok(Some(Value::Timestamp(add_interval_to_micros(
6682 base,
6683 signed_months,
6684 signed_micros,
6685 )?)))
6686 }
6687 }
6688 Value::Interval {
6689 months: lhs_months,
6690 micros: lhs_us,
6691 } => {
6692 let new_months = i64::from(*lhs_months)
6693 .checked_add(signed_months)
6694 .and_then(|n| i32::try_from(n).ok())
6695 .ok_or(EvalError::TypeMismatch {
6696 detail: "INTERVAL ± INTERVAL months overflows i32".into(),
6697 })?;
6698 let new_micros = lhs_us
6699 .checked_add(signed_micros)
6700 .ok_or(EvalError::TypeMismatch {
6701 detail: "INTERVAL ± INTERVAL micros overflows i64".into(),
6702 })?;
6703 Ok(Some(Value::Interval {
6704 months: new_months,
6705 micros: new_micros,
6706 }))
6707 }
6708 _ => Err(EvalError::TypeMismatch {
6709 detail: format!(
6710 "operator {op:?} not defined for {:?} and INTERVAL",
6711 lhs.data_type()
6712 ),
6713 }),
6714 }
6715}
6716
6717fn shift_date_by_months(d: i32, months: i64) -> Result<i32, EvalError> {
6719 let (y, m, day) = civil_from_days(d);
6720 let months_i32 = i32::try_from(months).map_err(|_| EvalError::TypeMismatch {
6721 detail: "INTERVAL months delta out of i32 range".into(),
6722 })?;
6723 let (ny, nm, nd) = add_months_to_civil(y, m, day, months_i32);
6724 Ok(days_from_civil(ny, nm, nd))
6725}
6726
6727fn add_interval_to_micros(t: i64, months: i64, micros: i64) -> Result<i64, EvalError> {
6731 let mut out = t;
6732 if months != 0 {
6733 const MICROS_PER_DAY: i64 = 86_400_000_000;
6734 let days = out.div_euclid(MICROS_PER_DAY);
6735 let day_micros = out.rem_euclid(MICROS_PER_DAY);
6736 let day_i32 = i32::try_from(days).map_err(|_| EvalError::TypeMismatch {
6737 detail: "TIMESTAMP day component out of i32 range for INTERVAL months math".into(),
6738 })?;
6739 let shifted_days = shift_date_by_months(day_i32, months)?;
6740 out = i64::from(shifted_days)
6741 .checked_mul(MICROS_PER_DAY)
6742 .and_then(|n| n.checked_add(day_micros))
6743 .ok_or(EvalError::TypeMismatch {
6744 detail: "TIMESTAMP ± INTERVAL months overflows i64 microseconds".into(),
6745 })?;
6746 }
6747 out.checked_add(micros).ok_or(EvalError::TypeMismatch {
6748 detail: "TIMESTAMP ± INTERVAL micros overflows i64".into(),
6749 })
6750}
6751
6752#[allow(clippy::needless_pass_by_value)] fn apply_binary_numeric(op: BinOp, l: Value, r: Value) -> Result<Value, EvalError> {
6757 let float_path = matches!(l, Value::Float(_)) || matches!(r, Value::Float(_));
6761 if float_path {
6762 let af = as_f64(&l)?;
6763 let bf = as_f64(&r)?;
6764 return match op {
6765 BinOp::Add => Ok(Value::Float(af + bf)),
6766 BinOp::Sub => Ok(Value::Float(af - bf)),
6767 BinOp::Mul => Ok(Value::Float(af * bf)),
6768 BinOp::Div => {
6769 if bf == 0.0 {
6770 Err(EvalError::DivisionByZero)
6771 } else {
6772 Ok(Value::Float(af / bf))
6773 }
6774 }
6775 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6776 let ord = af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
6777 detail: "NaN in NUMERIC/Float comparison".into(),
6778 })?;
6779 Ok(Value::Bool(cmp_to_bool(op, ord)))
6780 }
6781 BinOp::Concat => Ok(text_concat(&l, &r)),
6782 other => Err(EvalError::TypeMismatch {
6783 detail: format!("operator {other:?} not defined for NUMERIC and Float"),
6784 }),
6785 };
6786 }
6787 let (a, sa) = numeric_or_widen(&l).ok_or_else(|| EvalError::TypeMismatch {
6789 detail: format!("NUMERIC op against non-numeric {:?}", l.data_type()),
6790 })?;
6791 let (b, sb) = numeric_or_widen(&r).ok_or_else(|| EvalError::TypeMismatch {
6792 detail: format!("NUMERIC op against non-numeric {:?}", r.data_type()),
6793 })?;
6794 match op {
6795 BinOp::Add | BinOp::Sub => {
6796 let target_scale = sa.max(sb);
6797 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
6798 detail: "NUMERIC overflow on rescale".into(),
6799 })?;
6800 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
6801 detail: "NUMERIC overflow on rescale".into(),
6802 })?;
6803 let r = match op {
6804 BinOp::Add => lhs.checked_add(rhs),
6805 BinOp::Sub => lhs.checked_sub(rhs),
6806 _ => unreachable!(),
6807 }
6808 .ok_or(EvalError::TypeMismatch {
6809 detail: "NUMERIC overflow on +/-".into(),
6810 })?;
6811 Ok(Value::Numeric {
6812 scaled: r,
6813 scale: target_scale,
6814 })
6815 }
6816 BinOp::Mul => {
6817 let scaled = a.checked_mul(b).ok_or(EvalError::TypeMismatch {
6818 detail: "NUMERIC overflow on *".into(),
6819 })?;
6820 Ok(Value::Numeric {
6821 scaled,
6822 scale: sa.saturating_add(sb),
6823 })
6824 }
6825 BinOp::Div => {
6826 if b == 0 {
6827 return Err(EvalError::DivisionByZero);
6828 }
6829 let target_scale = sa.max(sb);
6833 let bump = pow10_i128(target_scale.saturating_add(sb).saturating_sub(sa));
6837 let num = a.checked_mul(bump).ok_or(EvalError::TypeMismatch {
6838 detail: "NUMERIC overflow on / scaling".into(),
6839 })?;
6840 let half = if b >= 0 { b / 2 } else { -(b / 2) };
6841 let adj = if (num >= 0) == (b >= 0) {
6842 num + half
6843 } else {
6844 num - half
6845 };
6846 Ok(Value::Numeric {
6847 scaled: adj / b,
6848 scale: target_scale,
6849 })
6850 }
6851 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
6852 let target_scale = sa.max(sb);
6853 let lhs = rescale(a, sa, target_scale).ok_or(EvalError::TypeMismatch {
6854 detail: "NUMERIC overflow on rescale".into(),
6855 })?;
6856 let rhs = rescale(b, sb, target_scale).ok_or(EvalError::TypeMismatch {
6857 detail: "NUMERIC overflow on rescale".into(),
6858 })?;
6859 Ok(Value::Bool(cmp_to_bool(op, lhs.cmp(&rhs))))
6860 }
6861 BinOp::Concat => Ok(text_concat(&l, &r)),
6862 other => Err(EvalError::TypeMismatch {
6863 detail: format!("operator {other:?} not defined for NUMERIC"),
6864 }),
6865 }
6866}
6867
6868fn numeric_or_widen(v: &Value) -> Option<(i128, u8)> {
6872 match v {
6873 Value::Numeric { scaled, scale } => Some((*scaled, *scale)),
6874 Value::Int(n) => Some((i128::from(*n), 0)),
6875 Value::SmallInt(n) => Some((i128::from(*n), 0)),
6876 Value::BigInt(n) => Some((i128::from(*n), 0)),
6877 _ => None,
6878 }
6879}
6880
6881fn rescale(scaled: i128, src: u8, dst: u8) -> Option<i128> {
6882 if src == dst {
6883 return Some(scaled);
6884 }
6885 if dst > src {
6886 scaled.checked_mul(pow10_i128(dst - src))
6887 } else {
6888 let drop = pow10_i128(src - dst);
6889 let half = drop / 2;
6890 let r = if scaled >= 0 {
6891 scaled + half
6892 } else {
6893 scaled - half
6894 };
6895 Some(r / drop)
6896 }
6897}
6898
6899const fn pow10_i128(p: u8) -> i128 {
6900 let mut acc: i128 = 1;
6901 let mut i = 0;
6902 while i < p {
6903 acc *= 10;
6904 i += 1;
6905 }
6906 acc
6907}
6908
6909const fn cmp_to_bool(op: BinOp, ord: core::cmp::Ordering) -> bool {
6910 use core::cmp::Ordering::{Equal, Greater, Less};
6911 match op {
6912 BinOp::Eq => matches!(ord, Equal),
6913 BinOp::NotEq => !matches!(ord, Equal),
6914 BinOp::Lt => matches!(ord, Less),
6915 BinOp::LtEq => matches!(ord, Less | Equal),
6916 BinOp::Gt => matches!(ord, Greater),
6917 BinOp::GtEq => matches!(ord, Greater | Equal),
6918 _ => false,
6919 }
6920}
6921
6922fn tsvector_concat(l: &[spg_storage::TsLexeme], r: &[spg_storage::TsLexeme]) -> Value {
6931 let shift = l
6932 .iter()
6933 .flat_map(|x| x.positions.iter().copied())
6934 .max()
6935 .unwrap_or(0);
6936 let mut out: Vec<spg_storage::TsLexeme> = l.to_vec();
6937 for lex in r {
6938 let shifted: Vec<u16> = lex
6939 .positions
6940 .iter()
6941 .map(|p| p.saturating_add(shift))
6942 .collect();
6943 if let Some(existing) = out.iter_mut().find(|x| x.word == lex.word) {
6944 existing.positions.extend(shifted);
6945 existing.positions.sort_unstable();
6946 existing.weight = existing.weight.max(lex.weight);
6947 } else {
6948 out.push(spg_storage::TsLexeme {
6949 word: lex.word.clone(),
6950 positions: shifted,
6951 weight: lex.weight,
6952 });
6953 }
6954 }
6955 out.sort_by(|a, b| a.word.cmp(&b.word));
6956 Value::TsVector(out)
6957}
6958
6959fn text_concat(l: &Value, r: &Value) -> Value {
6960 if let (Value::TsVector(a), Value::TsVector(b)) = (l, r) {
6961 return tsvector_concat(a, b);
6962 }
6963 match (l, r) {
6968 (Value::Null, _) | (_, Value::Null) => {
6969 if matches!(
6973 l,
6974 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
6975 ) || matches!(
6976 r,
6977 Value::TextArray(_) | Value::IntArray(_) | Value::BigIntArray(_) | Value::Bytes(_)
6978 ) {
6979 return Value::Null;
6980 }
6981 }
6982 (Value::TextArray(a), Value::TextArray(b)) => {
6983 let mut out = a.clone();
6984 out.extend(b.iter().cloned());
6985 return Value::TextArray(out);
6986 }
6987 (Value::TextArray(a), Value::Text(s)) => {
6988 let mut out = a.clone();
6989 out.push(Some(s.clone()));
6990 return Value::TextArray(out);
6991 }
6992 (Value::Text(s), Value::TextArray(b)) => {
6993 let mut out: alloc::vec::Vec<Option<alloc::string::String>> =
6994 alloc::vec::Vec::with_capacity(1 + b.len());
6995 out.push(Some(s.clone()));
6996 out.extend(b.iter().cloned());
6997 return Value::TextArray(out);
6998 }
6999 (Value::IntArray(a), Value::IntArray(b)) => {
7004 let mut out = a.clone();
7005 out.extend(b.iter().copied());
7006 return Value::IntArray(out);
7007 }
7008 (Value::IntArray(a), Value::Int(n)) => {
7009 let mut out = a.clone();
7010 out.push(Some(*n));
7011 return Value::IntArray(out);
7012 }
7013 (Value::IntArray(a), Value::SmallInt(n)) => {
7014 let mut out = a.clone();
7015 out.push(Some(i32::from(*n)));
7016 return Value::IntArray(out);
7017 }
7018 (Value::Int(n), Value::IntArray(b)) => {
7019 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
7020 out.push(Some(*n));
7021 out.extend(b.iter().copied());
7022 return Value::IntArray(out);
7023 }
7024 (Value::SmallInt(n), Value::IntArray(b)) => {
7025 let mut out: alloc::vec::Vec<Option<i32>> = alloc::vec::Vec::with_capacity(1 + b.len());
7026 out.push(Some(i32::from(*n)));
7027 out.extend(b.iter().copied());
7028 return Value::IntArray(out);
7029 }
7030 (Value::BigIntArray(a), Value::BigIntArray(b)) => {
7031 let mut out = a.clone();
7032 out.extend(b.iter().copied());
7033 return Value::BigIntArray(out);
7034 }
7035 (Value::BigIntArray(a), Value::IntArray(b)) => {
7036 let mut out = a.clone();
7037 out.extend(b.iter().map(|o| o.map(i64::from)));
7038 return Value::BigIntArray(out);
7039 }
7040 (Value::IntArray(a), Value::BigIntArray(b)) => {
7041 let mut out: alloc::vec::Vec<Option<i64>> =
7042 a.iter().map(|o| o.map(i64::from)).collect();
7043 out.extend(b.iter().copied());
7044 return Value::BigIntArray(out);
7045 }
7046 (Value::BigIntArray(a), Value::BigInt(n)) => {
7047 let mut out = a.clone();
7048 out.push(Some(*n));
7049 return Value::BigIntArray(out);
7050 }
7051 (Value::BigIntArray(a), Value::Int(n)) => {
7052 let mut out = a.clone();
7053 out.push(Some(i64::from(*n)));
7054 return Value::BigIntArray(out);
7055 }
7056 (Value::BigIntArray(a), Value::SmallInt(n)) => {
7057 let mut out = a.clone();
7058 out.push(Some(i64::from(*n)));
7059 return Value::BigIntArray(out);
7060 }
7061 (Value::BigInt(n), Value::BigIntArray(b)) => {
7062 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
7063 out.push(Some(*n));
7064 out.extend(b.iter().copied());
7065 return Value::BigIntArray(out);
7066 }
7067 (Value::Int(n), Value::BigIntArray(b)) => {
7068 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
7069 out.push(Some(i64::from(*n)));
7070 out.extend(b.iter().copied());
7071 return Value::BigIntArray(out);
7072 }
7073 (Value::SmallInt(n), Value::BigIntArray(b)) => {
7074 let mut out: alloc::vec::Vec<Option<i64>> = alloc::vec::Vec::with_capacity(1 + b.len());
7075 out.push(Some(i64::from(*n)));
7076 out.extend(b.iter().copied());
7077 return Value::BigIntArray(out);
7078 }
7079 (Value::Bytes(a), Value::Bytes(b)) => {
7081 let mut out = a.clone();
7082 out.extend_from_slice(b);
7083 return Value::Bytes(out);
7084 }
7085 _ => {}
7086 }
7087 let a = value_to_text(l);
7088 let b = value_to_text(r);
7089 Value::Text(a + &b)
7090}
7091
7092fn inner_product(l: Value, r: Value) -> Result<Value, EvalError> {
7095 let (a, b) = unwrap_vec_pair(l, r, "<#>")?;
7096 let mut dot: f64 = 0.0;
7097 for (x, y) in a.iter().zip(b.iter()) {
7098 dot += f64::from(*x) * f64::from(*y);
7099 }
7100 Ok(Value::Float(-dot))
7101}
7102
7103fn cosine_distance(l: Value, r: Value) -> Result<Value, EvalError> {
7106 let (a, b) = unwrap_vec_pair(l, r, "<=>")?;
7107 let mut dot: f64 = 0.0;
7108 let mut na: f64 = 0.0;
7109 let mut nb: f64 = 0.0;
7110 for (x, y) in a.iter().zip(b.iter()) {
7111 let xf = f64::from(*x);
7112 let yf = f64::from(*y);
7113 dot += xf * yf;
7114 na += xf * xf;
7115 nb += yf * yf;
7116 }
7117 let denom = sqrt_newton(na) * sqrt_newton(nb);
7118 if denom == 0.0 {
7119 return Ok(Value::Float(f64::NAN));
7120 }
7121 Ok(Value::Float(1.0 - dot / denom))
7122}
7123
7124fn unwrap_vec_pair(l: Value, r: Value, op: &str) -> Result<(Vec<f32>, Vec<f32>), EvalError> {
7125 let to_f32 = |v: Value| -> Option<Vec<f32>> {
7133 match v {
7134 Value::Vector(a) => Some(a),
7135 Value::Sq8Vector(q) => Some(spg_storage::quantize::dequantize(&q)),
7136 Value::HalfVector(h) => Some(h.to_f32_vec()),
7138 _ => None,
7139 }
7140 };
7141 let l_ty = l.data_type();
7142 let r_ty = r.data_type();
7143 match (to_f32(l), to_f32(r)) {
7144 (Some(a), Some(b)) => {
7145 if a.len() != b.len() {
7146 return Err(EvalError::TypeMismatch {
7147 detail: format!("vector dim mismatch in {op}: {} vs {}", a.len(), b.len()),
7148 });
7149 }
7150 Ok((a, b))
7151 }
7152 _ => Err(EvalError::TypeMismatch {
7153 detail: format!("{op} requires two vectors, got {l_ty:?} and {r_ty:?}"),
7154 }),
7155 }
7156}
7157
7158fn bitop(
7166 l: Value,
7167 r: Value,
7168 f: impl Fn(i64, i64) -> i64,
7169 op_name: &str,
7170) -> Result<Value, EvalError> {
7171 let widen = |v: Value| -> Value {
7172 match v {
7173 Value::SmallInt(n) => Value::Int(i32::from(n)),
7174 other => other,
7175 }
7176 };
7177 match (widen(l), widen(r)) {
7178 (Value::Int(a), Value::Int(b)) => {
7179 let result = f(i64::from(a), i64::from(b));
7180 Ok(Value::Int(result as i32))
7182 }
7183 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
7184 Ok(Value::BigInt(f(i64::from(a), b)))
7185 }
7186 (Value::BigInt(a), Value::BigInt(b)) => Ok(Value::BigInt(f(a, b))),
7187 (a, b) => Err(EvalError::TypeMismatch {
7188 detail: format!("cannot apply {op_name} to {a:?} and {b:?}"),
7189 }),
7190 }
7191}
7192
7193fn arith(
7194 l: Value,
7195 r: Value,
7196 int_op: impl Fn(i64, i64) -> Option<i64>,
7197 float_op: impl Fn(f64, f64) -> f64,
7198 op_name: &str,
7199) -> Result<Value, EvalError> {
7200 let widen = |v: Value| -> Value {
7203 match v {
7204 Value::SmallInt(n) => Value::Int(i32::from(n)),
7205 other => other,
7206 }
7207 };
7208 let l = widen(l);
7209 let r = widen(r);
7210 match (l, r) {
7211 (Value::Int(a), Value::Int(b)) => {
7212 let result = int_op(i64::from(a), i64::from(b)).ok_or(EvalError::TypeMismatch {
7213 detail: format!("integer overflow on {op_name}"),
7214 })?;
7215 if let Ok(small) = i32::try_from(result) {
7216 Ok(Value::Int(small))
7217 } else {
7218 Ok(Value::BigInt(result))
7219 }
7220 }
7221 (Value::Int(a), Value::BigInt(b)) | (Value::BigInt(b), Value::Int(a)) => {
7222 let result = int_op(i64::from(a), b).ok_or(EvalError::TypeMismatch {
7223 detail: format!("bigint overflow on {op_name}"),
7224 })?;
7225 Ok(Value::BigInt(result))
7226 }
7227 (Value::BigInt(a), Value::BigInt(b)) => {
7228 let result = int_op(a, b).ok_or(EvalError::TypeMismatch {
7229 detail: format!("bigint overflow on {op_name}"),
7230 })?;
7231 Ok(Value::BigInt(result))
7232 }
7233 (a, b)
7234 if a.data_type() == Some(DataType::Float) || b.data_type() == Some(DataType::Float) =>
7235 {
7236 let af = as_f64(&a)?;
7237 let bf = as_f64(&b)?;
7238 Ok(Value::Float(float_op(af, bf)))
7239 }
7240 (a, b) => Err(EvalError::TypeMismatch {
7241 detail: format!(
7242 "{op_name} applied to non-numeric: {:?} vs {:?}",
7243 a.data_type(),
7244 b.data_type()
7245 ),
7246 }),
7247 }
7248}
7249
7250#[allow(clippy::many_single_char_names)] fn l2_distance(l: Value, r: Value) -> Result<Value, EvalError> {
7256 let (a, b) = unwrap_vec_pair(l, r, "<->")?;
7261 let mut sum: f64 = 0.0;
7262 for (x, y) in a.iter().zip(b.iter()) {
7263 let d = f64::from(*x) - f64::from(*y);
7264 sum += d * d;
7265 }
7266 Ok(Value::Float(sqrt_newton(sum)))
7267}
7268
7269fn sqrt_newton(x: f64) -> f64 {
7274 if x <= 0.0 {
7275 return 0.0;
7276 }
7277 let mut g = x;
7278 for _ in 0..10 {
7281 g = 0.5 * (g + x / g);
7282 }
7283 g
7284}
7285
7286fn div_op(l: Value, r: Value) -> Result<Value, EvalError> {
7287 let any_float = matches!(l.data_type(), Some(DataType::Float))
7288 || matches!(r.data_type(), Some(DataType::Float));
7289 if any_float {
7290 let a = as_f64(&l)?;
7291 let b = as_f64(&r)?;
7292 if b == 0.0 {
7293 return Err(EvalError::DivisionByZero);
7294 }
7295 return Ok(Value::Float(a / b));
7296 }
7297 arith(
7298 l,
7299 r,
7300 |a, b| {
7301 if b == 0 { None } else { Some(a / b) }
7302 },
7303 |a, b| a / b,
7304 "/",
7305 )
7306 .map_err(|e| match e {
7307 EvalError::TypeMismatch { detail } if detail.contains('/') => EvalError::DivisionByZero,
7310 other => other,
7311 })
7312}
7313
7314fn as_f64(v: &Value) -> Result<f64, EvalError> {
7315 match v {
7316 Value::SmallInt(n) => Ok(f64::from(*n)),
7317 Value::Int(n) => Ok(f64::from(*n)),
7318 #[allow(clippy::cast_precision_loss)]
7319 Value::BigInt(n) => Ok(*n as f64),
7320 Value::Float(x) => Ok(*x),
7321 #[allow(clippy::cast_precision_loss)]
7322 Value::Numeric { scaled, scale } => {
7323 let mut div = 1.0_f64;
7324 for _ in 0..*scale {
7325 div *= 10.0;
7326 }
7327 Ok((*scaled as f64) / div)
7328 }
7329 other => Err(EvalError::TypeMismatch {
7330 detail: format!("cannot convert {:?} to FLOAT", other.data_type()),
7331 }),
7332 }
7333}
7334
7335fn compare(op: BinOp, l: &Value, r: &Value) -> Result<Value, EvalError> {
7336 let ord = match (l, r) {
7337 (Value::Int(a), Value::Int(b)) => i64::from(*a).cmp(&i64::from(*b)),
7338 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
7339 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
7340 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
7341 (a, b)
7342 if matches!(a.data_type(), Some(DataType::Float))
7343 || matches!(b.data_type(), Some(DataType::Float)) =>
7344 {
7345 let af = as_f64(a)?;
7346 let bf = as_f64(b)?;
7347 af.partial_cmp(&bf).ok_or(EvalError::TypeMismatch {
7348 detail: "NaN in comparison".into(),
7349 })?
7350 }
7351 (Value::Text(a), Value::Text(b)) => a.cmp(b),
7352 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
7353 (Value::Date(a), Value::Date(b)) => a.cmp(b),
7357 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
7358 (Value::Date(a), Value::Timestamp(b)) => (i64::from(*a) * 86_400_000_000).cmp(b),
7359 (Value::Timestamp(a), Value::Date(b)) => a.cmp(&(i64::from(*b) * 86_400_000_000)),
7360 (Value::Date(a), Value::Text(b)) => {
7364 let bd = parse_date_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7365 detail: format!("cannot parse {b:?} as DATE for comparison"),
7366 })?;
7367 a.cmp(&bd)
7368 }
7369 (Value::Text(a), Value::Date(b)) => {
7370 let ad = parse_date_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7371 detail: format!("cannot parse {a:?} as DATE for comparison"),
7372 })?;
7373 ad.cmp(b)
7374 }
7375 (Value::Timestamp(a), Value::Text(b)) => {
7376 let bt = parse_timestamp_literal(b).ok_or_else(|| EvalError::TypeMismatch {
7377 detail: format!("cannot parse {b:?} as TIMESTAMP for comparison"),
7378 })?;
7379 a.cmp(&bt)
7380 }
7381 (Value::Text(a), Value::Timestamp(b)) => {
7382 let at = parse_timestamp_literal(a).ok_or_else(|| EvalError::TypeMismatch {
7383 detail: format!("cannot parse {a:?} as TIMESTAMP for comparison"),
7384 })?;
7385 at.cmp(b)
7386 }
7387 (Value::Uuid(a), Value::Uuid(b)) => a.cmp(b),
7389 (Value::Uuid(a), Value::Text(b)) => {
7395 let bu = spg_storage::parse_uuid_str(b).ok_or_else(|| EvalError::TypeMismatch {
7396 detail: format!("invalid input syntax for type uuid: {b:?}"),
7397 })?;
7398 a.cmp(&bu)
7399 }
7400 (Value::Text(a), Value::Uuid(b)) => {
7401 let au = spg_storage::parse_uuid_str(a).ok_or_else(|| EvalError::TypeMismatch {
7402 detail: format!("invalid input syntax for type uuid: {a:?}"),
7403 })?;
7404 au.cmp(b)
7405 }
7406 (a, b) => {
7407 return Err(EvalError::TypeMismatch {
7408 detail: format!(
7409 "comparison between {:?} and {:?}",
7410 a.data_type(),
7411 b.data_type()
7412 ),
7413 });
7414 }
7415 };
7416 let result = match op {
7417 BinOp::Eq => ord.is_eq(),
7418 BinOp::NotEq => !ord.is_eq(),
7419 BinOp::Lt => ord.is_lt(),
7420 BinOp::LtEq => ord.is_le(),
7421 BinOp::Gt => ord.is_gt(),
7422 BinOp::GtEq => ord.is_ge(),
7423 BinOp::And
7424 | BinOp::Or
7425 | BinOp::BitOr
7426 | BinOp::BitAnd
7427 | BinOp::Add
7428 | BinOp::Sub
7429 | BinOp::Mul
7430 | BinOp::Div
7431 | BinOp::L2Distance
7432 | BinOp::InnerProduct
7433 | BinOp::CosineDistance
7434 | BinOp::Concat
7435 | BinOp::JsonGet
7436 | BinOp::JsonGetText
7437 | BinOp::JsonGetPath
7438 | BinOp::JsonGetPathText
7439 | BinOp::JsonContains
7440 | BinOp::TsMatch
7441 | BinOp::IsDistinctFrom
7442 | BinOp::IsNotDistinctFrom
7443 | BinOp::InetContainedBy
7444 | BinOp::InetContainedByEq
7445 | BinOp::InetContains
7446 | BinOp::InetContainsEq
7447 | BinOp::InetOverlap => {
7448 unreachable!("compare() only called with comparison ops")
7449 }
7450 };
7451 Ok(Value::Bool(result))
7452}
7453
7454fn and_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7456 match (l, r) {
7457 (Value::Bool(false), _) | (_, Value::Bool(false)) => Ok(Value::Bool(false)),
7458 (Value::Bool(true), Value::Bool(true)) => Ok(Value::Bool(true)),
7459 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7460 (a, b) => Err(EvalError::TypeMismatch {
7461 detail: format!(
7462 "AND on non-boolean: {:?} and {:?}",
7463 a.data_type(),
7464 b.data_type()
7465 ),
7466 }),
7467 }
7468}
7469
7470fn or_3vl(l: Value, r: Value) -> Result<Value, EvalError> {
7471 match (l, r) {
7472 (Value::Bool(true), _) | (_, Value::Bool(true)) => Ok(Value::Bool(true)),
7473 (Value::Bool(false), Value::Bool(false)) => Ok(Value::Bool(false)),
7474 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
7475 (a, b) => Err(EvalError::TypeMismatch {
7476 detail: format!(
7477 "OR on non-boolean: {:?} and {:?}",
7478 a.data_type(),
7479 b.data_type()
7480 ),
7481 }),
7482 }
7483}
7484
7485#[cfg(test)]
7486mod tests {
7487 use super::*;
7488 use alloc::vec;
7489 use spg_storage::{ColumnSchema, Row};
7490
7491 fn col(name: &str, ty: DataType) -> ColumnSchema {
7492 ColumnSchema::new(name, ty, true)
7493 }
7494
7495 fn ctx<'a>(cols: &'a [ColumnSchema], alias: Option<&'a str>) -> EvalContext<'a> {
7496 EvalContext::new(cols, alias)
7497 }
7498
7499 fn lit(n: i64) -> Expr {
7500 Expr::Literal(Literal::Integer(n))
7501 }
7502
7503 fn null() -> Expr {
7504 Expr::Literal(Literal::Null)
7505 }
7506
7507 fn col_ref(name: &str) -> Expr {
7508 Expr::Column(ColumnName {
7509 qualifier: None,
7510 name: name.into(),
7511 })
7512 }
7513
7514 #[test]
7515 fn literal_evaluates_to_value() {
7516 let r = Row::new(vec![]);
7517 let cs: [ColumnSchema; 0] = [];
7518 let c = ctx(&cs, None);
7519 assert_eq!(eval_expr(&lit(42), &r, &c).unwrap(), Value::Int(42));
7520 assert_eq!(
7521 eval_expr(&Expr::Literal(Literal::Float(1.5)), &r, &c).unwrap(),
7522 Value::Float(1.5)
7523 );
7524 assert_eq!(eval_expr(&null(), &r, &c).unwrap(), Value::Null);
7525 }
7526
7527 #[test]
7528 fn column_lookup_unqualified() {
7529 let cs = vec![col("a", DataType::Int), col("b", DataType::Text)];
7530 let r = Row::new(vec![Value::Int(7), Value::Text("hi".into())]);
7531 let c = ctx(&cs, None);
7532 assert_eq!(eval_expr(&col_ref("a"), &r, &c).unwrap(), Value::Int(7));
7533 assert_eq!(
7534 eval_expr(&col_ref("b"), &r, &c).unwrap(),
7535 Value::Text("hi".into())
7536 );
7537 }
7538
7539 #[test]
7540 fn column_not_found_errors() {
7541 let cs = vec![col("a", DataType::Int)];
7542 let r = Row::new(vec![Value::Int(0)]);
7543 let c = ctx(&cs, None);
7544 let err = eval_expr(&col_ref("ghost"), &r, &c).unwrap_err();
7545 assert!(matches!(err, EvalError::ColumnNotFound { ref name } if name == "ghost"));
7546 }
7547
7548 #[test]
7549 fn qualified_column_matches_alias() {
7550 let cs = vec![col("a", DataType::Int)];
7551 let r = Row::new(vec![Value::Int(5)]);
7552 let c = ctx(&cs, Some("u"));
7553 let qualified = Expr::Column(ColumnName {
7554 qualifier: Some("u".into()),
7555 name: "a".into(),
7556 });
7557 assert_eq!(eval_expr(&qualified, &r, &c).unwrap(), Value::Int(5));
7558 }
7559
7560 #[test]
7561 fn qualified_column_unknown_alias_errors() {
7562 let cs = vec![col("a", DataType::Int)];
7563 let r = Row::new(vec![Value::Int(5)]);
7564 let c = ctx(&cs, Some("u"));
7565 let wrong = Expr::Column(ColumnName {
7566 qualifier: Some("x".into()),
7567 name: "a".into(),
7568 });
7569 assert!(matches!(
7570 eval_expr(&wrong, &r, &c).unwrap_err(),
7571 EvalError::UnknownQualifier { .. }
7572 ));
7573 }
7574
7575 #[test]
7576 fn arithmetic_with_widening() {
7577 let r = Row::new(vec![]);
7578 let cs: [ColumnSchema; 0] = [];
7579 let c = ctx(&cs, None);
7580 let e = Expr::Binary {
7581 lhs: alloc::boxed::Box::new(lit(2)),
7582 op: BinOp::Add,
7583 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::Float(0.5))),
7584 };
7585 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Float(2.5));
7586 }
7587
7588 #[test]
7589 fn division_by_zero_errors() {
7590 let r = Row::new(vec![]);
7591 let cs: [ColumnSchema; 0] = [];
7592 let c = ctx(&cs, None);
7593 let e = Expr::Binary {
7594 lhs: alloc::boxed::Box::new(lit(1)),
7595 op: BinOp::Div,
7596 rhs: alloc::boxed::Box::new(lit(0)),
7597 };
7598 assert_eq!(
7599 eval_expr(&e, &r, &c).unwrap_err(),
7600 EvalError::DivisionByZero
7601 );
7602 }
7603
7604 #[test]
7605 fn comparison_returns_bool() {
7606 let r = Row::new(vec![]);
7607 let cs: [ColumnSchema; 0] = [];
7608 let c = ctx(&cs, None);
7609 let e = Expr::Binary {
7610 lhs: alloc::boxed::Box::new(lit(1)),
7611 op: BinOp::Lt,
7612 rhs: alloc::boxed::Box::new(lit(2)),
7613 };
7614 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7615 }
7616
7617 #[test]
7618 fn null_propagates_through_arithmetic() {
7619 let r = Row::new(vec![]);
7620 let cs: [ColumnSchema; 0] = [];
7621 let c = ctx(&cs, None);
7622 let e = Expr::Binary {
7623 lhs: alloc::boxed::Box::new(lit(1)),
7624 op: BinOp::Add,
7625 rhs: alloc::boxed::Box::new(null()),
7626 };
7627 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7628 }
7629
7630 #[test]
7631 fn and_three_valued_logic() {
7632 let r = Row::new(vec![]);
7633 let cs: [ColumnSchema; 0] = [];
7634 let c = ctx(&cs, None);
7635 let tt = |a: bool, b_null: bool| Expr::Binary {
7636 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7637 op: BinOp::And,
7638 rhs: alloc::boxed::Box::new(if b_null {
7639 null()
7640 } else {
7641 Expr::Literal(Literal::Bool(true))
7642 }),
7643 };
7644 assert_eq!(
7646 eval_expr(&tt(false, true), &r, &c).unwrap(),
7647 Value::Bool(false)
7648 );
7649 assert_eq!(eval_expr(&tt(true, true), &r, &c).unwrap(), Value::Null);
7651 assert_eq!(
7653 eval_expr(&tt(true, false), &r, &c).unwrap(),
7654 Value::Bool(true)
7655 );
7656 }
7657
7658 #[test]
7659 fn or_three_valued_logic() {
7660 let r = Row::new(vec![]);
7661 let cs: [ColumnSchema; 0] = [];
7662 let c = ctx(&cs, None);
7663 let or_with_null = |a: bool| Expr::Binary {
7664 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::Bool(a))),
7665 op: BinOp::Or,
7666 rhs: alloc::boxed::Box::new(null()),
7667 };
7668 assert_eq!(
7670 eval_expr(&or_with_null(true), &r, &c).unwrap(),
7671 Value::Bool(true)
7672 );
7673 assert_eq!(
7675 eval_expr(&or_with_null(false), &r, &c).unwrap(),
7676 Value::Null
7677 );
7678 }
7679
7680 #[test]
7681 fn not_on_null_is_null() {
7682 let r = Row::new(vec![]);
7683 let cs: [ColumnSchema; 0] = [];
7684 let c = ctx(&cs, None);
7685 let e = Expr::Unary {
7686 op: UnOp::Not,
7687 expr: alloc::boxed::Box::new(null()),
7688 };
7689 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Null);
7690 }
7691
7692 #[test]
7693 fn text_comparison_lexicographic() {
7694 let r = Row::new(vec![]);
7695 let cs: [ColumnSchema; 0] = [];
7696 let c = ctx(&cs, None);
7697 let e = Expr::Binary {
7698 lhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("apple".into()))),
7699 op: BinOp::Lt,
7700 rhs: alloc::boxed::Box::new(Expr::Literal(Literal::String("banana".into()))),
7701 };
7702 assert_eq!(eval_expr(&e, &r, &c).unwrap(), Value::Bool(true));
7703 }
7704
7705 #[test]
7706 fn interval_format_basics() {
7707 assert_eq!(format_interval(0, 0), "0");
7708 assert_eq!(format_interval(0, 86_400_000_000), "1 day");
7709 assert_eq!(format_interval(0, -86_400_000_000), "-1 days");
7710 assert_eq!(format_interval(0, 3_600_000_000), "01:00:00");
7711 assert_eq!(
7712 format_interval(0, 86_400_000_000 + 9_000_000),
7713 "1 day 00:00:09"
7714 );
7715 assert_eq!(format_interval(14, 0), "1 year 2 mons");
7716 assert_eq!(format_interval(-1, 0), "-1 mons");
7717 }
7718
7719 #[test]
7720 fn interval_add_to_timestamp_micros_part() {
7721 let ts = i64::from(days_from_civil(2024, 1, 1)) * 86_400_000_000;
7723 let r = add_interval_to_micros(ts, 0, 3_600_000_000).unwrap();
7724 let expected = ts + 3_600_000_000;
7725 assert_eq!(r, expected);
7726 }
7727
7728 #[test]
7729 fn interval_clamp_month_end() {
7730 let d = days_from_civil(2024, 1, 31);
7732 let shifted = shift_date_by_months(d, 1).unwrap();
7733 let (y, m, day) = civil_from_days(shifted);
7734 assert_eq!((y, m, day), (2024, 2, 29));
7735 let d = days_from_civil(2023, 1, 31);
7737 let shifted = shift_date_by_months(d, 1).unwrap();
7738 let (y, m, day) = civil_from_days(shifted);
7739 assert_eq!((y, m, day), (2023, 2, 28));
7740 let d = days_from_civil(2024, 3, 31);
7742 let shifted = shift_date_by_months(d, -1).unwrap();
7743 let (y, m, day) = civil_from_days(shifted);
7744 assert_eq!((y, m, day), (2024, 2, 29));
7745 }
7746
7747 #[test]
7748 fn interval_date_plus_pure_days_stays_date() {
7749 let d = days_from_civil(2024, 6, 1);
7751 let lhs = Value::Date(d);
7752 let rhs = Value::Interval {
7753 months: 0,
7754 micros: 7 * 86_400_000_000,
7755 };
7756 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7757 .unwrap()
7758 .unwrap();
7759 let expected = days_from_civil(2024, 6, 8);
7760 assert_eq!(v, Value::Date(expected));
7761 }
7762
7763 #[test]
7764 fn interval_date_plus_sub_day_lifts_to_timestamp() {
7765 let d = days_from_civil(2024, 6, 1);
7767 let lhs = Value::Date(d);
7768 let rhs = Value::Interval {
7769 months: 0,
7770 micros: 3_600_000_000,
7771 };
7772 let v = apply_binary_interval(BinOp::Add, &lhs, &rhs)
7773 .unwrap()
7774 .unwrap();
7775 let expected = i64::from(d) * 86_400_000_000 + 3_600_000_000;
7776 assert_eq!(v, Value::Timestamp(expected));
7777 }
7778}