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_round_to_integer,
18 parse_numeric_text,
19};
20
21pub(crate) fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, alloc::string::String> {
30 let s = s.trim();
31 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
32 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
34 if cleaned.len() % 2 != 0 {
35 return Err(alloc::string::String::from(
36 "invalid hexadecimal data: odd number of digits",
37 ));
38 }
39 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
40 let cleaned_bytes = cleaned.as_bytes();
41 for i in (0..cleaned_bytes.len()).step_by(2) {
42 let hi = hex_nibble(cleaned_bytes[i]).map_err(|()| bad_hex_digit(cleaned_bytes[i]))?;
43 let lo = hex_nibble(cleaned_bytes[i + 1])
44 .map_err(|()| bad_hex_digit(cleaned_bytes[i + 1]))?;
45 out.push((hi << 4) | lo);
46 }
47 return Ok(out);
48 }
49 let bytes = s.as_bytes();
52 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
53 let mut i = 0;
54 while i < bytes.len() {
55 let b = bytes[i];
56 if b == b'\\' && i + 1 < bytes.len() {
57 let n = bytes[i + 1];
58 if n == b'\\' {
59 out.push(b'\\');
60 i += 2;
61 continue;
62 }
63 if n.is_ascii_digit()
64 && i + 3 < bytes.len()
65 && bytes[i + 2].is_ascii_digit()
66 && bytes[i + 3].is_ascii_digit()
67 {
68 let oct = |x: u8| (x - b'0') as u32;
69 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
70 if v <= 0xFF {
71 out.push(v as u8);
72 i += 4;
73 continue;
74 }
75 }
76 }
77 out.push(b);
78 i += 1;
79 }
80 Ok(out)
81}
82
83pub(crate) fn hex_nibble(b: u8) -> Result<u8, ()> {
84 match b {
85 b'0'..=b'9' => Ok(b - b'0'),
86 b'a'..=b'f' => Ok(b - b'a' + 10),
87 b'A'..=b'F' => Ok(b - b'A' + 10),
88 _ => Err(()),
89 }
90}
91
92fn bad_hex_digit(b: u8) -> alloc::string::String {
94 alloc::format!("invalid hexadecimal digit: \"{}\"", b as char)
95}
96
97#[derive(Clone, Copy)]
102enum UniformArrayKind {
103 Bool,
104 Float,
105 Numeric,
106 Date,
107 Timestamp,
108 Uuid,
109 Bytes,
110 Interval,
111 Money,
112}
113
114impl UniformArrayKind {
115 fn build(self, items: alloc::vec::Vec<Value<'static>>) -> Value<'static> {
116 match self {
117 Self::Bool => Value::BoolArray(
118 items
119 .into_iter()
120 .map(|v| match v {
121 Value::Null => None,
122 Value::Bool(b) => Some(b),
123 _ => unreachable!("uniform Bool"),
124 })
125 .collect(),
126 ),
127 Self::Float => Value::FloatArray(
128 items
129 .into_iter()
130 .map(|v| match v {
131 Value::Null => None,
132 Value::Float(x) => Some(x),
133 _ => unreachable!("uniform Float"),
134 })
135 .collect(),
136 ),
137 Self::Numeric => Value::NumericArray(
138 items
139 .into_iter()
140 .map(|v| match v {
141 Value::Null => None,
142 Value::Numeric { scaled, scale, .. } => Some((scaled, scale)),
143 _ => unreachable!("uniform Numeric"),
144 })
145 .collect(),
146 ),
147 Self::Date => Value::DateArray(
148 items
149 .into_iter()
150 .map(|v| match v {
151 Value::Null => None,
152 Value::Date(d) => Some(d),
153 _ => unreachable!("uniform Date"),
154 })
155 .collect(),
156 ),
157 Self::Timestamp => Value::TimestampArray(
158 items
159 .into_iter()
160 .map(|v| match v {
161 Value::Null => None,
162 Value::Timestamp(t) => Some(t),
163 _ => unreachable!("uniform Timestamp"),
164 })
165 .collect(),
166 ),
167 Self::Uuid => Value::UuidArray(
168 items
169 .into_iter()
170 .map(|v| match v {
171 Value::Null => None,
172 Value::Uuid(b) => Some(b),
173 _ => unreachable!("uniform Uuid"),
174 })
175 .collect(),
176 ),
177 Self::Bytes => Value::BytesArray(
178 items
179 .into_iter()
180 .map(|v| match v {
181 Value::Null => None,
182 Value::Bytes(b) => Some(b.into_owned()),
183 _ => unreachable!("uniform Bytes"),
184 })
185 .collect(),
186 ),
187 Self::Interval => Value::IntervalArray(
188 items
189 .into_iter()
190 .map(|v| match v {
191 Value::Null => None,
192 Value::Interval {
193 months,
194 days,
195 micros,
196 } => Some(spg_storage::IntervalSpan {
197 months,
198 days,
199 micros,
200 }),
201 _ => unreachable!("uniform Interval"),
202 })
203 .collect(),
204 ),
205 Self::Money => Value::MoneyArray(
206 items
207 .into_iter()
208 .map(|v| match v {
209 Value::Null => None,
210 Value::Money(c) => Some(c),
211 _ => unreachable!("uniform Money"),
212 })
213 .collect(),
214 ),
215 }
216 }
217}
218
219fn widen_uniform_typed(items: &[Value<'static>]) -> Option<UniformArrayKind> {
220 let mut kind: Option<UniformArrayKind> = None;
221 let mut saw_non_null = false;
222 for v in items {
223 let this = match v {
224 Value::Null => continue,
225 Value::Bool(_) => UniformArrayKind::Bool,
226 Value::Float(_) => UniformArrayKind::Float,
227 Value::Numeric { .. } => UniformArrayKind::Numeric,
228 Value::Date(_) => UniformArrayKind::Date,
229 Value::Timestamp(_) => UniformArrayKind::Timestamp,
230 Value::Uuid(_) => UniformArrayKind::Uuid,
231 Value::Bytes(_) => UniformArrayKind::Bytes,
232 Value::Interval { .. } => UniformArrayKind::Interval,
233 Value::Money(_) => UniformArrayKind::Money,
234 _ => return None,
238 };
239 match kind {
240 None => kind = Some(this),
241 Some(prev) if discriminant_eq(prev, this) => {}
242 Some(_) => return None,
243 }
244 saw_non_null = true;
245 }
246 if saw_non_null { kind } else { None }
247}
248
249fn discriminant_eq(a: UniformArrayKind, b: UniformArrayKind) -> bool {
250 matches!(
251 (a, b),
252 (UniformArrayKind::Bool, UniformArrayKind::Bool)
253 | (UniformArrayKind::Float, UniformArrayKind::Float)
254 | (UniformArrayKind::Numeric, UniformArrayKind::Numeric)
255 | (UniformArrayKind::Date, UniformArrayKind::Date)
256 | (UniformArrayKind::Timestamp, UniformArrayKind::Timestamp)
257 | (UniformArrayKind::Uuid, UniformArrayKind::Uuid)
258 | (UniformArrayKind::Bytes, UniformArrayKind::Bytes)
259 | (UniformArrayKind::Interval, UniformArrayKind::Interval)
260 | (UniformArrayKind::Money, UniformArrayKind::Money)
261 )
262}
263
264pub(crate) fn array_literal_widen(items: alloc::vec::Vec<Value<'static>>) -> Value<'static> {
278 if let Some(m) = crate::eval::values::build_2d_from_rows(&items) {
289 return m;
290 }
291 if let Some(arr) = widen_uniform_typed(&items) {
292 return arr.build(items);
293 }
294 let mut has_text = false;
295 let mut has_bigint = false;
296 let mut has_int = false;
297 for v in &items {
298 match v {
299 Value::Null => {}
300 Value::Text(_) | Value::Json(_) => has_text = true,
301 Value::BigInt(_) => has_bigint = true,
302 Value::Int(_) | Value::SmallInt(_) => has_int = true,
303 _ => has_text = true,
304 }
305 }
306 if has_text || (!has_bigint && !has_int) {
307 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
308 .into_iter()
309 .map(|v| match v {
310 Value::Null => None,
311 Value::Text(s) | Value::Json(s) => Some(s.into_owned()),
312 other => Some(alloc::format!("{other:?}")),
313 })
314 .collect();
315 return Value::TextArray(out);
316 }
317 if has_bigint {
318 let out: alloc::vec::Vec<Option<i64>> = items
319 .into_iter()
320 .map(|v| match v {
321 Value::Null => None,
322 Value::Int(n) => Some(i64::from(n)),
323 Value::SmallInt(n) => Some(i64::from(n)),
324 Value::BigInt(n) => Some(n),
325 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
326 })
327 .collect();
328 return Value::BigIntArray(out);
329 }
330 let out: alloc::vec::Vec<Option<i32>> = items
331 .into_iter()
332 .map(|v| match v {
333 Value::Null => None,
334 Value::Int(n) => Some(n),
335 Value::SmallInt(n) => Some(i32::from(n)),
336 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
337 })
338 .collect();
339 Value::IntArray(out)
340}
341
342#[must_use]
358pub(crate) fn malformed_array_literal(text: &str) -> alloc::string::String {
359 let t = text.trim();
360 let detail = if !t.starts_with('{') {
361 "Array value must start with \"{\" or dimension information."
362 } else {
363 match first_unquoted_close_brace(&t[1..]) {
367 None => "Unexpected end of input.",
368 Some(close) => {
369 let inner = &t[1..1 + close];
370 if !t[1 + close + 1..].trim().is_empty() {
371 "Junk after closing right brace."
372 } else if inner.trim_end().ends_with(',') {
373 "Unexpected \"}\" character."
374 } else {
375 "Unexpected end of input."
376 }
377 }
378 }
379 };
380 alloc::format!("malformed array literal: \"{text}\" DETAIL: {detail}")
381}
382
383fn first_unquoted_close_brace(body: &str) -> Option<usize> {
385 let bs = body.as_bytes();
386 let mut in_quote = false;
387 let mut k = 0;
388 while k < bs.len() {
389 match bs[k] {
390 b'\\' if in_quote => k += 1,
391 b'"' => in_quote = !in_quote,
392 b'}' if !in_quote => return Some(k),
393 _ => {}
394 }
395 k += 1;
396 }
397 None
398}
399
400pub(crate) fn decode_text_array_literal(
401 s: &str,
402) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
403 let trimmed = s.trim();
404 let body = trimmed
410 .strip_prefix('{')
411 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
412 let close =
413 first_unquoted_close_brace(body).ok_or("TEXT[] literal must be enclosed in '{...}'")?;
414 if !body[close + 1..].trim().is_empty() {
415 return Err("junk after closing right brace");
416 }
417 let inner = &body[..close];
418 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
419 if inner.trim().is_empty() {
420 return Ok(out);
421 }
422 let bytes = inner.as_bytes();
423 let mut i = 0;
424 while i <= bytes.len() {
425 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
427 i += 1;
428 }
429 if i < bytes.len() && bytes[i] == b'"' {
431 i += 1; let mut buf = alloc::string::String::new();
433 while i < bytes.len() && bytes[i] != b'"' {
434 if bytes[i] == b'\\' && i + 1 < bytes.len() {
435 buf.push(bytes[i + 1] as char);
436 i += 2;
437 } else {
438 buf.push(bytes[i] as char);
439 i += 1;
440 }
441 }
442 if i >= bytes.len() {
443 return Err("unterminated quoted element");
444 }
445 i += 1; out.push(Some(buf));
447 } else {
448 let start = i;
450 while i < bytes.len() && bytes[i] != b',' {
451 i += 1;
452 }
453 let raw = inner[start..i].trim();
454 if raw.is_empty() {
459 return Err("empty array element");
460 }
461 if raw.eq_ignore_ascii_case("NULL") {
462 out.push(None);
463 } else {
464 out.push(Some(alloc::string::ToString::to_string(raw)));
465 }
466 }
467 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
469 i += 1;
470 }
471 if i >= bytes.len() {
472 break;
473 }
474 if bytes[i] != b',' {
475 return Err("expected ',' between TEXT[] elements");
476 }
477 i += 1;
478 }
479 Ok(out)
480}
481
482pub(crate) fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
487 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
488 out.push('{');
489 for (i, item) in items.iter().enumerate() {
490 if i > 0 {
491 out.push(',');
492 }
493 match item {
494 None => out.push_str("NULL"),
495 Some(s) => {
496 let needs_quote = s.is_empty()
497 || s.eq_ignore_ascii_case("NULL")
498 || s.chars()
499 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
500 if needs_quote {
501 out.push('"');
502 for c in s.chars() {
503 if c == '"' || c == '\\' {
504 out.push('\\');
505 }
506 out.push(c);
507 }
508 out.push('"');
509 } else {
510 out.push_str(s);
511 }
512 }
513 }
514 }
515 out.push('}');
516 out
517}
518
519pub(crate) fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
523 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
524 out.push_str("\\x");
525 for byte in b {
526 let hi = byte >> 4;
527 let lo = byte & 0x0F;
528 out.push(hex_digit(hi));
529 out.push(hex_digit(lo));
530 }
531 out
532}
533
534pub(crate) const fn hex_digit(n: u8) -> char {
535 match n {
536 0..=9 => (b'0' + n) as char,
537 10..=15 => (b'a' + n - 10) as char,
538 _ => '?',
539 }
540}
541
542pub(crate) fn parse_hstore_str(
555 s: &str,
556) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
557 let bytes = s.as_bytes();
558 let mut i = 0;
559 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
560 let skip_ws = |bytes: &[u8], i: &mut usize| {
561 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
562 *i += 1;
563 }
564 };
565 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
566 if *i >= bytes.len() {
567 return None;
568 }
569 if bytes[*i] == b'"' {
570 *i += 1;
571 let mut out = alloc::string::String::new();
572 while *i < bytes.len() {
573 match bytes[*i] {
574 b'"' => {
575 *i += 1;
576 return Some(out);
577 }
578 b'\\' if *i + 1 < bytes.len() => {
579 out.push(bytes[*i + 1] as char);
580 *i += 2;
581 }
582 c => {
583 out.push(c as char);
584 *i += 1;
585 }
586 }
587 }
588 None
589 } else {
590 let start = *i;
591 while *i < bytes.len()
592 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
593 {
594 *i += 1;
595 }
596 if *i == start {
597 return None;
598 }
599 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
600 }
601 };
602 skip_ws(bytes, &mut i);
603 while i < bytes.len() {
604 let key = parse_token(bytes, &mut i)?;
605 skip_ws(bytes, &mut i);
606 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
607 return None;
608 }
609 i += 2;
610 skip_ws(bytes, &mut i);
611 let val_token = if i + 4 <= bytes.len()
613 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
614 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
615 {
616 i += 4;
617 None
618 } else {
619 Some(parse_token(bytes, &mut i)?)
620 };
621 if out.iter().any(|(k, _)| k == &key) {
626 } else {
628 out.push((key, val_token));
629 }
630 skip_ws(bytes, &mut i);
631 if i >= bytes.len() {
632 break;
633 }
634 if bytes[i] == b',' {
635 i += 1;
636 skip_ws(bytes, &mut i);
637 continue;
638 }
639 return None;
640 }
641 Some(out)
642}
643
644pub(crate) fn format_hstore_str(
648 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
649) -> alloc::string::String {
650 let mut out = alloc::string::String::new();
651 for (i, (k, v)) in pairs.iter().enumerate() {
652 if i > 0 {
653 out.push_str(", ");
654 }
655 out.push('"');
656 out.push_str(k);
657 out.push_str("\"=>");
658 match v {
659 None => out.push_str("NULL"),
660 Some(val) => {
661 out.push('"');
662 out.push_str(val);
663 out.push('"');
664 }
665 }
666 }
667 out
668}
669
670pub fn format_hstore_text(
673 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
674) -> alloc::string::String {
675 format_hstore_str(pairs)
676}
677
678pub(crate) fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
683 let s = s.trim();
684 let outer = s
685 .strip_prefix('{')
686 .and_then(|x| x.strip_suffix('}'))
687 .ok_or("missing outer '{...}' braces")?;
688 let trimmed = outer.trim();
689 if trimmed.is_empty() {
690 return Ok(Vec::new());
691 }
692 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
693 let mut i = 0;
694 let bytes = trimmed.as_bytes();
695 while i < bytes.len() {
696 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
697 i += 1;
698 }
699 if i >= bytes.len() {
700 break;
701 }
702 if bytes[i] != b'{' {
703 return Err("expected '{' opening a row");
704 }
705 i += 1;
706 let row_start = i;
707 let mut depth = 1;
708 while i < bytes.len() && depth > 0 {
709 match bytes[i] {
710 b'{' => depth += 1,
711 b'}' => depth -= 1,
712 _ => {}
713 }
714 if depth > 0 {
715 i += 1;
716 }
717 }
718 if depth != 0 {
719 return Err("unbalanced '{...}' in row");
720 }
721 let row_text = &trimmed[row_start..i];
722 i += 1;
723 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
724 Vec::new()
725 } else {
726 row_text.split(',').map(|t| t.trim().to_string()).collect()
727 };
728 rows.push(cells);
729 }
730 if let Some(first) = rows.first() {
731 let cols = first.len();
732 for r in &rows {
733 if r.len() != cols {
734 return Err("ragged 2D array (rows have different column counts)");
735 }
736 }
737 }
738 Ok(rows)
739}
740
741pub(crate) fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
742 let raw = split_2d_literal(s)?;
743 raw.into_iter()
744 .map(|row| {
745 row.into_iter()
746 .map(|cell| {
747 if cell.eq_ignore_ascii_case("NULL") {
748 Ok(None)
749 } else {
750 cell.parse::<i32>()
751 .map(Some)
752 .map_err(|_| "invalid int element")
753 }
754 })
755 .collect()
756 })
757 .collect()
758}
759
760pub(crate) fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
761 let raw = split_2d_literal(s)?;
762 raw.into_iter()
763 .map(|row| {
764 row.into_iter()
765 .map(|cell| {
766 if cell.eq_ignore_ascii_case("NULL") {
767 Ok(None)
768 } else {
769 cell.parse::<i64>()
770 .map(Some)
771 .map_err(|_| "invalid bigint element")
772 }
773 })
774 .collect()
775 })
776 .collect()
777}
778
779pub(crate) fn parse_text_2d_literal(
780 s: &str,
781) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
782 let raw = split_2d_literal(s)?;
783 Ok(raw
784 .into_iter()
785 .map(|row| {
786 row.into_iter()
787 .map(|cell| {
788 if cell.eq_ignore_ascii_case("NULL") {
789 None
790 } else {
791 Some(cell.trim_matches('"').to_string())
792 }
793 })
794 .collect()
795 })
796 .collect())
797}
798
799pub(crate) fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
800 let mut out = alloc::string::String::from("{");
801 for (i, row) in rows.iter().enumerate() {
802 if i > 0 {
803 out.push(',');
804 }
805 out.push('{');
806 for (j, cell) in row.iter().enumerate() {
807 if j > 0 {
808 out.push(',');
809 }
810 match cell {
811 None => out.push_str("NULL"),
812 Some(n) => out.push_str(&alloc::format!("{n}")),
813 }
814 }
815 out.push('}');
816 }
817 out.push('}');
818 out
819}
820
821pub(crate) fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
822 let mut out = alloc::string::String::from("{");
823 for (i, row) in rows.iter().enumerate() {
824 if i > 0 {
825 out.push(',');
826 }
827 out.push('{');
828 for (j, cell) in row.iter().enumerate() {
829 if j > 0 {
830 out.push(',');
831 }
832 match cell {
833 None => out.push_str("NULL"),
834 Some(n) => out.push_str(&alloc::format!("{n}")),
835 }
836 }
837 out.push('}');
838 }
839 out.push('}');
840 out
841}
842
843pub(crate) fn format_text_2d_text(
844 rows: &[Vec<Option<alloc::string::String>>],
845) -> alloc::string::String {
846 let mut out = alloc::string::String::from("{");
847 for (i, row) in rows.iter().enumerate() {
848 if i > 0 {
849 out.push(',');
850 }
851 out.push('{');
852 for (j, cell) in row.iter().enumerate() {
853 if j > 0 {
854 out.push(',');
855 }
856 match cell {
857 None => out.push_str("NULL"),
858 Some(s) => out.push_str(s),
859 }
860 }
861 out.push('}');
862 }
863 out.push('}');
864 out
865}
866
867pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
870 format_int_2d_text(rows)
871}
872pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
873 format_bigint_2d_text(rows)
874}
875pub fn format_text_2d_text_pub(
876 rows: &[Vec<Option<alloc::string::String>>],
877) -> alloc::string::String {
878 format_text_2d_text(rows)
879}
880
881#[must_use]
885pub fn format_bool_2d_text_pub(rows: &[Vec<Option<bool>>]) -> alloc::string::String {
886 use core::fmt::Write as _;
887 let mut out = alloc::string::String::from("{");
888 for (i, row) in rows.iter().enumerate() {
889 if i > 0 {
890 out.push(',');
891 }
892 out.push('{');
893 for (j, cell) in row.iter().enumerate() {
894 if j > 0 {
895 out.push(',');
896 }
897 let _ = match cell {
898 None => write!(out, "NULL"),
899 Some(true) => write!(out, "t"),
900 Some(false) => write!(out, "f"),
901 };
902 }
903 out.push('}');
904 }
905 out.push('}');
906 out
907}
908
909pub(crate) type CanonRangeBounds = (
924 Option<Value<'static>>,
925 Option<Value<'static>>,
926 bool,
927 bool,
928 bool,
929);
930
931pub(crate) fn canonicalize_range_bounds(
933 kind: spg_storage::RangeKind,
934 lower: Option<Value<'static>>,
935 upper: Option<Value<'static>>,
936 lower_inc: bool,
937 upper_inc: bool,
938) -> Option<CanonRangeBounds> {
939 use spg_storage::RangeKind as K;
940 let mut lower_inc = lower.is_some() && lower_inc;
942 let mut upper_inc = upper.is_some() && upper_inc;
943 let mut lower = lower;
944 let mut upper = upper;
945 if matches!(kind, K::Int4 | K::Int8 | K::Date) {
946 fn succ(v: Value<'static>) -> Option<Value<'static>> {
947 Some(match v {
948 Value::Int(n) => Value::Int(n.checked_add(1)?),
949 Value::BigInt(n) => Value::BigInt(n.checked_add(1)?),
950 Value::Date(d) => Value::Date(d.checked_add(1)?),
951 other => other,
952 })
953 }
954 if let Some(l) = lower {
955 lower = Some(if lower_inc { l } else { succ(l)? });
956 lower_inc = true;
957 }
958 if let Some(u) = upper {
959 upper = Some(if upper_inc { succ(u)? } else { u });
960 upper_inc = false;
961 }
962 }
963 let empty = match (&lower, &upper) {
965 (Some(l), Some(u)) => l == u && !(lower_inc && upper_inc),
966 _ => false,
967 };
968 Some((lower, upper, lower_inc, upper_inc, empty))
969}
970
971pub(crate) enum RangeParseError {
975 Malformed,
976 Misordered,
977 BadElement(alloc::string::String),
983}
984
985fn range_element_type_name(kind: spg_storage::RangeKind) -> &'static str {
988 match kind {
989 spg_storage::RangeKind::Int4 => "integer",
990 spg_storage::RangeKind::Int8 => "bigint",
991 spg_storage::RangeKind::Num => "numeric",
992 spg_storage::RangeKind::Ts => "timestamp",
993 spg_storage::RangeKind::TsTz => "timestamp with time zone",
994 spg_storage::RangeKind::Date => "date",
995 }
996}
997
998pub(crate) fn range_bounds_misordered(
1001 lower: &Option<Value<'static>>,
1002 upper: &Option<Value<'static>>,
1003) -> bool {
1004 match (lower, upper) {
1005 (Some(l), Some(u)) => crate::orderby::value_cmp(l, u) == core::cmp::Ordering::Greater,
1006 _ => false,
1007 }
1008}
1009
1010pub(crate) fn parse_range_str(
1011 s: &str,
1012 kind: spg_storage::RangeKind,
1013) -> Result<Value<'static>, RangeParseError> {
1014 let s = s.trim();
1015 if s.eq_ignore_ascii_case("empty") {
1016 return Ok(Value::Range {
1017 kind,
1018 lower: None,
1019 upper: None,
1020 lower_inc: false,
1021 upper_inc: false,
1022 empty: true,
1023 });
1024 }
1025 let bytes = s.as_bytes();
1026 if bytes.len() < 3 {
1027 return Err(RangeParseError::Malformed);
1028 }
1029 let lower_inc = match bytes[0] {
1030 b'[' => true,
1031 b'(' => false,
1032 _ => return Err(RangeParseError::Malformed),
1033 };
1034 let upper_inc = match bytes[bytes.len() - 1] {
1035 b']' => true,
1036 b')' => false,
1037 _ => return Err(RangeParseError::Malformed),
1038 };
1039 let inner = &s[1..s.len() - 1];
1040 let (lo_text, up_text) = inner.split_once(',').ok_or(RangeParseError::Malformed)?;
1041 let lower = if lo_text.is_empty() {
1042 None
1043 } else {
1044 Some(
1045 parse_range_element(lo_text, kind)
1046 .ok_or_else(|| RangeParseError::BadElement(lo_text.trim().into()))?,
1047 )
1048 };
1049 let upper = if up_text.is_empty() {
1050 None
1051 } else {
1052 Some(
1053 parse_range_element(up_text, kind)
1054 .ok_or_else(|| RangeParseError::BadElement(up_text.trim().into()))?,
1055 )
1056 };
1057 if range_bounds_misordered(&lower, &upper) {
1060 return Err(RangeParseError::Misordered);
1061 }
1062 let (lower, upper, lower_inc, upper_inc, empty) =
1065 canonicalize_range_bounds(kind, lower, upper, lower_inc, upper_inc)
1066 .ok_or(RangeParseError::Malformed)?;
1067 Ok(Value::Range {
1068 kind,
1069 lower: lower.map(alloc::boxed::Box::new),
1070 upper: upper.map(alloc::boxed::Box::new),
1071 lower_inc,
1072 upper_inc,
1073 empty,
1074 })
1075}
1076
1077pub(crate) fn parse_multirange_str(
1084 s: &str,
1085 kind: spg_storage::RangeKind,
1086) -> Option<Vec<spg_storage::RangeSpan>> {
1087 let s = s.trim();
1088 let inner = s.strip_prefix('{').and_then(|x| x.strip_suffix('}'))?;
1089 let inner = inner.trim();
1090 if inner.is_empty() {
1091 return Some(Vec::new());
1092 }
1093 let mut spans: Vec<spg_storage::RangeSpan> = Vec::new();
1097 let bytes = inner.as_bytes();
1098 let mut depth: i32 = 0;
1099 let mut start = 0usize;
1100 for i in 0..=bytes.len() {
1101 let cut = i == bytes.len() || (depth == 0 && bytes[i] == b',');
1102 if !cut {
1103 match bytes.get(i) {
1104 Some(b'[') | Some(b'(') => depth += 1,
1105 Some(b']') | Some(b')') => depth -= 1,
1106 _ => {}
1107 }
1108 continue;
1109 }
1110 let piece = inner[start..i].trim();
1111 if piece.is_empty() {
1112 return None;
1113 }
1114 let r = parse_range_str(piece, kind).ok()?;
1115 let Value::Range {
1116 lower,
1117 upper,
1118 lower_inc,
1119 upper_inc,
1120 empty,
1121 ..
1122 } = r
1123 else {
1124 return None;
1125 };
1126 spans.push(spg_storage::RangeSpan {
1127 lower,
1128 upper,
1129 lower_inc,
1130 upper_inc,
1131 empty,
1132 });
1133 start = i + 1;
1134 }
1135 Some(spans)
1136}
1137
1138fn parse_hhmm_offset_secs(off: &str) -> Option<i32> {
1142 let (h, m) = match off.split_once(':') {
1143 Some((h, m)) => (h, m),
1144 None => (off, "0"),
1145 };
1146 let h: i32 = h.parse().ok()?;
1147 let m: i32 = m.parse().ok()?;
1148 if !(0..=15).contains(&h) || !(0..60).contains(&m) {
1149 return None;
1150 }
1151 Some(h * 3600 + m * 60)
1152}
1153
1154pub(crate) fn regtype_name_to_oid(name: &str) -> Option<i64> {
1158 if let Some(base) = name.trim().strip_suffix("[]") {
1162 return array_oid_for_element(regtype_name_to_oid(base)?);
1163 }
1164 Some(match name.trim() {
1165 "bool" | "boolean" => 16,
1166 "bytea" => 17,
1167 "name" => 19,
1168 "int8" | "bigint" => 20,
1169 "int2" | "smallint" => 21,
1170 "int4" | "int" | "integer" => 23,
1171 "text" => 25,
1172 "oid" => 26,
1173 "json" => 114,
1174 "xml" => 142,
1175 "float4" | "real" => 700,
1176 "float8" | "double precision" => 701,
1177 "cidr" => 650,
1178 "inet" => 869,
1179 "macaddr" => 829,
1180 "macaddr8" => 774,
1181 "money" => 790,
1182 "bpchar" | "char" | "character" => 1042,
1183 "varchar" | "character varying" => 1043,
1184 "date" => 1082,
1185 "time" | "time without time zone" => 1083,
1186 "timestamp" | "timestamp without time zone" => 1114,
1187 "timestamptz" | "timestamp with time zone" => 1184,
1188 "interval" => 1186,
1189 "timetz" | "time with time zone" => 1266,
1190 "numeric" | "decimal" => 1700,
1191 "uuid" => 2950,
1192 "jsonb" => 3802,
1193 "tsvector" => 3614,
1194 "tsquery" => 3615,
1195 "pg_lsn" => 3220,
1196 "regtype" => 2206,
1197 "regclass" => 2205,
1198 "regproc" => 24,
1199 "xid" => 28,
1204 "xid8" => 5069,
1205 "tid" => 27,
1206 "cid" => 29,
1207 _ => return None,
1208 })
1209}
1210
1211pub(crate) fn regtype_canonical_name(name: &str) -> Option<alloc::string::String> {
1215 let t = name.trim();
1216 if let Some(base) = t.strip_suffix("[]") {
1217 let inner = regtype_canonical_name(base)?;
1218 return Some(alloc::format!("{inner}[]"));
1219 }
1220 if let Some(base) = t.strip_prefix('_') {
1222 let inner = regtype_canonical_name(base)?;
1223 return Some(alloc::format!("{inner}[]"));
1224 }
1225 let oid = regtype_name_to_oid(&t.to_lowercase())?;
1226 regtype_oid_to_name(oid).map(alloc::string::String::from)
1227}
1228
1229pub(crate) fn parse_range_element(
1230 text: &str,
1231 kind: spg_storage::RangeKind,
1232) -> Option<Value<'static>> {
1233 let text = text.trim().trim_matches('"');
1234 use spg_storage::RangeKind as K;
1235 match kind {
1236 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
1237 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
1238 K::Num => {
1239 let dot = text.find('.');
1242 let scale: u16 = dot.map_or(0, |p| (text.len() - p - 1) as u16);
1243 let digits: alloc::string::String = text
1244 .chars()
1245 .filter(|c| *c == '-' || c.is_ascii_digit())
1246 .collect();
1247 let scaled: i128 = digits.parse().ok()?;
1248 Some(Value::Numeric {
1249 scaled,
1250 scale,
1251 kind: spg_storage::NumericKind::Finite,
1252 })
1253 }
1254 K::Ts | K::TsTz => {
1255 crate::eval::parse_timestamp_literal(text)
1259 .or_else(|| {
1260 let (date_part, off) = text.split_once(['+'])?;
1261 if !off.chars().all(|c| c.is_ascii_digit() || c == ':') {
1262 return None;
1263 }
1264 let d = crate::eval::parse_date_literal(date_part.trim())?;
1265 let mut t = i64::from(d) * 86_400_000_000;
1266 let secs = parse_hhmm_offset_secs(off)?;
1268 t -= i64::from(secs) * 1_000_000;
1269 Some(t)
1270 })
1271 .map(Value::Timestamp)
1272 }
1273 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
1274 }
1275}
1276
1277pub fn format_range_text(v: &Value) -> alloc::string::String {
1281 format_range_str(v)
1282}
1283
1284pub(crate) fn format_range_str(v: &Value) -> alloc::string::String {
1285 let Value::Range {
1286 kind,
1287 lower,
1288 upper,
1289 lower_inc,
1290 upper_inc,
1291 empty,
1292 } = v
1293 else {
1294 return alloc::string::String::new();
1295 };
1296 if *empty {
1297 return "empty".into();
1298 }
1299 let elem = |v: &Value| -> alloc::string::String {
1304 let base = format_range_element(v);
1305 if matches!(kind, spg_storage::RangeKind::TsTz) && matches!(v, Value::Timestamp(_)) {
1306 alloc::format!("{base}+00")
1307 } else {
1308 base
1309 }
1310 };
1311 let mut out = alloc::string::String::new();
1312 out.push(if *lower_inc { '[' } else { '(' });
1313 if let Some(l) = lower {
1314 out.push_str("e_range_bound(&elem(l)));
1315 }
1316 out.push(',');
1317 if let Some(u) = upper {
1318 out.push_str("e_range_bound(&elem(u)));
1319 }
1320 out.push(if *upper_inc { ']' } else { ')' });
1321 out
1322}
1323
1324fn quote_range_bound(s: &str) -> alloc::string::String {
1331 let needs_quote = s.is_empty()
1332 || s.chars()
1333 .any(|c| matches!(c, '"' | '\\' | '(' | ')' | '[' | ']' | ',') || c.is_whitespace());
1334 if !needs_quote {
1335 return s.into();
1336 }
1337 let mut out = alloc::string::String::with_capacity(s.len() + 2);
1338 out.push('"');
1339 for c in s.chars() {
1340 if c == '"' || c == '\\' {
1341 out.push('\\');
1342 }
1343 out.push(c);
1344 }
1345 out.push('"');
1346 out
1347}
1348
1349pub fn format_point(p: spg_storage::Point2D) -> alloc::string::String {
1351 alloc::format!("({},{})", p.x, p.y)
1352}
1353
1354pub fn format_lseg(p1: spg_storage::Point2D, p2: spg_storage::Point2D) -> alloc::string::String {
1356 alloc::format!("[({},{}),({},{})]", p1.x, p1.y, p2.x, p2.y)
1357}
1358
1359pub fn format_pg_box(ur: spg_storage::Point2D, ll: spg_storage::Point2D) -> alloc::string::String {
1364 alloc::format!("({},{}),({},{})", ur.x, ur.y, ll.x, ll.y)
1365}
1366
1367pub fn format_line(a: f64, b: f64, c: f64) -> alloc::string::String {
1369 alloc::format!("{{{},{},{}}}", a, b, c)
1370}
1371
1372pub fn format_circle(center: spg_storage::Point2D, radius: f64) -> alloc::string::String {
1374 alloc::format!("<({},{}),{}>", center.x, center.y, radius)
1375}
1376
1377pub fn format_path(points: &[spg_storage::Point2D], closed: bool) -> alloc::string::String {
1380 let (open, close) = if closed { ('(', ')') } else { ('[', ']') };
1381 let mut out = alloc::string::String::new();
1382 out.push(open);
1383 for (i, p) in points.iter().enumerate() {
1384 if i > 0 {
1385 out.push(',');
1386 }
1387 out.push_str(&alloc::format!("({},{})", p.x, p.y));
1388 }
1389 out.push(close);
1390 out
1391}
1392
1393pub fn format_polygon(points: &[spg_storage::Point2D]) -> alloc::string::String {
1395 let mut out = alloc::string::String::new();
1396 out.push('(');
1397 for (i, p) in points.iter().enumerate() {
1398 if i > 0 {
1399 out.push(',');
1400 }
1401 out.push_str(&alloc::format!("({},{})", p.x, p.y));
1402 }
1403 out.push(')');
1404 out
1405}
1406
1407fn parse_point(s: &str) -> Option<spg_storage::Point2D> {
1410 let s = s.trim();
1411 let inner = s
1412 .strip_prefix('(')
1413 .and_then(|x| x.strip_suffix(')'))
1414 .unwrap_or(s);
1415 let (xs, ys) = inner.split_once(',')?;
1416 let x: f64 = xs.trim().parse().ok()?;
1417 let y: f64 = ys.trim().parse().ok()?;
1418 Some(spg_storage::Point2D { x, y })
1419}
1420
1421fn parse_point_list(s: &str) -> Option<Vec<spg_storage::Point2D>> {
1426 let bytes = s.as_bytes();
1427 let mut out: Vec<spg_storage::Point2D> = Vec::new();
1428 let mut depth: i32 = 0;
1429 let mut start = 0usize;
1430 for i in 0..=bytes.len() {
1431 let cut = i == bytes.len() || (depth == 0 && bytes[i] == b',');
1432 if !cut {
1433 match bytes.get(i) {
1434 Some(b'(') | Some(b'[') | Some(b'<') => depth += 1,
1435 Some(b')') | Some(b']') | Some(b'>') => depth -= 1,
1436 _ => {}
1437 }
1438 continue;
1439 }
1440 let piece = s[start..i].trim();
1441 if !piece.is_empty() {
1442 out.push(parse_point(piece)?);
1443 }
1444 start = i + 1;
1445 }
1446 Some(out)
1447}
1448
1449pub fn parse_lseg_text(s: &str) -> Option<(spg_storage::Point2D, spg_storage::Point2D)> {
1451 let s = s.trim();
1452 let inner = s
1455 .strip_prefix('[')
1456 .and_then(|x| x.strip_suffix(']'))
1457 .unwrap_or(s);
1458 let two_points = |v: Option<alloc::vec::Vec<spg_storage::Point2D>>| v.filter(|p| p.len() == 2);
1459 let pts = if let Some(p) = two_points(parse_point_list(inner)) {
1460 p
1461 } else {
1462 inner
1463 .strip_prefix('(')
1464 .and_then(|x| x.strip_suffix(')'))
1465 .and_then(|w| two_points(parse_point_list(w)))?
1466 };
1467 Some((pts[0], pts[1]))
1468}
1469
1470pub fn parse_box_text(s: &str) -> Option<(spg_storage::Point2D, spg_storage::Point2D)> {
1474 let s = s.trim();
1478 let two_points = |v: Option<alloc::vec::Vec<spg_storage::Point2D>>| v.filter(|p| p.len() == 2);
1479 let pts = if let Some(p) = two_points(parse_point_list(s)) {
1480 p
1481 } else if let Some(p) = s
1482 .strip_prefix('(')
1483 .and_then(|x| x.strip_suffix(')'))
1484 .and_then(|inner| two_points(parse_point_list(inner)))
1485 {
1486 p
1487 } else {
1488 let nums: Option<alloc::vec::Vec<f64>> =
1489 s.split(',').map(|t| t.trim().parse::<f64>().ok()).collect();
1490 let nums = nums?;
1491 if nums.len() != 4 {
1492 return None;
1493 }
1494 alloc::vec![
1495 spg_storage::Point2D {
1496 x: nums[0],
1497 y: nums[1]
1498 },
1499 spg_storage::Point2D {
1500 x: nums[2],
1501 y: nums[3]
1502 },
1503 ]
1504 };
1505 if pts.len() != 2 {
1506 return None;
1507 }
1508 let (a, b) = (pts[0], pts[1]);
1509 let ur = spg_storage::Point2D {
1511 x: a.x.max(b.x),
1512 y: a.y.max(b.y),
1513 };
1514 let ll = spg_storage::Point2D {
1515 x: a.x.min(b.x),
1516 y: a.y.min(b.y),
1517 };
1518 Some((ur, ll))
1519}
1520
1521pub fn parse_line_text(s: &str) -> Option<(f64, f64, f64)> {
1523 let s = s.trim();
1524 if let Some(inner) = s.strip_prefix('{').and_then(|x| x.strip_suffix('}')) {
1525 let parts: Vec<&str> = inner.split(',').collect();
1526 if parts.len() != 3 {
1527 return None;
1528 }
1529 let a: f64 = parts[0].trim().parse().ok()?;
1530 let b: f64 = parts[1].trim().parse().ok()?;
1531 if a == 0.0 && b == 0.0 {
1533 return None;
1534 }
1535 let c: f64 = parts[2].trim().parse().ok()?;
1536 return Some((a, b, c));
1537 }
1538 let (p1, p2) = parse_lseg_text(s)?;
1543 if p1.x == p2.x && p1.y == p2.y {
1544 return None;
1545 }
1546 Some(line_from_points(p1, p2))
1547}
1548
1549pub fn line_from_points(p1: spg_storage::Point2D, p2: spg_storage::Point2D) -> (f64, f64, f64) {
1551 if p1.x == p2.x {
1552 (-1.0, 0.0, p1.x)
1553 } else if p1.y == p2.y {
1554 (0.0, -1.0, p1.y)
1555 } else {
1556 let m = (p1.y - p2.y) / (p1.x - p2.x);
1557 let c = p1.y - m * p1.x;
1558 (m, -1.0, if c == 0.0 { 0.0 } else { c })
1559 }
1560}
1561
1562pub fn parse_circle_text(s: &str) -> Option<(spg_storage::Point2D, f64)> {
1564 let s = s.trim();
1565 let inner = if let Some(i) = s.strip_prefix('<').and_then(|x| x.strip_suffix('>')) {
1567 i
1568 } else if let Some(i) = s.strip_prefix('(').and_then(|x| x.strip_suffix(')')) {
1569 i
1570 } else {
1571 s
1572 };
1573 let bytes = inner.as_bytes();
1575 let mut depth = 0i32;
1576 let mut split_at: Option<usize> = None;
1577 for (i, &b) in bytes.iter().enumerate() {
1578 match b {
1579 b'(' | b'[' | b'<' => depth += 1,
1580 b')' | b']' | b'>' => depth -= 1,
1581 b',' if depth == 0 => split_at = Some(i),
1582 _ => {}
1583 }
1584 }
1585 let i = split_at?;
1586 let center = parse_point(&inner[..i])?;
1587 let radius: f64 = inner[i + 1..].trim().parse().ok()?;
1588 Some((center, radius))
1589}
1590
1591pub fn parse_path_text(s: &str) -> Option<(Vec<spg_storage::Point2D>, bool)> {
1594 let s = s.trim();
1595 if let Some(i) = s.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
1600 if let Some(pts) = parse_point_list(i) {
1601 return Some((pts, false));
1602 }
1603 }
1604 if let Some(i) = s.strip_prefix('(').and_then(|x| x.strip_suffix(')')) {
1605 if let Some(pts) = parse_point_list(i) {
1606 return Some((pts, true));
1607 }
1608 }
1609 parse_point_list(s).map(|pts| (pts, true))
1610}
1611
1612pub fn parse_polygon_text(s: &str) -> Option<Vec<spg_storage::Point2D>> {
1614 let s = s.trim();
1615 if let Some(inner) = s.strip_prefix('(').and_then(|x| x.strip_suffix(')')) {
1619 if let Some(pts) = parse_point_list(inner) {
1620 return Some(pts);
1621 }
1622 }
1623 parse_point_list(s)
1624}
1625
1626pub fn format_inet_full(family: u8, bits: u8, addr: &[u8; 16]) -> alloc::string::String {
1634 let max = if family == 4 { 32 } else { 128 };
1635 let base = format_inet(family, max, addr);
1636 alloc::format!("{base}/{bits}")
1637}
1638
1639pub fn format_inet(family: u8, bits: u8, addr: &[u8; 16]) -> alloc::string::String {
1640 match family {
1641 4 => {
1642 let s = alloc::format!("{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3]);
1643 if bits == 32 {
1644 s
1645 } else {
1646 alloc::format!("{s}/{bits}")
1647 }
1648 }
1649 6 => {
1650 let mut groups = [0u16; 8];
1654 for (i, g) in groups.iter_mut().enumerate() {
1655 *g = (u16::from(addr[i * 2]) << 8) | u16::from(addr[i * 2 + 1]);
1656 }
1657 if groups[..5].iter().all(|&g| g == 0) && groups[5] == 0xffff {
1661 let s =
1662 alloc::format!("::ffff:{}.{}.{}.{}", addr[12], addr[13], addr[14], addr[15]);
1663 return if bits == 128 {
1664 s
1665 } else {
1666 alloc::format!("{s}/{bits}")
1667 };
1668 }
1669 let (mut best_start, mut best_len) = (usize::MAX, 0usize);
1670 let mut i = 0;
1671 while i < 8 {
1672 if groups[i] == 0 {
1673 let start = i;
1674 while i < 8 && groups[i] == 0 {
1675 i += 1;
1676 }
1677 if i - start > best_len {
1678 best_start = start;
1679 best_len = i - start;
1680 }
1681 } else {
1682 i += 1;
1683 }
1684 }
1685 let mut out = alloc::string::String::new();
1686 if best_len >= 2 {
1687 for (idx, g) in groups.iter().enumerate().take(best_start) {
1688 if idx > 0 {
1689 out.push(':');
1690 }
1691 out.push_str(&alloc::format!("{g:x}"));
1692 }
1693 out.push_str("::");
1694 for (idx, g) in groups.iter().enumerate().skip(best_start + best_len) {
1695 if idx > best_start + best_len {
1696 out.push(':');
1697 }
1698 out.push_str(&alloc::format!("{g:x}"));
1699 }
1700 } else {
1701 for (idx, g) in groups.iter().enumerate() {
1702 if idx > 0 {
1703 out.push(':');
1704 }
1705 out.push_str(&alloc::format!("{g:x}"));
1706 }
1707 }
1708 if bits == 128 {
1709 out
1710 } else {
1711 alloc::format!("{out}/{bits}")
1712 }
1713 }
1714 _ => alloc::format!("?invalid-inet-family-{family}"),
1715 }
1716}
1717
1718pub fn format_macaddr(m: &[u8; 6]) -> alloc::string::String {
1720 alloc::format!(
1721 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
1722 m[0],
1723 m[1],
1724 m[2],
1725 m[3],
1726 m[4],
1727 m[5]
1728 )
1729}
1730
1731pub fn format_macaddr8(m: &[u8; 8]) -> alloc::string::String {
1733 alloc::format!(
1734 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
1735 m[0],
1736 m[1],
1737 m[2],
1738 m[3],
1739 m[4],
1740 m[5],
1741 m[6],
1742 m[7]
1743 )
1744}
1745
1746pub fn format_bit_string(nbits: u32, bytes: &[u8]) -> alloc::string::String {
1751 let mut out = alloc::string::String::with_capacity(nbits as usize);
1752 for i in 0..nbits as usize {
1753 let byte = bytes[i / 8];
1754 let bit = (byte >> (7 - (i % 8))) & 1;
1755 out.push(if bit == 1 { '1' } else { '0' });
1756 }
1757 out
1758}
1759
1760pub fn bit_string_to_i64(nbits: u32, bytes: &[u8]) -> i64 {
1762 let mut val: i64 = 0;
1763 for i in 0..nbits as usize {
1764 let byte = bytes.get(i / 8).copied().unwrap_or(0);
1765 val = (val << 1) | i64::from((byte >> (7 - (i % 8))) & 1);
1766 }
1767 val
1768}
1769
1770pub fn format_money_array(items: &[Option<i64>]) -> alloc::string::String {
1774 let mut out = alloc::string::String::new();
1775 out.push('{');
1776 for (i, item) in items.iter().enumerate() {
1777 if i > 0 {
1778 out.push(',');
1779 }
1780 match item {
1781 None => out.push_str("NULL"),
1782 Some(c) => out.push_str(&crate::eval::format_money(*c)),
1783 }
1784 }
1785 out.push('}');
1786 out
1787}
1788
1789pub fn parse_inet_text(s: &str) -> Option<(u8, u8, [u8; 16])> {
1794 let s = s.trim();
1795 let (addr_s, bits_s) = match s.split_once('/') {
1796 Some((a, b)) => (a, Some(b)),
1797 None => (s, None),
1798 };
1799 if addr_s.contains(':') {
1800 let (head, tail) = match addr_s.find("::") {
1805 Some(idx) => (&addr_s[..idx], Some(&addr_s[idx + 2..])),
1806 None => (addr_s, None),
1807 };
1808 let mut head_groups: alloc::vec::Vec<&str> = if head.is_empty() {
1809 alloc::vec::Vec::new()
1810 } else {
1811 head.split(':').collect()
1812 };
1813 let mut tail_groups: alloc::vec::Vec<&str> = match tail {
1814 Some(t) if !t.is_empty() => t.split(':').collect(),
1815 _ => alloc::vec::Vec::new(),
1816 };
1817 let mut dotted_words: Option<[u16; 2]> = None;
1821 if let Some(g) = tail_groups.last().or_else(|| head_groups.last()) {
1822 if g.contains('.') {
1823 let oct: alloc::vec::Vec<&str> = g.split('.').collect();
1824 if oct.len() != 4 {
1825 return None;
1826 }
1827 let mut b = [0u8; 4];
1828 for (i, o) in oct.iter().enumerate() {
1829 b[i] = o.parse::<u8>().ok()?;
1830 }
1831 dotted_words = Some([
1832 (u16::from(b[0]) << 8) | u16::from(b[1]),
1833 (u16::from(b[2]) << 8) | u16::from(b[3]),
1834 ]);
1835 if !tail_groups.is_empty() {
1836 tail_groups.pop();
1837 } else {
1838 head_groups.pop();
1839 }
1840 }
1841 }
1842 let dq = if dotted_words.is_some() { 2 } else { 0 };
1843 let head_len = head_groups.len();
1844 let tail_len = tail_groups.len();
1845 if tail.is_none() {
1846 if head_len + dq != 8 {
1847 return None;
1848 }
1849 } else if head_len + tail_len + dq > 7 {
1850 return None;
1851 }
1852 let mut words = [0u16; 8];
1853 for (i, g) in head_groups.iter().enumerate() {
1854 words[i] = u16::from_str_radix(g, 16).ok()?;
1855 }
1856 let trailing_start = 8 - dq - tail_len;
1859 for (i, g) in tail_groups.iter().enumerate() {
1860 words[trailing_start + i] = u16::from_str_radix(g, 16).ok()?;
1861 }
1862 if let Some(dw) = dotted_words {
1863 words[6] = dw[0];
1864 words[7] = dw[1];
1865 }
1866 let mut addr = [0u8; 16];
1867 for (i, w) in words.iter().enumerate() {
1868 addr[i * 2] = (w >> 8) as u8;
1869 addr[i * 2 + 1] = (w & 0xff) as u8;
1870 }
1871 let bits = match bits_s {
1872 Some(b) => b.parse::<u8>().ok().filter(|&n| n <= 128)?,
1873 None => 128,
1874 };
1875 Some((6, bits, addr))
1876 } else {
1877 let parts: alloc::vec::Vec<&str> = addr_s.split('.').collect();
1879 if parts.len() != 4 {
1880 return None;
1881 }
1882 let mut addr = [0u8; 16];
1883 for (i, p) in parts.iter().enumerate() {
1884 addr[i] = p.parse::<u8>().ok()?;
1885 }
1886 let bits = match bits_s {
1887 Some(b) => b.parse::<u8>().ok().filter(|&n| n <= 32)?,
1888 None => 32,
1889 };
1890 Some((4, bits, addr))
1891 }
1892}
1893
1894pub fn parse_cidr_text(s: &str) -> Result<Option<(u8, u8, [u8; 16])>, ()> {
1901 let s = s.trim();
1902 let parsed = if !s.contains(':') {
1903 let (addr_s, bits_s) = match s.split_once('/') {
1904 Some((a, b)) => (a, Some(b)),
1905 None => (s, None),
1906 };
1907 let parts: alloc::vec::Vec<&str> = addr_s.split('.').collect();
1908 if parts.is_empty() || parts.len() > 4 || parts.iter().any(|p| p.is_empty()) {
1909 return Ok(None);
1910 }
1911 let mut addr = [0u8; 16];
1912 for (i, p) in parts.iter().enumerate() {
1913 match p.parse::<u8>() {
1914 Ok(v) => addr[i] = v,
1915 Err(_) => return Ok(None),
1916 }
1917 }
1918 let bits = match bits_s {
1919 Some(b) => match b.parse::<u8>() {
1920 Ok(n) if n <= 32 => n,
1921 _ => return Ok(None),
1922 },
1923 None => (parts.len() as u8) * 8,
1924 };
1925 Some((4u8, bits, addr))
1926 } else {
1927 parse_inet_text(s).map(|(f, b, a)| {
1928 (f, if s.contains('/') { b } else { 128 }, a)
1930 })
1931 };
1932 let Some((family, bits, addr)) = parsed else {
1933 return Ok(None);
1934 };
1935 let total = if family == 4 { 32u16 } else { 128 };
1937 let nbytes = if family == 4 { 4 } else { 16 };
1938 for byte in 0..nbytes {
1939 let bit_base = (byte as u16) * 8;
1940 let keep = (u16::from(bits)).saturating_sub(bit_base).min(8) as u8;
1941 let mask: u8 = if keep == 0 { 0 } else { 0xffu8 << (8 - keep) };
1942 if addr[byte] & !mask != 0 {
1943 return Err(());
1944 }
1945 if bit_base >= total {
1946 break;
1947 }
1948 }
1949 Ok(Some((family, bits, addr)))
1950}
1951
1952pub fn parse_macaddr_text(s: &str) -> Option<[u8; 6]> {
1955 let s = s.trim();
1956 let cleaned: alloc::string::String = s.chars().filter(|c| c.is_ascii_hexdigit()).collect();
1957 if cleaned.len() != 12 {
1958 return None;
1959 }
1960 let mut out = [0u8; 6];
1961 for i in 0..6 {
1962 out[i] = u8::from_str_radix(&cleaned[i * 2..i * 2 + 2], 16).ok()?;
1963 }
1964 Some(out)
1965}
1966
1967#[must_use]
1974pub fn date_days_to_micros(d: i32) -> i64 {
1975 match d {
1976 i32::MAX => i64::MAX,
1977 i32::MIN => i64::MIN,
1978 _ => i64::from(d) * 86_400_000_000,
1979 }
1980}
1981
1982pub fn parse_pg_lsn_text(s: &str) -> Option<u64> {
1983 let t = s.trim();
1984 let (hi, lo) = t.split_once('/')?;
1985 if hi.is_empty() || lo.is_empty() || hi.len() > 8 || lo.len() > 8 {
1986 return None;
1987 }
1988 let hi = u32::from_str_radix(hi, 16).ok()?;
1989 let lo = u32::from_str_radix(lo, 16).ok()?;
1990 Some((u64::from(hi) << 32) | u64::from(lo))
1991}
1992
1993#[must_use]
1995pub fn format_pg_lsn(l: u64) -> alloc::string::String {
1996 alloc::format!("{:X}/{:X}", l >> 32, l & 0xFFFF_FFFF)
1997}
1998
1999pub fn parse_macaddr8_text(s: &str) -> Option<[u8; 8]> {
2000 let s = s.trim();
2001 let cleaned: alloc::string::String = s.chars().filter(|c| c.is_ascii_hexdigit()).collect();
2002 if cleaned.len() == 12 {
2005 let mut six = [0u8; 6];
2006 for i in 0..6 {
2007 six[i] = u8::from_str_radix(&cleaned[i * 2..i * 2 + 2], 16).ok()?;
2008 }
2009 return Some([six[0], six[1], six[2], 0xff, 0xfe, six[3], six[4], six[5]]);
2010 }
2011 if cleaned.len() != 16 {
2012 return None;
2013 }
2014 let mut out = [0u8; 8];
2015 for i in 0..8 {
2016 out[i] = u8::from_str_radix(&cleaned[i * 2..i * 2 + 2], 16).ok()?;
2017 }
2018 Some(out)
2019}
2020
2021pub fn parse_bit_string_text(s: &str) -> Option<(u32, alloc::vec::Vec<u8>)> {
2025 let s = s.trim();
2026 let nbits = u32::try_from(s.len()).ok()?;
2027 let nbytes = (s.len()).div_ceil(8);
2028 let mut bytes = alloc::vec![0u8; nbytes];
2029 for (i, c) in s.chars().enumerate() {
2030 let bit = match c {
2031 '0' => 0u8,
2032 '1' => 1u8,
2033 _ => return None,
2034 };
2035 if bit == 1 {
2036 bytes[i / 8] |= 1 << (7 - (i % 8));
2037 }
2038 }
2039 Some((nbits, bytes))
2040}
2041
2042pub fn format_multirange(ranges: &[spg_storage::RangeSpan]) -> alloc::string::String {
2049 let mut out = alloc::string::String::new();
2050 out.push('{');
2051 for (i, r) in ranges.iter().enumerate() {
2052 if i > 0 {
2053 out.push(',');
2054 }
2055 if r.empty {
2056 out.push_str("empty");
2057 continue;
2058 }
2059 out.push(if r.lower_inc { '[' } else { '(' });
2060 if let Some(l) = &r.lower {
2061 out.push_str("e_range_bound(&format_range_element(l)));
2062 }
2063 out.push(',');
2064 if let Some(u) = &r.upper {
2065 out.push_str("e_range_bound(&format_range_element(u)));
2066 }
2067 out.push(if r.upper_inc { ']' } else { ')' });
2068 }
2069 out.push('}');
2070 out
2071}
2072
2073pub(crate) fn format_range_element(v: &Value) -> alloc::string::String {
2074 match v {
2075 Value::Int(n) => alloc::format!("{n}"),
2076 Value::BigInt(n) => alloc::format!("{n}"),
2077 Value::Date(d) => crate::eval::format_date(*d),
2078 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
2079 Value::Numeric {
2080 scaled,
2081 scale,
2082 kind,
2083 } => crate::eval::format_numeric_kind(*kind, *scaled, *scale),
2084 other => alloc::format!("{other:?}"),
2085 }
2086}
2087
2088pub(crate) fn parse_money_str(s: &str) -> Option<i64> {
2099 let mut rest = s.trim();
2104 let mut neg = false;
2105 loop {
2108 let before = rest;
2109 rest = rest.trim_start();
2110 if let Some(r) = rest.strip_prefix('$') {
2111 rest = r;
2112 } else if let Some(r) = rest.strip_prefix('-') {
2113 neg = true;
2114 rest = r;
2115 } else if let Some(r) = rest.strip_prefix('(') {
2116 neg = true;
2117 rest = r;
2118 } else if let Some(r) = rest.strip_prefix('+') {
2119 rest = r;
2120 }
2121 if rest == before {
2122 break;
2123 }
2124 }
2125 let (int_part, tail) = {
2126 let end = rest
2127 .find(|c: char| !(c.is_ascii_digit() || c == ','))
2128 .unwrap_or(rest.len());
2129 (&rest[..end], &rest[end..])
2130 };
2131 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
2133 for b in int_part.bytes() {
2134 match b {
2135 b',' => {}
2136 b'0'..=b'9' => int_digits.push(b as char),
2137 _ => return None,
2138 }
2139 }
2140 if int_digits.is_empty() {
2141 return None;
2142 }
2143 let dollars: i64 = int_digits.parse().ok()?;
2144 let (mut cents, tail) = match tail.strip_prefix('.') {
2146 None => (0i64, tail),
2147 Some(f) => {
2148 let end = f.find(|c: char| !c.is_ascii_digit()).unwrap_or(f.len());
2149 let (digits, rest_tail) = (&f[..end], &f[end..]);
2150 if digits.is_empty() {
2151 return None;
2152 }
2153 let b = digits.as_bytes();
2154 let mut c = i64::from(b[0] - b'0') * 10;
2155 if b.len() >= 2 {
2156 c += i64::from(b[1] - b'0');
2157 }
2158 if b.len() >= 3 && b[2] >= b'5' {
2159 c += 1;
2160 }
2161 (c, rest_tail)
2162 }
2163 };
2164 let mut tail = tail;
2166 while !tail.is_empty() {
2167 let t = tail.trim_start();
2168 if let Some(r) = t.strip_prefix(')') {
2169 tail = r;
2170 } else if let Some(r) = t.strip_prefix('-') {
2171 neg = true;
2172 tail = r;
2173 } else if let Some(r) = t.strip_prefix('+') {
2174 tail = r;
2175 } else if let Some(r) = t.strip_prefix('$') {
2176 tail = r;
2177 } else if t.is_empty() {
2178 break;
2179 } else {
2180 return None;
2181 }
2182 }
2183 let carry = cents / 100;
2185 cents %= 100;
2186 let total = dollars
2187 .checked_add(carry)?
2188 .checked_mul(100)?
2189 .checked_add(cents)?;
2190 Some(if neg { -total } else { total })
2191}
2192
2193pub(crate) fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
2204 let s = s.trim();
2205 let bytes = s.as_bytes();
2209 let sign_pos = bytes
2210 .iter()
2211 .enumerate()
2212 .rev()
2213 .find(|&(_, &b)| b == b'+' || b == b'-')
2214 .map(|(i, _)| i)?;
2215 if sign_pos == 0 {
2216 return None; }
2218 let time_part = &s[..sign_pos];
2219 let offset_part = &s[sign_pos..];
2220 let us = parse_time_str(time_part)?;
2221 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
2222 let offset_body = &offset_part[1..];
2223 let (hh_str, mm_str) = match offset_body.split_once(':') {
2226 Some((h, m)) => (h, m),
2227 None if offset_body.len() == 4 => offset_body.split_at(2),
2228 None if offset_body.len() == 3 => offset_body.split_at(1),
2229 None => (offset_body, "0"),
2230 };
2231 let hh: i32 = hh_str.parse().ok()?;
2232 let mm: i32 = mm_str.parse().ok()?;
2233 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
2234 return None;
2235 }
2236 let total = sign * (hh * 3600 + mm * 60);
2237 if total.abs() > 50_400 {
2238 return None;
2239 }
2240 Some((us, total))
2241}
2242
2243pub(crate) fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value<'static>, EngineError> {
2248 if n == 0 || (1901..=2155).contains(&n) {
2249 return Ok(Value::Year(n as u16));
2252 }
2253 Err(EngineError::Eval(EvalError::TypeMismatch {
2254 detail: alloc::format!(
2255 "year value out of range: {n} (column `{col_name}`; \
2256 MySQL accepts 0 or 1901..=2155)"
2257 ),
2258 }))
2259}
2260
2261pub(crate) fn parse_time_str(s: &str) -> Option<i64> {
2274 let s = s.trim();
2275 if s.eq_ignore_ascii_case("allballs") {
2277 return Some(0);
2278 }
2279 let (hms, frac) = match s.split_once('.') {
2280 Some((h, f)) => (h, Some(f)),
2281 None => (s, None),
2282 };
2283 let mut parts = hms.split(':');
2284 let hh: u32 = parts.next()?.parse().ok()?;
2285 let mm: u32 = parts.next()?.parse().ok()?;
2286 let ss: u32 = match parts.next() {
2289 Some(x) => x.parse().ok()?,
2290 None => 0,
2291 };
2292 if parts.next().is_some() {
2293 return None;
2294 }
2295 if hh > 24 || mm > 59 || ss > 59 || (hh == 24 && (mm != 0 || ss != 0)) {
2297 return None;
2298 }
2299 let frac_us: i64 = match frac {
2300 None => 0,
2301 Some(f) => {
2302 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
2303 return None;
2304 }
2305 let mut padded = alloc::string::String::with_capacity(6);
2307 padded.push_str(f);
2308 while padded.len() < 6 {
2309 padded.push('0');
2310 }
2311 padded.parse().ok()?
2312 }
2313 };
2314 if hh == 24 && frac_us != 0 {
2315 return None;
2316 }
2317 Some(
2318 i64::from(hh) * 3_600_000_000
2319 + i64::from(mm) * 60_000_000
2320 + i64::from(ss) * 1_000_000
2321 + frac_us,
2322 )
2323}
2324
2325pub(crate) fn numeric_typmod_in_range(precision: u16, scale: i16) -> bool {
2329 (1..=1000).contains(&precision) && (-1000..=1000).contains(&scale)
2330}
2331
2332pub(crate) fn numeric_typmod_error(name: &str) -> Option<alloc::string::String> {
2336 let lower = name.trim().to_ascii_lowercase();
2337 let (head, rest) = lower.split_once('(')?;
2338 if !matches!(head.trim(), "numeric" | "decimal") {
2339 return None;
2340 }
2341 let args = rest.strip_suffix(')')?;
2342 let mut it = args.split(',').map(str::trim);
2343 let p: i64 = it.next()?.parse().ok()?;
2344 if !(1..=1000).contains(&p) {
2345 return Some(alloc::format!(
2346 "NUMERIC precision {p} must be between 1 and 1000"
2347 ));
2348 }
2349 if let Some(s) = it.next() {
2350 let s: i64 = s.parse().ok()?;
2351 if !(-1000..=1000).contains(&s) {
2352 return Some(alloc::format!(
2353 "NUMERIC scale {s} must be between -1000 and 1000"
2354 ));
2355 }
2356 }
2357 None
2358}
2359
2360pub(crate) fn type_name_to_data_type(name: &str) -> Option<DataType> {
2367 with_lower_name(name.trim(), type_name_to_data_type_lower)
2368}
2369
2370pub(crate) fn with_lower_name<R>(name: &str, f: impl FnOnce(&str) -> R) -> R {
2383 const CAP: usize = 64;
2384 if name.len() <= CAP {
2385 let mut buf = [0u8; CAP];
2386 buf[..name.len()].copy_from_slice(name.as_bytes());
2387 buf[..name.len()].make_ascii_lowercase();
2388 if let Ok(s) = core::str::from_utf8(&buf[..name.len()]) {
2389 return f(s);
2390 }
2391 }
2392 f(&name.to_ascii_lowercase())
2393}
2394
2395fn type_name_to_data_type_lower(n: &str) -> Option<DataType> {
2396 if let Some((head, paren)) = n.split_once('(')
2399 && let Some(args) = paren.strip_suffix(')')
2400 {
2401 let mut wide: [Option<i32>; 2] = [None, None];
2409 for (slot, s) in wide.iter_mut().zip(args.split(',')) {
2410 *slot = s.trim().parse::<i32>().ok();
2411 }
2412 let nums: [u8; 2] = [
2413 wide[0].and_then(|v| u8::try_from(v).ok()).unwrap_or(0),
2414 wide[1].and_then(|v| u8::try_from(v).ok()).unwrap_or(0),
2415 ];
2416 match head {
2417 "bit" => {
2419 return Some(DataType::Bit(
2420 u32::try_from(wide.first().copied().flatten()?).ok()?,
2421 ));
2422 }
2423 "varbit" | "bit varying" => {
2424 return Some(DataType::BitVarying(
2425 u32::try_from(wide.first().copied().flatten()?).ok()?,
2426 ));
2427 }
2428 "numeric" | "decimal" => {
2429 let precision = u16::try_from(wide.first().copied().flatten()?).ok()?;
2430 let scale = i16::try_from(wide.get(1).copied().flatten().unwrap_or(0)).ok()?;
2432 if !numeric_typmod_in_range(precision, scale) {
2433 return None;
2434 }
2435 return Some(DataType::Numeric { precision, scale });
2436 }
2437 "varchar" => {
2443 return Some(DataType::Varchar(nums.first().copied().unwrap_or(0).into()));
2444 }
2445 "char" | "character" => {
2446 return Some(DataType::Char(nums.first().copied().unwrap_or(0).into()));
2447 }
2448 _ => {}
2449 }
2450 }
2451 Some(match n {
2452 "smallint" | "int2" => DataType::SmallInt,
2453 "numeric" | "decimal" => DataType::Numeric {
2454 precision: 0,
2455 scale: 0,
2456 },
2457 "inet" => DataType::Inet,
2460 "cidr" => DataType::Cidr,
2461 "macaddr" => DataType::Macaddr,
2462 "macaddr8" => DataType::Macaddr8,
2463 "pg_lsn" => DataType::PgLsn,
2464 "__bit_literal" => DataType::BitVarying(0),
2466 "xid" => DataType::Xid,
2471 "xid8" => DataType::Xid8,
2472 "bit" => DataType::Bit(0),
2473 "varbit" | "bit varying" => DataType::BitVarying(0),
2474 "xml" => DataType::Xml,
2475 "tsvector" => DataType::TsVector,
2485 "tsquery" => DataType::TsQuery,
2486 "money" => DataType::Money,
2493 "char1" => DataType::Char1,
2494 "point" => DataType::Point,
2496 "lseg" => DataType::Lseg,
2497 "path" => DataType::Path,
2498 "box" => DataType::PgBox,
2499 "polygon" => DataType::Polygon,
2500 "line" => DataType::Line,
2501 "circle" => DataType::Circle,
2502 "int4multirange" => DataType::Multirange(spg_storage::RangeKind::Int4),
2504 "int8multirange" => DataType::Multirange(spg_storage::RangeKind::Int8),
2505 "nummultirange" => DataType::Multirange(spg_storage::RangeKind::Num),
2506 "tsmultirange" => DataType::Multirange(spg_storage::RangeKind::Ts),
2507 "tstzmultirange" => DataType::Multirange(spg_storage::RangeKind::TsTz),
2508 "datemultirange" => DataType::Multirange(spg_storage::RangeKind::Date),
2509 "int4range" => DataType::Range(spg_storage::RangeKind::Int4),
2511 "int8range" => DataType::Range(spg_storage::RangeKind::Int8),
2512 "numrange" => DataType::Range(spg_storage::RangeKind::Num),
2513 "tsrange" => DataType::Range(spg_storage::RangeKind::Ts),
2514 "tstzrange" => DataType::Range(spg_storage::RangeKind::TsTz),
2515 "daterange" => DataType::Range(spg_storage::RangeKind::Date),
2516 "bool_array" | "boolean_array" => DataType::BoolArray,
2520 "smallint_array" | "int2_array" => DataType::SmallIntArray,
2521 "int_array" | "integer_array" | "int4_array" => DataType::IntArray,
2522 "bigint_array" | "int8_array" => DataType::BigIntArray,
2523 "float_array" | "double_array" | "real_array" | "float8_array" | "float4_array" => {
2524 DataType::FloatArray
2525 }
2526 "float4" | "real" => DataType::Real,
2529 "float8" | "double precision" | "float" => DataType::Float,
2530 "oid" => DataType::Oid,
2535 "oid_array" => DataType::OidArray,
2549 "name_array" | "regtype_array" | "regclass_array" | "regproc_array" => DataType::TextArray,
2550 "time" | "time without time zone" => DataType::Time,
2553 "timetz" | "time with time zone" => DataType::TimeTz,
2554 "hstore" => DataType::Hstore,
2561 "numeric_array" | "decimal_array" => DataType::NumericArray,
2562 "varchar_array" | "character varying_array" | "char_array" | "bpchar_array" => {
2563 DataType::TextArray
2564 }
2565 "text_array" => DataType::TextArray,
2566 "date_array" => DataType::DateArray,
2567 "timestamp_array" => DataType::TimestampArray,
2568 "timestamptz_array" => DataType::TimestamptzArray,
2569 "uuid_array" => DataType::UuidArray,
2570 "json_array" => DataType::JsonArray,
2571 "jsonb_array" => DataType::JsonbArray,
2572 "bytea_array" => DataType::BytesArray,
2573 "interval_array" => DataType::IntervalArray,
2574 "money_array" => DataType::MoneyArray,
2575 "int" | "int4" | "integer" => DataType::Int,
2580 "bigint" | "int8" => DataType::BigInt,
2581 "text" => DataType::Text,
2582 "name" => DataType::Name,
2586 "varchar" | "character varying" => DataType::Varchar(0),
2587 "char" | "character" => DataType::Char(1),
2591 "bpchar" => DataType::Char(0),
2592 "bool" | "boolean" => DataType::Bool,
2593 "date" => DataType::Date,
2594 "timestamp" | "timestamp without time zone" => DataType::Timestamp,
2595 "timestamptz" | "timestamp with time zone" => DataType::Timestamptz,
2596 "uuid" => DataType::Uuid,
2597 "json" => DataType::Json,
2598 "jsonb" => DataType::Jsonb,
2599 "bytea" => DataType::Bytes,
2600 "interval" => DataType::Interval,
2601 _ => return None,
2602 })
2603}
2604
2605pub(crate) const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
2606 match t {
2607 ColumnTypeName::SmallInt => DataType::SmallInt,
2608 ColumnTypeName::Int => DataType::Int,
2609 ColumnTypeName::BigInt => DataType::BigInt,
2610 ColumnTypeName::Float => DataType::Float,
2611 ColumnTypeName::Real => DataType::Real,
2612 ColumnTypeName::Text => DataType::Text,
2613 ColumnTypeName::Name => DataType::Name,
2614 ColumnTypeName::Xid => DataType::Xid,
2615 ColumnTypeName::Xid8 => DataType::Xid8,
2616 ColumnTypeName::Oid => DataType::Oid,
2617 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
2618 ColumnTypeName::Char(n) => DataType::Char(n),
2619 ColumnTypeName::Bool => DataType::Bool,
2620 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
2621 dim,
2622 encoding: match encoding {
2623 SqlVecEncoding::F32 => VecEncoding::F32,
2624 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
2625 SqlVecEncoding::F16 => VecEncoding::F16,
2626 },
2627 },
2628 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
2629 ColumnTypeName::Date => DataType::Date,
2630 ColumnTypeName::Timestamp => DataType::Timestamp,
2631 ColumnTypeName::Timestamptz => DataType::Timestamptz,
2632 ColumnTypeName::Json => DataType::Json,
2633 ColumnTypeName::Jsonb => DataType::Jsonb,
2634 ColumnTypeName::Bytes => DataType::Bytes,
2635 ColumnTypeName::TextArray => DataType::TextArray,
2636 ColumnTypeName::IntArray => DataType::IntArray,
2637 ColumnTypeName::BigIntArray => DataType::BigIntArray,
2638 ColumnTypeName::TsVector => DataType::TsVector,
2639 ColumnTypeName::TsQuery => DataType::TsQuery,
2640 ColumnTypeName::Uuid => DataType::Uuid,
2641 ColumnTypeName::Time => DataType::Time,
2642 ColumnTypeName::Year => DataType::Year,
2643 ColumnTypeName::TimeTz => DataType::TimeTz,
2644 ColumnTypeName::Money => DataType::Money,
2645 ColumnTypeName::Range(k) => DataType::Range(match k {
2646 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
2647 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
2648 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
2649 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
2650 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
2651 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
2652 }),
2653 ColumnTypeName::Hstore => DataType::Hstore,
2654 ColumnTypeName::IntArray2D => DataType::IntArray2D,
2655 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
2656 ColumnTypeName::TextArray2D => DataType::TextArray2D,
2657 ColumnTypeName::BoolArray2D => DataType::BoolArray2D,
2658 ColumnTypeName::Interval => DataType::Interval,
2659 ColumnTypeName::IntervalArray => DataType::IntervalArray,
2660 ColumnTypeName::BoolArray => DataType::BoolArray,
2661 ColumnTypeName::SmallIntArray => DataType::SmallIntArray,
2662 ColumnTypeName::FloatArray => DataType::FloatArray,
2663 ColumnTypeName::NumericArray => DataType::NumericArray,
2664 ColumnTypeName::DateArray => DataType::DateArray,
2665 ColumnTypeName::TimestampArray => DataType::TimestampArray,
2666 ColumnTypeName::TimestamptzArray => DataType::TimestamptzArray,
2667 ColumnTypeName::UuidArray => DataType::UuidArray,
2668 ColumnTypeName::JsonArray => DataType::JsonArray,
2669 ColumnTypeName::JsonbArray => DataType::JsonbArray,
2670 ColumnTypeName::BytesArray => DataType::BytesArray,
2671 ColumnTypeName::VarcharArray => DataType::VarcharArray,
2672 ColumnTypeName::CharArray => DataType::CharArray,
2673 ColumnTypeName::Multirange(k) => DataType::Multirange(match k {
2674 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
2675 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
2676 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
2677 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
2678 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
2679 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
2680 }),
2681 ColumnTypeName::Point => DataType::Point,
2682 ColumnTypeName::Lseg => DataType::Lseg,
2683 ColumnTypeName::Path => DataType::Path,
2684 ColumnTypeName::PgBox => DataType::PgBox,
2685 ColumnTypeName::Polygon => DataType::Polygon,
2686 ColumnTypeName::Line => DataType::Line,
2687 ColumnTypeName::Circle => DataType::Circle,
2688 ColumnTypeName::Inet => DataType::Inet,
2689 ColumnTypeName::Cidr => DataType::Cidr,
2690 ColumnTypeName::Macaddr => DataType::Macaddr,
2691 ColumnTypeName::Macaddr8 => DataType::Macaddr8,
2692 ColumnTypeName::Bit(n) => DataType::Bit(n),
2693 ColumnTypeName::BitVarying(n) => DataType::BitVarying(n),
2694 ColumnTypeName::Xml => DataType::Xml,
2695 ColumnTypeName::Char1 => DataType::Char1,
2696 ColumnTypeName::MoneyArray => DataType::MoneyArray,
2697 }
2698}
2699
2700pub(crate) fn literal_expr_to_value(expr: Expr) -> Result<Value<'static>, EngineError> {
2704 literal_expr_to_value_in(expr, None)
2705}
2706
2707pub(crate) fn literal_expr_to_value_in(
2715 expr: Expr,
2716 catalog: Option<&spg_storage::Catalog>,
2717) -> Result<Value<'static>, EngineError> {
2718 match expr {
2719 Expr::Literal(l) => Ok(literal_to_value(l)),
2720 Expr::Cast { expr, target } => {
2721 if catalog.is_some()
2724 && matches!(
2725 target,
2726 spg_sql::ast::CastTarget::Named(_) | spg_sql::ast::CastTarget::RegClass
2727 )
2728 {
2729 return eval_expr_with_catalog(Expr::Cast { expr, target }, catalog);
2730 }
2731 let inner_value = literal_expr_to_value_in(*expr, catalog)?;
2732 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
2733 }
2734 Expr::Unary {
2735 op: UnOp::Neg,
2736 expr,
2737 } => match *expr {
2738 Expr::Literal(Literal::Integer(n)) => {
2739 let neg = n.checked_neg().ok_or_else(|| {
2742 EngineError::Unsupported("integer literal overflow on negation".into())
2743 })?;
2744 Ok(int_value_for(neg))
2745 }
2746 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
2747 Expr::Literal(Literal::Numeric { unscaled, scale }) => Ok(Value::Numeric {
2749 scaled: -unscaled,
2750 scale,
2751 kind: spg_storage::NumericKind::Finite,
2752 }),
2753 Expr::Literal(Literal::NumericBig(ref s)) => {
2756 let flipped = if let Some(rest) = s.strip_prefix('-') {
2757 rest.to_string()
2758 } else {
2759 alloc::format!("-{s}")
2760 };
2761 Ok(big_literal_to_value(&flipped))
2762 }
2763 Expr::Cast {
2769 expr: inner,
2770 target,
2771 } => {
2772 let negated_inner = match *inner {
2773 Expr::Literal(Literal::Integer(n)) => {
2774 let neg = n.checked_neg().ok_or_else(|| {
2775 EngineError::Unsupported("integer literal overflow on negation".into())
2776 })?;
2777 Expr::Literal(Literal::Integer(neg))
2778 }
2779 Expr::Literal(Literal::Float(x)) => Expr::Literal(Literal::Float(-x)),
2780 Expr::Literal(Literal::Numeric { unscaled, scale }) => {
2781 Expr::Literal(Literal::Numeric {
2782 unscaled: -unscaled,
2783 scale,
2784 })
2785 }
2786 Expr::Literal(Literal::NumericBig(ref s)) => {
2789 let flipped = if let Some(rest) = s.strip_prefix('-') {
2790 rest.to_string()
2791 } else {
2792 alloc::format!("-{s}")
2793 };
2794 Expr::Literal(Literal::NumericBig(flipped))
2795 }
2796 other => Expr::Unary {
2797 op: spg_sql::ast::UnOp::Neg,
2798 expr: alloc::boxed::Box::new(other),
2799 },
2800 };
2801 literal_expr_to_value_in(
2802 Expr::Cast {
2803 expr: alloc::boxed::Box::new(negated_inner),
2804 target,
2805 },
2806 catalog,
2807 )
2808 }
2809 other => Err(EngineError::Unsupported(alloc::format!(
2810 "unary minus over non-literal expression: {other:?}"
2811 ))),
2812 },
2813 Expr::Array(items) => {
2821 let mut materialised: alloc::vec::Vec<Value<'static>> =
2822 alloc::vec::Vec::with_capacity(items.len());
2823 for elem in &items {
2824 materialised.push(literal_expr_to_value_in(elem.clone(), catalog)?);
2825 }
2826 Ok(crate::describe::upgrade_timestamptz_array(
2827 array_literal_widen(materialised),
2828 &items,
2829 &[],
2830 ))
2831 }
2832 other => eval_expr_with_catalog(other, catalog),
2845 }
2846}
2847
2848fn eval_expr_with_catalog(
2851 expr: Expr,
2852 catalog: Option<&spg_storage::Catalog>,
2853) -> Result<Value<'static>, EngineError> {
2854 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
2855 let mut ctx = EvalContext::new(&empty_schema, None);
2856 if let Some(cat) = catalog {
2857 ctx = ctx.with_catalog(cat);
2858 }
2859 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
2860 crate::eval::eval_expr(&expr, &empty_row, &ctx).map_err(EngineError::Eval)
2861}
2862
2863pub(crate) fn literal_to_value(l: Literal) -> Value<'static> {
2864 match l {
2865 Literal::Integer(n) => int_value_for(n),
2866 Literal::Float(x) => Value::Float(x),
2867 Literal::Numeric { unscaled, scale } => Value::Numeric {
2868 scaled: unscaled,
2869 scale,
2870 kind: spg_storage::NumericKind::Finite,
2871 },
2872 Literal::NumericBig(s) => big_literal_to_value(&s),
2873 Literal::Timestamp { micros, .. } => Value::Timestamp(micros),
2874 Literal::Date { days, .. } => Value::Date(days),
2875 Literal::String(s) => Value::text(s),
2876 Literal::Bool(b) => Value::Bool(b),
2877 Literal::Null => Value::Null,
2878 Literal::Vector(v) => Value::vector(v),
2879 Literal::TextArray(items) => Value::TextArray(items),
2880 Literal::IntArray(items) => Value::IntArray(items),
2881 Literal::BigIntArray(items) => Value::BigIntArray(items),
2882 Literal::Interval {
2883 months,
2884 days,
2885 micros,
2886 ..
2887 } => Value::Interval {
2888 months,
2889 days,
2890 micros,
2891 },
2892 }
2893}
2894
2895pub(crate) fn int_value_for(n: i64) -> Value<'static> {
2899 if let Ok(small) = i32::try_from(n) {
2900 Value::Int(small)
2901 } else {
2902 Value::BigInt(n)
2903 }
2904}
2905
2906pub(crate) fn truncate_to_column_fsp(v: Value<'static>, schema: &ColumnSchema) -> Value<'static> {
2928 let Some(fsp) = schema.mysql_fsp else {
2929 return v;
2930 };
2931 if fsp >= 6 {
2932 return v;
2933 }
2934 let scale = 10i64.pow(u32::from(6 - fsp));
2935 let cut = |micros: i64| (micros / scale) * scale;
2937 match v {
2938 Value::Timestamp(m) => Value::Timestamp(cut(m)),
2939 Value::Time(m) => Value::Time(cut(m)),
2940 other => other,
2941 }
2942}
2943
2944fn column_int_bounds(schema: &ColumnSchema) -> Option<(i128, i128)> {
2949 if let Some(width) = schema.mysql_int_width {
2950 return Some(match (width, schema.is_unsigned) {
2951 (spg_storage::MysqlIntWidth::Tiny, false) => (-128, 127),
2952 (spg_storage::MysqlIntWidth::Tiny, true) => (0, 255),
2953 (spg_storage::MysqlIntWidth::Small, false) => (-32_768, 32_767),
2954 (spg_storage::MysqlIntWidth::Small, true) => (0, 65_535),
2955 (spg_storage::MysqlIntWidth::Medium, false) => (-8_388_608, 8_388_607),
2956 (spg_storage::MysqlIntWidth::Medium, true) => (0, 16_777_215),
2957 (spg_storage::MysqlIntWidth::Int, false) => (-2_147_483_648, 2_147_483_647),
2958 (spg_storage::MysqlIntWidth::Int, true) => (0, 4_294_967_295),
2959 (spg_storage::MysqlIntWidth::Big, false) => {
2962 (i128::from(i64::MIN), i128::from(i64::MAX))
2963 }
2964 (spg_storage::MysqlIntWidth::Big, true) => (0, i128::from(u64::MAX)),
2965 });
2966 }
2967 let (lo, hi) = match schema.ty {
2968 DataType::SmallInt => (i128::from(i16::MIN), i128::from(i16::MAX)),
2969 DataType::Int => (i128::from(i32::MIN), i128::from(i32::MAX)),
2970 DataType::BigInt => (i128::from(i64::MIN), i128::from(i64::MAX)),
2971 _ => return None,
2972 };
2973 Some(if schema.is_unsigned {
2974 (0, hi)
2975 } else {
2976 (lo, hi)
2977 })
2978}
2979
2980pub(crate) fn mysql_ignore_fit(v: Value<'static>, schema: &ColumnSchema) -> Value<'static> {
2999 if v.is_null() {
3000 if schema.nullable {
3001 return v;
3002 }
3003 return match schema.ty {
3005 DataType::SmallInt | DataType::Int | DataType::BigInt => Value::BigInt(0),
3006 DataType::Float | DataType::Real => Value::Float(0.0),
3007 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(""),
3008 _ => v,
3009 };
3010 }
3011 if let Value::Text(ref s) = v
3014 && matches!(
3015 schema.ty,
3016 DataType::SmallInt | DataType::Int | DataType::BigInt
3017 )
3018 && s.trim().parse::<i64>().is_err()
3019 {
3020 return Value::BigInt(leading_numeric_prefix(s));
3021 }
3022 let as_int = match v {
3024 Value::SmallInt(n) => Some(i128::from(n)),
3025 Value::Int(n) => Some(i128::from(n)),
3026 Value::BigInt(n) => Some(i128::from(n)),
3027 Value::Numeric {
3029 scaled, scale: 0, ..
3030 } => Some(scaled),
3031 _ => None,
3032 };
3033 if let Some(n) = as_int
3034 && let Some((lo, hi)) = column_int_bounds(schema)
3035 && (n < lo || n > hi)
3036 {
3037 return int_value_for_column(n.clamp(lo, hi));
3038 }
3039 if let Value::Text(ref s) = v {
3041 let max = match schema.ty {
3042 DataType::Varchar(m) | DataType::Char(m) if m > 0 => m as usize,
3043 _ => return v,
3044 };
3045 if s.chars().count() > max {
3046 return Value::text(s.chars().take(max).collect::<alloc::string::String>());
3047 }
3048 }
3049 v
3050}
3051
3052fn leading_numeric_prefix(s: &str) -> i64 {
3061 let t = s.trim_start();
3062 let b = t.as_bytes();
3063 let mut i = 0;
3064 if i < b.len() && (b[i] == b'-' || b[i] == b'+') {
3065 i += 1;
3066 }
3067 let int_start = i;
3068 while i < b.len() && b[i].is_ascii_digit() {
3069 i += 1;
3070 }
3071 let mut end = i;
3072 if i < b.len() && b[i] == b'.' {
3073 i += 1;
3074 while i < b.len() && b[i].is_ascii_digit() {
3075 i += 1;
3076 }
3077 if i > int_start + 1 {
3080 end = i;
3081 }
3082 }
3083 if end > int_start && i < b.len() && (b[i] == b'e' || b[i] == b'E') {
3085 let mut j = i + 1;
3086 if j < b.len() && (b[j] == b'-' || b[j] == b'+') {
3087 j += 1;
3088 }
3089 let digits_start = j;
3090 while j < b.len() && b[j].is_ascii_digit() {
3091 j += 1;
3092 }
3093 if j > digits_start {
3094 end = j;
3095 }
3096 }
3097 let Ok(f) = t[..end].parse::<f64>() else {
3098 return 0;
3099 };
3100 let r = f.round();
3102 if r >= i64::MAX as f64 {
3103 i64::MAX
3104 } else if r <= i64::MIN as f64 {
3105 i64::MIN
3106 } else {
3107 r as i64
3108 }
3109}
3110
3111fn int_value_for_column(n: i128) -> Value<'static> {
3115 match i64::try_from(n) {
3116 Ok(v) => Value::BigInt(v),
3117 Err(_) => Value::numeric(n, 0),
3118 }
3119}
3120
3121pub(crate) fn check_unsigned_range(
3122 v: &Value,
3123 schema: &ColumnSchema,
3124 position: usize,
3125) -> Result<(), EngineError> {
3126 let n: i128 = match v {
3127 Value::SmallInt(x) => i128::from(*x),
3128 Value::Int(x) => i128::from(*x),
3129 Value::BigInt(x) => i128::from(*x),
3130 Value::Numeric { scaled, scale, .. } if *scale == 0 => *scaled,
3133 _ => return Ok(()), };
3135 if let Some(width) = schema.mysql_int_width {
3139 let _ = width;
3145 let (lo, hi) = column_int_bounds(schema).unwrap_or((i128::MIN, i128::MAX));
3146 if n < lo || n > hi {
3147 return Err(EngineError::Unsupported(alloc::format!(
3150 "Out of range value for column '{}'",
3151 schema.name
3152 )));
3153 }
3154 return Ok(());
3155 }
3156 if schema.is_unsigned && n < 0 {
3158 return Err(EngineError::Unsupported(alloc::format!(
3159 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
3160 schema.name
3161 )));
3162 }
3163 Ok(())
3164}
3165
3166fn coerce_text_array_to(
3172 items: alloc::vec::Vec<Option<alloc::string::String>>,
3173 target: DataType,
3174 col: &str,
3175) -> Result<Option<Value<'static>>, EngineError> {
3176 let elem_dt = match target {
3177 DataType::BoolArray => DataType::Bool,
3178 DataType::NumericArray => DataType::Numeric {
3179 precision: 0,
3180 scale: 0,
3181 },
3182 DataType::DateArray => DataType::Date,
3183 DataType::TimestampArray => DataType::Timestamp,
3184 DataType::TimestamptzArray => DataType::Timestamptz,
3185 DataType::UuidArray => DataType::Uuid,
3186 DataType::IntervalArray => DataType::Interval,
3189 _ => return Ok(None),
3190 };
3191 let mut scal: alloc::vec::Vec<Option<Value<'static>>> =
3192 alloc::vec::Vec::with_capacity(items.len());
3193 for item in items {
3194 match item {
3195 None => scal.push(None),
3196 Some(s) => scal.push(Some(coerce_value(Value::text(s), elem_dt, col, 0)?)),
3197 }
3198 }
3199 let out = match target {
3200 DataType::BoolArray => Value::BoolArray(
3201 scal.into_iter()
3202 .map(|o| o.map(|v| matches!(v, Value::Bool(true))))
3203 .collect(),
3204 ),
3205 DataType::NumericArray => Value::NumericArray(
3206 scal.into_iter()
3207 .map(|o| {
3208 o.map(|v| match v {
3209 Value::Numeric { scaled, scale, .. } => (scaled, scale),
3210 _ => (0, 0),
3211 })
3212 })
3213 .collect(),
3214 ),
3215 DataType::DateArray => Value::DateArray(
3216 scal.into_iter()
3217 .map(|o| {
3218 o.map(|v| match v {
3219 Value::Date(d) => d,
3220 _ => 0,
3221 })
3222 })
3223 .collect(),
3224 ),
3225 DataType::TimestampArray => Value::TimestampArray(
3226 scal.into_iter()
3227 .map(|o| {
3228 o.map(|v| match v {
3229 Value::Timestamp(t) => t,
3230 _ => 0,
3231 })
3232 })
3233 .collect(),
3234 ),
3235 DataType::TimestamptzArray => Value::TimestamptzArray(
3236 scal.into_iter()
3237 .map(|o| {
3238 o.map(|v| match v {
3239 Value::Timestamp(t) => t,
3240 _ => 0,
3241 })
3242 })
3243 .collect(),
3244 ),
3245 DataType::UuidArray => Value::UuidArray(
3246 scal.into_iter()
3247 .map(|o| {
3248 o.map(|v| match v {
3249 Value::Uuid(u) => u,
3250 _ => [0u8; 16],
3251 })
3252 })
3253 .collect(),
3254 ),
3255 DataType::IntervalArray => Value::IntervalArray(
3256 scal.into_iter()
3257 .map(|o| {
3258 o.and_then(|v| match v {
3259 Value::Interval {
3260 months,
3261 days,
3262 micros,
3263 } => Some(spg_storage::IntervalSpan {
3264 months,
3265 days,
3266 micros,
3267 }),
3268 _ => None,
3269 })
3270 })
3271 .collect(),
3272 ),
3273 _ => return Ok(None),
3274 };
3275 Ok(Some(out))
3276}
3277
3278pub(crate) fn array_oid_element(oid: i64) -> Option<i64> {
3289 Some(match oid {
3290 1000 => 16, 1001 => 17, 1002 => 18, 1003 => 19, 1016 => 20, 1005 => 21, 1007 => 23, 1009 => 25, 1028 => 26, 199 => 114, 143 => 142, 651 => 650, 1021 => 700, 1022 => 701, 775 => 774, 791 => 790, 1040 => 829, 1041 => 869, 1014 => 1042, 1015 => 1043, 1182 => 1082, 1183 => 1083, 1115 => 1114, 1185 => 1184, 1187 => 1186, 1270 => 1266, 1561 => 1560, 1563 => 1562, 1231 => 1700, 2951 => 2950, 3643 => 3614, 3645 => 3615, 3807 => 3802, _ => return None,
3324 })
3325}
3326
3327pub(crate) fn regtype_oid_to_name_owned(oid: i64) -> Option<alloc::string::String> {
3334 if let Some(scalar) = regtype_oid_to_name(oid) {
3335 return Some(alloc::string::String::from(scalar));
3336 }
3337 let (_, _, elem) = crate::system_catalog::ARRAY_TYPE_OIDS
3338 .iter()
3339 .find(|(arr, _, _)| *arr == oid)?;
3340 Some(alloc::format!("{}[]", regtype_oid_to_name(*elem)?))
3341}
3342
3343pub(crate) fn array_oid_for_element(elem: i64) -> Option<i64> {
3345 crate::system_catalog::ARRAY_TYPE_OIDS
3346 .iter()
3347 .find(|(_, _, e)| *e == elem)
3348 .map(|(arr, _, _)| *arr)
3349}
3350
3351pub(crate) fn regtype_oid_to_name(oid: i64) -> Option<&'static str> {
3352 Some(match oid {
3353 4600 => "pg_brin_bloom_summary",
3354 16 => "boolean",
3355 17 => "bytea",
3356 18 => "\"char\"",
3357 19 => "name",
3358 20 => "bigint",
3359 21 => "smallint",
3360 23 => "integer",
3361 25 => "text",
3362 26 => "oid",
3363 27 => "tid",
3365 28 => "xid",
3366 29 => "cid",
3367 5069 => "xid8",
3368 114 => "json",
3369 142 => "xml",
3370 650 => "cidr",
3371 700 => "real",
3372 701 => "double precision",
3373 774 => "macaddr8",
3374 790 => "money",
3375 829 => "macaddr",
3376 869 => "inet",
3377 1042 => "character",
3378 1043 => "character varying",
3379 1082 => "date",
3380 1083 => "time without time zone",
3381 1114 => "timestamp without time zone",
3382 1184 => "timestamp with time zone",
3383 1186 => "interval",
3384 1266 => "time with time zone",
3385 1560 => "bit",
3386 1562 => "bit varying",
3387 1700 => "numeric",
3388 2950 => "uuid",
3389 3614 => "tsvector",
3390 3615 => "tsquery",
3391 3802 => "jsonb",
3392 3904 => "int4range",
3393 3906 => "numrange",
3394 3908 => "tsrange",
3395 3910 => "tstzrange",
3396 3912 => "daterange",
3397 3926 => "int8range",
3398 _ => return None,
3399 })
3400}
3401
3402pub(crate) fn parse_pg_int(s: &str) -> Option<i64> {
3403 let s = s.trim();
3404 let (neg, rest) = if let Some(r) = s.strip_prefix('-') {
3405 (true, r)
3406 } else if let Some(r) = s.strip_prefix('+') {
3407 (false, r)
3408 } else {
3409 (false, s)
3410 };
3411 let (radix, digits, has_prefix) =
3416 if let Some(h) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
3417 (16u32, h, true)
3418 } else if let Some(o) = rest.strip_prefix("0o").or_else(|| rest.strip_prefix("0O")) {
3419 (8, o, true)
3420 } else if let Some(b) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) {
3421 (2, b, true)
3422 } else {
3423 (10, rest, false)
3424 };
3425 let db = digits.as_bytes();
3426 if db.last() == Some(&b'_')
3430 || digits.contains("__")
3431 || (!has_prefix && db.first() == Some(&b'_'))
3432 {
3433 return None;
3434 }
3435 let cleaned: alloc::string::String = digits.chars().filter(|&c| c != '_').collect();
3436 if cleaned.is_empty() {
3437 return None;
3438 }
3439 let mag = i64::from_str_radix(&cleaned, radix).ok()?;
3440 Some(if neg { mag.checked_neg()? } else { mag })
3441}
3442
3443fn xml_content_is_well_formed(s: &str) -> bool {
3452 let b = s.as_bytes();
3453 let is_name =
3454 |c: u8| c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_' | b'.' | b':') || c >= 0x80;
3455 let mut stack: alloc::vec::Vec<&[u8]> = alloc::vec::Vec::new();
3456 let mut i = 0;
3457 while i < b.len() {
3458 if b[i] != b'<' {
3459 i += 1;
3460 continue;
3461 }
3462 let rest = &s[i..];
3463 if rest.starts_with("<!--") {
3464 match rest.find("-->") {
3465 Some(p) => i += p + 3,
3466 None => return false,
3467 }
3468 } else if rest.starts_with("<![CDATA[") {
3469 match rest.find("]]>") {
3470 Some(p) => i += p + 3,
3471 None => return false,
3472 }
3473 } else if rest.starts_with("<?") {
3474 match rest.find("?>") {
3475 Some(p) => i += p + 2,
3476 None => return false,
3477 }
3478 } else if rest.starts_with("<!") {
3479 match rest.find('>') {
3480 Some(p) => i += p + 1,
3481 None => return false,
3482 }
3483 } else {
3484 let close = i + 1 < b.len() && b[i + 1] == b'/';
3486 let name_start = if close { i + 2 } else { i + 1 };
3487 let mut j = name_start;
3488 while j < b.len() && is_name(b[j]) {
3489 j += 1;
3490 }
3491 if j == name_start {
3492 return false; }
3494 let name = &b[name_start..j];
3495 let mut k = j;
3497 let mut quote = 0u8;
3498 let mut prev = 0u8;
3499 loop {
3500 if k >= b.len() {
3501 return false; }
3503 let c = b[k];
3504 if quote != 0 {
3505 if c == quote {
3506 quote = 0;
3507 }
3508 } else if c == b'"' || c == b'\'' {
3509 quote = c;
3510 } else if c == b'>' {
3511 break;
3512 }
3513 prev = c;
3514 k += 1;
3515 }
3516 let self_closing = prev == b'/';
3517 i = k + 1;
3518 if close {
3519 match stack.pop() {
3520 Some(top) if top == name => {}
3521 _ => return false,
3522 }
3523 } else if !self_closing {
3524 stack.push(name);
3525 }
3526 }
3527 }
3528 stack.is_empty()
3529}
3530
3531pub(crate) fn parse_float8(s: &str) -> Option<f64> {
3537 let t = s.trim();
3538 let parsed = t.parse::<f64>().ok()?;
3539 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3540 let numeric_looking = body
3541 .bytes()
3542 .next()
3543 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
3544 if numeric_looking {
3545 if parsed.is_infinite() {
3546 return None; }
3548 if parsed == 0.0 {
3549 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
3551 if mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0') {
3552 return None;
3553 }
3554 }
3555 }
3556 Some(parsed)
3557}
3558
3559fn decode_array_elems(
3563 s: &str,
3564 elem: DataType,
3565 col_name: &str,
3566 position: usize,
3567) -> Result<Vec<Option<Value<'static>>>, EngineError> {
3568 let raw = decode_text_array_literal(s).map_err(|_| {
3574 EngineError::Eval(EvalError::TypeMismatch {
3575 detail: malformed_array_literal(s),
3576 })
3577 })?;
3578 let mut out = Vec::with_capacity(raw.len());
3579 for e in raw {
3580 match e {
3581 None => out.push(None),
3582 Some(t) => out.push(Some(coerce_value(
3583 Value::text(t),
3584 elem,
3585 col_name,
3586 position,
3587 )?)),
3588 }
3589 }
3590 Ok(out)
3591}
3592
3593fn coerce_untyped_value(
3597 v: Value<'static>,
3598 expected: DataType,
3599 col_name: &str,
3600 position: usize,
3601) -> Result<Value<'static>, EngineError> {
3602 match (&v, expected) {
3603 (
3614 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3615 DataType::BigInt | DataType::Oid,
3616 ) => Ok(Value::BigInt(*oid)),
3617 (
3618 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3619 DataType::Int,
3620 ) => Ok(Value::Int(i32::try_from(*oid).unwrap_or(i32::MAX))),
3621 (
3622 Value::RegClass(_, name) | Value::RegProc(_, name) | Value::RegType(_, name),
3623 DataType::Text,
3624 ) => Ok(Value::text(alloc::string::String::from(name.as_ref()))),
3625 (Value::Composite(fields), DataType::Jsonb | DataType::Json) => {
3631 let mut obj = alloc::string::String::from("{");
3632 for (i, (name, val)) in fields.iter().enumerate() {
3633 if i > 0 {
3634 obj.push(',');
3635 }
3636 obj.push_str(&crate::json::value_to_json_text(&Value::text(
3638 alloc::string::String::from(name.as_str()),
3639 )));
3640 obj.push(':');
3641 obj.push_str(&crate::json::value_to_json_text(val));
3642 }
3643 obj.push('}');
3644 Ok(Value::Json(alloc::borrow::Cow::Owned(obj)))
3645 }
3646 (Value::Composite(_), DataType::Text) => Ok(Value::text(crate::eval::value_to_text(&v))),
3648 _ => Err(EngineError::Unsupported(alloc::format!(
3649 "cannot coerce {:?} to {expected:?} for column {col_name:?} (position {position})",
3650 v
3651 ))),
3652 }
3653}
3654
3655fn invalid_input_syntax(ty: &str, value: &str) -> EngineError {
3659 EngineError::Eval(EvalError::TypeMismatch {
3660 detail: alloc::format!("invalid input syntax for type {ty}: \"{value}\""),
3661 })
3662}
3663
3664fn real_out_of_range(value: &str) -> EngineError {
3667 float_out_of_range(value, "real")
3668}
3669
3670fn float_out_of_range(value: &str, ty: &str) -> EngineError {
3672 EngineError::Eval(EvalError::TypeMismatch {
3673 detail: alloc::format!("\"{value}\" is out of range for type {ty}"),
3674 })
3675}
3676
3677fn float_text_error(s: &str, ty: &str) -> EngineError {
3683 let t = s.trim();
3684 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3685 let numeric_looking = body
3686 .bytes()
3687 .next()
3688 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
3689 if numeric_looking && t.parse::<f64>().is_ok() {
3690 float_out_of_range(t, ty)
3691 } else {
3692 invalid_input_syntax(ty, s)
3693 }
3694}
3695
3696fn float_text_is_nonzero(t: &str) -> bool {
3700 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3701 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
3702 mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0')
3703}
3704
3705fn text_is_explicit_infinity(t: &str) -> bool {
3708 let t = t.trim_start_matches(['+', '-']);
3709 t.eq_ignore_ascii_case("inf") || t.eq_ignore_ascii_case("infinity")
3710}
3711
3712fn datetime_parse_error(ty: &str, s: &str) -> EngineError {
3721 let t = s.trim();
3722 let date_shaped = t.chars().any(|c| c.is_ascii_digit())
3723 && t.chars().all(|c| {
3724 c.is_ascii_digit() || matches!(c, '-' | '/' | ':' | '.' | ' ' | '+' | 'T' | 't')
3725 });
3726 let detail = if date_shaped {
3727 alloc::format!("date/time field value out of range: \"{t}\"")
3728 } else {
3729 alloc::format!("invalid input syntax for type {ty}: \"{t}\"")
3730 };
3731 EngineError::Eval(EvalError::TypeMismatch { detail })
3732}
3733
3734pub(crate) enum JsonbScalar {
3740 Numeric(Value<'static>),
3741 Bool(bool),
3742 Null,
3743}
3744
3745pub(crate) fn jsonb_cast_type_error(kind: &str, target: &str) -> EvalError {
3747 EvalError::TypeMismatch {
3748 detail: alloc::format!("cannot cast jsonb {kind} to type {target}"),
3749 }
3750}
3751
3752pub(crate) fn jsonb_scalar_for_cast(s: &str, target: &str) -> Result<JsonbScalar, EvalError> {
3755 use crate::json::JsonValue;
3756 match crate::json::parse(s) {
3757 Ok(JsonValue::Null) => Ok(JsonbScalar::Null),
3758 Ok(JsonValue::Bool(b)) => Ok(JsonbScalar::Bool(b)),
3759 Ok(JsonValue::Number(x)) => {
3763 let num = coerce_value(
3764 Value::text(alloc::format!("{x}")),
3765 DataType::Numeric {
3766 precision: 0,
3767 scale: 0,
3768 },
3769 "",
3770 0,
3771 )
3772 .map_err(|e| match e {
3773 EngineError::Eval(ev) => ev,
3774 _ => jsonb_cast_type_error("numeric", target),
3775 })?;
3776 Ok(JsonbScalar::Numeric(num))
3777 }
3778 Ok(JsonValue::NumberText(text)) => {
3779 let num = coerce_value(
3780 Value::text(text),
3781 DataType::Numeric {
3782 precision: 0,
3783 scale: 0,
3784 },
3785 "",
3786 0,
3787 )
3788 .map_err(|e| match e {
3789 EngineError::Eval(ev) => ev,
3790 _ => jsonb_cast_type_error("numeric", target),
3791 })?;
3792 Ok(JsonbScalar::Numeric(num))
3793 }
3794 Ok(JsonValue::String(_)) => Err(jsonb_cast_type_error("string", target)),
3795 Ok(JsonValue::Array(_)) => Err(jsonb_cast_type_error("array", target)),
3796 Ok(JsonValue::Object(_)) => Err(jsonb_cast_type_error("object", target)),
3797 Err(_) => Err(jsonb_cast_type_error("value", target)),
3798 }
3799}
3800pub(crate) fn normalize_composite_for_column(
3818 v: Value<'static>,
3819 col: &ColumnSchema,
3820 catalog: Option<&spg_storage::Catalog>,
3821) -> Result<Value<'static>, EngineError> {
3822 let Some(tname) = col.user_composite_type.as_deref() else {
3823 return Ok(v);
3824 };
3825 if matches!(v, Value::Null) {
3826 return Ok(v);
3827 }
3828 let Some(def) = catalog.and_then(|c| c.composite_types().get(tname)) else {
3831 return Ok(v);
3832 };
3833 if matches!(v, Value::Json(_)) {
3836 return Ok(v);
3837 }
3838 crate::eval::apply_composite_cast_pub(v, def, catalog).map_err(EngineError::Eval)
3839}
3840
3841fn try_coerce_json_scalar(
3845 s: &str,
3846 expected: DataType,
3847 col_name: &str,
3848 position: usize,
3849) -> Option<Result<Value<'static>, EngineError>> {
3850 let target = match expected {
3851 DataType::Int => "integer",
3852 DataType::BigInt => "bigint",
3853 DataType::SmallInt => "smallint",
3854 DataType::Numeric { .. } => "numeric",
3855 DataType::Real => "real",
3856 DataType::Float => "double precision",
3857 DataType::Bool => "boolean",
3858 _ => return None,
3859 };
3860 Some(
3861 (|| match jsonb_scalar_for_cast(s, target).map_err(EngineError::Eval)? {
3862 JsonbScalar::Null => Ok(Value::Null),
3863 JsonbScalar::Bool(b) => {
3864 if matches!(expected, DataType::Bool) {
3865 Ok(Value::Bool(b))
3866 } else {
3867 Err(EngineError::Eval(jsonb_cast_type_error("boolean", target)))
3868 }
3869 }
3870 JsonbScalar::Numeric(n) => {
3871 if matches!(expected, DataType::Bool) {
3872 Err(EngineError::Eval(jsonb_cast_type_error("numeric", target)))
3873 } else {
3874 coerce_value(n, expected, col_name, position)
3875 }
3876 }
3877 })(),
3878 )
3879}
3880
3881pub(crate) fn mysql_bytes_for_column(
3891 v: Value<'static>,
3892 expected: DataType,
3893 mysql: bool,
3894) -> Value<'static> {
3895 if !mysql {
3896 return v;
3897 }
3898 let Value::Bytes(ref b) = v else {
3899 return v;
3900 };
3901 match expected {
3902 DataType::SmallInt
3903 | DataType::Int
3904 | DataType::BigInt
3905 | DataType::Float
3906 | DataType::Real
3907 | DataType::Numeric { .. } => {
3908 let start = b.len().saturating_sub(16);
3909 let acc = b[start..]
3910 .iter()
3911 .fold(0u128, |a, &x| (a << 8) | u128::from(x));
3912 if acc <= i64::MAX as u128 {
3913 #[allow(clippy::cast_possible_truncation)]
3914 Value::BigInt(acc as i64)
3915 } else {
3916 big_literal_to_value(&alloc::format!("{acc}"))
3917 }
3918 }
3919 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(
3920 b.iter()
3921 .map(|&x| x as char)
3922 .collect::<alloc::string::String>(),
3923 ),
3924 _ => v,
3925 }
3926}
3927
3928fn try_coerce_time_family(
3947 v: &Value<'static>,
3948 expected: DataType,
3949) -> Option<Result<Value<'static>, EngineError>> {
3950 const DAY_US: i64 = 86_400_000_000;
3951 if expected != DataType::Time {
3952 return None;
3953 }
3954 match v {
3955 Value::TimeTz { us, .. } => Some(Ok(Value::Time(*us))),
3956 Value::Interval { micros, .. } => Some(Ok(Value::Time(micros.rem_euclid(DAY_US)))),
3957 _ => None,
3958 }
3959}
3960
3961pub(crate) fn coerce_to_oid(v: &Value<'_>) -> Result<Option<Value<'static>>, EvalError> {
3971 let as_i64 = match v {
3972 Value::Null => return Ok(Some(Value::Null)),
3973 Value::SmallInt(n) => i64::from(*n),
3974 Value::Int(n) => i64::from(*n),
3975 Value::BigInt(n) => *n,
3976 Value::Text(t) => match t.trim().parse::<i64>() {
3977 Ok(n) => n,
3978 Err(_) => {
3979 return Err(EvalError::TypeMismatch {
3980 detail: alloc::format!("invalid input syntax for type oid: {:?}", t.trim()),
3981 });
3982 }
3983 },
3984 _ => return Ok(None),
3985 };
3986 if (-(1i64 << 31)..0).contains(&as_i64) {
3988 return Ok(Some(Value::BigInt(as_i64 + (1i64 << 32))));
3989 }
3990 if !(0..=i64::from(u32::MAX)).contains(&as_i64) {
3991 return Err(EvalError::TypeMismatch {
3992 detail: "OID out of range".into(),
3993 });
3994 }
3995 Ok(Some(Value::BigInt(as_i64)))
3996}
3997
3998pub(crate) fn coerce_value(
3999 v: Value<'static>,
4000 expected: DataType,
4001 col_name: &str,
4002 position: usize,
4003) -> Result<Value<'static>, EngineError> {
4004 if v.is_null() {
4005 return Ok(Value::Null);
4006 }
4007 if let Value::Json(ref s) = v {
4012 if let Some(res) = try_coerce_json_scalar(s, expected, col_name, position) {
4013 return res;
4014 }
4015 }
4016 if let Some(res) = try_coerce_time_family(&v, expected) {
4020 return res;
4021 }
4022 if let Value::Numeric { kind, .. } = v
4038 && kind != spg_storage::NumericKind::Finite
4039 {
4040 use spg_storage::NumericKind as K;
4041 let as_f64 = match kind {
4042 K::NaN => f64::NAN,
4043 K::PosInf => f64::INFINITY,
4044 K::NegInf => f64::NEG_INFINITY,
4045 K::Finite => unreachable!("checked above"),
4046 };
4047 let what = if kind == K::NaN { "NaN" } else { "infinity" };
4049 let int_err = |target: &str| {
4050 Err(EngineError::Eval(EvalError::TypeMismatch {
4051 detail: alloc::format!("cannot convert {what} to {target}"),
4052 }))
4053 };
4054 match expected {
4055 DataType::Float => return Ok(Value::Float(as_f64)),
4056 #[allow(clippy::cast_possible_truncation)]
4057 DataType::Real => return Ok(Value::Real(as_f64 as f32)),
4058 DataType::Int => return int_err("integer"),
4059 DataType::BigInt => return int_err("bigint"),
4060 DataType::SmallInt => return int_err("smallint"),
4061 DataType::Numeric { precision, scale } => {
4062 if precision != 0 && kind != K::NaN {
4066 return Err(EngineError::Eval(EvalError::TypeMismatch {
4067 detail: alloc::string::String::from("numeric field overflow"),
4068 }));
4069 }
4070 let _ = scale;
4071 return Ok(v);
4072 }
4073 _ => {}
4074 }
4075 }
4076 if let DataType::Numeric { precision, .. } = expected {
4080 let f = match v {
4081 Value::Float(f) if !f.is_finite() => Some(f),
4082 #[allow(clippy::cast_lossless)]
4083 Value::Real(f) if !f.is_finite() => Some(f as f64),
4084 _ => None,
4085 };
4086 if let Some(f) = f {
4087 use spg_storage::NumericKind as K;
4088 if f.is_nan() {
4089 return Ok(Value::numeric_special(K::NaN));
4090 }
4091 if precision != 0 {
4092 return Err(EngineError::Eval(EvalError::TypeMismatch {
4093 detail: alloc::string::String::from("numeric field overflow"),
4094 }));
4095 }
4096 return Ok(Value::numeric_special(if f > 0.0 {
4097 K::PosInf
4098 } else {
4099 K::NegInf
4100 }));
4101 }
4102 }
4103 let Some(actual) = v.data_type() else {
4104 return coerce_untyped_value(v, expected, col_name, position);
4105 };
4106 if actual == expected {
4107 return Ok(v);
4108 }
4109 if matches!(expected, DataType::Json | DataType::Jsonb)
4133 && let Value::Text(ref s) | Value::Json(ref s) = v
4134 {
4135 let bad = || {
4136 EngineError::Eval(crate::eval::EvalError::TypeMismatch {
4137 detail: alloc::string::String::from("invalid input syntax for type json"),
4138 })
4139 };
4140 return if expected == DataType::Jsonb {
4141 crate::json::canonicalize_jsonb(s.as_ref())
4142 .map(Value::json)
4143 .map_err(|_| bad())
4144 } else {
4145 crate::json::parse(s.as_ref())
4146 .map_err(|_| bad())
4147 .map(|_| Value::json(s.clone()))
4148 };
4149 }
4150 let coerced: Option<Value<'static>> = match (v, expected) {
4151 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4152 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4153 (Value::Int(n), DataType::SmallInt) => match i16::try_from(n) {
4156 Ok(v) => Some(Value::SmallInt(v)),
4157 Err(_) => {
4158 return Err(EngineError::Eval(EvalError::TypeMismatch {
4159 detail: "smallint out of range".into(),
4160 }));
4161 }
4162 },
4163 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4164 i128::from(n),
4165 precision,
4166 scale,
4167 col_name,
4168 )?),
4169 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
4170 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4171 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4172 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4173 i128::from(n),
4174 precision,
4175 scale,
4176 col_name,
4177 )?),
4178 (Value::BigInt(n), DataType::Int) => match i32::try_from(n) {
4179 Ok(v) => Some(Value::Int(v)),
4180 Err(_) => {
4181 return Err(EngineError::Eval(EvalError::TypeMismatch {
4182 detail: "integer out of range".into(),
4183 }));
4184 }
4185 },
4186 (Value::BigInt(n), DataType::SmallInt) => match i16::try_from(n) {
4187 Ok(v) => Some(Value::SmallInt(v)),
4188 Err(_) => {
4189 return Err(EngineError::Eval(EvalError::TypeMismatch {
4190 detail: "smallint out of range".into(),
4191 }));
4192 }
4193 },
4194 #[allow(clippy::cast_precision_loss)]
4195 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
4196 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4197 i128::from(n),
4198 precision,
4199 scale,
4200 col_name,
4201 )?),
4202 (Value::Float(x), DataType::Numeric { precision, scale }) => {
4203 if precision == 0 && scale == 0 && x.is_finite() {
4209 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{x}")) {
4210 Some(Value::Numeric {
4211 scaled: mantissa,
4212 scale: src_scale,
4213 kind: spg_storage::NumericKind::Finite,
4214 })
4215 } else {
4216 Some(numeric_from_float(x, precision, scale, col_name)?)
4217 }
4218 } else {
4219 Some(numeric_from_float(x, precision, scale, col_name)?)
4220 }
4221 }
4222 (Value::Real(x), DataType::Numeric { precision, scale }) => {
4228 if precision == 0 && scale == 0 && x.is_finite() {
4229 let six = alloc::format!("{:.5e}", x);
4243 let six: f64 = six.parse().unwrap_or_else(|_| f64::from(x));
4244 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{six}")) {
4245 Some(Value::Numeric {
4246 scaled: mantissa,
4247 scale: src_scale,
4248 kind: spg_storage::NumericKind::Finite,
4249 })
4250 } else {
4251 Some(numeric_from_float(
4252 f64::from(x),
4253 precision,
4254 scale,
4255 col_name,
4256 )?)
4257 }
4258 } else {
4259 Some(numeric_from_float(
4260 f64::from(x),
4261 precision,
4262 scale,
4263 col_name,
4264 )?)
4265 }
4266 }
4267 (Value::Text(s), DataType::Numeric { precision, scale }) => {
4278 if let Some(kind) = crate::numeric::parse_numeric_special(&s) {
4281 return Ok(Value::numeric_special(kind));
4282 }
4283 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
4284 match spg_sql::parser::expand_scientific_literal(&s) {
4289 spg_sql::parser::SciExpanded::Expanded(plain) => {
4290 return coerce_value(
4291 Value::Text(plain.into()),
4292 DataType::Numeric { precision, scale },
4293 col_name,
4294 position,
4295 );
4296 }
4297 spg_sql::parser::SciExpanded::Overflow => {
4298 return Err(EngineError::Eval(EvalError::TypeMismatch {
4299 detail: "value overflows numeric format".into(),
4300 }));
4301 }
4302 spg_sql::parser::SciExpanded::NotScientific => {}
4303 }
4304 if precision == 0 && scale == 0 {
4307 if let Some(b) = spg_storage::bignum::BigNumeric::from_decimal_str(&s) {
4308 return Ok(Value::NumericBig(alloc::boxed::Box::new(b)));
4309 }
4310 }
4311 return Err(EngineError::Eval(EvalError::TypeMismatch {
4312 detail: alloc::format!("invalid input syntax for type numeric: \"{s}\""),
4313 }));
4314 };
4315 if precision == 0 && scale == 0 {
4317 Some(Value::Numeric {
4318 scaled: mantissa,
4319 scale: src_scale,
4320 kind: spg_storage::NumericKind::Finite,
4321 })
4322 } else {
4323 Some(numeric_rescale(
4324 mantissa, src_scale, precision, scale, col_name,
4325 )?)
4326 }
4327 }
4328 (Value::Text(s), DataType::Date) => {
4330 let d = eval::parse_date_literal(&s)
4337 .or_else(|| {
4338 eval::parse_timestamp_literal(&s)
4339 .and_then(|t| i32::try_from(t.div_euclid(86_400_000_000)).ok())
4340 })
4341 .ok_or_else(|| datetime_parse_error("date", &s))?;
4342 Some(Value::Date(d))
4343 }
4344 (Value::Text(s), DataType::SmallInt) => Some(Value::SmallInt(
4359 parse_pg_int(&s)
4360 .and_then(|n| i16::try_from(n).ok())
4361 .ok_or_else(|| invalid_input_syntax("smallint", &s))?,
4362 )),
4363 (Value::Text(s), DataType::Int) => Some(Value::Int(
4364 parse_pg_int(&s)
4365 .and_then(|n| i32::try_from(n).ok())
4366 .ok_or_else(|| invalid_input_syntax("integer", &s))?,
4367 )),
4368 (Value::Text(s), DataType::BigInt) => Some(Value::BigInt(
4369 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("bigint", &s))?,
4370 )),
4371 (Value::Text(s), DataType::Xid) => Some(Value::Xid(
4377 s.parse::<u32>()
4378 .map_err(|_| invalid_input_syntax("xid", &s))?,
4379 )),
4380 (Value::Xid(x), DataType::Xid) => Some(Value::Xid(x)),
4381 (Value::Text(s), DataType::Xid8) => Some(Value::BigInt(
4382 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("xid8", &s))?,
4383 )),
4384 (Value::BigInt(n), DataType::Xid8) => Some(Value::BigInt(n)),
4391 (ref other, DataType::Oid) => coerce_to_oid(other)?,
4395 (Value::Text(s), DataType::Float) => {
4396 Some(Value::Float(
4400 parse_float8(&s).ok_or_else(|| float_text_error(&s, "double precision"))?,
4401 ))
4402 }
4403 (Value::Int(n), DataType::Real) => Some(Value::Real(n as f32)),
4405 (Value::SmallInt(n), DataType::Real) => Some(Value::Real(f32::from(n))),
4406 (Value::BigInt(n), DataType::Real) => Some(Value::Real(n as f32)),
4407 (Value::Float(x), DataType::Real) => {
4408 let narrowed = x as f32;
4412 if narrowed.is_infinite() && x.is_finite() {
4413 return Err(EngineError::Eval(EvalError::TypeMismatch {
4414 detail: "value out of range: overflow".into(),
4415 }));
4416 }
4417 if narrowed == 0.0 && x != 0.0 {
4419 return Err(EngineError::Eval(EvalError::TypeMismatch {
4420 detail: "value out of range: underflow".into(),
4421 }));
4422 }
4423 Some(Value::Real(narrowed))
4424 }
4425 (
4426 Value::Numeric {
4427 scaled,
4428 scale,
4429 kind,
4430 },
4431 DataType::Real,
4432 ) => Some(Value::Real(match kind {
4433 spg_storage::NumericKind::NaN => f32::NAN,
4434 spg_storage::NumericKind::PosInf => f32::INFINITY,
4435 spg_storage::NumericKind::NegInf => f32::NEG_INFINITY,
4436 spg_storage::NumericKind::Finite => {
4437 let mut div = 1.0f64;
4438 for _ in 0..scale {
4439 div *= 10.0;
4440 }
4441 let x = (scaled as f64 / div) as f32;
4442 if x == 0.0 && scaled != 0 {
4445 return Err(real_out_of_range(&crate::eval::format_numeric(
4446 scaled, scale,
4447 )));
4448 }
4449 x
4450 }
4451 })),
4452 (Value::Real(x), DataType::Float) => Some(Value::Float(f64::from(x))),
4453 (Value::Text(s), DataType::Real) => {
4460 let t = s.trim();
4461 let x = t
4462 .parse::<f32>()
4463 .ok()
4464 .ok_or_else(|| invalid_input_syntax("real", &s))?;
4465 if x.is_infinite() && !text_is_explicit_infinity(t) {
4466 return Err(real_out_of_range(t));
4467 }
4468 if x == 0.0 && float_text_is_nonzero(t) {
4471 return Err(real_out_of_range(t));
4472 }
4473 Some(Value::Real(x))
4474 }
4475 (Value::Text(s), DataType::Bool) => match s.trim().to_ascii_lowercase().as_str() {
4479 "0" | "f" | "fa" | "fal" | "fals" | "false" | "n" | "no" | "of" | "off" => {
4480 Some(Value::Bool(false))
4481 }
4482 "1" | "t" | "tr" | "tru" | "true" | "y" | "ye" | "yes" | "on" => {
4483 Some(Value::Bool(true))
4484 }
4485 _ => return Err(invalid_input_syntax("boolean", &s)),
4486 },
4487 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4496 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4497 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4498 (Value::Json(s), DataType::Text) => Some(Value::text(s)),
4520 (Value::Json(s), DataType::Json) => Some(Value::json(s)),
4528 (Value::Json(s), DataType::Jsonb) => Some(Value::json(
4529 crate::json::canonicalize_jsonb(s.as_ref()).unwrap_or_else(|_| s.into_owned()),
4530 )),
4531 (Value::Text(s), DataType::Bytes) => {
4538 let bytes = decode_bytea_literal(&s)
4539 .map_err(|e| EngineError::Eval(EvalError::TypeMismatch { detail: e }))?;
4540 Some(Value::bytes(bytes))
4541 }
4542 (Value::Bytes(b), DataType::Text) => Some(Value::text(encode_bytea_hex(&b))),
4546 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
4554 Some(b) => Some(Value::Uuid(b)),
4555 None => {
4556 return Err(EngineError::Eval(EvalError::TypeMismatch {
4557 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4558 }));
4559 }
4560 },
4561 (Value::Uuid(b), DataType::Text) => Some(Value::text(spg_storage::format_uuid(&b))),
4566 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
4572 Some(us) => Some(Value::Time(us)),
4573 None => {
4574 let time_shaped = {
4580 let core = s.trim().split('.').next().unwrap_or("");
4581 !core.is_empty()
4582 && core.split(':').count() >= 2
4583 && core
4584 .split(':')
4585 .all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()))
4586 };
4587 let detail = if time_shaped {
4588 alloc::format!("date/time field value out of range: {s:?}")
4589 } else {
4590 alloc::format!("invalid input syntax for type time: {s:?}")
4591 };
4592 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
4593 }
4594 },
4595 (Value::Time(us), DataType::Text) => Some(Value::text(eval::format_time(us))),
4597 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4602 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4603 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
4604 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
4608 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
4609 Err(_) => {
4610 return Err(EngineError::Eval(EvalError::TypeMismatch {
4611 detail: alloc::format!("invalid input syntax for type year: {s:?}"),
4612 }));
4613 }
4614 },
4615 (Value::Year(y), DataType::Text) => Some(Value::text(alloc::format!("{y:04}"))),
4617 (Value::Time(t), DataType::TimeTz) => Some(Value::TimeTz {
4631 us: t,
4632 offset_secs: 0,
4633 }),
4634 (Value::Timestamp(t), DataType::TimeTz) => Some(Value::TimeTz {
4635 us: t.rem_euclid(86_400_000_000),
4636 offset_secs: 0,
4637 }),
4638 (Value::Text(s), DataType::TimeTz) => {
4639 match parse_timetz_str(&s).or_else(|| parse_time_str(s.trim()).map(|us| (us, 0))) {
4640 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
4641 None => {
4642 return Err(EngineError::Eval(EvalError::TypeMismatch {
4643 detail: alloc::format!(
4644 "invalid input syntax for type time with time zone: \
4645 {s:?}"
4646 ),
4647 }));
4648 }
4649 }
4650 }
4651 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
4653 Some(Value::text(eval::format_timetz(us, offset_secs)))
4654 }
4655 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
4659 Some(c) => Some(Value::Money(c)),
4660 None => {
4661 return Err(EngineError::Eval(EvalError::TypeMismatch {
4662 detail: alloc::format!("invalid input syntax for type money: {s:?}"),
4663 }));
4664 }
4665 },
4666 (Value::SmallInt(n), DataType::Money) => {
4670 Some(Value::Money(i64::from(n).saturating_mul(100)))
4671 }
4672 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
4673 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
4674 (Value::Float(x), DataType::Money) => {
4675 let scaled = x * 100.0;
4678 let cents = if scaled >= 0.0 {
4679 (scaled + 0.5) as i64
4680 } else {
4681 (scaled - 0.5) as i64
4682 };
4683 Some(Value::Money(cents))
4684 }
4685 (Value::Numeric { scaled, scale, .. }, DataType::Money) => {
4686 let cents = if scale == 2 {
4689 scaled
4690 } else if scale < 2 {
4691 let mult = 10_i128.pow(u32::from(2 - scale));
4692 scaled.saturating_mul(mult)
4693 } else {
4694 let div = 10_i128.pow(u32::from(scale - 2));
4695 let half = div / 2;
4696 let bias = if scaled >= 0 { half } else { -half };
4697 (scaled + bias) / div
4698 };
4699 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
4700 }
4701 (Value::Money(c), DataType::Text) => Some(Value::text(eval::format_money(c))),
4703 (Value::Money(c), DataType::Numeric { .. }) => Some(Value::Numeric {
4705 scaled: i128::from(c),
4706 scale: 2,
4707 kind: spg_storage::NumericKind::Finite,
4708 }),
4709 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
4713 Ok(v) => Some(v),
4714 Err(RangeParseError::Misordered) => {
4716 return Err(EngineError::Eval(EvalError::TypeMismatch {
4717 detail: alloc::string::String::from(
4718 "range lower bound must be less than or equal to range upper bound",
4719 ),
4720 }));
4721 }
4722 Err(RangeParseError::Malformed) => {
4723 return Err(EngineError::Eval(EvalError::TypeMismatch {
4724 detail: alloc::format!("malformed range literal: \"{s}\""),
4725 }));
4726 }
4727 Err(RangeParseError::BadElement(bad)) => {
4728 return Err(EngineError::Eval(EvalError::TypeMismatch {
4729 detail: alloc::format!(
4730 "invalid input syntax for type {}: \"{bad}\"",
4731 range_element_type_name(kind)
4732 ),
4733 }));
4734 }
4735 },
4736 (v @ Value::Range { .. }, DataType::Text) => Some(Value::text(format_range_str(&v))),
4738 (Value::Text(s), DataType::Inet) => match parse_inet_text(&s) {
4740 Some((family, bits, addr)) => Some(Value::Inet { family, bits, addr }),
4741 None => {
4742 return Err(EngineError::Eval(EvalError::TypeMismatch {
4746 detail: alloc::format!("invalid input syntax for type inet: {s:?}"),
4747 }));
4748 }
4749 },
4750 (Value::Inet { family, bits, addr }, DataType::Cidr) => {
4757 let full = if family == 6 { 128 } else { 32 };
4758 let bits = if bits > full { full } else { bits };
4759 let mut masked = addr;
4760 for i in 0..16usize {
4761 let bit_start = i * 8;
4762 if bit_start >= usize::from(bits) {
4763 masked[i] = 0;
4764 } else if bit_start + 8 > usize::from(bits) {
4765 let keep = usize::from(bits) - bit_start;
4766 masked[i] &= 0xffu8 << (8 - keep);
4767 }
4768 }
4769 Some(Value::Cidr {
4770 family,
4771 bits,
4772 addr: masked,
4773 })
4774 }
4775 (Value::Cidr { family, bits, addr }, DataType::Inet) => {
4776 Some(Value::Inet { family, bits, addr })
4777 }
4778 (Value::Text(s), DataType::Cidr) => match parse_cidr_text(&s) {
4779 Ok(Some((family, bits, addr))) => Some(Value::Cidr { family, bits, addr }),
4780 Err(()) => {
4781 return Err(EngineError::Eval(EvalError::TypeMismatch {
4782 detail: alloc::format!(
4783 "invalid cidr value: {s:?} DETAIL: Value has bits set to right of mask."
4784 ),
4785 }));
4786 }
4787 Ok(None) => {
4788 return Err(EngineError::Eval(EvalError::TypeMismatch {
4789 detail: alloc::format!("invalid input syntax for type cidr: {s:?}"),
4790 }));
4791 }
4792 },
4793 (Value::Text(s), DataType::Interval) => match spg_sql::parser::parse_interval_text(&s) {
4796 Some((months, days, micros)) => Some(Value::Interval {
4797 months,
4798 days,
4799 micros,
4800 }),
4801 None => {
4802 return Err(EngineError::Eval(EvalError::TypeMismatch {
4803 detail: alloc::format!("invalid input syntax for type interval: {s:?}"),
4804 }));
4805 }
4806 },
4807 (Value::Text(s), DataType::Macaddr) => match parse_macaddr_text(&s) {
4808 Some(m) => Some(Value::Macaddr(m)),
4809 None => {
4810 return Err(EngineError::Eval(EvalError::TypeMismatch {
4811 detail: alloc::format!("invalid input syntax for type macaddr: {s:?}"),
4812 }));
4813 }
4814 },
4815 (Value::Text(s), DataType::PgLsn) => match parse_pg_lsn_text(&s) {
4817 Some(l) => Some(Value::PgLsn(l)),
4818 None => {
4819 return Err(EngineError::Eval(EvalError::TypeMismatch {
4820 detail: alloc::format!("invalid input syntax for type pg_lsn: \"{s}\""),
4821 }));
4822 }
4823 },
4824 (Value::Text(s), DataType::Macaddr8) => match parse_macaddr8_text(&s) {
4825 Some(m) => Some(Value::Macaddr8(m)),
4826 None => {
4827 return Err(EngineError::Eval(EvalError::TypeMismatch {
4828 detail: alloc::format!("invalid input syntax for type macaddr8: {s:?}"),
4829 }));
4830 }
4831 },
4832 (Value::BitString { nbits, bytes }, DataType::Bit(n)) => {
4844 let want = if n == 0 { 1 } else { n };
4846 if nbits != want {
4847 return Err(EngineError::Unsupported(alloc::format!(
4848 "bit string length {nbits} does not match type bit({want})"
4849 )));
4850 }
4851 Some(Value::BitString { nbits, bytes })
4852 }
4853 (Value::BitString { nbits, bytes }, DataType::BitVarying(n)) => {
4854 if n != 0 && nbits > n {
4855 return Err(EngineError::Unsupported(alloc::format!(
4856 "bit string too long for type bit varying({n})"
4857 )));
4858 }
4859 Some(Value::BitString { nbits, bytes })
4860 }
4861 (Value::Text(s), bit_ty @ (DataType::Bit(_) | DataType::BitVarying(_))) => {
4862 match parse_bit_string_text(&s) {
4863 Some((nbits, bytes)) => {
4864 match bit_ty {
4874 DataType::Bit(n) => {
4876 let want = if n == 0 { 1 } else { n };
4877 if nbits != want {
4878 return Err(EngineError::Unsupported(alloc::format!(
4879 "bit string length {nbits} does not match type bit({want})"
4880 )));
4881 }
4882 }
4883 DataType::BitVarying(n) if n != 0 && nbits > n => {
4884 return Err(EngineError::Unsupported(alloc::format!(
4885 "bit string too long for type bit varying({n})"
4886 )));
4887 }
4888 _ => {}
4889 }
4890 Some(Value::bit_string(nbits, bytes))
4891 }
4892 None => {
4893 let bad = s.chars().find(|c| *c != '0' && *c != '1');
4895 return Err(EngineError::Eval(EvalError::TypeMismatch {
4896 detail: match bad {
4897 Some(c) => {
4898 alloc::format!("\"{c}\" is not a valid binary digit")
4899 }
4900 None => alloc::format!("invalid input syntax for BIT: {s:?}"),
4901 },
4902 }));
4903 }
4904 }
4905 }
4906 (Value::Text(s), DataType::Xml) => {
4907 if !xml_content_is_well_formed(&s) {
4912 return Err(EngineError::Eval(EvalError::TypeMismatch {
4913 detail: alloc::format!("invalid XML content: {s:?}"),
4914 }));
4915 }
4916 Some(Value::xml(s))
4917 }
4918 (Value::BpChar(s), DataType::Char1) => {
4925 Some(Value::Char1(s.as_bytes().first().copied().unwrap_or(0)))
4926 }
4927 (Value::BpChar(s), DataType::Xml) => {
4928 let stripped = s.trim_end_matches(' ');
4929 if !xml_content_is_well_formed(stripped) {
4930 return Err(EngineError::Eval(EvalError::TypeMismatch {
4931 detail: alloc::format!("invalid XML content: {stripped:?}"),
4932 }));
4933 }
4934 Some(Value::xml(alloc::string::String::from(stripped)))
4935 }
4936 (Value::Bytes(b), DataType::SmallInt | DataType::Int | DataType::BigInt) => {
4942 let mut acc: i128 = 0;
4943 for byte in b.iter() {
4944 acc = acc.saturating_mul(256).saturating_add(i128::from(*byte));
4945 }
4946 let (fits, made) = match expected {
4947 DataType::SmallInt => (
4948 i16::try_from(acc).is_ok(),
4949 i16::try_from(acc).map(Value::SmallInt).ok(),
4950 ),
4951 DataType::Int => (
4952 i32::try_from(acc).is_ok(),
4953 i32::try_from(acc).map(Value::Int).ok(),
4954 ),
4955 _ => (
4956 i64::try_from(acc).is_ok(),
4957 i64::try_from(acc).map(Value::BigInt).ok(),
4958 ),
4959 };
4960 if !fits {
4961 return Err(EngineError::Eval(EvalError::TypeMismatch {
4962 detail: alloc::format!("{} out of range", pg_type_name_for_error(expected)),
4963 }));
4964 }
4965 made
4966 }
4967 (Value::Int(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
4970 (Value::SmallInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
4971 (Value::BigInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
4972 (Value::Text(s), DataType::Char1) => {
4973 let bytes = s.as_bytes();
4979 if bytes.len() == 4
4980 && bytes[0] == b'\\'
4981 && bytes[1..].iter().all(|b| (b'0'..=b'7').contains(b))
4982 {
4983 let v = ((bytes[1] - b'0') << 6) | ((bytes[2] - b'0') << 3) | (bytes[3] - b'0');
4984 Some(Value::Char1(v))
4985 } else {
4986 let b = s.bytes().next().unwrap_or(0);
4987 Some(Value::Char1(b))
4988 }
4989 }
4990 (Value::Inet { family, bits, addr }, DataType::Text) => {
4992 let base = format_inet(family, bits, &addr);
4996 Some(Value::text(if base.contains('/') {
4997 base
4998 } else {
4999 alloc::format!("{base}/{bits}")
5000 }))
5001 }
5002 (Value::Cidr { family, bits, addr }, DataType::Text) => {
5003 Some(Value::text(format_inet(family, bits, &addr)))
5004 }
5005 (Value::Macaddr(m), DataType::Text) => Some(Value::text(format_macaddr(&m))),
5006 (Value::Macaddr8(m), DataType::Text) => Some(Value::text(format_macaddr8(&m))),
5007 (Value::PgLsn(l), DataType::Text) => Some(Value::text(format_pg_lsn(l))),
5008 (Value::Macaddr(m), DataType::Macaddr8) => Some(Value::Macaddr8([
5011 m[0], m[1], m[2], 0xff, 0xfe, m[3], m[4], m[5],
5012 ])),
5013 (Value::BitString { nbits, bytes }, DataType::Text) => {
5014 Some(Value::text(format_bit_string(nbits, &bytes)))
5015 }
5016 #[allow(clippy::cast_possible_truncation)]
5018 (Value::BitString { nbits, bytes }, DataType::SmallInt) => {
5019 Some(Value::SmallInt(bit_string_to_i64(nbits, &bytes) as i16))
5020 }
5021 #[allow(clippy::cast_possible_truncation)]
5022 (Value::BitString { nbits, bytes }, DataType::Int) => {
5023 Some(Value::Int(bit_string_to_i64(nbits, &bytes) as i32))
5024 }
5025 (Value::BitString { nbits, bytes }, DataType::BigInt) => {
5026 Some(Value::BigInt(bit_string_to_i64(nbits, &bytes)))
5027 }
5028 (Value::Xml(s), DataType::Text) => Some(Value::text(s)),
5029 (Value::Char1(b), DataType::Text) => Some(Value::text((b as char).to_string())),
5030 (Value::Text(s), DataType::Point) => match parse_point(&s) {
5034 Some(p) => Some(Value::Point(p)),
5035 None => {
5036 return Err(EngineError::Eval(EvalError::TypeMismatch {
5037 detail: alloc::format!("invalid input syntax for type point: {s:?}"),
5038 }));
5039 }
5040 },
5041 (Value::Text(s), DataType::Lseg) => match parse_lseg_text(&s) {
5042 Some((p1, p2)) => Some(Value::Lseg(p1, p2)),
5043 None => {
5044 return Err(EngineError::Eval(EvalError::TypeMismatch {
5045 detail: alloc::format!("invalid input syntax for type lseg: {s:?}"),
5046 }));
5047 }
5048 },
5049 (Value::Text(s), DataType::PgBox) => match parse_box_text(&s) {
5050 Some((ur, ll)) => Some(Value::PgBox(ur, ll)),
5051 None => {
5052 return Err(EngineError::Eval(EvalError::TypeMismatch {
5053 detail: alloc::format!("invalid input syntax for type box: {s:?}"),
5054 }));
5055 }
5056 },
5057 (Value::Text(s), DataType::Line) => match parse_line_text(&s) {
5058 Some((a, b, c)) => Some(Value::Line { a, b, c }),
5059 None => {
5060 let zero_ab = s
5064 .trim()
5065 .strip_prefix('{')
5066 .and_then(|x| x.strip_suffix('}'))
5067 .map(|inner| inner.split(',').collect::<alloc::vec::Vec<_>>())
5068 .is_some_and(|parts| {
5069 parts.len() == 3
5070 && parts[0].trim().parse::<f64>() == Ok(0.0)
5071 && parts[1].trim().parse::<f64>() == Ok(0.0)
5072 && parts[2].trim().parse::<f64>().is_ok()
5073 });
5074 let detail = if zero_ab {
5075 alloc::string::String::from(
5076 "invalid line specification: A and B cannot both be zero",
5077 )
5078 } else {
5079 alloc::format!("invalid input syntax for type line: {s:?}")
5080 };
5081 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
5082 }
5083 },
5084 (Value::Text(s), DataType::Circle) => match parse_circle_text(&s) {
5085 Some((center, radius)) => Some(Value::Circle { center, radius }),
5086 None => {
5087 return Err(EngineError::Eval(EvalError::TypeMismatch {
5088 detail: alloc::format!("invalid input syntax for type circle: {s:?}"),
5089 }));
5090 }
5091 },
5092 (Value::Text(s), DataType::Path) => match parse_path_text(&s) {
5093 Some((points, closed)) => Some(Value::Path { points, closed }),
5094 None => {
5095 return Err(EngineError::Eval(EvalError::TypeMismatch {
5096 detail: alloc::format!("invalid input syntax for type path: {s:?}"),
5097 }));
5098 }
5099 },
5100 (Value::PgBox(a, b), DataType::Polygon) => {
5103 let (hx, hy) = (a.x.max(b.x), a.y.max(b.y));
5104 let (lx, ly) = (a.x.min(b.x), a.y.min(b.y));
5105 let p = |x: f64, y: f64| spg_storage::Point2D { x, y };
5106 Some(Value::Polygon(alloc::vec![
5107 p(lx, ly),
5108 p(lx, hy),
5109 p(hx, hy),
5110 p(hx, ly),
5111 ]))
5112 }
5113 (Value::Text(s), DataType::Polygon) => match parse_polygon_text(&s) {
5114 Some(points) => Some(Value::Polygon(points)),
5115 None => {
5116 return Err(EngineError::Eval(EvalError::TypeMismatch {
5117 detail: alloc::format!("invalid input syntax for type polygon: {s:?}"),
5118 }));
5119 }
5120 },
5121 (Value::Point(p), DataType::Text) => Some(Value::text(format_point(p))),
5123 (Value::Lseg(p1, p2), DataType::Text) => Some(Value::text(format_lseg(p1, p2))),
5124 (Value::PgBox(ur, ll), DataType::Text) => Some(Value::text(format_pg_box(ur, ll))),
5125 (Value::Line { a, b, c }, DataType::Text) => Some(Value::text(format_line(a, b, c))),
5126 (Value::Circle { center, radius }, DataType::Text) => {
5127 Some(Value::text(format_circle(center, radius)))
5128 }
5129 (Value::Path { points, closed }, DataType::Text) => {
5130 Some(Value::text(format_path(&points, closed)))
5131 }
5132 (Value::Polygon(points), DataType::Text) => Some(Value::text(format_polygon(&points))),
5133 (ref rv @ Value::Range { kind: rk, .. }, DataType::Multirange(kind)) => {
5140 if rk != kind {
5141 return Err(EngineError::Eval(EvalError::TypeMismatch {
5142 detail: alloc::format!(
5143 "cannot cast type {} to {}",
5144 DataType::Range(rk),
5145 DataType::Multirange(kind)
5146 ),
5147 }));
5148 }
5149 crate::eval::binop::range_as_multirange(rv)
5150 }
5151 (Value::Text(s), DataType::Multirange(kind)) => match parse_multirange_str(&s, kind) {
5152 Some(ranges) => Some(Value::Multirange {
5158 kind,
5159 ranges: crate::eval::binop::normalize_multirange_spans(kind, &ranges),
5160 }),
5161 None => {
5162 return Err(EngineError::Eval(EvalError::TypeMismatch {
5163 detail: alloc::format!("invalid input syntax for multirange type: {s:?}"),
5164 }));
5165 }
5166 },
5167 (Value::Multirange { ranges, .. }, DataType::Text) => {
5169 Some(Value::text(format_multirange(&ranges)))
5170 }
5171 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
5173 Some(pairs) => Some(Value::Hstore(pairs)),
5174 None => {
5175 return Err(EngineError::Eval(EvalError::TypeMismatch {
5176 detail: alloc::format!("invalid input syntax for type hstore: {s:?}"),
5177 }));
5178 }
5179 },
5180 (Value::Hstore(pairs), DataType::Text) => Some(Value::text(format_hstore_str(&pairs))),
5182 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
5185 Ok(m) => Some(Value::IntArray2D(m)),
5186 Err(e) => {
5187 return Err(EngineError::Eval(EvalError::TypeMismatch {
5188 detail: alloc::format!("invalid input syntax for INT[][]: {s:?}: {e}"),
5189 }));
5190 }
5191 },
5192 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
5193 Ok(m) => Some(Value::BigIntArray2D(m)),
5194 Err(e) => {
5195 return Err(EngineError::Eval(EvalError::TypeMismatch {
5196 detail: alloc::format!("invalid input syntax for BIGINT[][]: {s:?}: {e}"),
5197 }));
5198 }
5199 },
5200 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
5201 Ok(m) => Some(Value::TextArray2D(m)),
5202 Err(e) => {
5203 return Err(EngineError::Eval(EvalError::TypeMismatch {
5204 detail: alloc::format!("invalid input syntax for TEXT[][]: {s:?}: {e}"),
5205 }));
5206 }
5207 },
5208 (Value::IntArray2D(rows), DataType::Text) => Some(Value::text(format_int_2d_text(&rows))),
5210 (Value::BigIntArray2D(rows), DataType::Text) => {
5211 Some(Value::text(format_bigint_2d_text(&rows)))
5212 }
5213 (Value::TextArray2D(rows), DataType::Text) => Some(Value::text(format_text_2d_text(&rows))),
5214 (Value::Text(s), DataType::TextArray) => {
5219 let arr = decode_text_array_literal(&s).map_err(|_| {
5223 EngineError::Eval(EvalError::TypeMismatch {
5224 detail: malformed_array_literal(&s),
5225 })
5226 })?;
5227 Some(Value::TextArray(arr))
5228 }
5229 (Value::Text(s), DataType::IntArray) => {
5235 let arr = decode_text_array_literal(&s).map_err(|_| {
5239 EngineError::Eval(EvalError::TypeMismatch {
5240 detail: malformed_array_literal(&s),
5241 })
5242 })?;
5243 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
5244 for elem in arr {
5245 match elem {
5246 None => out.push(None),
5247 Some(t) => {
5248 let n: i32 = t.parse().map_err(|_| {
5249 EngineError::Eval(EvalError::TypeMismatch {
5250 detail: alloc::format!(
5251 "invalid input syntax for type integer: {t:?}"
5252 ),
5253 })
5254 })?;
5255 out.push(Some(n));
5256 }
5257 }
5258 }
5259 Some(Value::IntArray(out))
5260 }
5261 (Value::Text(s), DataType::SmallIntArray) => Some(Value::SmallIntArray(
5265 decode_array_elems(&s, DataType::SmallInt, col_name, position)?
5266 .into_iter()
5267 .map(|o| match o {
5268 Some(Value::SmallInt(n)) => Some(n),
5269 _ => None,
5270 })
5271 .collect(),
5272 )),
5273 (Value::Text(s), DataType::BoolArray) => {
5274 if let Some(rows) = crate::eval::values::split_2d_rows(&s) {
5279 let mut row_vals: Vec<Value<'static>> = Vec::with_capacity(rows.len());
5280 for r in &rows {
5281 let bools: Vec<Option<bool>> =
5282 decode_array_elems(r, DataType::Bool, col_name, position)?
5283 .into_iter()
5284 .map(|o| match o {
5285 Some(Value::Bool(b)) => Some(b),
5286 _ => None,
5287 })
5288 .collect();
5289 row_vals.push(Value::BoolArray(bools));
5290 }
5291 return crate::eval::values::build_2d_from_rows(&row_vals).ok_or_else(|| {
5292 EngineError::Eval(EvalError::TypeMismatch {
5293 detail: malformed_array_literal(&s),
5294 })
5295 });
5296 }
5297 Some(Value::BoolArray(
5298 decode_array_elems(&s, DataType::Bool, col_name, position)?
5299 .into_iter()
5300 .map(|o| match o {
5301 Some(Value::Bool(b)) => Some(b),
5302 _ => None,
5303 })
5304 .collect(),
5305 ))
5306 }
5307 (Value::Text(s), DataType::FloatArray) => Some(Value::FloatArray(
5308 decode_array_elems(&s, DataType::Float, col_name, position)?
5309 .into_iter()
5310 .map(|o| match o {
5311 Some(Value::Float(f)) => Some(f),
5312 _ => None,
5313 })
5314 .collect(),
5315 )),
5316 (Value::Text(s), DataType::NumericArray) => Some(Value::NumericArray(
5317 decode_array_elems(
5318 &s,
5319 DataType::Numeric {
5320 precision: 0,
5321 scale: 0,
5322 },
5323 col_name,
5324 position,
5325 )?
5326 .into_iter()
5327 .map(|o| match o {
5328 Some(Value::Numeric { scaled, scale, .. }) => Some((scaled, scale)),
5329 _ => None,
5330 })
5331 .collect(),
5332 )),
5333 (Value::Text(s), DataType::DateArray) => Some(Value::DateArray(
5334 decode_array_elems(&s, DataType::Date, col_name, position)?
5335 .into_iter()
5336 .map(|o| match o {
5337 Some(Value::Date(d)) => Some(d),
5338 _ => None,
5339 })
5340 .collect(),
5341 )),
5342 (Value::Text(s), DataType::UuidArray) => Some(Value::UuidArray(
5343 decode_array_elems(&s, DataType::Uuid, col_name, position)?
5344 .into_iter()
5345 .map(|o| match o {
5346 Some(Value::Uuid(u)) => Some(u),
5347 _ => None,
5348 })
5349 .collect(),
5350 )),
5351 (Value::Text(s), DataType::BigIntArray | DataType::OidArray) => {
5358 let arr = decode_text_array_literal(&s).map_err(|_| {
5362 EngineError::Eval(EvalError::TypeMismatch {
5363 detail: malformed_array_literal(&s),
5364 })
5365 })?;
5366 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
5367 for elem in arr {
5368 match elem {
5369 None => out.push(None),
5370 Some(t) => {
5371 let n: i64 = t.parse().map_err(|_| {
5372 EngineError::Eval(EvalError::TypeMismatch {
5373 detail: alloc::format!(
5374 "invalid input syntax for type bigint: {t:?}"
5375 ),
5376 })
5377 })?;
5378 out.push(Some(n));
5379 }
5380 }
5381 }
5382 Some(Value::BigIntArray(out))
5383 }
5384 (Value::TextArray(items), DataType::Text) => Some(Value::text(encode_text_array(&items))),
5388 (Value::TextArray(items), DataType::BoolArray) if items.is_empty() => {
5396 Some(Value::BoolArray(alloc::vec::Vec::new()))
5397 }
5398 (Value::TextArray(items), DataType::SmallIntArray) if items.is_empty() => {
5399 Some(Value::SmallIntArray(alloc::vec::Vec::new()))
5400 }
5401 (Value::TextArray(items), DataType::IntArray) if items.is_empty() => {
5402 Some(Value::IntArray(alloc::vec::Vec::new()))
5403 }
5404 (Value::TextArray(items), DataType::BigIntArray) if items.is_empty() => {
5405 Some(Value::BigIntArray(alloc::vec::Vec::new()))
5406 }
5407 (Value::TextArray(items), DataType::FloatArray) if items.is_empty() => {
5408 Some(Value::FloatArray(alloc::vec::Vec::new()))
5409 }
5410 (Value::TextArray(items), DataType::FloatArray) => {
5413 let mut out = alloc::vec::Vec::with_capacity(items.len());
5414 let mut ok = true;
5415 for item in items {
5416 match item {
5417 None => out.push(None),
5418 Some(s) => match s.trim().parse::<f64>() {
5419 Ok(x) => out.push(Some(x)),
5420 Err(_) => {
5421 ok = false;
5422 break;
5423 }
5424 },
5425 }
5426 }
5427 if ok {
5428 Some(Value::FloatArray(out))
5429 } else {
5430 None
5431 }
5432 }
5433 (Value::FloatArray(items), DataType::FloatArray) => Some(Value::FloatArray(items)),
5436 #[allow(clippy::cast_precision_loss)]
5437 (Value::IntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5438 items.into_iter().map(|o| o.map(|n| f64::from(n))).collect(),
5439 )),
5440 #[allow(clippy::cast_precision_loss)]
5441 (Value::BigIntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5442 items.into_iter().map(|o| o.map(|n| n as f64)).collect(),
5443 )),
5444 #[allow(clippy::cast_precision_loss)]
5448 (Value::NumericArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5449 items
5450 .into_iter()
5451 .map(|o| {
5452 o.map(|(scaled, scale)| {
5453 crate::eval::format_numeric(scaled, scale)
5454 .parse()
5455 .unwrap_or(f64::NAN)
5456 })
5457 })
5458 .collect(),
5459 )),
5460 (Value::IntArray(items), DataType::BigIntArray) => Some(Value::BigIntArray(
5465 items.into_iter().map(|o| o.map(i64::from)).collect(),
5466 )),
5467 (Value::BigIntArray(items), DataType::IntArray) => {
5468 let mut out = alloc::vec::Vec::with_capacity(items.len());
5469 let mut ok = true;
5470 for o in items {
5471 match o {
5472 None => out.push(None),
5473 Some(n) => match i32::try_from(n) {
5474 Ok(v) => out.push(Some(v)),
5475 Err(_) => {
5476 ok = false;
5477 break;
5478 }
5479 },
5480 }
5481 }
5482 if ok { Some(Value::IntArray(out)) } else { None }
5483 }
5484 (Value::IntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5485 items
5486 .into_iter()
5487 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5488 .collect(),
5489 )),
5490 (Value::BigIntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5491 items
5492 .into_iter()
5493 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5494 .collect(),
5495 )),
5496 (Value::FloatArray(items), DataType::NumericArray) => {
5497 let mut out = alloc::vec::Vec::with_capacity(items.len());
5498 let mut ok = true;
5499 for o in items {
5500 match o {
5501 None => out.push(None),
5502 Some(x) => match parse_numeric_text(&alloc::format!("{x}")) {
5503 Some((mantissa, scale)) => out.push(Some((mantissa, scale))),
5504 None => {
5505 ok = false;
5506 break;
5507 }
5508 },
5509 }
5510 }
5511 if ok {
5512 Some(Value::NumericArray(out))
5513 } else {
5514 None
5515 }
5516 }
5517 (Value::NumericArray(items), DataType::IntArray) => {
5521 let mut out = alloc::vec::Vec::with_capacity(items.len());
5522 let mut ok = true;
5523 for o in items {
5524 match o {
5525 None => out.push(None),
5526 Some((scaled, scale)) => {
5527 match i32::try_from(numeric_round_to_integer(scaled, scale)) {
5528 Ok(v) => out.push(Some(v)),
5529 Err(_) => {
5530 ok = false;
5531 break;
5532 }
5533 }
5534 }
5535 }
5536 }
5537 if ok { Some(Value::IntArray(out)) } else { None }
5538 }
5539 (Value::NumericArray(items), DataType::BigIntArray) => {
5540 let mut out = alloc::vec::Vec::with_capacity(items.len());
5541 let mut ok = true;
5542 for o in items {
5543 match o {
5544 None => out.push(None),
5545 Some((scaled, scale)) => {
5546 match i64::try_from(numeric_round_to_integer(scaled, scale)) {
5547 Ok(v) => out.push(Some(v)),
5548 Err(_) => {
5549 ok = false;
5550 break;
5551 }
5552 }
5553 }
5554 }
5555 }
5556 if ok {
5557 Some(Value::BigIntArray(out))
5558 } else {
5559 None
5560 }
5561 }
5562 #[allow(clippy::cast_possible_truncation)]
5566 (Value::FloatArray(items), DataType::IntArray) => {
5567 let mut out = alloc::vec::Vec::with_capacity(items.len());
5568 let mut ok = true;
5569 for o in items {
5570 match o {
5571 None => out.push(None),
5572 Some(x) if x.is_finite() => {
5573 let r = crate::eval::math::f64_round_half_even(x);
5574 if r >= f64::from(i32::MIN) && r <= f64::from(i32::MAX) {
5575 out.push(Some(r as i32));
5576 } else {
5577 ok = false;
5578 break;
5579 }
5580 }
5581 Some(_) => {
5582 ok = false;
5583 break;
5584 }
5585 }
5586 }
5587 if ok { Some(Value::IntArray(out)) } else { None }
5588 }
5589 #[allow(clippy::cast_possible_truncation)]
5590 (Value::FloatArray(items), DataType::BigIntArray) => {
5591 let mut out = alloc::vec::Vec::with_capacity(items.len());
5592 let mut ok = true;
5593 for o in items {
5594 match o {
5595 None => out.push(None),
5596 Some(x) if x.is_finite() => {
5597 out.push(Some(crate::eval::math::f64_round_half_even(x) as i64));
5598 }
5599 Some(_) => {
5600 ok = false;
5601 break;
5602 }
5603 }
5604 }
5605 if ok {
5606 Some(Value::BigIntArray(out))
5607 } else {
5608 None
5609 }
5610 }
5611 (Value::TextArray(items), DataType::NumericArray) if items.is_empty() => {
5612 Some(Value::NumericArray(alloc::vec::Vec::new()))
5613 }
5614 (Value::TextArray(items), DataType::DateArray) if items.is_empty() => {
5615 Some(Value::DateArray(alloc::vec::Vec::new()))
5616 }
5617 (Value::TextArray(items), DataType::TimestampArray) if items.is_empty() => {
5618 Some(Value::TimestampArray(alloc::vec::Vec::new()))
5619 }
5620 (Value::TextArray(items), DataType::TimestamptzArray) if items.is_empty() => {
5621 Some(Value::TimestamptzArray(alloc::vec::Vec::new()))
5622 }
5623 (Value::TextArray(items), DataType::UuidArray) if items.is_empty() => {
5624 Some(Value::UuidArray(alloc::vec::Vec::new()))
5625 }
5626 (Value::TextArray(items), DataType::JsonArray) if items.is_empty() => {
5627 Some(Value::JsonArray(alloc::vec::Vec::new()))
5628 }
5629 (Value::TextArray(items), DataType::JsonbArray) if items.is_empty() => {
5630 Some(Value::JsonbArray(alloc::vec::Vec::new()))
5631 }
5632 (Value::TextArray(items), DataType::BytesArray) if items.is_empty() => {
5633 Some(Value::BytesArray(alloc::vec::Vec::new()))
5634 }
5635 (Value::TextArray(items), DataType::IntervalArray) if items.is_empty() => {
5636 Some(Value::IntervalArray(alloc::vec::Vec::new()))
5637 }
5638 (
5642 Value::TextArray(items),
5643 dt @ (DataType::BoolArray
5644 | DataType::NumericArray
5645 | DataType::DateArray
5646 | DataType::TimestampArray
5647 | DataType::TimestamptzArray
5648 | DataType::IntervalArray
5649 | DataType::UuidArray),
5650 ) => coerce_text_array_to(items, dt, col_name)?,
5651 (
5657 Value::Text(s),
5658 dt @ (DataType::TimestampArray | DataType::TimestamptzArray | DataType::IntervalArray),
5659 ) => {
5660 let items = decode_text_array_literal(&s).map_err(|_| {
5661 EngineError::Eval(EvalError::TypeMismatch {
5662 detail: malformed_array_literal(&s),
5663 })
5664 })?;
5665 coerce_text_array_to(items, dt, col_name)?
5666 }
5667 (Value::TextArray(items), DataType::MoneyArray) if items.is_empty() => {
5668 Some(Value::MoneyArray(alloc::vec::Vec::new()))
5669 }
5670 (Value::IntArray(items), DataType::SmallIntArray) => {
5675 let mut out = alloc::vec::Vec::with_capacity(items.len());
5676 let mut ok = true;
5677 for item in items {
5678 match item {
5679 None => out.push(None),
5680 Some(n) => match i16::try_from(n) {
5681 Ok(x) => out.push(Some(x)),
5682 Err(_) => {
5683 ok = false;
5684 break;
5685 }
5686 },
5687 }
5688 }
5689 if ok {
5690 Some(Value::SmallIntArray(out))
5691 } else {
5692 None
5693 }
5694 }
5695 (Value::Text(s), DataType::Vector { dim, encoding }) => {
5704 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
5705 EngineError::Eval(EvalError::TypeMismatch {
5706 detail: alloc::format!("cannot parse {s:?} as VECTOR"),
5707 })
5708 })?;
5709 if parsed.len() != dim as usize {
5710 return Err(EngineError::Eval(EvalError::TypeMismatch {
5711 detail: alloc::format!(
5712 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
5713 parsed.len()
5714 ),
5715 }));
5716 }
5717 Some(match encoding {
5718 VecEncoding::F32 => Value::vector(parsed),
5719 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
5720 VecEncoding::F16 => {
5721 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
5722 }
5723 })
5724 }
5725 (Value::Text(s), DataType::TsVector) => {
5735 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
5736 EngineError::Eval(EvalError::TypeMismatch {
5737 detail: alloc::format!("cannot parse {s:?} as TSVECTOR: {e}"),
5738 })
5739 })?;
5740 Some(Value::TsVector(lexs))
5741 }
5742 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
5743 let t = eval::parse_timestamp_literal(&s)
5744 .ok_or_else(|| datetime_parse_error("timestamp", &s))?;
5745 Some(Value::Timestamp(t))
5746 }
5747 (Value::Date(i32::MAX), DataType::Timestamp | DataType::Timestamptz) => {
5750 Some(Value::Timestamp(i64::MAX))
5751 }
5752 (Value::Date(i32::MIN), DataType::Timestamp | DataType::Timestamptz) => {
5753 Some(Value::Timestamp(i64::MIN))
5754 }
5755 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
5756 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
5757 }
5758 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
5762 (Value::Timestamp(t), DataType::Date) => {
5763 let days = t.div_euclid(86_400_000_000);
5764 i32::try_from(days).ok().map(Value::Date)
5765 }
5766 (Value::Timestamp(t), DataType::Time) => Some(Value::Time(t.rem_euclid(86_400_000_000))),
5775 (
5779 Value::NumericBig(b),
5780 DataType::Numeric {
5781 precision: 0,
5782 scale: 0,
5783 },
5784 ) => Some(Value::NumericBig(b)),
5785 (
5786 Value::Numeric {
5787 scaled,
5788 scale: src_scale,
5789 ..
5790 },
5791 DataType::Numeric { precision, scale },
5792 ) => {
5793 if precision == 0 && scale == 0 {
5799 Some(Value::Numeric {
5800 scaled,
5801 scale: src_scale,
5802 kind: spg_storage::NumericKind::Finite,
5803 })
5804 } else {
5805 Some(numeric_rescale(
5806 scaled, src_scale, precision, scale, col_name,
5807 )?)
5808 }
5809 }
5810 (Value::NumericBig(b), DataType::Numeric { precision, scale }) => {
5815 if precision == 0 && scale == 0 {
5816 Some(Value::NumericBig(b))
5817 } else {
5818 #[allow(clippy::cast_sign_loss)]
5819 let rounded = if scale < 0 {
5820 b.round_to(0)
5822 } else {
5823 b.round_to(scale as u16)
5824 };
5825 let out = crate::eval::binop::bignum_to_value(rounded);
5826 crate::numeric::check_precision_text(&out, precision, scale, col_name)?;
5829 Some(out)
5830 }
5831 }
5832 #[allow(clippy::cast_precision_loss)]
5833 (Value::Numeric { scaled, scale, .. }, DataType::Float) => {
5834 let text = crate::eval::format_numeric(scaled, scale);
5841 let x: f64 = text.parse().unwrap_or(f64::NAN);
5842 if x == 0.0 && scaled != 0 {
5846 return Err(float_out_of_range(
5847 &crate::eval::format_numeric(scaled, scale),
5848 "double precision",
5849 ));
5850 }
5851 Some(Value::Float(x))
5852 }
5853 (Value::NumericBig(b), DataType::Real) => {
5861 let text = b.to_decimal_str();
5862 let x: f32 = text.parse().map_err(|_| real_out_of_range(&text))?;
5863 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
5864 return Err(real_out_of_range(&text));
5865 }
5866 Some(Value::Real(x))
5867 }
5868 (Value::NumericBig(b), DataType::Float) => {
5869 let text = b.to_decimal_str();
5873 let x: f64 = text
5874 .parse()
5875 .map_err(|_| float_out_of_range(&text, "double precision"))?;
5876 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
5877 return Err(float_out_of_range(&text, "double precision"));
5878 }
5879 Some(Value::Float(x))
5880 }
5881 (Value::Float(x), DataType::Int) => {
5889 let r = crate::eval::math::f64_round_half_even(x);
5890 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
5891 return Err(EngineError::Eval(EvalError::TypeMismatch {
5892 detail: "integer out of range".into(),
5893 }));
5894 }
5895 #[allow(clippy::cast_possible_truncation)]
5896 Some(Value::Int(r as i32))
5897 }
5898 (Value::Float(x), DataType::BigInt) => {
5899 let r = crate::eval::math::f64_round_half_even(x);
5900 if !r.is_finite()
5901 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
5902 {
5903 return Err(EngineError::Eval(EvalError::TypeMismatch {
5904 detail: "bigint out of range".into(),
5905 }));
5906 }
5907 #[allow(clippy::cast_possible_truncation)]
5908 Some(Value::BigInt(r as i64))
5909 }
5910 (Value::Float(x), DataType::SmallInt) => {
5911 let r = crate::eval::math::f64_round_half_even(x);
5912 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
5913 return Err(EngineError::Eval(EvalError::TypeMismatch {
5914 detail: "smallint out of range".into(),
5915 }));
5916 }
5917 #[allow(clippy::cast_possible_truncation)]
5918 Some(Value::SmallInt(r as i16))
5919 }
5920 (Value::Real(x), DataType::Int) => {
5924 let r = crate::eval::math::f64_round_half_even(f64::from(x));
5925 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
5926 return Err(EngineError::Eval(EvalError::TypeMismatch {
5927 detail: "integer out of range".into(),
5928 }));
5929 }
5930 #[allow(clippy::cast_possible_truncation)]
5931 Some(Value::Int(r as i32))
5932 }
5933 (Value::Real(x), DataType::BigInt) => {
5934 let r = crate::eval::math::f64_round_half_even(f64::from(x));
5935 if !r.is_finite()
5936 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
5937 {
5938 return Err(EngineError::Eval(EvalError::TypeMismatch {
5939 detail: "bigint out of range".into(),
5940 }));
5941 }
5942 #[allow(clippy::cast_possible_truncation)]
5943 Some(Value::BigInt(r as i64))
5944 }
5945 (Value::Real(x), DataType::SmallInt) => {
5946 let r = crate::eval::math::f64_round_half_even(f64::from(x));
5947 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
5948 return Err(EngineError::Eval(EvalError::TypeMismatch {
5949 detail: "smallint out of range".into(),
5950 }));
5951 }
5952 #[allow(clippy::cast_possible_truncation)]
5953 Some(Value::SmallInt(r as i16))
5954 }
5955 (Value::Numeric { scaled, scale, .. }, DataType::Int) => {
5956 let rounded = numeric_round_to_integer(scaled, scale);
5957 i32::try_from(rounded).ok().map(Value::Int)
5958 }
5959 (Value::Numeric { scaled, scale, .. }, DataType::BigInt) => {
5960 let rounded = numeric_round_to_integer(scaled, scale);
5961 i64::try_from(rounded).ok().map(Value::BigInt)
5962 }
5963 (Value::Numeric { scaled, scale, .. }, DataType::SmallInt) => {
5964 let rounded = numeric_round_to_integer(scaled, scale);
5965 i16::try_from(rounded).ok().map(Value::SmallInt)
5966 }
5967 (Value::Text(s), DataType::Name) => {
5974 let mut cut = s.into_owned();
5975 if cut.len() > 63 {
5976 let mut idx = 63;
5977 while !cut.is_char_boundary(idx) {
5978 idx -= 1;
5979 }
5980 cut.truncate(idx);
5981 }
5982 Some(Value::text(cut))
5983 }
5984 (Value::Text(s), DataType::Varchar(max)) => {
5985 if max == 0 || u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
5986 Some(Value::text(s))
5987 } else {
5988 let excess_all_blanks = s.chars().skip(max as usize).all(|c| c == ' ');
5993 if excess_all_blanks {
5994 Some(Value::text(
5995 s.chars()
5996 .take(max as usize)
5997 .collect::<alloc::string::String>(),
5998 ))
5999 } else {
6000 return Err(EngineError::Unsupported(alloc::format!(
6001 "value too long for type character varying({max})"
6002 )));
6003 }
6004 }
6005 }
6006 (
6014 Value::Vector(v),
6015 DataType::Vector {
6016 dim,
6017 encoding: VecEncoding::Sq8,
6018 },
6019 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
6020 (
6025 Value::Vector(v),
6026 DataType::Vector {
6027 dim,
6028 encoding: VecEncoding::F16,
6029 },
6030 ) if v.len() == dim as usize => Some(Value::HalfVector(
6031 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
6032 )),
6033 (Value::Text(s), DataType::Char(size)) => {
6037 if size == 0 {
6041 return Ok(Value::BpChar(alloc::borrow::Cow::Owned(
6042 s.trim_end_matches(' ').to_string(),
6043 )));
6044 }
6045 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
6046 let body = if len > size {
6047 let trimmed = s.trim_end_matches(' ');
6048 let tlen = u32::try_from(trimmed.chars().count()).unwrap_or(u32::MAX);
6049 if tlen > size {
6050 return Err(EngineError::Unsupported(alloc::format!(
6051 "value too long for type character({size})"
6052 )));
6053 }
6054 trimmed.to_string()
6055 } else {
6056 s.into_owned()
6057 };
6058 let need = (size as usize) - body.chars().count();
6059 let mut padded = body;
6060 padded.reserve(need);
6061 for _ in 0..need {
6062 padded.push(' ');
6063 }
6064 Some(Value::BpChar(alloc::borrow::Cow::Owned(padded)))
6068 }
6069 _ => None,
6070 };
6071 coerced.ok_or_else(|| {
6072 EngineError::Storage(StorageError::TypeMismatch {
6073 column: col_name.into(),
6074 expected,
6075 actual,
6076 position,
6077 })
6078 })
6079}
6080
6081pub(crate) fn big_literal_to_value(s: &str) -> Value<'static> {
6084 let b = spg_storage::bignum::BigNumeric::from_decimal_str(s).expect("lexer-validated decimal");
6085 match b.to_i128() {
6086 Some(scaled) => Value::Numeric {
6087 scaled,
6088 scale: b.scale(),
6089 kind: spg_storage::NumericKind::Finite,
6090 },
6091 None => Value::NumericBig(alloc::boxed::Box::new(b)),
6092 }
6093}
6094
6095pub(crate) fn types_unify(a: DataType, b: DataType) -> bool {
6104 fn category(t: DataType) -> Option<u8> {
6105 Some(match t {
6106 DataType::SmallInt
6107 | DataType::Int
6108 | DataType::BigInt
6109 | DataType::Numeric { .. }
6110 | DataType::Real
6111 | DataType::Float => 1,
6112 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => 2,
6113 DataType::Date | DataType::Timestamp | DataType::Timestamptz => 3,
6114 _ => return None,
6115 })
6116 }
6117 if a == b {
6118 return true;
6119 }
6120 match (category(a), category(b)) {
6121 (Some(x), Some(y)) => x == y,
6122 _ => false,
6125 }
6126}
6127
6128pub(crate) fn pg_type_name_for_error_opt(t: Option<DataType>) -> alloc::string::String {
6142 match t {
6143 Some(t) => pg_type_name_for_error(t),
6144 None => alloc::string::String::from("unknown"),
6145 }
6146}
6147
6148pub(crate) fn pg_type_name_for_error(t: DataType) -> alloc::string::String {
6149 use spg_storage::DataType as D;
6150 let elem = match t {
6151 D::TextArray => Some(D::Text),
6152 D::IntArray => Some(D::Int),
6153 D::BigIntArray => Some(D::BigInt),
6154 D::SmallIntArray => Some(D::SmallInt),
6155 D::FloatArray => Some(D::Float),
6156 D::NumericArray => Some(D::Numeric {
6157 precision: 0,
6158 scale: 0,
6159 }),
6160 D::BoolArray => Some(D::Bool),
6161 D::DateArray => Some(D::Date),
6162 D::TimestampArray => Some(D::Timestamp),
6163 D::TimestamptzArray => Some(D::Timestamptz),
6164 D::IntervalArray => Some(D::Interval),
6165 D::UuidArray => Some(D::Uuid),
6166 D::JsonArray | D::JsonbArray => Some(D::Jsonb),
6167 D::BytesArray => Some(D::Bytes),
6168 D::MoneyArray => Some(D::Money),
6169 _ => None,
6170 };
6171 match elem {
6172 Some(e) => alloc::format!("{}[]", crate::system_catalog::pg_data_type_text(e)),
6173 None => crate::system_catalog::pg_data_type_text(t),
6174 }
6175}