1use alloc::string::ToString;
9use alloc::vec::Vec;
10
11use spg_sql::ast::{ColumnTypeName, Expr, Literal, UnOp, VecEncoding as SqlVecEncoding};
12use spg_storage::{ColumnSchema, DataType, StorageError, Value, VecEncoding};
13
14use crate::EngineError;
15use crate::eval::{self, EvalContext, EvalError};
16use crate::numeric::{
17 numeric_from_float, numeric_from_integer, numeric_rescale, numeric_truncate_to_integer,
18 parse_numeric_text,
19};
20
21pub(crate) fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
26 let s = s.trim();
27 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
28 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
30 if cleaned.len() % 2 != 0 {
31 return Err("odd-length hex literal");
32 }
33 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
34 let cleaned_bytes = cleaned.as_bytes();
35 for i in (0..cleaned_bytes.len()).step_by(2) {
36 let hi = hex_nibble(cleaned_bytes[i])?;
37 let lo = hex_nibble(cleaned_bytes[i + 1])?;
38 out.push((hi << 4) | lo);
39 }
40 return Ok(out);
41 }
42 let bytes = s.as_bytes();
45 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
46 let mut i = 0;
47 while i < bytes.len() {
48 let b = bytes[i];
49 if b == b'\\' && i + 1 < bytes.len() {
50 let n = bytes[i + 1];
51 if n == b'\\' {
52 out.push(b'\\');
53 i += 2;
54 continue;
55 }
56 if n.is_ascii_digit()
57 && i + 3 < bytes.len()
58 && bytes[i + 2].is_ascii_digit()
59 && bytes[i + 3].is_ascii_digit()
60 {
61 let oct = |x: u8| (x - b'0') as u32;
62 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
63 if v <= 0xFF {
64 out.push(v as u8);
65 i += 4;
66 continue;
67 }
68 }
69 }
70 out.push(b);
71 i += 1;
72 }
73 Ok(out)
74}
75
76pub(crate) fn hex_nibble(b: u8) -> Result<u8, &'static str> {
77 match b {
78 b'0'..=b'9' => Ok(b - b'0'),
79 b'a'..=b'f' => Ok(b - b'a' + 10),
80 b'A'..=b'F' => Ok(b - b'A' + 10),
81 _ => Err("invalid hex digit"),
82 }
83}
84
85#[derive(Clone, Copy)]
90enum UniformArrayKind {
91 Bool,
92 Float,
93 Numeric,
94 Date,
95 Timestamp,
96 Uuid,
97 Bytes,
98 Interval,
99 Money,
100}
101
102impl UniformArrayKind {
103 fn build(self, items: alloc::vec::Vec<Value<'static>>) -> Value<'static> {
104 match self {
105 Self::Bool => Value::BoolArray(
106 items
107 .into_iter()
108 .map(|v| match v {
109 Value::Null => None,
110 Value::Bool(b) => Some(b),
111 _ => unreachable!("uniform Bool"),
112 })
113 .collect(),
114 ),
115 Self::Float => Value::FloatArray(
116 items
117 .into_iter()
118 .map(|v| match v {
119 Value::Null => None,
120 Value::Float(x) => Some(x),
121 _ => unreachable!("uniform Float"),
122 })
123 .collect(),
124 ),
125 Self::Numeric => Value::NumericArray(
126 items
127 .into_iter()
128 .map(|v| match v {
129 Value::Null => None,
130 Value::Numeric { scaled, scale } => Some((scaled, scale)),
131 _ => unreachable!("uniform Numeric"),
132 })
133 .collect(),
134 ),
135 Self::Date => Value::DateArray(
136 items
137 .into_iter()
138 .map(|v| match v {
139 Value::Null => None,
140 Value::Date(d) => Some(d),
141 _ => unreachable!("uniform Date"),
142 })
143 .collect(),
144 ),
145 Self::Timestamp => Value::TimestampArray(
146 items
147 .into_iter()
148 .map(|v| match v {
149 Value::Null => None,
150 Value::Timestamp(t) => Some(t),
151 _ => unreachable!("uniform Timestamp"),
152 })
153 .collect(),
154 ),
155 Self::Uuid => Value::UuidArray(
156 items
157 .into_iter()
158 .map(|v| match v {
159 Value::Null => None,
160 Value::Uuid(b) => Some(b),
161 _ => unreachable!("uniform Uuid"),
162 })
163 .collect(),
164 ),
165 Self::Bytes => Value::BytesArray(
166 items
167 .into_iter()
168 .map(|v| match v {
169 Value::Null => None,
170 Value::Bytes(b) => Some(b.into_owned()),
171 _ => unreachable!("uniform Bytes"),
172 })
173 .collect(),
174 ),
175 Self::Interval => Value::IntervalArray(
176 items
177 .into_iter()
178 .map(|v| match v {
179 Value::Null => None,
180 Value::Interval {
181 months,
182 days,
183 micros,
184 } => Some(spg_storage::IntervalSpan {
185 months,
186 days,
187 micros,
188 }),
189 _ => unreachable!("uniform Interval"),
190 })
191 .collect(),
192 ),
193 Self::Money => Value::MoneyArray(
194 items
195 .into_iter()
196 .map(|v| match v {
197 Value::Null => None,
198 Value::Money(c) => Some(c),
199 _ => unreachable!("uniform Money"),
200 })
201 .collect(),
202 ),
203 }
204 }
205}
206
207fn widen_uniform_typed(items: &[Value<'static>]) -> Option<UniformArrayKind> {
208 let mut kind: Option<UniformArrayKind> = None;
209 let mut saw_non_null = false;
210 for v in items {
211 let this = match v {
212 Value::Null => continue,
213 Value::Bool(_) => UniformArrayKind::Bool,
214 Value::Float(_) => UniformArrayKind::Float,
215 Value::Numeric { .. } => UniformArrayKind::Numeric,
216 Value::Date(_) => UniformArrayKind::Date,
217 Value::Timestamp(_) => UniformArrayKind::Timestamp,
218 Value::Uuid(_) => UniformArrayKind::Uuid,
219 Value::Bytes(_) => UniformArrayKind::Bytes,
220 Value::Interval { .. } => UniformArrayKind::Interval,
221 Value::Money(_) => UniformArrayKind::Money,
222 _ => return None,
226 };
227 match kind {
228 None => kind = Some(this),
229 Some(prev) if discriminant_eq(prev, this) => {}
230 Some(_) => return None,
231 }
232 saw_non_null = true;
233 }
234 if saw_non_null { kind } else { None }
235}
236
237fn discriminant_eq(a: UniformArrayKind, b: UniformArrayKind) -> bool {
238 matches!(
239 (a, b),
240 (UniformArrayKind::Bool, UniformArrayKind::Bool)
241 | (UniformArrayKind::Float, UniformArrayKind::Float)
242 | (UniformArrayKind::Numeric, UniformArrayKind::Numeric)
243 | (UniformArrayKind::Date, UniformArrayKind::Date)
244 | (UniformArrayKind::Timestamp, UniformArrayKind::Timestamp)
245 | (UniformArrayKind::Uuid, UniformArrayKind::Uuid)
246 | (UniformArrayKind::Bytes, UniformArrayKind::Bytes)
247 | (UniformArrayKind::Interval, UniformArrayKind::Interval)
248 | (UniformArrayKind::Money, UniformArrayKind::Money)
249 )
250}
251
252pub(crate) fn array_literal_widen(items: alloc::vec::Vec<Value<'static>>) -> Value<'static> {
266 if let Some(arr) = widen_uniform_typed(&items) {
273 return arr.build(items);
274 }
275 let mut has_text = false;
276 let mut has_bigint = false;
277 let mut has_int = false;
278 for v in &items {
279 match v {
280 Value::Null => {}
281 Value::Text(_) | Value::Json(_) => has_text = true,
282 Value::BigInt(_) => has_bigint = true,
283 Value::Int(_) | Value::SmallInt(_) => has_int = true,
284 _ => has_text = true,
285 }
286 }
287 if has_text || (!has_bigint && !has_int) {
288 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
289 .into_iter()
290 .map(|v| match v {
291 Value::Null => None,
292 Value::Text(s) | Value::Json(s) => Some(s.into_owned()),
293 other => Some(alloc::format!("{other:?}")),
294 })
295 .collect();
296 return Value::TextArray(out);
297 }
298 if has_bigint {
299 let out: alloc::vec::Vec<Option<i64>> = items
300 .into_iter()
301 .map(|v| match v {
302 Value::Null => None,
303 Value::Int(n) => Some(i64::from(n)),
304 Value::SmallInt(n) => Some(i64::from(n)),
305 Value::BigInt(n) => Some(n),
306 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
307 })
308 .collect();
309 return Value::BigIntArray(out);
310 }
311 let out: alloc::vec::Vec<Option<i32>> = items
312 .into_iter()
313 .map(|v| match v {
314 Value::Null => None,
315 Value::Int(n) => Some(n),
316 Value::SmallInt(n) => Some(i32::from(n)),
317 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
318 })
319 .collect();
320 Value::IntArray(out)
321}
322
323pub(crate) fn decode_text_array_literal(
324 s: &str,
325) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
326 let trimmed = s.trim();
327 let inner = trimmed
328 .strip_prefix('{')
329 .and_then(|x| x.strip_suffix('}'))
330 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
331 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
332 if inner.trim().is_empty() {
333 return Ok(out);
334 }
335 let bytes = inner.as_bytes();
336 let mut i = 0;
337 while i <= bytes.len() {
338 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
340 i += 1;
341 }
342 if i < bytes.len() && bytes[i] == b'"' {
344 i += 1; let mut buf = alloc::string::String::new();
346 while i < bytes.len() && bytes[i] != b'"' {
347 if bytes[i] == b'\\' && i + 1 < bytes.len() {
348 buf.push(bytes[i + 1] as char);
349 i += 2;
350 } else {
351 buf.push(bytes[i] as char);
352 i += 1;
353 }
354 }
355 if i >= bytes.len() {
356 return Err("unterminated quoted element");
357 }
358 i += 1; out.push(Some(buf));
360 } else {
361 let start = i;
363 while i < bytes.len() && bytes[i] != b',' {
364 i += 1;
365 }
366 let raw = inner[start..i].trim();
367 if raw.eq_ignore_ascii_case("NULL") {
368 out.push(None);
369 } else {
370 out.push(Some(alloc::string::ToString::to_string(raw)));
371 }
372 }
373 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
375 i += 1;
376 }
377 if i >= bytes.len() {
378 break;
379 }
380 if bytes[i] != b',' {
381 return Err("expected ',' between TEXT[] elements");
382 }
383 i += 1;
384 }
385 Ok(out)
386}
387
388pub(crate) fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
393 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
394 out.push('{');
395 for (i, item) in items.iter().enumerate() {
396 if i > 0 {
397 out.push(',');
398 }
399 match item {
400 None => out.push_str("NULL"),
401 Some(s) => {
402 let needs_quote = s.is_empty()
403 || s.eq_ignore_ascii_case("NULL")
404 || s.chars()
405 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
406 if needs_quote {
407 out.push('"');
408 for c in s.chars() {
409 if c == '"' || c == '\\' {
410 out.push('\\');
411 }
412 out.push(c);
413 }
414 out.push('"');
415 } else {
416 out.push_str(s);
417 }
418 }
419 }
420 }
421 out.push('}');
422 out
423}
424
425pub(crate) fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
429 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
430 out.push_str("\\x");
431 for byte in b {
432 let hi = byte >> 4;
433 let lo = byte & 0x0F;
434 out.push(hex_digit(hi));
435 out.push(hex_digit(lo));
436 }
437 out
438}
439
440pub(crate) const fn hex_digit(n: u8) -> char {
441 match n {
442 0..=9 => (b'0' + n) as char,
443 10..=15 => (b'a' + n - 10) as char,
444 _ => '?',
445 }
446}
447
448pub(crate) fn parse_hstore_str(
460 s: &str,
461) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
462 let bytes = s.as_bytes();
463 let mut i = 0;
464 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
465 let skip_ws = |bytes: &[u8], i: &mut usize| {
466 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
467 *i += 1;
468 }
469 };
470 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
471 if *i >= bytes.len() {
472 return None;
473 }
474 if bytes[*i] == b'"' {
475 *i += 1;
476 let mut out = alloc::string::String::new();
477 while *i < bytes.len() {
478 match bytes[*i] {
479 b'"' => {
480 *i += 1;
481 return Some(out);
482 }
483 b'\\' if *i + 1 < bytes.len() => {
484 out.push(bytes[*i + 1] as char);
485 *i += 2;
486 }
487 c => {
488 out.push(c as char);
489 *i += 1;
490 }
491 }
492 }
493 None
494 } else {
495 let start = *i;
496 while *i < bytes.len()
497 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
498 {
499 *i += 1;
500 }
501 if *i == start {
502 return None;
503 }
504 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
505 }
506 };
507 skip_ws(bytes, &mut i);
508 while i < bytes.len() {
509 let key = parse_token(bytes, &mut i)?;
510 skip_ws(bytes, &mut i);
511 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
512 return None;
513 }
514 i += 2;
515 skip_ws(bytes, &mut i);
516 let val_token = if i + 4 <= bytes.len()
518 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
519 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
520 {
521 i += 4;
522 None
523 } else {
524 Some(parse_token(bytes, &mut i)?)
525 };
526 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
528 out[pos] = (key, val_token);
529 } else {
530 out.push((key, val_token));
531 }
532 skip_ws(bytes, &mut i);
533 if i >= bytes.len() {
534 break;
535 }
536 if bytes[i] == b',' {
537 i += 1;
538 skip_ws(bytes, &mut i);
539 continue;
540 }
541 return None;
542 }
543 Some(out)
544}
545
546pub(crate) fn format_hstore_str(
550 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
551) -> alloc::string::String {
552 let mut out = alloc::string::String::new();
553 for (i, (k, v)) in pairs.iter().enumerate() {
554 if i > 0 {
555 out.push_str(", ");
556 }
557 out.push('"');
558 out.push_str(k);
559 out.push_str("\"=>");
560 match v {
561 None => out.push_str("NULL"),
562 Some(val) => {
563 out.push('"');
564 out.push_str(val);
565 out.push('"');
566 }
567 }
568 }
569 out
570}
571
572pub fn format_hstore_text(
575 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
576) -> alloc::string::String {
577 format_hstore_str(pairs)
578}
579
580pub(crate) fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
585 let s = s.trim();
586 let outer = s
587 .strip_prefix('{')
588 .and_then(|x| x.strip_suffix('}'))
589 .ok_or("missing outer '{...}' braces")?;
590 let trimmed = outer.trim();
591 if trimmed.is_empty() {
592 return Ok(Vec::new());
593 }
594 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
595 let mut i = 0;
596 let bytes = trimmed.as_bytes();
597 while i < bytes.len() {
598 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
599 i += 1;
600 }
601 if i >= bytes.len() {
602 break;
603 }
604 if bytes[i] != b'{' {
605 return Err("expected '{' opening a row");
606 }
607 i += 1;
608 let row_start = i;
609 let mut depth = 1;
610 while i < bytes.len() && depth > 0 {
611 match bytes[i] {
612 b'{' => depth += 1,
613 b'}' => depth -= 1,
614 _ => {}
615 }
616 if depth > 0 {
617 i += 1;
618 }
619 }
620 if depth != 0 {
621 return Err("unbalanced '{...}' in row");
622 }
623 let row_text = &trimmed[row_start..i];
624 i += 1;
625 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
626 Vec::new()
627 } else {
628 row_text.split(',').map(|t| t.trim().to_string()).collect()
629 };
630 rows.push(cells);
631 }
632 if let Some(first) = rows.first() {
633 let cols = first.len();
634 for r in &rows {
635 if r.len() != cols {
636 return Err("ragged 2D array (rows have different column counts)");
637 }
638 }
639 }
640 Ok(rows)
641}
642
643pub(crate) fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
644 let raw = split_2d_literal(s)?;
645 raw.into_iter()
646 .map(|row| {
647 row.into_iter()
648 .map(|cell| {
649 if cell.eq_ignore_ascii_case("NULL") {
650 Ok(None)
651 } else {
652 cell.parse::<i32>()
653 .map(Some)
654 .map_err(|_| "invalid int element")
655 }
656 })
657 .collect()
658 })
659 .collect()
660}
661
662pub(crate) fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
663 let raw = split_2d_literal(s)?;
664 raw.into_iter()
665 .map(|row| {
666 row.into_iter()
667 .map(|cell| {
668 if cell.eq_ignore_ascii_case("NULL") {
669 Ok(None)
670 } else {
671 cell.parse::<i64>()
672 .map(Some)
673 .map_err(|_| "invalid bigint element")
674 }
675 })
676 .collect()
677 })
678 .collect()
679}
680
681pub(crate) fn parse_text_2d_literal(
682 s: &str,
683) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
684 let raw = split_2d_literal(s)?;
685 Ok(raw
686 .into_iter()
687 .map(|row| {
688 row.into_iter()
689 .map(|cell| {
690 if cell.eq_ignore_ascii_case("NULL") {
691 None
692 } else {
693 Some(cell.trim_matches('"').to_string())
694 }
695 })
696 .collect()
697 })
698 .collect())
699}
700
701pub(crate) fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
702 let mut out = alloc::string::String::from("{");
703 for (i, row) in rows.iter().enumerate() {
704 if i > 0 {
705 out.push(',');
706 }
707 out.push('{');
708 for (j, cell) in row.iter().enumerate() {
709 if j > 0 {
710 out.push(',');
711 }
712 match cell {
713 None => out.push_str("NULL"),
714 Some(n) => out.push_str(&alloc::format!("{n}")),
715 }
716 }
717 out.push('}');
718 }
719 out.push('}');
720 out
721}
722
723pub(crate) fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
724 let mut out = alloc::string::String::from("{");
725 for (i, row) in rows.iter().enumerate() {
726 if i > 0 {
727 out.push(',');
728 }
729 out.push('{');
730 for (j, cell) in row.iter().enumerate() {
731 if j > 0 {
732 out.push(',');
733 }
734 match cell {
735 None => out.push_str("NULL"),
736 Some(n) => out.push_str(&alloc::format!("{n}")),
737 }
738 }
739 out.push('}');
740 }
741 out.push('}');
742 out
743}
744
745pub(crate) fn format_text_2d_text(
746 rows: &[Vec<Option<alloc::string::String>>],
747) -> alloc::string::String {
748 let mut out = alloc::string::String::from("{");
749 for (i, row) in rows.iter().enumerate() {
750 if i > 0 {
751 out.push(',');
752 }
753 out.push('{');
754 for (j, cell) in row.iter().enumerate() {
755 if j > 0 {
756 out.push(',');
757 }
758 match cell {
759 None => out.push_str("NULL"),
760 Some(s) => out.push_str(s),
761 }
762 }
763 out.push('}');
764 }
765 out.push('}');
766 out
767}
768
769pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
772 format_int_2d_text(rows)
773}
774pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
775 format_bigint_2d_text(rows)
776}
777pub fn format_text_2d_text_pub(
778 rows: &[Vec<Option<alloc::string::String>>],
779) -> alloc::string::String {
780 format_text_2d_text(rows)
781}
782
783pub(crate) fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value<'static>> {
788 let s = s.trim();
789 if s.eq_ignore_ascii_case("empty") {
790 return Some(Value::Range {
791 kind,
792 lower: None,
793 upper: None,
794 lower_inc: false,
795 upper_inc: false,
796 empty: true,
797 });
798 }
799 let bytes = s.as_bytes();
800 if bytes.len() < 3 {
801 return None;
802 }
803 let lower_inc = match bytes[0] {
804 b'[' => true,
805 b'(' => false,
806 _ => return None,
807 };
808 let upper_inc = match bytes[bytes.len() - 1] {
809 b']' => true,
810 b')' => false,
811 _ => return None,
812 };
813 let inner = &s[1..s.len() - 1];
814 let (lo_text, up_text) = inner.split_once(',')?;
815 let lower = if lo_text.is_empty() {
816 None
817 } else {
818 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
819 };
820 let upper = if up_text.is_empty() {
821 None
822 } else {
823 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
824 };
825 Some(Value::Range {
826 kind,
827 lower,
828 upper,
829 lower_inc,
830 upper_inc,
831 empty: false,
832 })
833}
834
835pub(crate) fn parse_multirange_str(
842 s: &str,
843 kind: spg_storage::RangeKind,
844) -> Option<Vec<spg_storage::RangeSpan>> {
845 let s = s.trim();
846 let inner = s.strip_prefix('{').and_then(|x| x.strip_suffix('}'))?;
847 let inner = inner.trim();
848 if inner.is_empty() {
849 return Some(Vec::new());
850 }
851 let mut spans: Vec<spg_storage::RangeSpan> = Vec::new();
855 let bytes = inner.as_bytes();
856 let mut depth: i32 = 0;
857 let mut start = 0usize;
858 for i in 0..=bytes.len() {
859 let cut = i == bytes.len() || (depth == 0 && bytes[i] == b',');
860 if !cut {
861 match bytes.get(i) {
862 Some(b'[') | Some(b'(') => depth += 1,
863 Some(b']') | Some(b')') => depth -= 1,
864 _ => {}
865 }
866 continue;
867 }
868 let piece = inner[start..i].trim();
869 if piece.is_empty() {
870 return None;
871 }
872 let r = parse_range_str(piece, kind)?;
873 let Value::Range {
874 lower,
875 upper,
876 lower_inc,
877 upper_inc,
878 empty,
879 ..
880 } = r
881 else {
882 return None;
883 };
884 spans.push(spg_storage::RangeSpan {
885 lower,
886 upper,
887 lower_inc,
888 upper_inc,
889 empty,
890 });
891 start = i + 1;
892 }
893 Some(spans)
894}
895
896pub(crate) fn parse_range_element(
899 text: &str,
900 kind: spg_storage::RangeKind,
901) -> Option<Value<'static>> {
902 let text = text.trim().trim_matches('"');
903 use spg_storage::RangeKind as K;
904 match kind {
905 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
906 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
907 K::Num => {
908 let dot = text.find('.');
911 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
912 let digits: alloc::string::String = text
913 .chars()
914 .filter(|c| *c == '-' || c.is_ascii_digit())
915 .collect();
916 let scaled: i128 = digits.parse().ok()?;
917 Some(Value::Numeric { scaled, scale })
918 }
919 K::Ts | K::TsTz => {
920 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
925 }
926 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
927 }
928}
929
930pub fn format_range_text(v: &Value) -> alloc::string::String {
934 format_range_str(v)
935}
936
937pub(crate) fn format_range_str(v: &Value) -> alloc::string::String {
938 let Value::Range {
939 lower,
940 upper,
941 lower_inc,
942 upper_inc,
943 empty,
944 ..
945 } = v
946 else {
947 return alloc::string::String::new();
948 };
949 if *empty {
950 return "empty".into();
951 }
952 let mut out = alloc::string::String::new();
953 out.push(if *lower_inc { '[' } else { '(' });
954 if let Some(l) = lower {
955 out.push_str(&format_range_element(l));
956 }
957 out.push(',');
958 if let Some(u) = upper {
959 out.push_str(&format_range_element(u));
960 }
961 out.push(if *upper_inc { ']' } else { ')' });
962 out
963}
964
965pub fn format_point(p: spg_storage::Point2D) -> alloc::string::String {
967 alloc::format!("({},{})", p.x, p.y)
968}
969
970pub fn format_lseg(p1: spg_storage::Point2D, p2: spg_storage::Point2D) -> alloc::string::String {
972 alloc::format!("[({},{}),({},{})]", p1.x, p1.y, p2.x, p2.y)
973}
974
975pub fn format_pg_box(ur: spg_storage::Point2D, ll: spg_storage::Point2D) -> alloc::string::String {
980 alloc::format!("({},{}),({},{})", ur.x, ur.y, ll.x, ll.y)
981}
982
983pub fn format_line(a: f64, b: f64, c: f64) -> alloc::string::String {
985 alloc::format!("{{{},{},{}}}", a, b, c)
986}
987
988pub fn format_circle(center: spg_storage::Point2D, radius: f64) -> alloc::string::String {
990 alloc::format!("<({},{}),{}>", center.x, center.y, radius)
991}
992
993pub fn format_path(points: &[spg_storage::Point2D], closed: bool) -> alloc::string::String {
996 let (open, close) = if closed { ('(', ')') } else { ('[', ']') };
997 let mut out = alloc::string::String::new();
998 out.push(open);
999 for (i, p) in points.iter().enumerate() {
1000 if i > 0 {
1001 out.push(',');
1002 }
1003 out.push_str(&alloc::format!("({},{})", p.x, p.y));
1004 }
1005 out.push(close);
1006 out
1007}
1008
1009pub fn format_polygon(points: &[spg_storage::Point2D]) -> alloc::string::String {
1011 let mut out = alloc::string::String::new();
1012 out.push('(');
1013 for (i, p) in points.iter().enumerate() {
1014 if i > 0 {
1015 out.push(',');
1016 }
1017 out.push_str(&alloc::format!("({},{})", p.x, p.y));
1018 }
1019 out.push(')');
1020 out
1021}
1022
1023fn parse_point(s: &str) -> Option<spg_storage::Point2D> {
1026 let s = s.trim();
1027 let inner = s
1028 .strip_prefix('(')
1029 .and_then(|x| x.strip_suffix(')'))
1030 .unwrap_or(s);
1031 let (xs, ys) = inner.split_once(',')?;
1032 let x: f64 = xs.trim().parse().ok()?;
1033 let y: f64 = ys.trim().parse().ok()?;
1034 Some(spg_storage::Point2D { x, y })
1035}
1036
1037fn parse_point_list(s: &str) -> Option<Vec<spg_storage::Point2D>> {
1042 let bytes = s.as_bytes();
1043 let mut out: Vec<spg_storage::Point2D> = Vec::new();
1044 let mut depth: i32 = 0;
1045 let mut start = 0usize;
1046 for i in 0..=bytes.len() {
1047 let cut = i == bytes.len() || (depth == 0 && bytes[i] == b',');
1048 if !cut {
1049 match bytes.get(i) {
1050 Some(b'(') | Some(b'[') | Some(b'<') => depth += 1,
1051 Some(b')') | Some(b']') | Some(b'>') => depth -= 1,
1052 _ => {}
1053 }
1054 continue;
1055 }
1056 let piece = s[start..i].trim();
1057 if !piece.is_empty() {
1058 out.push(parse_point(piece)?);
1059 }
1060 start = i + 1;
1061 }
1062 Some(out)
1063}
1064
1065pub fn parse_lseg_text(s: &str) -> Option<(spg_storage::Point2D, spg_storage::Point2D)> {
1067 let s = s.trim();
1068 let inner = s.strip_prefix('[').and_then(|x| x.strip_suffix(']'))?;
1069 let pts = parse_point_list(inner)?;
1070 if pts.len() != 2 {
1071 return None;
1072 }
1073 Some((pts[0], pts[1]))
1074}
1075
1076pub fn parse_box_text(s: &str) -> Option<(spg_storage::Point2D, spg_storage::Point2D)> {
1080 let pts = parse_point_list(s)?;
1081 if pts.len() != 2 {
1082 return None;
1083 }
1084 let (a, b) = (pts[0], pts[1]);
1085 let ur = spg_storage::Point2D {
1087 x: a.x.max(b.x),
1088 y: a.y.max(b.y),
1089 };
1090 let ll = spg_storage::Point2D {
1091 x: a.x.min(b.x),
1092 y: a.y.min(b.y),
1093 };
1094 Some((ur, ll))
1095}
1096
1097pub fn parse_line_text(s: &str) -> Option<(f64, f64, f64)> {
1099 let s = s.trim();
1100 let inner = s.strip_prefix('{').and_then(|x| x.strip_suffix('}'))?;
1101 let parts: Vec<&str> = inner.split(',').collect();
1102 if parts.len() != 3 {
1103 return None;
1104 }
1105 let a: f64 = parts[0].trim().parse().ok()?;
1106 let b: f64 = parts[1].trim().parse().ok()?;
1107 let c: f64 = parts[2].trim().parse().ok()?;
1108 Some((a, b, c))
1109}
1110
1111pub fn parse_circle_text(s: &str) -> Option<(spg_storage::Point2D, f64)> {
1113 let s = s.trim();
1114 let inner = if let Some(i) = s.strip_prefix('<').and_then(|x| x.strip_suffix('>')) {
1115 i
1116 } else {
1117 s.strip_prefix('(').and_then(|x| x.strip_suffix(')'))?
1118 };
1119 let bytes = inner.as_bytes();
1121 let mut depth = 0i32;
1122 let mut split_at: Option<usize> = None;
1123 for (i, &b) in bytes.iter().enumerate() {
1124 match b {
1125 b'(' | b'[' | b'<' => depth += 1,
1126 b')' | b']' | b'>' => depth -= 1,
1127 b',' if depth == 0 => split_at = Some(i),
1128 _ => {}
1129 }
1130 }
1131 let i = split_at?;
1132 let center = parse_point(&inner[..i])?;
1133 let radius: f64 = inner[i + 1..].trim().parse().ok()?;
1134 Some((center, radius))
1135}
1136
1137pub fn parse_path_text(s: &str) -> Option<(Vec<spg_storage::Point2D>, bool)> {
1140 let s = s.trim();
1141 let (closed, inner) = if let Some(i) = s.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
1142 (false, i)
1143 } else if let Some(i) = s.strip_prefix('(').and_then(|x| x.strip_suffix(')')) {
1144 (true, i)
1145 } else {
1146 return None;
1147 };
1148 let pts = parse_point_list(inner)?;
1149 Some((pts, closed))
1150}
1151
1152pub fn parse_polygon_text(s: &str) -> Option<Vec<spg_storage::Point2D>> {
1154 let s = s.trim();
1155 let inner = s.strip_prefix('(').and_then(|x| x.strip_suffix(')'))?;
1156 parse_point_list(inner)
1157}
1158
1159pub fn format_inet(family: u8, bits: u8, addr: &[u8; 16]) -> alloc::string::String {
1164 match family {
1165 4 => {
1166 let s = alloc::format!("{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3]);
1167 if bits == 32 {
1168 s
1169 } else {
1170 alloc::format!("{s}/{bits}")
1171 }
1172 }
1173 6 => {
1174 let mut out = alloc::string::String::new();
1178 for i in 0..8 {
1179 if i > 0 {
1180 out.push(':');
1181 }
1182 let hi = addr[i * 2];
1183 let lo = addr[i * 2 + 1];
1184 let word = (u16::from(hi) << 8) | u16::from(lo);
1185 out.push_str(&alloc::format!("{word:x}"));
1186 }
1187 if bits == 128 {
1188 out
1189 } else {
1190 alloc::format!("{out}/{bits}")
1191 }
1192 }
1193 _ => alloc::format!("?invalid-inet-family-{family}"),
1194 }
1195}
1196
1197pub fn format_macaddr(m: &[u8; 6]) -> alloc::string::String {
1199 alloc::format!(
1200 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
1201 m[0],
1202 m[1],
1203 m[2],
1204 m[3],
1205 m[4],
1206 m[5]
1207 )
1208}
1209
1210pub fn format_macaddr8(m: &[u8; 8]) -> alloc::string::String {
1212 alloc::format!(
1213 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
1214 m[0],
1215 m[1],
1216 m[2],
1217 m[3],
1218 m[4],
1219 m[5],
1220 m[6],
1221 m[7]
1222 )
1223}
1224
1225pub fn format_bit_string(nbits: u32, bytes: &[u8]) -> alloc::string::String {
1230 let mut out = alloc::string::String::with_capacity(nbits as usize);
1231 for i in 0..nbits as usize {
1232 let byte = bytes[i / 8];
1233 let bit = (byte >> (7 - (i % 8))) & 1;
1234 out.push(if bit == 1 { '1' } else { '0' });
1235 }
1236 out
1237}
1238
1239pub fn format_money_array(items: &[Option<i64>]) -> alloc::string::String {
1243 let mut out = alloc::string::String::new();
1244 out.push('{');
1245 for (i, item) in items.iter().enumerate() {
1246 if i > 0 {
1247 out.push(',');
1248 }
1249 match item {
1250 None => out.push_str("NULL"),
1251 Some(c) => out.push_str(&crate::eval::format_money(*c)),
1252 }
1253 }
1254 out.push('}');
1255 out
1256}
1257
1258pub fn parse_inet_text(s: &str) -> Option<(u8, u8, [u8; 16])> {
1263 let s = s.trim();
1264 let (addr_s, bits_s) = match s.split_once('/') {
1265 Some((a, b)) => (a, Some(b)),
1266 None => (s, None),
1267 };
1268 if addr_s.contains(':') {
1269 let (head, tail) = match addr_s.find("::") {
1274 Some(idx) => (&addr_s[..idx], Some(&addr_s[idx + 2..])),
1275 None => (addr_s, None),
1276 };
1277 let head_groups: alloc::vec::Vec<&str> = if head.is_empty() {
1278 alloc::vec::Vec::new()
1279 } else {
1280 head.split(':').collect()
1281 };
1282 let tail_groups: alloc::vec::Vec<&str> = match tail {
1283 Some(t) if !t.is_empty() => t.split(':').collect(),
1284 _ => alloc::vec::Vec::new(),
1285 };
1286 let head_len = head_groups.len();
1287 let tail_len = tail_groups.len();
1288 if tail.is_none() {
1289 if head_len != 8 {
1290 return None;
1291 }
1292 } else if head_len + tail_len > 7 {
1293 return None;
1294 }
1295 let mut words = [0u16; 8];
1296 for (i, g) in head_groups.iter().enumerate() {
1297 words[i] = u16::from_str_radix(g, 16).ok()?;
1298 }
1299 let tail_start = 8 - tail_len;
1300 for (i, g) in tail_groups.iter().enumerate() {
1301 words[tail_start + i] = u16::from_str_radix(g, 16).ok()?;
1302 }
1303 let mut addr = [0u8; 16];
1304 for (i, w) in words.iter().enumerate() {
1305 addr[i * 2] = (w >> 8) as u8;
1306 addr[i * 2 + 1] = (w & 0xff) as u8;
1307 }
1308 let bits = match bits_s {
1309 Some(b) => b.parse::<u8>().ok().filter(|&n| n <= 128)?,
1310 None => 128,
1311 };
1312 Some((6, bits, addr))
1313 } else {
1314 let parts: alloc::vec::Vec<&str> = addr_s.split('.').collect();
1316 if parts.len() != 4 {
1317 return None;
1318 }
1319 let mut addr = [0u8; 16];
1320 for (i, p) in parts.iter().enumerate() {
1321 addr[i] = p.parse::<u8>().ok()?;
1322 }
1323 let bits = match bits_s {
1324 Some(b) => b.parse::<u8>().ok().filter(|&n| n <= 32)?,
1325 None => 32,
1326 };
1327 Some((4, bits, addr))
1328 }
1329}
1330
1331pub fn parse_macaddr_text(s: &str) -> Option<[u8; 6]> {
1334 let s = s.trim();
1335 let cleaned: alloc::string::String = s.chars().filter(|c| c.is_ascii_hexdigit()).collect();
1336 if cleaned.len() != 12 {
1337 return None;
1338 }
1339 let mut out = [0u8; 6];
1340 for i in 0..6 {
1341 out[i] = u8::from_str_radix(&cleaned[i * 2..i * 2 + 2], 16).ok()?;
1342 }
1343 Some(out)
1344}
1345
1346pub fn parse_macaddr8_text(s: &str) -> Option<[u8; 8]> {
1348 let s = s.trim();
1349 let cleaned: alloc::string::String = s.chars().filter(|c| c.is_ascii_hexdigit()).collect();
1350 if cleaned.len() != 16 {
1351 return None;
1352 }
1353 let mut out = [0u8; 8];
1354 for i in 0..8 {
1355 out[i] = u8::from_str_radix(&cleaned[i * 2..i * 2 + 2], 16).ok()?;
1356 }
1357 Some(out)
1358}
1359
1360pub fn parse_bit_string_text(s: &str) -> Option<(u32, alloc::vec::Vec<u8>)> {
1364 let s = s.trim();
1365 let nbits = u32::try_from(s.len()).ok()?;
1366 let nbytes = (s.len()).div_ceil(8);
1367 let mut bytes = alloc::vec![0u8; nbytes];
1368 for (i, c) in s.chars().enumerate() {
1369 let bit = match c {
1370 '0' => 0u8,
1371 '1' => 1u8,
1372 _ => return None,
1373 };
1374 if bit == 1 {
1375 bytes[i / 8] |= 1 << (7 - (i % 8));
1376 }
1377 }
1378 Some((nbits, bytes))
1379}
1380
1381pub fn format_multirange(ranges: &[spg_storage::RangeSpan]) -> alloc::string::String {
1388 let mut out = alloc::string::String::new();
1389 out.push('{');
1390 for (i, r) in ranges.iter().enumerate() {
1391 if i > 0 {
1392 out.push(',');
1393 }
1394 if r.empty {
1395 out.push_str("empty");
1396 continue;
1397 }
1398 out.push(if r.lower_inc { '[' } else { '(' });
1399 if let Some(l) = &r.lower {
1400 out.push_str(&format_range_element(l));
1401 }
1402 out.push(',');
1403 if let Some(u) = &r.upper {
1404 out.push_str(&format_range_element(u));
1405 }
1406 out.push(if r.upper_inc { ']' } else { ')' });
1407 }
1408 out.push('}');
1409 out
1410}
1411
1412pub(crate) fn format_range_element(v: &Value) -> alloc::string::String {
1413 match v {
1414 Value::Int(n) => alloc::format!("{n}"),
1415 Value::BigInt(n) => alloc::format!("{n}"),
1416 Value::Date(d) => crate::eval::format_date(*d),
1417 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
1418 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
1419 other => alloc::format!("{other:?}"),
1420 }
1421}
1422
1423pub(crate) fn parse_money_str(s: &str) -> Option<i64> {
1434 let s = s.trim();
1435 let (neg, rest) = match s.strip_prefix('-') {
1436 Some(r) => (true, r.trim_start()),
1437 None => (false, s),
1438 };
1439 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
1440 let (int_part, frac_part) = match rest.split_once('.') {
1441 Some((i, f)) => (i, Some(f)),
1442 None => (rest, None),
1443 };
1444 if int_part.is_empty() {
1445 return None;
1446 }
1447 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
1449 for b in int_part.bytes() {
1450 match b {
1451 b',' => {}
1452 b'0'..=b'9' => int_digits.push(b as char),
1453 _ => return None,
1454 }
1455 }
1456 if int_digits.is_empty() {
1457 return None;
1458 }
1459 let dollars: i64 = int_digits.parse().ok()?;
1460 let cents: i64 = match frac_part {
1461 None => 0,
1462 Some(f) => {
1463 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
1464 return None;
1465 }
1466 let padded = if f.len() == 1 {
1467 alloc::format!("{f}0")
1468 } else {
1469 f.to_string()
1470 };
1471 padded.parse().ok()?
1472 }
1473 };
1474 let total = dollars.checked_mul(100)?.checked_add(cents)?;
1475 Some(if neg { -total } else { total })
1476}
1477
1478pub(crate) fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
1489 let s = s.trim();
1490 let bytes = s.as_bytes();
1494 let sign_pos = bytes
1495 .iter()
1496 .enumerate()
1497 .rev()
1498 .find(|&(_, &b)| b == b'+' || b == b'-')
1499 .map(|(i, _)| i)?;
1500 if sign_pos == 0 {
1501 return None; }
1503 let time_part = &s[..sign_pos];
1504 let offset_part = &s[sign_pos..];
1505 let us = parse_time_str(time_part)?;
1506 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
1507 let offset_body = &offset_part[1..];
1508 let (hh_str, mm_str) = match offset_body.split_once(':') {
1509 Some((h, m)) => (h, m),
1510 None => (offset_body, "0"),
1511 };
1512 let hh: i32 = hh_str.parse().ok()?;
1513 let mm: i32 = mm_str.parse().ok()?;
1514 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
1515 return None;
1516 }
1517 let total = sign * (hh * 3600 + mm * 60);
1518 if total.abs() > 50_400 {
1519 return None;
1520 }
1521 Some((us, total))
1522}
1523
1524pub(crate) fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value<'static>, EngineError> {
1529 if n == 0 || (1901..=2155).contains(&n) {
1530 return Ok(Value::Year(n as u16));
1533 }
1534 Err(EngineError::Eval(EvalError::TypeMismatch {
1535 detail: alloc::format!(
1536 "year value out of range: {n} (column `{col_name}`; \
1537 MySQL accepts 0 or 1901..=2155)"
1538 ),
1539 }))
1540}
1541
1542pub(crate) fn parse_time_str(s: &str) -> Option<i64> {
1554 let s = s.trim();
1555 let (hms, frac) = match s.split_once('.') {
1556 Some((h, f)) => (h, Some(f)),
1557 None => (s, None),
1558 };
1559 let mut parts = hms.split(':');
1560 let hh: u32 = parts.next()?.parse().ok()?;
1561 let mm: u32 = parts.next()?.parse().ok()?;
1562 let ss: u32 = parts.next()?.parse().ok()?;
1563 if parts.next().is_some() {
1564 return None;
1565 }
1566 if hh > 23 || mm > 59 || ss > 59 {
1567 return None;
1568 }
1569 let frac_us: i64 = match frac {
1570 None => 0,
1571 Some(f) => {
1572 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
1573 return None;
1574 }
1575 let mut padded = alloc::string::String::with_capacity(6);
1577 padded.push_str(f);
1578 while padded.len() < 6 {
1579 padded.push('0');
1580 }
1581 padded.parse().ok()?
1582 }
1583 };
1584 Some(
1585 i64::from(hh) * 3_600_000_000
1586 + i64::from(mm) * 60_000_000
1587 + i64::from(ss) * 1_000_000
1588 + frac_us,
1589 )
1590}
1591
1592pub(crate) fn type_name_to_data_type(name: &str) -> Option<DataType> {
1599 let n = name.trim().to_ascii_lowercase();
1600 if let Some((head, paren)) = n.split_once('(')
1603 && let Some(args) = paren.strip_suffix(')')
1604 {
1605 let nums: alloc::vec::Vec<u8> = args
1606 .split(',')
1607 .map(|s| s.trim().parse::<u8>().unwrap_or(0))
1608 .collect();
1609 match head {
1610 "numeric" | "decimal" => {
1611 let precision = nums.first().copied().unwrap_or(0);
1612 let scale = nums.get(1).copied().unwrap_or(0);
1613 return Some(DataType::Numeric { precision, scale });
1614 }
1615 "varchar" => {
1621 return Some(DataType::Varchar(nums.first().copied().unwrap_or(0).into()));
1622 }
1623 "char" | "character" => {
1624 return Some(DataType::Char(nums.first().copied().unwrap_or(0).into()));
1625 }
1626 "bit" => return Some(DataType::Bit),
1629 "varbit" => return Some(DataType::BitVarying),
1630 _ => {}
1631 }
1632 }
1633 Some(match n.as_str() {
1634 "smallint" | "int2" => DataType::SmallInt,
1635 "numeric" | "decimal" => DataType::Numeric {
1636 precision: 0,
1637 scale: 0,
1638 },
1639 "inet" => DataType::Inet,
1642 "cidr" => DataType::Cidr,
1643 "macaddr" => DataType::Macaddr,
1644 "macaddr8" => DataType::Macaddr8,
1645 "bit" => DataType::Bit,
1646 "varbit" | "bit varying" => DataType::BitVarying,
1647 "xml" => DataType::Xml,
1648 "money" => DataType::Money,
1649 "char1" => DataType::Char1,
1650 "point" => DataType::Point,
1652 "lseg" => DataType::Lseg,
1653 "path" => DataType::Path,
1654 "box" => DataType::PgBox,
1655 "polygon" => DataType::Polygon,
1656 "line" => DataType::Line,
1657 "circle" => DataType::Circle,
1658 "int4multirange" => DataType::Multirange(spg_storage::RangeKind::Int4),
1660 "int8multirange" => DataType::Multirange(spg_storage::RangeKind::Int8),
1661 "nummultirange" => DataType::Multirange(spg_storage::RangeKind::Num),
1662 "tsmultirange" => DataType::Multirange(spg_storage::RangeKind::Ts),
1663 "tstzmultirange" => DataType::Multirange(spg_storage::RangeKind::TsTz),
1664 "datemultirange" => DataType::Multirange(spg_storage::RangeKind::Date),
1665 "int4range" => DataType::Range(spg_storage::RangeKind::Int4),
1667 "int8range" => DataType::Range(spg_storage::RangeKind::Int8),
1668 "numrange" => DataType::Range(spg_storage::RangeKind::Num),
1669 "tsrange" => DataType::Range(spg_storage::RangeKind::Ts),
1670 "tstzrange" => DataType::Range(spg_storage::RangeKind::TsTz),
1671 "daterange" => DataType::Range(spg_storage::RangeKind::Date),
1672 "bool_array" | "boolean_array" => DataType::BoolArray,
1676 "smallint_array" | "int2_array" => DataType::SmallIntArray,
1677 "int_array" | "integer_array" | "int4_array" => DataType::IntArray,
1678 "bigint_array" | "int8_array" => DataType::BigIntArray,
1679 "float_array" | "double_array" | "real_array" => DataType::FloatArray,
1680 "numeric_array" | "decimal_array" => DataType::NumericArray,
1681 "text_array" => DataType::TextArray,
1682 "date_array" => DataType::DateArray,
1683 "timestamp_array" => DataType::TimestampArray,
1684 "timestamptz_array" => DataType::TimestamptzArray,
1685 "uuid_array" => DataType::UuidArray,
1686 "json_array" => DataType::JsonArray,
1687 "jsonb_array" => DataType::JsonbArray,
1688 "bytea_array" => DataType::BytesArray,
1689 "interval_array" => DataType::IntervalArray,
1690 "money_array" => DataType::MoneyArray,
1691 _ => return None,
1692 })
1693}
1694
1695pub(crate) const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
1696 match t {
1697 ColumnTypeName::SmallInt => DataType::SmallInt,
1698 ColumnTypeName::Int => DataType::Int,
1699 ColumnTypeName::BigInt => DataType::BigInt,
1700 ColumnTypeName::Float => DataType::Float,
1701 ColumnTypeName::Text => DataType::Text,
1702 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
1703 ColumnTypeName::Char(n) => DataType::Char(n),
1704 ColumnTypeName::Bool => DataType::Bool,
1705 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
1706 dim,
1707 encoding: match encoding {
1708 SqlVecEncoding::F32 => VecEncoding::F32,
1709 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
1710 SqlVecEncoding::F16 => VecEncoding::F16,
1711 },
1712 },
1713 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
1714 ColumnTypeName::Date => DataType::Date,
1715 ColumnTypeName::Timestamp => DataType::Timestamp,
1716 ColumnTypeName::Timestamptz => DataType::Timestamptz,
1717 ColumnTypeName::Json => DataType::Json,
1718 ColumnTypeName::Jsonb => DataType::Jsonb,
1719 ColumnTypeName::Bytes => DataType::Bytes,
1720 ColumnTypeName::TextArray => DataType::TextArray,
1721 ColumnTypeName::IntArray => DataType::IntArray,
1722 ColumnTypeName::BigIntArray => DataType::BigIntArray,
1723 ColumnTypeName::TsVector => DataType::TsVector,
1724 ColumnTypeName::TsQuery => DataType::TsQuery,
1725 ColumnTypeName::Uuid => DataType::Uuid,
1726 ColumnTypeName::Time => DataType::Time,
1727 ColumnTypeName::Year => DataType::Year,
1728 ColumnTypeName::TimeTz => DataType::TimeTz,
1729 ColumnTypeName::Money => DataType::Money,
1730 ColumnTypeName::Range(k) => DataType::Range(match k {
1731 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
1732 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
1733 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
1734 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
1735 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
1736 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
1737 }),
1738 ColumnTypeName::Hstore => DataType::Hstore,
1739 ColumnTypeName::IntArray2D => DataType::IntArray2D,
1740 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
1741 ColumnTypeName::TextArray2D => DataType::TextArray2D,
1742 ColumnTypeName::Interval => DataType::Interval,
1743 ColumnTypeName::IntervalArray => DataType::IntervalArray,
1744 ColumnTypeName::BoolArray => DataType::BoolArray,
1745 ColumnTypeName::SmallIntArray => DataType::SmallIntArray,
1746 ColumnTypeName::FloatArray => DataType::FloatArray,
1747 ColumnTypeName::NumericArray => DataType::NumericArray,
1748 ColumnTypeName::DateArray => DataType::DateArray,
1749 ColumnTypeName::TimestampArray => DataType::TimestampArray,
1750 ColumnTypeName::TimestamptzArray => DataType::TimestamptzArray,
1751 ColumnTypeName::UuidArray => DataType::UuidArray,
1752 ColumnTypeName::JsonArray => DataType::JsonArray,
1753 ColumnTypeName::JsonbArray => DataType::JsonbArray,
1754 ColumnTypeName::BytesArray => DataType::BytesArray,
1755 ColumnTypeName::VarcharArray => DataType::VarcharArray,
1756 ColumnTypeName::CharArray => DataType::CharArray,
1757 ColumnTypeName::Multirange(k) => DataType::Multirange(match k {
1758 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
1759 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
1760 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
1761 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
1762 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
1763 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
1764 }),
1765 ColumnTypeName::Point => DataType::Point,
1766 ColumnTypeName::Lseg => DataType::Lseg,
1767 ColumnTypeName::Path => DataType::Path,
1768 ColumnTypeName::PgBox => DataType::PgBox,
1769 ColumnTypeName::Polygon => DataType::Polygon,
1770 ColumnTypeName::Line => DataType::Line,
1771 ColumnTypeName::Circle => DataType::Circle,
1772 ColumnTypeName::Inet => DataType::Inet,
1773 ColumnTypeName::Cidr => DataType::Cidr,
1774 ColumnTypeName::Macaddr => DataType::Macaddr,
1775 ColumnTypeName::Macaddr8 => DataType::Macaddr8,
1776 ColumnTypeName::Bit => DataType::Bit,
1777 ColumnTypeName::BitVarying => DataType::BitVarying,
1778 ColumnTypeName::Xml => DataType::Xml,
1779 ColumnTypeName::Char1 => DataType::Char1,
1780 ColumnTypeName::MoneyArray => DataType::MoneyArray,
1781 }
1782}
1783
1784pub(crate) fn literal_expr_to_value(expr: Expr) -> Result<Value<'static>, EngineError> {
1788 match expr {
1789 Expr::Literal(l) => Ok(literal_to_value(l)),
1790 Expr::Cast { expr, target } => {
1791 let inner_value = literal_expr_to_value(*expr)?;
1792 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
1793 }
1794 Expr::Unary {
1795 op: UnOp::Neg,
1796 expr,
1797 } => match *expr {
1798 Expr::Literal(Literal::Integer(n)) => {
1799 let neg = n.checked_neg().ok_or_else(|| {
1802 EngineError::Unsupported("integer literal overflow on negation".into())
1803 })?;
1804 Ok(int_value_for(neg))
1805 }
1806 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
1807 Expr::Cast {
1813 expr: inner,
1814 target,
1815 } => {
1816 let negated_inner = match *inner {
1817 Expr::Literal(Literal::Integer(n)) => {
1818 let neg = n.checked_neg().ok_or_else(|| {
1819 EngineError::Unsupported("integer literal overflow on negation".into())
1820 })?;
1821 Expr::Literal(Literal::Integer(neg))
1822 }
1823 Expr::Literal(Literal::Float(x)) => Expr::Literal(Literal::Float(-x)),
1824 other => Expr::Unary {
1825 op: spg_sql::ast::UnOp::Neg,
1826 expr: alloc::boxed::Box::new(other),
1827 },
1828 };
1829 literal_expr_to_value(Expr::Cast {
1830 expr: alloc::boxed::Box::new(negated_inner),
1831 target,
1832 })
1833 }
1834 other => Err(EngineError::Unsupported(alloc::format!(
1835 "unary minus over non-literal expression: {other:?}"
1836 ))),
1837 },
1838 Expr::Array(items) => {
1846 let mut materialised: alloc::vec::Vec<Value<'static>> =
1847 alloc::vec::Vec::with_capacity(items.len());
1848 for elem in items {
1849 materialised.push(literal_expr_to_value(elem)?);
1850 }
1851 Ok(array_literal_widen(materialised))
1852 }
1853 other => {
1866 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
1867 let ctx = EvalContext::new(&empty_schema, None);
1868 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
1869 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
1870 }
1871 }
1872}
1873
1874pub(crate) fn literal_to_value(l: Literal) -> Value<'static> {
1875 match l {
1876 Literal::Integer(n) => int_value_for(n),
1877 Literal::Float(x) => Value::Float(x),
1878 Literal::String(s) => Value::text(s),
1879 Literal::Bool(b) => Value::Bool(b),
1880 Literal::Null => Value::Null,
1881 Literal::Vector(v) => Value::vector(v),
1882 Literal::TextArray(items) => Value::TextArray(items),
1883 Literal::IntArray(items) => Value::IntArray(items),
1884 Literal::BigIntArray(items) => Value::BigIntArray(items),
1885 Literal::Interval {
1886 months,
1887 days,
1888 micros,
1889 ..
1890 } => Value::Interval {
1891 months,
1892 days,
1893 micros,
1894 },
1895 }
1896}
1897
1898pub(crate) fn int_value_for(n: i64) -> Value<'static> {
1902 if let Ok(small) = i32::try_from(n) {
1903 Value::Int(small)
1904 } else {
1905 Value::BigInt(n)
1906 }
1907}
1908
1909#[allow(clippy::too_many_lines)]
1915pub(crate) fn check_unsigned_range(
1920 v: &Value,
1921 schema: &ColumnSchema,
1922 position: usize,
1923) -> Result<(), EngineError> {
1924 if !schema.is_unsigned {
1925 return Ok(());
1926 }
1927 let n = match v {
1928 Value::SmallInt(x) => i64::from(*x),
1929 Value::Int(x) => i64::from(*x),
1930 Value::BigInt(x) => *x,
1931 _ => return Ok(()), };
1933 if n < 0 {
1934 return Err(EngineError::Unsupported(alloc::format!(
1935 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
1936 schema.name
1937 )));
1938 }
1939 Ok(())
1940}
1941
1942pub(crate) fn coerce_value(
1943 v: Value<'static>,
1944 expected: DataType,
1945 col_name: &str,
1946 position: usize,
1947) -> Result<Value<'static>, EngineError> {
1948 if v.is_null() {
1949 return Ok(Value::Null);
1950 }
1951 let actual = v.data_type().expect("non-null");
1952 if actual == expected {
1953 return Ok(v);
1954 }
1955 let coerced: Option<Value<'static>> = match (v, expected) {
1956 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
1957 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
1958 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
1959 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
1960 i128::from(n),
1961 precision,
1962 scale,
1963 col_name,
1964 )?),
1965 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
1966 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
1967 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
1968 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
1969 i128::from(n),
1970 precision,
1971 scale,
1972 col_name,
1973 )?),
1974 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
1975 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
1976 #[allow(clippy::cast_precision_loss)]
1977 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
1978 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
1979 i128::from(n),
1980 precision,
1981 scale,
1982 col_name,
1983 )?),
1984 (Value::Float(x), DataType::Numeric { precision, scale }) => {
1985 Some(numeric_from_float(x, precision, scale, col_name)?)
1986 }
1987 (Value::Text(s), DataType::Numeric { precision, scale }) => {
1998 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
1999 return Err(EngineError::Eval(EvalError::TypeMismatch {
2000 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
2001 }));
2002 };
2003 Some(numeric_rescale(
2004 mantissa, src_scale, precision, scale, col_name,
2005 )?)
2006 }
2007 (Value::Text(s), DataType::Date) => {
2009 let d = eval::parse_date_literal(&s).ok_or_else(|| {
2010 EngineError::Eval(EvalError::TypeMismatch {
2011 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
2012 })
2013 })?;
2014 Some(Value::Date(d))
2015 }
2016 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
2023 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
2024 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
2025 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
2026 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
2027 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
2028 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
2029 _ => None,
2030 },
2031 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
2040 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
2041 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
2042 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::json(s)),
2046 (Value::Json(s), DataType::Text) => Some(Value::text(s)),
2047 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::json(s)),
2055 (Value::Text(s), DataType::Bytes) => {
2062 let bytes = decode_bytea_literal(&s).map_err(|e| {
2063 EngineError::Eval(EvalError::TypeMismatch {
2064 detail: alloc::format!(
2065 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
2066 ),
2067 })
2068 })?;
2069 Some(Value::bytes(bytes))
2070 }
2071 (Value::Bytes(b), DataType::Text) => Some(Value::text(encode_bytea_hex(&b))),
2075 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
2083 Some(b) => Some(Value::Uuid(b)),
2084 None => {
2085 return Err(EngineError::Eval(EvalError::TypeMismatch {
2086 detail: alloc::format!(
2087 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
2088 ),
2089 }));
2090 }
2091 },
2092 (Value::Uuid(b), DataType::Text) => Some(Value::text(spg_storage::format_uuid(&b))),
2097 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
2103 Some(us) => Some(Value::Time(us)),
2104 None => {
2105 return Err(EngineError::Eval(EvalError::TypeMismatch {
2106 detail: alloc::format!(
2107 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
2108 ),
2109 }));
2110 }
2111 },
2112 (Value::Time(us), DataType::Text) => Some(Value::text(eval::format_time(us))),
2114 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
2119 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
2120 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
2121 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
2125 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
2126 Err(_) => {
2127 return Err(EngineError::Eval(EvalError::TypeMismatch {
2128 detail: alloc::format!(
2129 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
2130 ),
2131 }));
2132 }
2133 },
2134 (Value::Year(y), DataType::Text) => Some(Value::text(alloc::format!("{y:04}"))),
2136 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
2140 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
2141 None => {
2142 return Err(EngineError::Eval(EvalError::TypeMismatch {
2143 detail: alloc::format!(
2144 "invalid input syntax for type time with time zone: \
2145 {s:?} (column `{col_name}`)"
2146 ),
2147 }));
2148 }
2149 },
2150 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
2152 Some(Value::text(eval::format_timetz(us, offset_secs)))
2153 }
2154 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
2158 Some(c) => Some(Value::Money(c)),
2159 None => {
2160 return Err(EngineError::Eval(EvalError::TypeMismatch {
2161 detail: alloc::format!(
2162 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
2163 ),
2164 }));
2165 }
2166 },
2167 (Value::SmallInt(n), DataType::Money) => {
2171 Some(Value::Money(i64::from(n).saturating_mul(100)))
2172 }
2173 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
2174 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
2175 (Value::Float(x), DataType::Money) => {
2176 let scaled = x * 100.0;
2179 let cents = if scaled >= 0.0 {
2180 (scaled + 0.5) as i64
2181 } else {
2182 (scaled - 0.5) as i64
2183 };
2184 Some(Value::Money(cents))
2185 }
2186 (Value::Numeric { scaled, scale }, DataType::Money) => {
2187 let cents = if scale == 2 {
2190 scaled
2191 } else if scale < 2 {
2192 let mult = 10_i128.pow(u32::from(2 - scale));
2193 scaled.saturating_mul(mult)
2194 } else {
2195 let div = 10_i128.pow(u32::from(scale - 2));
2196 let half = div / 2;
2197 let bias = if scaled >= 0 { half } else { -half };
2198 (scaled + bias) / div
2199 };
2200 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
2201 }
2202 (Value::Money(c), DataType::Text) => Some(Value::text(eval::format_money(c))),
2204 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
2208 Some(v) => Some(v),
2209 None => {
2210 return Err(EngineError::Eval(EvalError::TypeMismatch {
2211 detail: alloc::format!(
2212 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
2213 ),
2214 }));
2215 }
2216 },
2217 (v @ Value::Range { .. }, DataType::Text) => Some(Value::text(format_range_str(&v))),
2219 (Value::Text(s), DataType::Inet) => match parse_inet_text(&s) {
2221 Some((family, bits, addr)) => Some(Value::Inet { family, bits, addr }),
2222 None => {
2223 return Err(EngineError::Eval(EvalError::TypeMismatch {
2224 detail: alloc::format!(
2225 "invalid input syntax for INET: {s:?} (column `{col_name}`)"
2226 ),
2227 }));
2228 }
2229 },
2230 (Value::Text(s), DataType::Cidr) => match parse_inet_text(&s) {
2231 Some((family, bits, addr)) => Some(Value::Cidr { family, bits, addr }),
2232 None => {
2233 return Err(EngineError::Eval(EvalError::TypeMismatch {
2234 detail: alloc::format!(
2235 "invalid input syntax for CIDR: {s:?} (column `{col_name}`)"
2236 ),
2237 }));
2238 }
2239 },
2240 (Value::Text(s), DataType::Macaddr) => match parse_macaddr_text(&s) {
2241 Some(m) => Some(Value::Macaddr(m)),
2242 None => {
2243 return Err(EngineError::Eval(EvalError::TypeMismatch {
2244 detail: alloc::format!(
2245 "invalid input syntax for MACADDR: {s:?} (column `{col_name}`)"
2246 ),
2247 }));
2248 }
2249 },
2250 (Value::Text(s), DataType::Macaddr8) => match parse_macaddr8_text(&s) {
2251 Some(m) => Some(Value::Macaddr8(m)),
2252 None => {
2253 return Err(EngineError::Eval(EvalError::TypeMismatch {
2254 detail: alloc::format!(
2255 "invalid input syntax for MACADDR8: {s:?} (column `{col_name}`)"
2256 ),
2257 }));
2258 }
2259 },
2260 (Value::BitString { nbits, bytes }, DataType::Bit | DataType::BitVarying) => {
2267 Some(Value::BitString { nbits, bytes })
2268 }
2269 (Value::Text(s), DataType::Bit | DataType::BitVarying) => match parse_bit_string_text(&s) {
2270 Some((nbits, bytes)) => Some(Value::bit_string(nbits, bytes)),
2271 None => {
2272 return Err(EngineError::Eval(EvalError::TypeMismatch {
2273 detail: alloc::format!(
2274 "invalid input syntax for BIT: {s:?} (column `{col_name}`)"
2275 ),
2276 }));
2277 }
2278 },
2279 (Value::Text(s), DataType::Xml) => Some(Value::xml(s)),
2280 (Value::Text(s), DataType::Char1) => {
2281 let b = s.bytes().next().unwrap_or(0);
2282 Some(Value::Char1(b))
2283 }
2284 (Value::Inet { family, bits, addr }, DataType::Text) => {
2286 Some(Value::text(format_inet(family, bits, &addr)))
2287 }
2288 (Value::Cidr { family, bits, addr }, DataType::Text) => {
2289 Some(Value::text(format_inet(family, bits, &addr)))
2290 }
2291 (Value::Macaddr(m), DataType::Text) => Some(Value::text(format_macaddr(&m))),
2292 (Value::Macaddr8(m), DataType::Text) => Some(Value::text(format_macaddr8(&m))),
2293 (Value::BitString { nbits, bytes }, DataType::Text) => {
2294 Some(Value::text(format_bit_string(nbits, &bytes)))
2295 }
2296 (Value::Xml(s), DataType::Text) => Some(Value::text(s)),
2297 (Value::Char1(b), DataType::Text) => Some(Value::text((b as char).to_string())),
2298 (Value::Text(s), DataType::Point) => match parse_point(&s) {
2302 Some(p) => Some(Value::Point(p)),
2303 None => {
2304 return Err(EngineError::Eval(EvalError::TypeMismatch {
2305 detail: alloc::format!(
2306 "invalid input syntax for POINT: {s:?} (column `{col_name}`)"
2307 ),
2308 }));
2309 }
2310 },
2311 (Value::Text(s), DataType::Lseg) => match parse_lseg_text(&s) {
2312 Some((p1, p2)) => Some(Value::Lseg(p1, p2)),
2313 None => {
2314 return Err(EngineError::Eval(EvalError::TypeMismatch {
2315 detail: alloc::format!(
2316 "invalid input syntax for LSEG: {s:?} (column `{col_name}`)"
2317 ),
2318 }));
2319 }
2320 },
2321 (Value::Text(s), DataType::PgBox) => match parse_box_text(&s) {
2322 Some((ur, ll)) => Some(Value::PgBox(ur, ll)),
2323 None => {
2324 return Err(EngineError::Eval(EvalError::TypeMismatch {
2325 detail: alloc::format!(
2326 "invalid input syntax for BOX: {s:?} (column `{col_name}`)"
2327 ),
2328 }));
2329 }
2330 },
2331 (Value::Text(s), DataType::Line) => match parse_line_text(&s) {
2332 Some((a, b, c)) => Some(Value::Line { a, b, c }),
2333 None => {
2334 return Err(EngineError::Eval(EvalError::TypeMismatch {
2335 detail: alloc::format!(
2336 "invalid input syntax for LINE: {s:?} (column `{col_name}`)"
2337 ),
2338 }));
2339 }
2340 },
2341 (Value::Text(s), DataType::Circle) => match parse_circle_text(&s) {
2342 Some((center, radius)) => Some(Value::Circle { center, radius }),
2343 None => {
2344 return Err(EngineError::Eval(EvalError::TypeMismatch {
2345 detail: alloc::format!(
2346 "invalid input syntax for CIRCLE: {s:?} (column `{col_name}`)"
2347 ),
2348 }));
2349 }
2350 },
2351 (Value::Text(s), DataType::Path) => match parse_path_text(&s) {
2352 Some((points, closed)) => Some(Value::Path { points, closed }),
2353 None => {
2354 return Err(EngineError::Eval(EvalError::TypeMismatch {
2355 detail: alloc::format!(
2356 "invalid input syntax for PATH: {s:?} (column `{col_name}`)"
2357 ),
2358 }));
2359 }
2360 },
2361 (Value::Text(s), DataType::Polygon) => match parse_polygon_text(&s) {
2362 Some(points) => Some(Value::Polygon(points)),
2363 None => {
2364 return Err(EngineError::Eval(EvalError::TypeMismatch {
2365 detail: alloc::format!(
2366 "invalid input syntax for POLYGON: {s:?} (column `{col_name}`)"
2367 ),
2368 }));
2369 }
2370 },
2371 (Value::Point(p), DataType::Text) => Some(Value::text(format_point(p))),
2373 (Value::Lseg(p1, p2), DataType::Text) => Some(Value::text(format_lseg(p1, p2))),
2374 (Value::PgBox(ur, ll), DataType::Text) => Some(Value::text(format_pg_box(ur, ll))),
2375 (Value::Line { a, b, c }, DataType::Text) => Some(Value::text(format_line(a, b, c))),
2376 (Value::Circle { center, radius }, DataType::Text) => {
2377 Some(Value::text(format_circle(center, radius)))
2378 }
2379 (Value::Path { points, closed }, DataType::Text) => {
2380 Some(Value::text(format_path(&points, closed)))
2381 }
2382 (Value::Polygon(points), DataType::Text) => Some(Value::text(format_polygon(&points))),
2383 (Value::Text(s), DataType::Multirange(kind)) => match parse_multirange_str(&s, kind) {
2387 Some(ranges) => Some(Value::Multirange { kind, ranges }),
2388 None => {
2389 return Err(EngineError::Eval(EvalError::TypeMismatch {
2390 detail: alloc::format!(
2391 "invalid input syntax for multirange type: {s:?} (column `{col_name}`)"
2392 ),
2393 }));
2394 }
2395 },
2396 (Value::Multirange { ranges, .. }, DataType::Text) => {
2398 Some(Value::text(format_multirange(&ranges)))
2399 }
2400 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
2402 Some(pairs) => Some(Value::Hstore(pairs)),
2403 None => {
2404 return Err(EngineError::Eval(EvalError::TypeMismatch {
2405 detail: alloc::format!(
2406 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
2407 ),
2408 }));
2409 }
2410 },
2411 (Value::Hstore(pairs), DataType::Text) => Some(Value::text(format_hstore_str(&pairs))),
2413 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
2416 Ok(m) => Some(Value::IntArray2D(m)),
2417 Err(e) => {
2418 return Err(EngineError::Eval(EvalError::TypeMismatch {
2419 detail: alloc::format!(
2420 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
2421 ),
2422 }));
2423 }
2424 },
2425 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
2426 Ok(m) => Some(Value::BigIntArray2D(m)),
2427 Err(e) => {
2428 return Err(EngineError::Eval(EvalError::TypeMismatch {
2429 detail: alloc::format!(
2430 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
2431 ),
2432 }));
2433 }
2434 },
2435 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
2436 Ok(m) => Some(Value::TextArray2D(m)),
2437 Err(e) => {
2438 return Err(EngineError::Eval(EvalError::TypeMismatch {
2439 detail: alloc::format!(
2440 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
2441 ),
2442 }));
2443 }
2444 },
2445 (Value::IntArray2D(rows), DataType::Text) => Some(Value::text(format_int_2d_text(&rows))),
2447 (Value::BigIntArray2D(rows), DataType::Text) => {
2448 Some(Value::text(format_bigint_2d_text(&rows)))
2449 }
2450 (Value::TextArray2D(rows), DataType::Text) => Some(Value::text(format_text_2d_text(&rows))),
2451 (Value::Text(s), DataType::TextArray) => {
2456 let arr = decode_text_array_literal(&s).map_err(|e| {
2457 EngineError::Eval(EvalError::TypeMismatch {
2458 detail: alloc::format!(
2459 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
2460 ),
2461 })
2462 })?;
2463 Some(Value::TextArray(arr))
2464 }
2465 (Value::Text(s), DataType::IntArray) => {
2471 let arr = decode_text_array_literal(&s).map_err(|e| {
2472 EngineError::Eval(EvalError::TypeMismatch {
2473 detail: alloc::format!(
2474 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
2475 ),
2476 })
2477 })?;
2478 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
2479 for elem in arr {
2480 match elem {
2481 None => out.push(None),
2482 Some(t) => {
2483 let n: i32 = t.parse().map_err(|_| {
2484 EngineError::Eval(EvalError::TypeMismatch {
2485 detail: alloc::format!(
2486 "cannot parse {t:?} as INT element for `{col_name}`"
2487 ),
2488 })
2489 })?;
2490 out.push(Some(n));
2491 }
2492 }
2493 }
2494 Some(Value::IntArray(out))
2495 }
2496 (Value::Text(s), DataType::BigIntArray) => {
2497 let arr = decode_text_array_literal(&s).map_err(|e| {
2498 EngineError::Eval(EvalError::TypeMismatch {
2499 detail: alloc::format!(
2500 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
2501 ),
2502 })
2503 })?;
2504 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
2505 for elem in arr {
2506 match elem {
2507 None => out.push(None),
2508 Some(t) => {
2509 let n: i64 = t.parse().map_err(|_| {
2510 EngineError::Eval(EvalError::TypeMismatch {
2511 detail: alloc::format!(
2512 "cannot parse {t:?} as BIGINT element for `{col_name}`"
2513 ),
2514 })
2515 })?;
2516 out.push(Some(n));
2517 }
2518 }
2519 }
2520 Some(Value::BigIntArray(out))
2521 }
2522 (Value::TextArray(items), DataType::Text) => Some(Value::text(encode_text_array(&items))),
2526 (Value::TextArray(items), DataType::BoolArray) if items.is_empty() => {
2534 Some(Value::BoolArray(alloc::vec::Vec::new()))
2535 }
2536 (Value::TextArray(items), DataType::SmallIntArray) if items.is_empty() => {
2537 Some(Value::SmallIntArray(alloc::vec::Vec::new()))
2538 }
2539 (Value::TextArray(items), DataType::IntArray) if items.is_empty() => {
2540 Some(Value::IntArray(alloc::vec::Vec::new()))
2541 }
2542 (Value::TextArray(items), DataType::BigIntArray) if items.is_empty() => {
2543 Some(Value::BigIntArray(alloc::vec::Vec::new()))
2544 }
2545 (Value::TextArray(items), DataType::FloatArray) if items.is_empty() => {
2546 Some(Value::FloatArray(alloc::vec::Vec::new()))
2547 }
2548 (Value::TextArray(items), DataType::NumericArray) if items.is_empty() => {
2549 Some(Value::NumericArray(alloc::vec::Vec::new()))
2550 }
2551 (Value::TextArray(items), DataType::DateArray) if items.is_empty() => {
2552 Some(Value::DateArray(alloc::vec::Vec::new()))
2553 }
2554 (Value::TextArray(items), DataType::TimestampArray) if items.is_empty() => {
2555 Some(Value::TimestampArray(alloc::vec::Vec::new()))
2556 }
2557 (Value::TextArray(items), DataType::TimestamptzArray) if items.is_empty() => {
2558 Some(Value::TimestamptzArray(alloc::vec::Vec::new()))
2559 }
2560 (Value::TextArray(items), DataType::UuidArray) if items.is_empty() => {
2561 Some(Value::UuidArray(alloc::vec::Vec::new()))
2562 }
2563 (Value::TextArray(items), DataType::JsonArray) if items.is_empty() => {
2564 Some(Value::JsonArray(alloc::vec::Vec::new()))
2565 }
2566 (Value::TextArray(items), DataType::JsonbArray) if items.is_empty() => {
2567 Some(Value::JsonbArray(alloc::vec::Vec::new()))
2568 }
2569 (Value::TextArray(items), DataType::BytesArray) if items.is_empty() => {
2570 Some(Value::BytesArray(alloc::vec::Vec::new()))
2571 }
2572 (Value::TextArray(items), DataType::IntervalArray) if items.is_empty() => {
2573 Some(Value::IntervalArray(alloc::vec::Vec::new()))
2574 }
2575 (Value::TextArray(items), DataType::MoneyArray) if items.is_empty() => {
2576 Some(Value::MoneyArray(alloc::vec::Vec::new()))
2577 }
2578 (Value::IntArray(items), DataType::SmallIntArray) => {
2583 let mut out = alloc::vec::Vec::with_capacity(items.len());
2584 let mut ok = true;
2585 for item in items {
2586 match item {
2587 None => out.push(None),
2588 Some(n) => match i16::try_from(n) {
2589 Ok(x) => out.push(Some(x)),
2590 Err(_) => {
2591 ok = false;
2592 break;
2593 }
2594 },
2595 }
2596 }
2597 if ok {
2598 Some(Value::SmallIntArray(out))
2599 } else {
2600 None
2601 }
2602 }
2603 (Value::Text(s), DataType::Vector { dim, encoding }) => {
2612 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
2613 EngineError::Eval(EvalError::TypeMismatch {
2614 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
2615 })
2616 })?;
2617 if parsed.len() != dim as usize {
2618 return Err(EngineError::Eval(EvalError::TypeMismatch {
2619 detail: alloc::format!(
2620 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
2621 parsed.len()
2622 ),
2623 }));
2624 }
2625 Some(match encoding {
2626 VecEncoding::F32 => Value::vector(parsed),
2627 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
2628 VecEncoding::F16 => {
2629 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
2630 }
2631 })
2632 }
2633 (Value::Text(s), DataType::TsVector) => {
2643 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
2644 EngineError::Eval(EvalError::TypeMismatch {
2645 detail: alloc::format!(
2646 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
2647 ),
2648 })
2649 })?;
2650 Some(Value::TsVector(lexs))
2651 }
2652 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
2653 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
2654 EngineError::Eval(EvalError::TypeMismatch {
2655 detail: alloc::format!(
2656 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
2657 ),
2658 })
2659 })?;
2660 Some(Value::Timestamp(t))
2661 }
2662 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
2665 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
2666 }
2667 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
2671 (Value::Timestamp(t), DataType::Date) => {
2672 let days = t.div_euclid(86_400_000_000);
2673 i32::try_from(days).ok().map(Value::Date)
2674 }
2675 (
2676 Value::Numeric {
2677 scaled,
2678 scale: src_scale,
2679 },
2680 DataType::Numeric { precision, scale },
2681 ) => Some(numeric_rescale(
2682 scaled, src_scale, precision, scale, col_name,
2683 )?),
2684 #[allow(clippy::cast_precision_loss)]
2685 (Value::Numeric { scaled, scale }, DataType::Float) => {
2686 let mut div = 1.0_f64;
2687 for _ in 0..scale {
2688 div *= 10.0;
2689 }
2690 Some(Value::Float((scaled as f64) / div))
2691 }
2692 (Value::Numeric { scaled, scale }, DataType::Int) => {
2693 let truncated = numeric_truncate_to_integer(scaled, scale);
2694 i32::try_from(truncated).ok().map(Value::Int)
2695 }
2696 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
2697 let truncated = numeric_truncate_to_integer(scaled, scale);
2698 i64::try_from(truncated).ok().map(Value::BigInt)
2699 }
2700 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
2701 let truncated = numeric_truncate_to_integer(scaled, scale);
2702 i16::try_from(truncated).ok().map(Value::SmallInt)
2703 }
2704 (Value::Text(s), DataType::Varchar(max)) => {
2706 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
2707 Some(Value::text(s))
2708 } else {
2709 return Err(EngineError::Unsupported(alloc::format!(
2710 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
2711 {} chars",
2712 s.chars().count()
2713 )));
2714 }
2715 }
2716 (
2724 Value::Vector(v),
2725 DataType::Vector {
2726 dim,
2727 encoding: VecEncoding::Sq8,
2728 },
2729 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
2730 (
2735 Value::Vector(v),
2736 DataType::Vector {
2737 dim,
2738 encoding: VecEncoding::F16,
2739 },
2740 ) if v.len() == dim as usize => Some(Value::HalfVector(
2741 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
2742 )),
2743 (Value::Text(s), DataType::Char(size)) => {
2747 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
2748 if len > size {
2749 return Err(EngineError::Unsupported(alloc::format!(
2750 "value for CHAR({size}) column `{col_name}` exceeds length: \
2751 {len} chars"
2752 )));
2753 }
2754 let need = (size - len) as usize;
2755 let mut padded = s.into_owned();
2756 padded.reserve(need);
2757 for _ in 0..need {
2758 padded.push(' ');
2759 }
2760 Some(Value::text(padded))
2761 }
2762 _ => None,
2763 };
2764 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
2765 column: col_name.into(),
2766 expected,
2767 actual,
2768 position,
2769 }))
2770}