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::String(s) => Value::text(s),
2874 Literal::Bool(b) => Value::Bool(b),
2875 Literal::Null => Value::Null,
2876 Literal::Vector(v) => Value::vector(v),
2877 Literal::TextArray(items) => Value::TextArray(items),
2878 Literal::IntArray(items) => Value::IntArray(items),
2879 Literal::BigIntArray(items) => Value::BigIntArray(items),
2880 Literal::Interval {
2881 months,
2882 days,
2883 micros,
2884 ..
2885 } => Value::Interval {
2886 months,
2887 days,
2888 micros,
2889 },
2890 }
2891}
2892
2893pub(crate) fn int_value_for(n: i64) -> Value<'static> {
2897 if let Ok(small) = i32::try_from(n) {
2898 Value::Int(small)
2899 } else {
2900 Value::BigInt(n)
2901 }
2902}
2903
2904pub(crate) fn truncate_to_column_fsp(v: Value<'static>, schema: &ColumnSchema) -> Value<'static> {
2926 let Some(fsp) = schema.mysql_fsp else {
2927 return v;
2928 };
2929 if fsp >= 6 {
2930 return v;
2931 }
2932 let scale = 10i64.pow(u32::from(6 - fsp));
2933 let cut = |micros: i64| (micros / scale) * scale;
2935 match v {
2936 Value::Timestamp(m) => Value::Timestamp(cut(m)),
2937 Value::Time(m) => Value::Time(cut(m)),
2938 other => other,
2939 }
2940}
2941
2942fn column_int_bounds(schema: &ColumnSchema) -> Option<(i128, i128)> {
2947 if let Some(width) = schema.mysql_int_width {
2948 return Some(match (width, schema.is_unsigned) {
2949 (spg_storage::MysqlIntWidth::Tiny, false) => (-128, 127),
2950 (spg_storage::MysqlIntWidth::Tiny, true) => (0, 255),
2951 (spg_storage::MysqlIntWidth::Small, false) => (-32_768, 32_767),
2952 (spg_storage::MysqlIntWidth::Small, true) => (0, 65_535),
2953 (spg_storage::MysqlIntWidth::Medium, false) => (-8_388_608, 8_388_607),
2954 (spg_storage::MysqlIntWidth::Medium, true) => (0, 16_777_215),
2955 (spg_storage::MysqlIntWidth::Int, false) => (-2_147_483_648, 2_147_483_647),
2956 (spg_storage::MysqlIntWidth::Int, true) => (0, 4_294_967_295),
2957 (spg_storage::MysqlIntWidth::Big, false) => {
2960 (i128::from(i64::MIN), i128::from(i64::MAX))
2961 }
2962 (spg_storage::MysqlIntWidth::Big, true) => (0, i128::from(u64::MAX)),
2963 });
2964 }
2965 let (lo, hi) = match schema.ty {
2966 DataType::SmallInt => (i128::from(i16::MIN), i128::from(i16::MAX)),
2967 DataType::Int => (i128::from(i32::MIN), i128::from(i32::MAX)),
2968 DataType::BigInt => (i128::from(i64::MIN), i128::from(i64::MAX)),
2969 _ => return None,
2970 };
2971 Some(if schema.is_unsigned {
2972 (0, hi)
2973 } else {
2974 (lo, hi)
2975 })
2976}
2977
2978pub(crate) fn mysql_ignore_fit(v: Value<'static>, schema: &ColumnSchema) -> Value<'static> {
2997 if v.is_null() {
2998 if schema.nullable {
2999 return v;
3000 }
3001 return match schema.ty {
3003 DataType::SmallInt | DataType::Int | DataType::BigInt => Value::BigInt(0),
3004 DataType::Float | DataType::Real => Value::Float(0.0),
3005 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(""),
3006 _ => v,
3007 };
3008 }
3009 if let Value::Text(ref s) = v
3012 && matches!(
3013 schema.ty,
3014 DataType::SmallInt | DataType::Int | DataType::BigInt
3015 )
3016 && s.trim().parse::<i64>().is_err()
3017 {
3018 return Value::BigInt(leading_numeric_prefix(s));
3019 }
3020 let as_int = match v {
3022 Value::SmallInt(n) => Some(i128::from(n)),
3023 Value::Int(n) => Some(i128::from(n)),
3024 Value::BigInt(n) => Some(i128::from(n)),
3025 Value::Numeric {
3027 scaled, scale: 0, ..
3028 } => Some(scaled),
3029 _ => None,
3030 };
3031 if let Some(n) = as_int
3032 && let Some((lo, hi)) = column_int_bounds(schema)
3033 && (n < lo || n > hi)
3034 {
3035 return int_value_for_column(n.clamp(lo, hi));
3036 }
3037 if let Value::Text(ref s) = v {
3039 let max = match schema.ty {
3040 DataType::Varchar(m) | DataType::Char(m) if m > 0 => m as usize,
3041 _ => return v,
3042 };
3043 if s.chars().count() > max {
3044 return Value::text(s.chars().take(max).collect::<alloc::string::String>());
3045 }
3046 }
3047 v
3048}
3049
3050fn leading_numeric_prefix(s: &str) -> i64 {
3059 let t = s.trim_start();
3060 let b = t.as_bytes();
3061 let mut i = 0;
3062 if i < b.len() && (b[i] == b'-' || b[i] == b'+') {
3063 i += 1;
3064 }
3065 let int_start = i;
3066 while i < b.len() && b[i].is_ascii_digit() {
3067 i += 1;
3068 }
3069 let mut end = i;
3070 if i < b.len() && b[i] == b'.' {
3071 i += 1;
3072 while i < b.len() && b[i].is_ascii_digit() {
3073 i += 1;
3074 }
3075 if i > int_start + 1 {
3078 end = i;
3079 }
3080 }
3081 if end > int_start && i < b.len() && (b[i] == b'e' || b[i] == b'E') {
3083 let mut j = i + 1;
3084 if j < b.len() && (b[j] == b'-' || b[j] == b'+') {
3085 j += 1;
3086 }
3087 let digits_start = j;
3088 while j < b.len() && b[j].is_ascii_digit() {
3089 j += 1;
3090 }
3091 if j > digits_start {
3092 end = j;
3093 }
3094 }
3095 let Ok(f) = t[..end].parse::<f64>() else {
3096 return 0;
3097 };
3098 let r = f.round();
3100 if r >= i64::MAX as f64 {
3101 i64::MAX
3102 } else if r <= i64::MIN as f64 {
3103 i64::MIN
3104 } else {
3105 r as i64
3106 }
3107}
3108
3109fn int_value_for_column(n: i128) -> Value<'static> {
3113 match i64::try_from(n) {
3114 Ok(v) => Value::BigInt(v),
3115 Err(_) => Value::numeric(n, 0),
3116 }
3117}
3118
3119pub(crate) fn check_unsigned_range(
3120 v: &Value,
3121 schema: &ColumnSchema,
3122 position: usize,
3123) -> Result<(), EngineError> {
3124 let n: i128 = match v {
3125 Value::SmallInt(x) => i128::from(*x),
3126 Value::Int(x) => i128::from(*x),
3127 Value::BigInt(x) => i128::from(*x),
3128 Value::Numeric { scaled, scale, .. } if *scale == 0 => *scaled,
3131 _ => return Ok(()), };
3133 if let Some(width) = schema.mysql_int_width {
3137 let _ = width;
3143 let (lo, hi) = column_int_bounds(schema).unwrap_or((i128::MIN, i128::MAX));
3144 if n < lo || n > hi {
3145 return Err(EngineError::Unsupported(alloc::format!(
3148 "Out of range value for column '{}'",
3149 schema.name
3150 )));
3151 }
3152 return Ok(());
3153 }
3154 if schema.is_unsigned && n < 0 {
3156 return Err(EngineError::Unsupported(alloc::format!(
3157 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
3158 schema.name
3159 )));
3160 }
3161 Ok(())
3162}
3163
3164fn coerce_text_array_to(
3170 items: alloc::vec::Vec<Option<alloc::string::String>>,
3171 target: DataType,
3172 col: &str,
3173) -> Result<Option<Value<'static>>, EngineError> {
3174 let elem_dt = match target {
3175 DataType::BoolArray => DataType::Bool,
3176 DataType::NumericArray => DataType::Numeric {
3177 precision: 0,
3178 scale: 0,
3179 },
3180 DataType::DateArray => DataType::Date,
3181 DataType::TimestampArray => DataType::Timestamp,
3182 DataType::TimestamptzArray => DataType::Timestamptz,
3183 DataType::UuidArray => DataType::Uuid,
3184 DataType::IntervalArray => DataType::Interval,
3187 _ => return Ok(None),
3188 };
3189 let mut scal: alloc::vec::Vec<Option<Value<'static>>> =
3190 alloc::vec::Vec::with_capacity(items.len());
3191 for item in items {
3192 match item {
3193 None => scal.push(None),
3194 Some(s) => scal.push(Some(coerce_value(Value::text(s), elem_dt, col, 0)?)),
3195 }
3196 }
3197 let out = match target {
3198 DataType::BoolArray => Value::BoolArray(
3199 scal.into_iter()
3200 .map(|o| o.map(|v| matches!(v, Value::Bool(true))))
3201 .collect(),
3202 ),
3203 DataType::NumericArray => Value::NumericArray(
3204 scal.into_iter()
3205 .map(|o| {
3206 o.map(|v| match v {
3207 Value::Numeric { scaled, scale, .. } => (scaled, scale),
3208 _ => (0, 0),
3209 })
3210 })
3211 .collect(),
3212 ),
3213 DataType::DateArray => Value::DateArray(
3214 scal.into_iter()
3215 .map(|o| {
3216 o.map(|v| match v {
3217 Value::Date(d) => d,
3218 _ => 0,
3219 })
3220 })
3221 .collect(),
3222 ),
3223 DataType::TimestampArray => Value::TimestampArray(
3224 scal.into_iter()
3225 .map(|o| {
3226 o.map(|v| match v {
3227 Value::Timestamp(t) => t,
3228 _ => 0,
3229 })
3230 })
3231 .collect(),
3232 ),
3233 DataType::TimestamptzArray => Value::TimestamptzArray(
3234 scal.into_iter()
3235 .map(|o| {
3236 o.map(|v| match v {
3237 Value::Timestamp(t) => t,
3238 _ => 0,
3239 })
3240 })
3241 .collect(),
3242 ),
3243 DataType::UuidArray => Value::UuidArray(
3244 scal.into_iter()
3245 .map(|o| {
3246 o.map(|v| match v {
3247 Value::Uuid(u) => u,
3248 _ => [0u8; 16],
3249 })
3250 })
3251 .collect(),
3252 ),
3253 DataType::IntervalArray => Value::IntervalArray(
3254 scal.into_iter()
3255 .map(|o| {
3256 o.and_then(|v| match v {
3257 Value::Interval {
3258 months,
3259 days,
3260 micros,
3261 } => Some(spg_storage::IntervalSpan {
3262 months,
3263 days,
3264 micros,
3265 }),
3266 _ => None,
3267 })
3268 })
3269 .collect(),
3270 ),
3271 _ => return Ok(None),
3272 };
3273 Ok(Some(out))
3274}
3275
3276pub(crate) fn array_oid_element(oid: i64) -> Option<i64> {
3287 Some(match oid {
3288 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,
3322 })
3323}
3324
3325pub(crate) fn regtype_oid_to_name_owned(oid: i64) -> Option<alloc::string::String> {
3332 if let Some(scalar) = regtype_oid_to_name(oid) {
3333 return Some(alloc::string::String::from(scalar));
3334 }
3335 let (_, _, elem) = crate::system_catalog::ARRAY_TYPE_OIDS
3336 .iter()
3337 .find(|(arr, _, _)| *arr == oid)?;
3338 Some(alloc::format!("{}[]", regtype_oid_to_name(*elem)?))
3339}
3340
3341pub(crate) fn array_oid_for_element(elem: i64) -> Option<i64> {
3343 crate::system_catalog::ARRAY_TYPE_OIDS
3344 .iter()
3345 .find(|(_, _, e)| *e == elem)
3346 .map(|(arr, _, _)| *arr)
3347}
3348
3349pub(crate) fn regtype_oid_to_name(oid: i64) -> Option<&'static str> {
3350 Some(match oid {
3351 4600 => "pg_brin_bloom_summary",
3352 16 => "boolean",
3353 17 => "bytea",
3354 18 => "\"char\"",
3355 19 => "name",
3356 20 => "bigint",
3357 21 => "smallint",
3358 23 => "integer",
3359 25 => "text",
3360 26 => "oid",
3361 27 => "tid",
3363 28 => "xid",
3364 29 => "cid",
3365 5069 => "xid8",
3366 114 => "json",
3367 142 => "xml",
3368 650 => "cidr",
3369 700 => "real",
3370 701 => "double precision",
3371 774 => "macaddr8",
3372 790 => "money",
3373 829 => "macaddr",
3374 869 => "inet",
3375 1042 => "character",
3376 1043 => "character varying",
3377 1082 => "date",
3378 1083 => "time without time zone",
3379 1114 => "timestamp without time zone",
3380 1184 => "timestamp with time zone",
3381 1186 => "interval",
3382 1266 => "time with time zone",
3383 1560 => "bit",
3384 1562 => "bit varying",
3385 1700 => "numeric",
3386 2950 => "uuid",
3387 3614 => "tsvector",
3388 3615 => "tsquery",
3389 3802 => "jsonb",
3390 3904 => "int4range",
3391 3906 => "numrange",
3392 3908 => "tsrange",
3393 3910 => "tstzrange",
3394 3912 => "daterange",
3395 3926 => "int8range",
3396 _ => return None,
3397 })
3398}
3399
3400pub(crate) fn parse_pg_int(s: &str) -> Option<i64> {
3401 let s = s.trim();
3402 let (neg, rest) = if let Some(r) = s.strip_prefix('-') {
3403 (true, r)
3404 } else if let Some(r) = s.strip_prefix('+') {
3405 (false, r)
3406 } else {
3407 (false, s)
3408 };
3409 let (radix, digits, has_prefix) =
3414 if let Some(h) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
3415 (16u32, h, true)
3416 } else if let Some(o) = rest.strip_prefix("0o").or_else(|| rest.strip_prefix("0O")) {
3417 (8, o, true)
3418 } else if let Some(b) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) {
3419 (2, b, true)
3420 } else {
3421 (10, rest, false)
3422 };
3423 let db = digits.as_bytes();
3424 if db.last() == Some(&b'_')
3428 || digits.contains("__")
3429 || (!has_prefix && db.first() == Some(&b'_'))
3430 {
3431 return None;
3432 }
3433 let cleaned: alloc::string::String = digits.chars().filter(|&c| c != '_').collect();
3434 if cleaned.is_empty() {
3435 return None;
3436 }
3437 let mag = i64::from_str_radix(&cleaned, radix).ok()?;
3438 Some(if neg { mag.checked_neg()? } else { mag })
3439}
3440
3441fn xml_content_is_well_formed(s: &str) -> bool {
3450 let b = s.as_bytes();
3451 let is_name =
3452 |c: u8| c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_' | b'.' | b':') || c >= 0x80;
3453 let mut stack: alloc::vec::Vec<&[u8]> = alloc::vec::Vec::new();
3454 let mut i = 0;
3455 while i < b.len() {
3456 if b[i] != b'<' {
3457 i += 1;
3458 continue;
3459 }
3460 let rest = &s[i..];
3461 if rest.starts_with("<!--") {
3462 match rest.find("-->") {
3463 Some(p) => i += p + 3,
3464 None => return false,
3465 }
3466 } else if rest.starts_with("<![CDATA[") {
3467 match rest.find("]]>") {
3468 Some(p) => i += p + 3,
3469 None => return false,
3470 }
3471 } else if rest.starts_with("<?") {
3472 match rest.find("?>") {
3473 Some(p) => i += p + 2,
3474 None => return false,
3475 }
3476 } else if rest.starts_with("<!") {
3477 match rest.find('>') {
3478 Some(p) => i += p + 1,
3479 None => return false,
3480 }
3481 } else {
3482 let close = i + 1 < b.len() && b[i + 1] == b'/';
3484 let name_start = if close { i + 2 } else { i + 1 };
3485 let mut j = name_start;
3486 while j < b.len() && is_name(b[j]) {
3487 j += 1;
3488 }
3489 if j == name_start {
3490 return false; }
3492 let name = &b[name_start..j];
3493 let mut k = j;
3495 let mut quote = 0u8;
3496 let mut prev = 0u8;
3497 loop {
3498 if k >= b.len() {
3499 return false; }
3501 let c = b[k];
3502 if quote != 0 {
3503 if c == quote {
3504 quote = 0;
3505 }
3506 } else if c == b'"' || c == b'\'' {
3507 quote = c;
3508 } else if c == b'>' {
3509 break;
3510 }
3511 prev = c;
3512 k += 1;
3513 }
3514 let self_closing = prev == b'/';
3515 i = k + 1;
3516 if close {
3517 match stack.pop() {
3518 Some(top) if top == name => {}
3519 _ => return false,
3520 }
3521 } else if !self_closing {
3522 stack.push(name);
3523 }
3524 }
3525 }
3526 stack.is_empty()
3527}
3528
3529pub(crate) fn parse_float8(s: &str) -> Option<f64> {
3535 let t = s.trim();
3536 let parsed = t.parse::<f64>().ok()?;
3537 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3538 let numeric_looking = body
3539 .bytes()
3540 .next()
3541 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
3542 if numeric_looking {
3543 if parsed.is_infinite() {
3544 return None; }
3546 if parsed == 0.0 {
3547 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
3549 if mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0') {
3550 return None;
3551 }
3552 }
3553 }
3554 Some(parsed)
3555}
3556
3557fn decode_array_elems(
3561 s: &str,
3562 elem: DataType,
3563 col_name: &str,
3564 position: usize,
3565) -> Result<Vec<Option<Value<'static>>>, EngineError> {
3566 let raw = decode_text_array_literal(s).map_err(|_| {
3572 EngineError::Eval(EvalError::TypeMismatch {
3573 detail: malformed_array_literal(s),
3574 })
3575 })?;
3576 let mut out = Vec::with_capacity(raw.len());
3577 for e in raw {
3578 match e {
3579 None => out.push(None),
3580 Some(t) => out.push(Some(coerce_value(
3581 Value::text(t),
3582 elem,
3583 col_name,
3584 position,
3585 )?)),
3586 }
3587 }
3588 Ok(out)
3589}
3590
3591fn coerce_untyped_value(
3595 v: Value<'static>,
3596 expected: DataType,
3597 col_name: &str,
3598 position: usize,
3599) -> Result<Value<'static>, EngineError> {
3600 match (&v, expected) {
3601 (
3612 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3613 DataType::BigInt | DataType::Oid,
3614 ) => Ok(Value::BigInt(*oid)),
3615 (
3616 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3617 DataType::Int,
3618 ) => Ok(Value::Int(i32::try_from(*oid).unwrap_or(i32::MAX))),
3619 (
3620 Value::RegClass(_, name) | Value::RegProc(_, name) | Value::RegType(_, name),
3621 DataType::Text,
3622 ) => Ok(Value::text(alloc::string::String::from(name.as_ref()))),
3623 (Value::Composite(fields), DataType::Jsonb | DataType::Json) => {
3629 let mut obj = alloc::string::String::from("{");
3630 for (i, (name, val)) in fields.iter().enumerate() {
3631 if i > 0 {
3632 obj.push(',');
3633 }
3634 obj.push_str(&crate::json::value_to_json_text(&Value::text(
3636 alloc::string::String::from(name.as_str()),
3637 )));
3638 obj.push(':');
3639 obj.push_str(&crate::json::value_to_json_text(val));
3640 }
3641 obj.push('}');
3642 Ok(Value::Json(alloc::borrow::Cow::Owned(obj)))
3643 }
3644 (Value::Composite(_), DataType::Text) => Ok(Value::text(crate::eval::value_to_text(&v))),
3646 _ => Err(EngineError::Unsupported(alloc::format!(
3647 "cannot coerce {:?} to {expected:?} for column {col_name:?} (position {position})",
3648 v
3649 ))),
3650 }
3651}
3652
3653fn invalid_input_syntax(ty: &str, value: &str) -> EngineError {
3657 EngineError::Eval(EvalError::TypeMismatch {
3658 detail: alloc::format!("invalid input syntax for type {ty}: \"{value}\""),
3659 })
3660}
3661
3662fn real_out_of_range(value: &str) -> EngineError {
3665 float_out_of_range(value, "real")
3666}
3667
3668fn float_out_of_range(value: &str, ty: &str) -> EngineError {
3670 EngineError::Eval(EvalError::TypeMismatch {
3671 detail: alloc::format!("\"{value}\" is out of range for type {ty}"),
3672 })
3673}
3674
3675fn float_text_error(s: &str, ty: &str) -> EngineError {
3681 let t = s.trim();
3682 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3683 let numeric_looking = body
3684 .bytes()
3685 .next()
3686 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
3687 if numeric_looking && t.parse::<f64>().is_ok() {
3688 float_out_of_range(t, ty)
3689 } else {
3690 invalid_input_syntax(ty, s)
3691 }
3692}
3693
3694fn float_text_is_nonzero(t: &str) -> bool {
3698 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3699 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
3700 mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0')
3701}
3702
3703fn text_is_explicit_infinity(t: &str) -> bool {
3706 let t = t.trim_start_matches(['+', '-']);
3707 t.eq_ignore_ascii_case("inf") || t.eq_ignore_ascii_case("infinity")
3708}
3709
3710fn datetime_parse_error(ty: &str, s: &str) -> EngineError {
3719 let t = s.trim();
3720 let date_shaped = t.chars().any(|c| c.is_ascii_digit())
3721 && t.chars().all(|c| {
3722 c.is_ascii_digit() || matches!(c, '-' | '/' | ':' | '.' | ' ' | '+' | 'T' | 't')
3723 });
3724 let detail = if date_shaped {
3725 alloc::format!("date/time field value out of range: \"{t}\"")
3726 } else {
3727 alloc::format!("invalid input syntax for type {ty}: \"{t}\"")
3728 };
3729 EngineError::Eval(EvalError::TypeMismatch { detail })
3730}
3731
3732pub(crate) enum JsonbScalar {
3738 Numeric(Value<'static>),
3739 Bool(bool),
3740 Null,
3741}
3742
3743pub(crate) fn jsonb_cast_type_error(kind: &str, target: &str) -> EvalError {
3745 EvalError::TypeMismatch {
3746 detail: alloc::format!("cannot cast jsonb {kind} to type {target}"),
3747 }
3748}
3749
3750pub(crate) fn jsonb_scalar_for_cast(s: &str, target: &str) -> Result<JsonbScalar, EvalError> {
3753 use crate::json::JsonValue;
3754 match crate::json::parse(s) {
3755 Ok(JsonValue::Null) => Ok(JsonbScalar::Null),
3756 Ok(JsonValue::Bool(b)) => Ok(JsonbScalar::Bool(b)),
3757 Ok(JsonValue::Number(x)) => {
3761 let num = coerce_value(
3762 Value::text(alloc::format!("{x}")),
3763 DataType::Numeric {
3764 precision: 0,
3765 scale: 0,
3766 },
3767 "",
3768 0,
3769 )
3770 .map_err(|e| match e {
3771 EngineError::Eval(ev) => ev,
3772 _ => jsonb_cast_type_error("numeric", target),
3773 })?;
3774 Ok(JsonbScalar::Numeric(num))
3775 }
3776 Ok(JsonValue::NumberText(text)) => {
3777 let num = coerce_value(
3778 Value::text(text),
3779 DataType::Numeric {
3780 precision: 0,
3781 scale: 0,
3782 },
3783 "",
3784 0,
3785 )
3786 .map_err(|e| match e {
3787 EngineError::Eval(ev) => ev,
3788 _ => jsonb_cast_type_error("numeric", target),
3789 })?;
3790 Ok(JsonbScalar::Numeric(num))
3791 }
3792 Ok(JsonValue::String(_)) => Err(jsonb_cast_type_error("string", target)),
3793 Ok(JsonValue::Array(_)) => Err(jsonb_cast_type_error("array", target)),
3794 Ok(JsonValue::Object(_)) => Err(jsonb_cast_type_error("object", target)),
3795 Err(_) => Err(jsonb_cast_type_error("value", target)),
3796 }
3797}
3798pub(crate) fn normalize_composite_for_column(
3816 v: Value<'static>,
3817 col: &ColumnSchema,
3818 catalog: Option<&spg_storage::Catalog>,
3819) -> Result<Value<'static>, EngineError> {
3820 let Some(tname) = col.user_composite_type.as_deref() else {
3821 return Ok(v);
3822 };
3823 if matches!(v, Value::Null) {
3824 return Ok(v);
3825 }
3826 let Some(def) = catalog.and_then(|c| c.composite_types().get(tname)) else {
3829 return Ok(v);
3830 };
3831 if matches!(v, Value::Json(_)) {
3834 return Ok(v);
3835 }
3836 crate::eval::apply_composite_cast_pub(v, def, catalog).map_err(EngineError::Eval)
3837}
3838
3839fn try_coerce_json_scalar(
3843 s: &str,
3844 expected: DataType,
3845 col_name: &str,
3846 position: usize,
3847) -> Option<Result<Value<'static>, EngineError>> {
3848 let target = match expected {
3849 DataType::Int => "integer",
3850 DataType::BigInt => "bigint",
3851 DataType::SmallInt => "smallint",
3852 DataType::Numeric { .. } => "numeric",
3853 DataType::Real => "real",
3854 DataType::Float => "double precision",
3855 DataType::Bool => "boolean",
3856 _ => return None,
3857 };
3858 Some(
3859 (|| match jsonb_scalar_for_cast(s, target).map_err(EngineError::Eval)? {
3860 JsonbScalar::Null => Ok(Value::Null),
3861 JsonbScalar::Bool(b) => {
3862 if matches!(expected, DataType::Bool) {
3863 Ok(Value::Bool(b))
3864 } else {
3865 Err(EngineError::Eval(jsonb_cast_type_error("boolean", target)))
3866 }
3867 }
3868 JsonbScalar::Numeric(n) => {
3869 if matches!(expected, DataType::Bool) {
3870 Err(EngineError::Eval(jsonb_cast_type_error("numeric", target)))
3871 } else {
3872 coerce_value(n, expected, col_name, position)
3873 }
3874 }
3875 })(),
3876 )
3877}
3878
3879pub(crate) fn mysql_bytes_for_column(
3889 v: Value<'static>,
3890 expected: DataType,
3891 mysql: bool,
3892) -> Value<'static> {
3893 if !mysql {
3894 return v;
3895 }
3896 let Value::Bytes(ref b) = v else {
3897 return v;
3898 };
3899 match expected {
3900 DataType::SmallInt
3901 | DataType::Int
3902 | DataType::BigInt
3903 | DataType::Float
3904 | DataType::Real
3905 | DataType::Numeric { .. } => {
3906 let start = b.len().saturating_sub(16);
3907 let acc = b[start..]
3908 .iter()
3909 .fold(0u128, |a, &x| (a << 8) | u128::from(x));
3910 if acc <= i64::MAX as u128 {
3911 #[allow(clippy::cast_possible_truncation)]
3912 Value::BigInt(acc as i64)
3913 } else {
3914 big_literal_to_value(&alloc::format!("{acc}"))
3915 }
3916 }
3917 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(
3918 b.iter()
3919 .map(|&x| x as char)
3920 .collect::<alloc::string::String>(),
3921 ),
3922 _ => v,
3923 }
3924}
3925
3926fn try_coerce_time_family(
3945 v: &Value<'static>,
3946 expected: DataType,
3947) -> Option<Result<Value<'static>, EngineError>> {
3948 const DAY_US: i64 = 86_400_000_000;
3949 if expected != DataType::Time {
3950 return None;
3951 }
3952 match v {
3953 Value::TimeTz { us, .. } => Some(Ok(Value::Time(*us))),
3954 Value::Interval { micros, .. } => Some(Ok(Value::Time(micros.rem_euclid(DAY_US)))),
3955 _ => None,
3956 }
3957}
3958
3959pub(crate) fn coerce_to_oid(v: &Value<'_>) -> Result<Option<Value<'static>>, EvalError> {
3969 let as_i64 = match v {
3970 Value::Null => return Ok(Some(Value::Null)),
3971 Value::SmallInt(n) => i64::from(*n),
3972 Value::Int(n) => i64::from(*n),
3973 Value::BigInt(n) => *n,
3974 Value::Text(t) => match t.trim().parse::<i64>() {
3975 Ok(n) => n,
3976 Err(_) => {
3977 return Err(EvalError::TypeMismatch {
3978 detail: alloc::format!("invalid input syntax for type oid: {:?}", t.trim()),
3979 });
3980 }
3981 },
3982 _ => return Ok(None),
3983 };
3984 if (-(1i64 << 31)..0).contains(&as_i64) {
3986 return Ok(Some(Value::BigInt(as_i64 + (1i64 << 32))));
3987 }
3988 if !(0..=i64::from(u32::MAX)).contains(&as_i64) {
3989 return Err(EvalError::TypeMismatch {
3990 detail: "OID out of range".into(),
3991 });
3992 }
3993 Ok(Some(Value::BigInt(as_i64)))
3994}
3995
3996pub(crate) fn coerce_value(
3997 v: Value<'static>,
3998 expected: DataType,
3999 col_name: &str,
4000 position: usize,
4001) -> Result<Value<'static>, EngineError> {
4002 if v.is_null() {
4003 return Ok(Value::Null);
4004 }
4005 if let Value::Json(ref s) = v {
4010 if let Some(res) = try_coerce_json_scalar(s, expected, col_name, position) {
4011 return res;
4012 }
4013 }
4014 if let Some(res) = try_coerce_time_family(&v, expected) {
4018 return res;
4019 }
4020 if let Value::Numeric { kind, .. } = v
4036 && kind != spg_storage::NumericKind::Finite
4037 {
4038 use spg_storage::NumericKind as K;
4039 let as_f64 = match kind {
4040 K::NaN => f64::NAN,
4041 K::PosInf => f64::INFINITY,
4042 K::NegInf => f64::NEG_INFINITY,
4043 K::Finite => unreachable!("checked above"),
4044 };
4045 let what = if kind == K::NaN { "NaN" } else { "infinity" };
4047 let int_err = |target: &str| {
4048 Err(EngineError::Eval(EvalError::TypeMismatch {
4049 detail: alloc::format!("cannot convert {what} to {target}"),
4050 }))
4051 };
4052 match expected {
4053 DataType::Float => return Ok(Value::Float(as_f64)),
4054 #[allow(clippy::cast_possible_truncation)]
4055 DataType::Real => return Ok(Value::Real(as_f64 as f32)),
4056 DataType::Int => return int_err("integer"),
4057 DataType::BigInt => return int_err("bigint"),
4058 DataType::SmallInt => return int_err("smallint"),
4059 DataType::Numeric { precision, scale } => {
4060 if precision != 0 && kind != K::NaN {
4064 return Err(EngineError::Eval(EvalError::TypeMismatch {
4065 detail: alloc::string::String::from("numeric field overflow"),
4066 }));
4067 }
4068 let _ = scale;
4069 return Ok(v);
4070 }
4071 _ => {}
4072 }
4073 }
4074 if let DataType::Numeric { precision, .. } = expected {
4078 let f = match v {
4079 Value::Float(f) if !f.is_finite() => Some(f),
4080 #[allow(clippy::cast_lossless)]
4081 Value::Real(f) if !f.is_finite() => Some(f as f64),
4082 _ => None,
4083 };
4084 if let Some(f) = f {
4085 use spg_storage::NumericKind as K;
4086 if f.is_nan() {
4087 return Ok(Value::numeric_special(K::NaN));
4088 }
4089 if precision != 0 {
4090 return Err(EngineError::Eval(EvalError::TypeMismatch {
4091 detail: alloc::string::String::from("numeric field overflow"),
4092 }));
4093 }
4094 return Ok(Value::numeric_special(if f > 0.0 {
4095 K::PosInf
4096 } else {
4097 K::NegInf
4098 }));
4099 }
4100 }
4101 let Some(actual) = v.data_type() else {
4102 return coerce_untyped_value(v, expected, col_name, position);
4103 };
4104 if actual == expected {
4105 return Ok(v);
4106 }
4107 let coerced: Option<Value<'static>> = match (v, expected) {
4108 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4109 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4110 (Value::Int(n), DataType::SmallInt) => match i16::try_from(n) {
4113 Ok(v) => Some(Value::SmallInt(v)),
4114 Err(_) => {
4115 return Err(EngineError::Eval(EvalError::TypeMismatch {
4116 detail: "smallint out of range".into(),
4117 }));
4118 }
4119 },
4120 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4121 i128::from(n),
4122 precision,
4123 scale,
4124 col_name,
4125 )?),
4126 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
4127 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4128 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4129 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4130 i128::from(n),
4131 precision,
4132 scale,
4133 col_name,
4134 )?),
4135 (Value::BigInt(n), DataType::Int) => match i32::try_from(n) {
4136 Ok(v) => Some(Value::Int(v)),
4137 Err(_) => {
4138 return Err(EngineError::Eval(EvalError::TypeMismatch {
4139 detail: "integer out of range".into(),
4140 }));
4141 }
4142 },
4143 (Value::BigInt(n), DataType::SmallInt) => match i16::try_from(n) {
4144 Ok(v) => Some(Value::SmallInt(v)),
4145 Err(_) => {
4146 return Err(EngineError::Eval(EvalError::TypeMismatch {
4147 detail: "smallint out of range".into(),
4148 }));
4149 }
4150 },
4151 #[allow(clippy::cast_precision_loss)]
4152 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
4153 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4154 i128::from(n),
4155 precision,
4156 scale,
4157 col_name,
4158 )?),
4159 (Value::Float(x), DataType::Numeric { precision, scale }) => {
4160 if precision == 0 && scale == 0 && x.is_finite() {
4166 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{x}")) {
4167 Some(Value::Numeric {
4168 scaled: mantissa,
4169 scale: src_scale,
4170 kind: spg_storage::NumericKind::Finite,
4171 })
4172 } else {
4173 Some(numeric_from_float(x, precision, scale, col_name)?)
4174 }
4175 } else {
4176 Some(numeric_from_float(x, precision, scale, col_name)?)
4177 }
4178 }
4179 (Value::Real(x), DataType::Numeric { precision, scale }) => {
4185 if precision == 0 && scale == 0 && x.is_finite() {
4186 let six = alloc::format!("{:.5e}", x);
4200 let six: f64 = six.parse().unwrap_or_else(|_| f64::from(x));
4201 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{six}")) {
4202 Some(Value::Numeric {
4203 scaled: mantissa,
4204 scale: src_scale,
4205 kind: spg_storage::NumericKind::Finite,
4206 })
4207 } else {
4208 Some(numeric_from_float(
4209 f64::from(x),
4210 precision,
4211 scale,
4212 col_name,
4213 )?)
4214 }
4215 } else {
4216 Some(numeric_from_float(
4217 f64::from(x),
4218 precision,
4219 scale,
4220 col_name,
4221 )?)
4222 }
4223 }
4224 (Value::Text(s), DataType::Numeric { precision, scale }) => {
4235 if let Some(kind) = crate::numeric::parse_numeric_special(&s) {
4238 return Ok(Value::numeric_special(kind));
4239 }
4240 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
4241 match spg_sql::parser::expand_scientific_literal(&s) {
4246 spg_sql::parser::SciExpanded::Expanded(plain) => {
4247 return coerce_value(
4248 Value::Text(plain.into()),
4249 DataType::Numeric { precision, scale },
4250 col_name,
4251 position,
4252 );
4253 }
4254 spg_sql::parser::SciExpanded::Overflow => {
4255 return Err(EngineError::Eval(EvalError::TypeMismatch {
4256 detail: "value overflows numeric format".into(),
4257 }));
4258 }
4259 spg_sql::parser::SciExpanded::NotScientific => {}
4260 }
4261 if precision == 0 && scale == 0 {
4264 if let Some(b) = spg_storage::bignum::BigNumeric::from_decimal_str(&s) {
4265 return Ok(Value::NumericBig(alloc::boxed::Box::new(b)));
4266 }
4267 }
4268 return Err(EngineError::Eval(EvalError::TypeMismatch {
4269 detail: alloc::format!("invalid input syntax for type numeric: \"{s}\""),
4270 }));
4271 };
4272 if precision == 0 && scale == 0 {
4274 Some(Value::Numeric {
4275 scaled: mantissa,
4276 scale: src_scale,
4277 kind: spg_storage::NumericKind::Finite,
4278 })
4279 } else {
4280 Some(numeric_rescale(
4281 mantissa, src_scale, precision, scale, col_name,
4282 )?)
4283 }
4284 }
4285 (Value::Text(s), DataType::Date) => {
4287 let d = eval::parse_date_literal(&s)
4294 .or_else(|| {
4295 eval::parse_timestamp_literal(&s)
4296 .and_then(|t| i32::try_from(t.div_euclid(86_400_000_000)).ok())
4297 })
4298 .ok_or_else(|| datetime_parse_error("date", &s))?;
4299 Some(Value::Date(d))
4300 }
4301 (Value::Text(s), DataType::SmallInt) => Some(Value::SmallInt(
4316 parse_pg_int(&s)
4317 .and_then(|n| i16::try_from(n).ok())
4318 .ok_or_else(|| invalid_input_syntax("smallint", &s))?,
4319 )),
4320 (Value::Text(s), DataType::Int) => Some(Value::Int(
4321 parse_pg_int(&s)
4322 .and_then(|n| i32::try_from(n).ok())
4323 .ok_or_else(|| invalid_input_syntax("integer", &s))?,
4324 )),
4325 (Value::Text(s), DataType::BigInt) => Some(Value::BigInt(
4326 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("bigint", &s))?,
4327 )),
4328 (Value::Text(s), DataType::Xid) => Some(Value::Xid(
4334 s.parse::<u32>()
4335 .map_err(|_| invalid_input_syntax("xid", &s))?,
4336 )),
4337 (Value::Xid(x), DataType::Xid) => Some(Value::Xid(x)),
4338 (Value::Text(s), DataType::Xid8) => Some(Value::BigInt(
4339 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("xid8", &s))?,
4340 )),
4341 (Value::BigInt(n), DataType::Xid8) => Some(Value::BigInt(n)),
4348 (ref other, DataType::Oid) => coerce_to_oid(other)?,
4352 (Value::Text(s), DataType::Float) => {
4353 Some(Value::Float(
4357 parse_float8(&s).ok_or_else(|| float_text_error(&s, "double precision"))?,
4358 ))
4359 }
4360 (Value::Int(n), DataType::Real) => Some(Value::Real(n as f32)),
4362 (Value::SmallInt(n), DataType::Real) => Some(Value::Real(f32::from(n))),
4363 (Value::BigInt(n), DataType::Real) => Some(Value::Real(n as f32)),
4364 (Value::Float(x), DataType::Real) => {
4365 let narrowed = x as f32;
4369 if narrowed.is_infinite() && x.is_finite() {
4370 return Err(EngineError::Eval(EvalError::TypeMismatch {
4371 detail: "value out of range: overflow".into(),
4372 }));
4373 }
4374 if narrowed == 0.0 && x != 0.0 {
4376 return Err(EngineError::Eval(EvalError::TypeMismatch {
4377 detail: "value out of range: underflow".into(),
4378 }));
4379 }
4380 Some(Value::Real(narrowed))
4381 }
4382 (
4383 Value::Numeric {
4384 scaled,
4385 scale,
4386 kind,
4387 },
4388 DataType::Real,
4389 ) => Some(Value::Real(match kind {
4390 spg_storage::NumericKind::NaN => f32::NAN,
4391 spg_storage::NumericKind::PosInf => f32::INFINITY,
4392 spg_storage::NumericKind::NegInf => f32::NEG_INFINITY,
4393 spg_storage::NumericKind::Finite => {
4394 let mut div = 1.0f64;
4395 for _ in 0..scale {
4396 div *= 10.0;
4397 }
4398 let x = (scaled as f64 / div) as f32;
4399 if x == 0.0 && scaled != 0 {
4402 return Err(real_out_of_range(&crate::eval::format_numeric(
4403 scaled, scale,
4404 )));
4405 }
4406 x
4407 }
4408 })),
4409 (Value::Real(x), DataType::Float) => Some(Value::Float(f64::from(x))),
4410 (Value::Text(s), DataType::Real) => {
4417 let t = s.trim();
4418 let x = t
4419 .parse::<f32>()
4420 .ok()
4421 .ok_or_else(|| invalid_input_syntax("real", &s))?;
4422 if x.is_infinite() && !text_is_explicit_infinity(t) {
4423 return Err(real_out_of_range(t));
4424 }
4425 if x == 0.0 && float_text_is_nonzero(t) {
4428 return Err(real_out_of_range(t));
4429 }
4430 Some(Value::Real(x))
4431 }
4432 (Value::Text(s), DataType::Bool) => match s.trim().to_ascii_lowercase().as_str() {
4436 "0" | "f" | "fa" | "fal" | "fals" | "false" | "n" | "no" | "of" | "off" => {
4437 Some(Value::Bool(false))
4438 }
4439 "1" | "t" | "tr" | "tru" | "true" | "y" | "ye" | "yes" | "on" => {
4440 Some(Value::Bool(true))
4441 }
4442 _ => return Err(invalid_input_syntax("boolean", &s)),
4443 },
4444 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4453 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4454 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4455 (Value::Text(s), DataType::Json) => Some(Value::json(s)),
4459 (Value::Text(s), DataType::Jsonb) => Some(Value::json(
4462 crate::json::canonicalize_jsonb(s.as_ref()).unwrap_or_else(|_| s.into_owned()),
4463 )),
4464 (Value::Json(s), DataType::Text) => Some(Value::text(s)),
4465 (Value::Json(s), DataType::Json) => Some(Value::json(s)),
4473 (Value::Json(s), DataType::Jsonb) => Some(Value::json(
4474 crate::json::canonicalize_jsonb(s.as_ref()).unwrap_or_else(|_| s.into_owned()),
4475 )),
4476 (Value::Text(s), DataType::Bytes) => {
4483 let bytes = decode_bytea_literal(&s)
4484 .map_err(|e| EngineError::Eval(EvalError::TypeMismatch { detail: e }))?;
4485 Some(Value::bytes(bytes))
4486 }
4487 (Value::Bytes(b), DataType::Text) => Some(Value::text(encode_bytea_hex(&b))),
4491 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
4499 Some(b) => Some(Value::Uuid(b)),
4500 None => {
4501 return Err(EngineError::Eval(EvalError::TypeMismatch {
4502 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4503 }));
4504 }
4505 },
4506 (Value::Uuid(b), DataType::Text) => Some(Value::text(spg_storage::format_uuid(&b))),
4511 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
4517 Some(us) => Some(Value::Time(us)),
4518 None => {
4519 let time_shaped = {
4525 let core = s.trim().split('.').next().unwrap_or("");
4526 !core.is_empty()
4527 && core.split(':').count() >= 2
4528 && core
4529 .split(':')
4530 .all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()))
4531 };
4532 let detail = if time_shaped {
4533 alloc::format!("date/time field value out of range: {s:?}")
4534 } else {
4535 alloc::format!("invalid input syntax for type time: {s:?}")
4536 };
4537 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
4538 }
4539 },
4540 (Value::Time(us), DataType::Text) => Some(Value::text(eval::format_time(us))),
4542 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4547 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4548 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
4549 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
4553 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
4554 Err(_) => {
4555 return Err(EngineError::Eval(EvalError::TypeMismatch {
4556 detail: alloc::format!("invalid input syntax for type year: {s:?}"),
4557 }));
4558 }
4559 },
4560 (Value::Year(y), DataType::Text) => Some(Value::text(alloc::format!("{y:04}"))),
4562 (Value::Time(t), DataType::TimeTz) => Some(Value::TimeTz {
4576 us: t,
4577 offset_secs: 0,
4578 }),
4579 (Value::Timestamp(t), DataType::TimeTz) => Some(Value::TimeTz {
4580 us: t.rem_euclid(86_400_000_000),
4581 offset_secs: 0,
4582 }),
4583 (Value::Text(s), DataType::TimeTz) => {
4584 match parse_timetz_str(&s).or_else(|| parse_time_str(s.trim()).map(|us| (us, 0))) {
4585 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
4586 None => {
4587 return Err(EngineError::Eval(EvalError::TypeMismatch {
4588 detail: alloc::format!(
4589 "invalid input syntax for type time with time zone: \
4590 {s:?}"
4591 ),
4592 }));
4593 }
4594 }
4595 }
4596 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
4598 Some(Value::text(eval::format_timetz(us, offset_secs)))
4599 }
4600 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
4604 Some(c) => Some(Value::Money(c)),
4605 None => {
4606 return Err(EngineError::Eval(EvalError::TypeMismatch {
4607 detail: alloc::format!("invalid input syntax for type money: {s:?}"),
4608 }));
4609 }
4610 },
4611 (Value::SmallInt(n), DataType::Money) => {
4615 Some(Value::Money(i64::from(n).saturating_mul(100)))
4616 }
4617 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
4618 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
4619 (Value::Float(x), DataType::Money) => {
4620 let scaled = x * 100.0;
4623 let cents = if scaled >= 0.0 {
4624 (scaled + 0.5) as i64
4625 } else {
4626 (scaled - 0.5) as i64
4627 };
4628 Some(Value::Money(cents))
4629 }
4630 (Value::Numeric { scaled, scale, .. }, DataType::Money) => {
4631 let cents = if scale == 2 {
4634 scaled
4635 } else if scale < 2 {
4636 let mult = 10_i128.pow(u32::from(2 - scale));
4637 scaled.saturating_mul(mult)
4638 } else {
4639 let div = 10_i128.pow(u32::from(scale - 2));
4640 let half = div / 2;
4641 let bias = if scaled >= 0 { half } else { -half };
4642 (scaled + bias) / div
4643 };
4644 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
4645 }
4646 (Value::Money(c), DataType::Text) => Some(Value::text(eval::format_money(c))),
4648 (Value::Money(c), DataType::Numeric { .. }) => Some(Value::Numeric {
4650 scaled: i128::from(c),
4651 scale: 2,
4652 kind: spg_storage::NumericKind::Finite,
4653 }),
4654 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
4658 Ok(v) => Some(v),
4659 Err(RangeParseError::Misordered) => {
4661 return Err(EngineError::Eval(EvalError::TypeMismatch {
4662 detail: alloc::string::String::from(
4663 "range lower bound must be less than or equal to range upper bound",
4664 ),
4665 }));
4666 }
4667 Err(RangeParseError::Malformed) => {
4668 return Err(EngineError::Eval(EvalError::TypeMismatch {
4669 detail: alloc::format!("malformed range literal: \"{s}\""),
4670 }));
4671 }
4672 Err(RangeParseError::BadElement(bad)) => {
4673 return Err(EngineError::Eval(EvalError::TypeMismatch {
4674 detail: alloc::format!(
4675 "invalid input syntax for type {}: \"{bad}\"",
4676 range_element_type_name(kind)
4677 ),
4678 }));
4679 }
4680 },
4681 (v @ Value::Range { .. }, DataType::Text) => Some(Value::text(format_range_str(&v))),
4683 (Value::Text(s), DataType::Inet) => match parse_inet_text(&s) {
4685 Some((family, bits, addr)) => Some(Value::Inet { family, bits, addr }),
4686 None => {
4687 return Err(EngineError::Eval(EvalError::TypeMismatch {
4691 detail: alloc::format!("invalid input syntax for type inet: {s:?}"),
4692 }));
4693 }
4694 },
4695 (Value::Inet { family, bits, addr }, DataType::Cidr) => {
4702 let full = if family == 6 { 128 } else { 32 };
4703 let bits = if bits > full { full } else { bits };
4704 let mut masked = addr;
4705 for i in 0..16usize {
4706 let bit_start = i * 8;
4707 if bit_start >= usize::from(bits) {
4708 masked[i] = 0;
4709 } else if bit_start + 8 > usize::from(bits) {
4710 let keep = usize::from(bits) - bit_start;
4711 masked[i] &= 0xffu8 << (8 - keep);
4712 }
4713 }
4714 Some(Value::Cidr {
4715 family,
4716 bits,
4717 addr: masked,
4718 })
4719 }
4720 (Value::Cidr { family, bits, addr }, DataType::Inet) => {
4721 Some(Value::Inet { family, bits, addr })
4722 }
4723 (Value::Text(s), DataType::Cidr) => match parse_cidr_text(&s) {
4724 Ok(Some((family, bits, addr))) => Some(Value::Cidr { family, bits, addr }),
4725 Err(()) => {
4726 return Err(EngineError::Eval(EvalError::TypeMismatch {
4727 detail: alloc::format!(
4728 "invalid cidr value: {s:?} DETAIL: Value has bits set to right of mask."
4729 ),
4730 }));
4731 }
4732 Ok(None) => {
4733 return Err(EngineError::Eval(EvalError::TypeMismatch {
4734 detail: alloc::format!("invalid input syntax for type cidr: {s:?}"),
4735 }));
4736 }
4737 },
4738 (Value::Text(s), DataType::Interval) => match spg_sql::parser::parse_interval_text(&s) {
4741 Some((months, days, micros)) => Some(Value::Interval {
4742 months,
4743 days,
4744 micros,
4745 }),
4746 None => {
4747 return Err(EngineError::Eval(EvalError::TypeMismatch {
4748 detail: alloc::format!("invalid input syntax for type interval: {s:?}"),
4749 }));
4750 }
4751 },
4752 (Value::Text(s), DataType::Macaddr) => match parse_macaddr_text(&s) {
4753 Some(m) => Some(Value::Macaddr(m)),
4754 None => {
4755 return Err(EngineError::Eval(EvalError::TypeMismatch {
4756 detail: alloc::format!("invalid input syntax for type macaddr: {s:?}"),
4757 }));
4758 }
4759 },
4760 (Value::Text(s), DataType::PgLsn) => match parse_pg_lsn_text(&s) {
4762 Some(l) => Some(Value::PgLsn(l)),
4763 None => {
4764 return Err(EngineError::Eval(EvalError::TypeMismatch {
4765 detail: alloc::format!("invalid input syntax for type pg_lsn: \"{s}\""),
4766 }));
4767 }
4768 },
4769 (Value::Text(s), DataType::Macaddr8) => match parse_macaddr8_text(&s) {
4770 Some(m) => Some(Value::Macaddr8(m)),
4771 None => {
4772 return Err(EngineError::Eval(EvalError::TypeMismatch {
4773 detail: alloc::format!("invalid input syntax for type macaddr8: {s:?}"),
4774 }));
4775 }
4776 },
4777 (Value::BitString { nbits, bytes }, DataType::Bit(n)) => {
4789 let want = if n == 0 { 1 } else { n };
4791 if nbits != want {
4792 return Err(EngineError::Unsupported(alloc::format!(
4793 "bit string length {nbits} does not match type bit({want})"
4794 )));
4795 }
4796 Some(Value::BitString { nbits, bytes })
4797 }
4798 (Value::BitString { nbits, bytes }, DataType::BitVarying(n)) => {
4799 if n != 0 && nbits > n {
4800 return Err(EngineError::Unsupported(alloc::format!(
4801 "bit string too long for type bit varying({n})"
4802 )));
4803 }
4804 Some(Value::BitString { nbits, bytes })
4805 }
4806 (Value::Text(s), bit_ty @ (DataType::Bit(_) | DataType::BitVarying(_))) => {
4807 match parse_bit_string_text(&s) {
4808 Some((nbits, bytes)) => {
4809 match bit_ty {
4819 DataType::Bit(n) => {
4821 let want = if n == 0 { 1 } else { n };
4822 if nbits != want {
4823 return Err(EngineError::Unsupported(alloc::format!(
4824 "bit string length {nbits} does not match type bit({want})"
4825 )));
4826 }
4827 }
4828 DataType::BitVarying(n) if n != 0 && nbits > n => {
4829 return Err(EngineError::Unsupported(alloc::format!(
4830 "bit string too long for type bit varying({n})"
4831 )));
4832 }
4833 _ => {}
4834 }
4835 Some(Value::bit_string(nbits, bytes))
4836 }
4837 None => {
4838 let bad = s.chars().find(|c| *c != '0' && *c != '1');
4840 return Err(EngineError::Eval(EvalError::TypeMismatch {
4841 detail: match bad {
4842 Some(c) => {
4843 alloc::format!("\"{c}\" is not a valid binary digit")
4844 }
4845 None => alloc::format!("invalid input syntax for BIT: {s:?}"),
4846 },
4847 }));
4848 }
4849 }
4850 }
4851 (Value::Text(s), DataType::Xml) => {
4852 if !xml_content_is_well_formed(&s) {
4857 return Err(EngineError::Eval(EvalError::TypeMismatch {
4858 detail: alloc::format!("invalid XML content: {s:?}"),
4859 }));
4860 }
4861 Some(Value::xml(s))
4862 }
4863 (Value::BpChar(s), DataType::Char1) => {
4870 Some(Value::Char1(s.as_bytes().first().copied().unwrap_or(0)))
4871 }
4872 (Value::BpChar(s), DataType::Xml) => {
4873 let stripped = s.trim_end_matches(' ');
4874 if !xml_content_is_well_formed(stripped) {
4875 return Err(EngineError::Eval(EvalError::TypeMismatch {
4876 detail: alloc::format!("invalid XML content: {stripped:?}"),
4877 }));
4878 }
4879 Some(Value::xml(alloc::string::String::from(stripped)))
4880 }
4881 (Value::Bytes(b), DataType::SmallInt | DataType::Int | DataType::BigInt) => {
4887 let mut acc: i128 = 0;
4888 for byte in b.iter() {
4889 acc = acc.saturating_mul(256).saturating_add(i128::from(*byte));
4890 }
4891 let (fits, made) = match expected {
4892 DataType::SmallInt => (
4893 i16::try_from(acc).is_ok(),
4894 i16::try_from(acc).map(Value::SmallInt).ok(),
4895 ),
4896 DataType::Int => (
4897 i32::try_from(acc).is_ok(),
4898 i32::try_from(acc).map(Value::Int).ok(),
4899 ),
4900 _ => (
4901 i64::try_from(acc).is_ok(),
4902 i64::try_from(acc).map(Value::BigInt).ok(),
4903 ),
4904 };
4905 if !fits {
4906 return Err(EngineError::Eval(EvalError::TypeMismatch {
4907 detail: alloc::format!("{} out of range", pg_type_name_for_error(expected)),
4908 }));
4909 }
4910 made
4911 }
4912 (Value::Int(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
4915 (Value::SmallInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
4916 (Value::BigInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
4917 (Value::Text(s), DataType::Char1) => {
4918 let bytes = s.as_bytes();
4924 if bytes.len() == 4
4925 && bytes[0] == b'\\'
4926 && bytes[1..].iter().all(|b| (b'0'..=b'7').contains(b))
4927 {
4928 let v = ((bytes[1] - b'0') << 6) | ((bytes[2] - b'0') << 3) | (bytes[3] - b'0');
4929 Some(Value::Char1(v))
4930 } else {
4931 let b = s.bytes().next().unwrap_or(0);
4932 Some(Value::Char1(b))
4933 }
4934 }
4935 (Value::Inet { family, bits, addr }, DataType::Text) => {
4937 let base = format_inet(family, bits, &addr);
4941 Some(Value::text(if base.contains('/') {
4942 base
4943 } else {
4944 alloc::format!("{base}/{bits}")
4945 }))
4946 }
4947 (Value::Cidr { family, bits, addr }, DataType::Text) => {
4948 Some(Value::text(format_inet(family, bits, &addr)))
4949 }
4950 (Value::Macaddr(m), DataType::Text) => Some(Value::text(format_macaddr(&m))),
4951 (Value::Macaddr8(m), DataType::Text) => Some(Value::text(format_macaddr8(&m))),
4952 (Value::PgLsn(l), DataType::Text) => Some(Value::text(format_pg_lsn(l))),
4953 (Value::Macaddr(m), DataType::Macaddr8) => Some(Value::Macaddr8([
4956 m[0], m[1], m[2], 0xff, 0xfe, m[3], m[4], m[5],
4957 ])),
4958 (Value::BitString { nbits, bytes }, DataType::Text) => {
4959 Some(Value::text(format_bit_string(nbits, &bytes)))
4960 }
4961 #[allow(clippy::cast_possible_truncation)]
4963 (Value::BitString { nbits, bytes }, DataType::SmallInt) => {
4964 Some(Value::SmallInt(bit_string_to_i64(nbits, &bytes) as i16))
4965 }
4966 #[allow(clippy::cast_possible_truncation)]
4967 (Value::BitString { nbits, bytes }, DataType::Int) => {
4968 Some(Value::Int(bit_string_to_i64(nbits, &bytes) as i32))
4969 }
4970 (Value::BitString { nbits, bytes }, DataType::BigInt) => {
4971 Some(Value::BigInt(bit_string_to_i64(nbits, &bytes)))
4972 }
4973 (Value::Xml(s), DataType::Text) => Some(Value::text(s)),
4974 (Value::Char1(b), DataType::Text) => Some(Value::text((b as char).to_string())),
4975 (Value::Text(s), DataType::Point) => match parse_point(&s) {
4979 Some(p) => Some(Value::Point(p)),
4980 None => {
4981 return Err(EngineError::Eval(EvalError::TypeMismatch {
4982 detail: alloc::format!("invalid input syntax for type point: {s:?}"),
4983 }));
4984 }
4985 },
4986 (Value::Text(s), DataType::Lseg) => match parse_lseg_text(&s) {
4987 Some((p1, p2)) => Some(Value::Lseg(p1, p2)),
4988 None => {
4989 return Err(EngineError::Eval(EvalError::TypeMismatch {
4990 detail: alloc::format!("invalid input syntax for type lseg: {s:?}"),
4991 }));
4992 }
4993 },
4994 (Value::Text(s), DataType::PgBox) => match parse_box_text(&s) {
4995 Some((ur, ll)) => Some(Value::PgBox(ur, ll)),
4996 None => {
4997 return Err(EngineError::Eval(EvalError::TypeMismatch {
4998 detail: alloc::format!("invalid input syntax for type box: {s:?}"),
4999 }));
5000 }
5001 },
5002 (Value::Text(s), DataType::Line) => match parse_line_text(&s) {
5003 Some((a, b, c)) => Some(Value::Line { a, b, c }),
5004 None => {
5005 let zero_ab = s
5009 .trim()
5010 .strip_prefix('{')
5011 .and_then(|x| x.strip_suffix('}'))
5012 .map(|inner| inner.split(',').collect::<alloc::vec::Vec<_>>())
5013 .is_some_and(|parts| {
5014 parts.len() == 3
5015 && parts[0].trim().parse::<f64>() == Ok(0.0)
5016 && parts[1].trim().parse::<f64>() == Ok(0.0)
5017 && parts[2].trim().parse::<f64>().is_ok()
5018 });
5019 let detail = if zero_ab {
5020 alloc::string::String::from(
5021 "invalid line specification: A and B cannot both be zero",
5022 )
5023 } else {
5024 alloc::format!("invalid input syntax for type line: {s:?}")
5025 };
5026 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
5027 }
5028 },
5029 (Value::Text(s), DataType::Circle) => match parse_circle_text(&s) {
5030 Some((center, radius)) => Some(Value::Circle { center, radius }),
5031 None => {
5032 return Err(EngineError::Eval(EvalError::TypeMismatch {
5033 detail: alloc::format!("invalid input syntax for type circle: {s:?}"),
5034 }));
5035 }
5036 },
5037 (Value::Text(s), DataType::Path) => match parse_path_text(&s) {
5038 Some((points, closed)) => Some(Value::Path { points, closed }),
5039 None => {
5040 return Err(EngineError::Eval(EvalError::TypeMismatch {
5041 detail: alloc::format!("invalid input syntax for type path: {s:?}"),
5042 }));
5043 }
5044 },
5045 (Value::PgBox(a, b), DataType::Polygon) => {
5048 let (hx, hy) = (a.x.max(b.x), a.y.max(b.y));
5049 let (lx, ly) = (a.x.min(b.x), a.y.min(b.y));
5050 let p = |x: f64, y: f64| spg_storage::Point2D { x, y };
5051 Some(Value::Polygon(alloc::vec![
5052 p(lx, ly),
5053 p(lx, hy),
5054 p(hx, hy),
5055 p(hx, ly),
5056 ]))
5057 }
5058 (Value::Text(s), DataType::Polygon) => match parse_polygon_text(&s) {
5059 Some(points) => Some(Value::Polygon(points)),
5060 None => {
5061 return Err(EngineError::Eval(EvalError::TypeMismatch {
5062 detail: alloc::format!("invalid input syntax for type polygon: {s:?}"),
5063 }));
5064 }
5065 },
5066 (Value::Point(p), DataType::Text) => Some(Value::text(format_point(p))),
5068 (Value::Lseg(p1, p2), DataType::Text) => Some(Value::text(format_lseg(p1, p2))),
5069 (Value::PgBox(ur, ll), DataType::Text) => Some(Value::text(format_pg_box(ur, ll))),
5070 (Value::Line { a, b, c }, DataType::Text) => Some(Value::text(format_line(a, b, c))),
5071 (Value::Circle { center, radius }, DataType::Text) => {
5072 Some(Value::text(format_circle(center, radius)))
5073 }
5074 (Value::Path { points, closed }, DataType::Text) => {
5075 Some(Value::text(format_path(&points, closed)))
5076 }
5077 (Value::Polygon(points), DataType::Text) => Some(Value::text(format_polygon(&points))),
5078 (ref rv @ Value::Range { kind: rk, .. }, DataType::Multirange(kind)) => {
5085 if rk != kind {
5086 return Err(EngineError::Eval(EvalError::TypeMismatch {
5087 detail: alloc::format!(
5088 "cannot cast type {} to {}",
5089 DataType::Range(rk),
5090 DataType::Multirange(kind)
5091 ),
5092 }));
5093 }
5094 crate::eval::binop::range_as_multirange(rv)
5095 }
5096 (Value::Text(s), DataType::Multirange(kind)) => match parse_multirange_str(&s, kind) {
5097 Some(ranges) => Some(Value::Multirange {
5103 kind,
5104 ranges: crate::eval::binop::normalize_multirange_spans(kind, &ranges),
5105 }),
5106 None => {
5107 return Err(EngineError::Eval(EvalError::TypeMismatch {
5108 detail: alloc::format!("invalid input syntax for multirange type: {s:?}"),
5109 }));
5110 }
5111 },
5112 (Value::Multirange { ranges, .. }, DataType::Text) => {
5114 Some(Value::text(format_multirange(&ranges)))
5115 }
5116 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
5118 Some(pairs) => Some(Value::Hstore(pairs)),
5119 None => {
5120 return Err(EngineError::Eval(EvalError::TypeMismatch {
5121 detail: alloc::format!("invalid input syntax for type hstore: {s:?}"),
5122 }));
5123 }
5124 },
5125 (Value::Hstore(pairs), DataType::Text) => Some(Value::text(format_hstore_str(&pairs))),
5127 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
5130 Ok(m) => Some(Value::IntArray2D(m)),
5131 Err(e) => {
5132 return Err(EngineError::Eval(EvalError::TypeMismatch {
5133 detail: alloc::format!("invalid input syntax for INT[][]: {s:?}: {e}"),
5134 }));
5135 }
5136 },
5137 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
5138 Ok(m) => Some(Value::BigIntArray2D(m)),
5139 Err(e) => {
5140 return Err(EngineError::Eval(EvalError::TypeMismatch {
5141 detail: alloc::format!("invalid input syntax for BIGINT[][]: {s:?}: {e}"),
5142 }));
5143 }
5144 },
5145 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
5146 Ok(m) => Some(Value::TextArray2D(m)),
5147 Err(e) => {
5148 return Err(EngineError::Eval(EvalError::TypeMismatch {
5149 detail: alloc::format!("invalid input syntax for TEXT[][]: {s:?}: {e}"),
5150 }));
5151 }
5152 },
5153 (Value::IntArray2D(rows), DataType::Text) => Some(Value::text(format_int_2d_text(&rows))),
5155 (Value::BigIntArray2D(rows), DataType::Text) => {
5156 Some(Value::text(format_bigint_2d_text(&rows)))
5157 }
5158 (Value::TextArray2D(rows), DataType::Text) => Some(Value::text(format_text_2d_text(&rows))),
5159 (Value::Text(s), DataType::TextArray) => {
5164 let arr = decode_text_array_literal(&s).map_err(|_| {
5168 EngineError::Eval(EvalError::TypeMismatch {
5169 detail: malformed_array_literal(&s),
5170 })
5171 })?;
5172 Some(Value::TextArray(arr))
5173 }
5174 (Value::Text(s), DataType::IntArray) => {
5180 let arr = decode_text_array_literal(&s).map_err(|_| {
5184 EngineError::Eval(EvalError::TypeMismatch {
5185 detail: malformed_array_literal(&s),
5186 })
5187 })?;
5188 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
5189 for elem in arr {
5190 match elem {
5191 None => out.push(None),
5192 Some(t) => {
5193 let n: i32 = t.parse().map_err(|_| {
5194 EngineError::Eval(EvalError::TypeMismatch {
5195 detail: alloc::format!(
5196 "invalid input syntax for type integer: {t:?}"
5197 ),
5198 })
5199 })?;
5200 out.push(Some(n));
5201 }
5202 }
5203 }
5204 Some(Value::IntArray(out))
5205 }
5206 (Value::Text(s), DataType::SmallIntArray) => Some(Value::SmallIntArray(
5210 decode_array_elems(&s, DataType::SmallInt, col_name, position)?
5211 .into_iter()
5212 .map(|o| match o {
5213 Some(Value::SmallInt(n)) => Some(n),
5214 _ => None,
5215 })
5216 .collect(),
5217 )),
5218 (Value::Text(s), DataType::BoolArray) => {
5219 if let Some(rows) = crate::eval::values::split_2d_rows(&s) {
5224 let mut row_vals: Vec<Value<'static>> = Vec::with_capacity(rows.len());
5225 for r in &rows {
5226 let bools: Vec<Option<bool>> =
5227 decode_array_elems(r, DataType::Bool, col_name, position)?
5228 .into_iter()
5229 .map(|o| match o {
5230 Some(Value::Bool(b)) => Some(b),
5231 _ => None,
5232 })
5233 .collect();
5234 row_vals.push(Value::BoolArray(bools));
5235 }
5236 return crate::eval::values::build_2d_from_rows(&row_vals).ok_or_else(|| {
5237 EngineError::Eval(EvalError::TypeMismatch {
5238 detail: malformed_array_literal(&s),
5239 })
5240 });
5241 }
5242 Some(Value::BoolArray(
5243 decode_array_elems(&s, DataType::Bool, col_name, position)?
5244 .into_iter()
5245 .map(|o| match o {
5246 Some(Value::Bool(b)) => Some(b),
5247 _ => None,
5248 })
5249 .collect(),
5250 ))
5251 }
5252 (Value::Text(s), DataType::FloatArray) => Some(Value::FloatArray(
5253 decode_array_elems(&s, DataType::Float, col_name, position)?
5254 .into_iter()
5255 .map(|o| match o {
5256 Some(Value::Float(f)) => Some(f),
5257 _ => None,
5258 })
5259 .collect(),
5260 )),
5261 (Value::Text(s), DataType::NumericArray) => Some(Value::NumericArray(
5262 decode_array_elems(
5263 &s,
5264 DataType::Numeric {
5265 precision: 0,
5266 scale: 0,
5267 },
5268 col_name,
5269 position,
5270 )?
5271 .into_iter()
5272 .map(|o| match o {
5273 Some(Value::Numeric { scaled, scale, .. }) => Some((scaled, scale)),
5274 _ => None,
5275 })
5276 .collect(),
5277 )),
5278 (Value::Text(s), DataType::DateArray) => Some(Value::DateArray(
5279 decode_array_elems(&s, DataType::Date, col_name, position)?
5280 .into_iter()
5281 .map(|o| match o {
5282 Some(Value::Date(d)) => Some(d),
5283 _ => None,
5284 })
5285 .collect(),
5286 )),
5287 (Value::Text(s), DataType::UuidArray) => Some(Value::UuidArray(
5288 decode_array_elems(&s, DataType::Uuid, col_name, position)?
5289 .into_iter()
5290 .map(|o| match o {
5291 Some(Value::Uuid(u)) => Some(u),
5292 _ => None,
5293 })
5294 .collect(),
5295 )),
5296 (Value::Text(s), DataType::BigIntArray | DataType::OidArray) => {
5303 let arr = decode_text_array_literal(&s).map_err(|_| {
5307 EngineError::Eval(EvalError::TypeMismatch {
5308 detail: malformed_array_literal(&s),
5309 })
5310 })?;
5311 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
5312 for elem in arr {
5313 match elem {
5314 None => out.push(None),
5315 Some(t) => {
5316 let n: i64 = t.parse().map_err(|_| {
5317 EngineError::Eval(EvalError::TypeMismatch {
5318 detail: alloc::format!(
5319 "invalid input syntax for type bigint: {t:?}"
5320 ),
5321 })
5322 })?;
5323 out.push(Some(n));
5324 }
5325 }
5326 }
5327 Some(Value::BigIntArray(out))
5328 }
5329 (Value::TextArray(items), DataType::Text) => Some(Value::text(encode_text_array(&items))),
5333 (Value::TextArray(items), DataType::BoolArray) if items.is_empty() => {
5341 Some(Value::BoolArray(alloc::vec::Vec::new()))
5342 }
5343 (Value::TextArray(items), DataType::SmallIntArray) if items.is_empty() => {
5344 Some(Value::SmallIntArray(alloc::vec::Vec::new()))
5345 }
5346 (Value::TextArray(items), DataType::IntArray) if items.is_empty() => {
5347 Some(Value::IntArray(alloc::vec::Vec::new()))
5348 }
5349 (Value::TextArray(items), DataType::BigIntArray) if items.is_empty() => {
5350 Some(Value::BigIntArray(alloc::vec::Vec::new()))
5351 }
5352 (Value::TextArray(items), DataType::FloatArray) if items.is_empty() => {
5353 Some(Value::FloatArray(alloc::vec::Vec::new()))
5354 }
5355 (Value::TextArray(items), DataType::FloatArray) => {
5358 let mut out = alloc::vec::Vec::with_capacity(items.len());
5359 let mut ok = true;
5360 for item in items {
5361 match item {
5362 None => out.push(None),
5363 Some(s) => match s.trim().parse::<f64>() {
5364 Ok(x) => out.push(Some(x)),
5365 Err(_) => {
5366 ok = false;
5367 break;
5368 }
5369 },
5370 }
5371 }
5372 if ok {
5373 Some(Value::FloatArray(out))
5374 } else {
5375 None
5376 }
5377 }
5378 (Value::FloatArray(items), DataType::FloatArray) => Some(Value::FloatArray(items)),
5381 #[allow(clippy::cast_precision_loss)]
5382 (Value::IntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5383 items.into_iter().map(|o| o.map(|n| f64::from(n))).collect(),
5384 )),
5385 #[allow(clippy::cast_precision_loss)]
5386 (Value::BigIntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5387 items.into_iter().map(|o| o.map(|n| n as f64)).collect(),
5388 )),
5389 #[allow(clippy::cast_precision_loss)]
5393 (Value::NumericArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5394 items
5395 .into_iter()
5396 .map(|o| {
5397 o.map(|(scaled, scale)| {
5398 crate::eval::format_numeric(scaled, scale)
5399 .parse()
5400 .unwrap_or(f64::NAN)
5401 })
5402 })
5403 .collect(),
5404 )),
5405 (Value::IntArray(items), DataType::BigIntArray) => Some(Value::BigIntArray(
5410 items.into_iter().map(|o| o.map(i64::from)).collect(),
5411 )),
5412 (Value::BigIntArray(items), DataType::IntArray) => {
5413 let mut out = alloc::vec::Vec::with_capacity(items.len());
5414 let mut ok = true;
5415 for o in items {
5416 match o {
5417 None => out.push(None),
5418 Some(n) => match i32::try_from(n) {
5419 Ok(v) => out.push(Some(v)),
5420 Err(_) => {
5421 ok = false;
5422 break;
5423 }
5424 },
5425 }
5426 }
5427 if ok { Some(Value::IntArray(out)) } else { None }
5428 }
5429 (Value::IntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5430 items
5431 .into_iter()
5432 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5433 .collect(),
5434 )),
5435 (Value::BigIntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5436 items
5437 .into_iter()
5438 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5439 .collect(),
5440 )),
5441 (Value::FloatArray(items), DataType::NumericArray) => {
5442 let mut out = alloc::vec::Vec::with_capacity(items.len());
5443 let mut ok = true;
5444 for o in items {
5445 match o {
5446 None => out.push(None),
5447 Some(x) => match parse_numeric_text(&alloc::format!("{x}")) {
5448 Some((mantissa, scale)) => out.push(Some((mantissa, scale))),
5449 None => {
5450 ok = false;
5451 break;
5452 }
5453 },
5454 }
5455 }
5456 if ok {
5457 Some(Value::NumericArray(out))
5458 } else {
5459 None
5460 }
5461 }
5462 (Value::NumericArray(items), DataType::IntArray) => {
5466 let mut out = alloc::vec::Vec::with_capacity(items.len());
5467 let mut ok = true;
5468 for o in items {
5469 match o {
5470 None => out.push(None),
5471 Some((scaled, scale)) => {
5472 match i32::try_from(numeric_round_to_integer(scaled, scale)) {
5473 Ok(v) => out.push(Some(v)),
5474 Err(_) => {
5475 ok = false;
5476 break;
5477 }
5478 }
5479 }
5480 }
5481 }
5482 if ok { Some(Value::IntArray(out)) } else { None }
5483 }
5484 (Value::NumericArray(items), DataType::BigIntArray) => {
5485 let mut out = alloc::vec::Vec::with_capacity(items.len());
5486 let mut ok = true;
5487 for o in items {
5488 match o {
5489 None => out.push(None),
5490 Some((scaled, scale)) => {
5491 match i64::try_from(numeric_round_to_integer(scaled, scale)) {
5492 Ok(v) => out.push(Some(v)),
5493 Err(_) => {
5494 ok = false;
5495 break;
5496 }
5497 }
5498 }
5499 }
5500 }
5501 if ok {
5502 Some(Value::BigIntArray(out))
5503 } else {
5504 None
5505 }
5506 }
5507 #[allow(clippy::cast_possible_truncation)]
5511 (Value::FloatArray(items), DataType::IntArray) => {
5512 let mut out = alloc::vec::Vec::with_capacity(items.len());
5513 let mut ok = true;
5514 for o in items {
5515 match o {
5516 None => out.push(None),
5517 Some(x) if x.is_finite() => {
5518 let r = crate::eval::math::f64_round_half_even(x);
5519 if r >= f64::from(i32::MIN) && r <= f64::from(i32::MAX) {
5520 out.push(Some(r as i32));
5521 } else {
5522 ok = false;
5523 break;
5524 }
5525 }
5526 Some(_) => {
5527 ok = false;
5528 break;
5529 }
5530 }
5531 }
5532 if ok { Some(Value::IntArray(out)) } else { None }
5533 }
5534 #[allow(clippy::cast_possible_truncation)]
5535 (Value::FloatArray(items), DataType::BigIntArray) => {
5536 let mut out = alloc::vec::Vec::with_capacity(items.len());
5537 let mut ok = true;
5538 for o in items {
5539 match o {
5540 None => out.push(None),
5541 Some(x) if x.is_finite() => {
5542 out.push(Some(crate::eval::math::f64_round_half_even(x) as i64));
5543 }
5544 Some(_) => {
5545 ok = false;
5546 break;
5547 }
5548 }
5549 }
5550 if ok {
5551 Some(Value::BigIntArray(out))
5552 } else {
5553 None
5554 }
5555 }
5556 (Value::TextArray(items), DataType::NumericArray) if items.is_empty() => {
5557 Some(Value::NumericArray(alloc::vec::Vec::new()))
5558 }
5559 (Value::TextArray(items), DataType::DateArray) if items.is_empty() => {
5560 Some(Value::DateArray(alloc::vec::Vec::new()))
5561 }
5562 (Value::TextArray(items), DataType::TimestampArray) if items.is_empty() => {
5563 Some(Value::TimestampArray(alloc::vec::Vec::new()))
5564 }
5565 (Value::TextArray(items), DataType::TimestamptzArray) if items.is_empty() => {
5566 Some(Value::TimestamptzArray(alloc::vec::Vec::new()))
5567 }
5568 (Value::TextArray(items), DataType::UuidArray) if items.is_empty() => {
5569 Some(Value::UuidArray(alloc::vec::Vec::new()))
5570 }
5571 (Value::TextArray(items), DataType::JsonArray) if items.is_empty() => {
5572 Some(Value::JsonArray(alloc::vec::Vec::new()))
5573 }
5574 (Value::TextArray(items), DataType::JsonbArray) if items.is_empty() => {
5575 Some(Value::JsonbArray(alloc::vec::Vec::new()))
5576 }
5577 (Value::TextArray(items), DataType::BytesArray) if items.is_empty() => {
5578 Some(Value::BytesArray(alloc::vec::Vec::new()))
5579 }
5580 (Value::TextArray(items), DataType::IntervalArray) if items.is_empty() => {
5581 Some(Value::IntervalArray(alloc::vec::Vec::new()))
5582 }
5583 (
5587 Value::TextArray(items),
5588 dt @ (DataType::BoolArray
5589 | DataType::NumericArray
5590 | DataType::DateArray
5591 | DataType::TimestampArray
5592 | DataType::TimestamptzArray
5593 | DataType::IntervalArray
5594 | DataType::UuidArray),
5595 ) => coerce_text_array_to(items, dt, col_name)?,
5596 (
5602 Value::Text(s),
5603 dt @ (DataType::TimestampArray | DataType::TimestamptzArray | DataType::IntervalArray),
5604 ) => {
5605 let items = decode_text_array_literal(&s).map_err(|_| {
5606 EngineError::Eval(EvalError::TypeMismatch {
5607 detail: malformed_array_literal(&s),
5608 })
5609 })?;
5610 coerce_text_array_to(items, dt, col_name)?
5611 }
5612 (Value::TextArray(items), DataType::MoneyArray) if items.is_empty() => {
5613 Some(Value::MoneyArray(alloc::vec::Vec::new()))
5614 }
5615 (Value::IntArray(items), DataType::SmallIntArray) => {
5620 let mut out = alloc::vec::Vec::with_capacity(items.len());
5621 let mut ok = true;
5622 for item in items {
5623 match item {
5624 None => out.push(None),
5625 Some(n) => match i16::try_from(n) {
5626 Ok(x) => out.push(Some(x)),
5627 Err(_) => {
5628 ok = false;
5629 break;
5630 }
5631 },
5632 }
5633 }
5634 if ok {
5635 Some(Value::SmallIntArray(out))
5636 } else {
5637 None
5638 }
5639 }
5640 (Value::Text(s), DataType::Vector { dim, encoding }) => {
5649 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
5650 EngineError::Eval(EvalError::TypeMismatch {
5651 detail: alloc::format!("cannot parse {s:?} as VECTOR"),
5652 })
5653 })?;
5654 if parsed.len() != dim as usize {
5655 return Err(EngineError::Eval(EvalError::TypeMismatch {
5656 detail: alloc::format!(
5657 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
5658 parsed.len()
5659 ),
5660 }));
5661 }
5662 Some(match encoding {
5663 VecEncoding::F32 => Value::vector(parsed),
5664 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
5665 VecEncoding::F16 => {
5666 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
5667 }
5668 })
5669 }
5670 (Value::Text(s), DataType::TsVector) => {
5680 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
5681 EngineError::Eval(EvalError::TypeMismatch {
5682 detail: alloc::format!("cannot parse {s:?} as TSVECTOR: {e}"),
5683 })
5684 })?;
5685 Some(Value::TsVector(lexs))
5686 }
5687 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
5688 let t = eval::parse_timestamp_literal(&s)
5689 .ok_or_else(|| datetime_parse_error("timestamp", &s))?;
5690 Some(Value::Timestamp(t))
5691 }
5692 (Value::Date(i32::MAX), DataType::Timestamp | DataType::Timestamptz) => {
5695 Some(Value::Timestamp(i64::MAX))
5696 }
5697 (Value::Date(i32::MIN), DataType::Timestamp | DataType::Timestamptz) => {
5698 Some(Value::Timestamp(i64::MIN))
5699 }
5700 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
5701 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
5702 }
5703 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
5707 (Value::Timestamp(t), DataType::Date) => {
5708 let days = t.div_euclid(86_400_000_000);
5709 i32::try_from(days).ok().map(Value::Date)
5710 }
5711 (Value::Timestamp(t), DataType::Time) => Some(Value::Time(t.rem_euclid(86_400_000_000))),
5720 (
5724 Value::NumericBig(b),
5725 DataType::Numeric {
5726 precision: 0,
5727 scale: 0,
5728 },
5729 ) => Some(Value::NumericBig(b)),
5730 (
5731 Value::Numeric {
5732 scaled,
5733 scale: src_scale,
5734 ..
5735 },
5736 DataType::Numeric { precision, scale },
5737 ) => {
5738 if precision == 0 && scale == 0 {
5744 Some(Value::Numeric {
5745 scaled,
5746 scale: src_scale,
5747 kind: spg_storage::NumericKind::Finite,
5748 })
5749 } else {
5750 Some(numeric_rescale(
5751 scaled, src_scale, precision, scale, col_name,
5752 )?)
5753 }
5754 }
5755 (Value::NumericBig(b), DataType::Numeric { precision, scale }) => {
5760 if precision == 0 && scale == 0 {
5761 Some(Value::NumericBig(b))
5762 } else {
5763 #[allow(clippy::cast_sign_loss)]
5764 let rounded = if scale < 0 {
5765 b.round_to(0)
5767 } else {
5768 b.round_to(scale as u16)
5769 };
5770 let out = crate::eval::binop::bignum_to_value(rounded);
5771 crate::numeric::check_precision_text(&out, precision, scale, col_name)?;
5774 Some(out)
5775 }
5776 }
5777 #[allow(clippy::cast_precision_loss)]
5778 (Value::Numeric { scaled, scale, .. }, DataType::Float) => {
5779 let text = crate::eval::format_numeric(scaled, scale);
5786 let x: f64 = text.parse().unwrap_or(f64::NAN);
5787 if x == 0.0 && scaled != 0 {
5791 return Err(float_out_of_range(
5792 &crate::eval::format_numeric(scaled, scale),
5793 "double precision",
5794 ));
5795 }
5796 Some(Value::Float(x))
5797 }
5798 (Value::NumericBig(b), DataType::Real) => {
5806 let text = b.to_decimal_str();
5807 let x: f32 = text.parse().map_err(|_| real_out_of_range(&text))?;
5808 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
5809 return Err(real_out_of_range(&text));
5810 }
5811 Some(Value::Real(x))
5812 }
5813 (Value::NumericBig(b), DataType::Float) => {
5814 let text = b.to_decimal_str();
5818 let x: f64 = text
5819 .parse()
5820 .map_err(|_| float_out_of_range(&text, "double precision"))?;
5821 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
5822 return Err(float_out_of_range(&text, "double precision"));
5823 }
5824 Some(Value::Float(x))
5825 }
5826 (Value::Float(x), DataType::Int) => {
5834 let r = crate::eval::math::f64_round_half_even(x);
5835 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
5836 return Err(EngineError::Eval(EvalError::TypeMismatch {
5837 detail: "integer out of range".into(),
5838 }));
5839 }
5840 #[allow(clippy::cast_possible_truncation)]
5841 Some(Value::Int(r as i32))
5842 }
5843 (Value::Float(x), DataType::BigInt) => {
5844 let r = crate::eval::math::f64_round_half_even(x);
5845 if !r.is_finite()
5846 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
5847 {
5848 return Err(EngineError::Eval(EvalError::TypeMismatch {
5849 detail: "bigint out of range".into(),
5850 }));
5851 }
5852 #[allow(clippy::cast_possible_truncation)]
5853 Some(Value::BigInt(r as i64))
5854 }
5855 (Value::Float(x), DataType::SmallInt) => {
5856 let r = crate::eval::math::f64_round_half_even(x);
5857 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
5858 return Err(EngineError::Eval(EvalError::TypeMismatch {
5859 detail: "smallint out of range".into(),
5860 }));
5861 }
5862 #[allow(clippy::cast_possible_truncation)]
5863 Some(Value::SmallInt(r as i16))
5864 }
5865 (Value::Real(x), DataType::Int) => {
5869 let r = crate::eval::math::f64_round_half_even(f64::from(x));
5870 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
5871 return Err(EngineError::Eval(EvalError::TypeMismatch {
5872 detail: "integer out of range".into(),
5873 }));
5874 }
5875 #[allow(clippy::cast_possible_truncation)]
5876 Some(Value::Int(r as i32))
5877 }
5878 (Value::Real(x), DataType::BigInt) => {
5879 let r = crate::eval::math::f64_round_half_even(f64::from(x));
5880 if !r.is_finite()
5881 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
5882 {
5883 return Err(EngineError::Eval(EvalError::TypeMismatch {
5884 detail: "bigint out of range".into(),
5885 }));
5886 }
5887 #[allow(clippy::cast_possible_truncation)]
5888 Some(Value::BigInt(r as i64))
5889 }
5890 (Value::Real(x), DataType::SmallInt) => {
5891 let r = crate::eval::math::f64_round_half_even(f64::from(x));
5892 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
5893 return Err(EngineError::Eval(EvalError::TypeMismatch {
5894 detail: "smallint out of range".into(),
5895 }));
5896 }
5897 #[allow(clippy::cast_possible_truncation)]
5898 Some(Value::SmallInt(r as i16))
5899 }
5900 (Value::Numeric { scaled, scale, .. }, DataType::Int) => {
5901 let rounded = numeric_round_to_integer(scaled, scale);
5902 i32::try_from(rounded).ok().map(Value::Int)
5903 }
5904 (Value::Numeric { scaled, scale, .. }, DataType::BigInt) => {
5905 let rounded = numeric_round_to_integer(scaled, scale);
5906 i64::try_from(rounded).ok().map(Value::BigInt)
5907 }
5908 (Value::Numeric { scaled, scale, .. }, DataType::SmallInt) => {
5909 let rounded = numeric_round_to_integer(scaled, scale);
5910 i16::try_from(rounded).ok().map(Value::SmallInt)
5911 }
5912 (Value::Text(s), DataType::Name) => {
5919 let mut cut = s.into_owned();
5920 if cut.len() > 63 {
5921 let mut idx = 63;
5922 while !cut.is_char_boundary(idx) {
5923 idx -= 1;
5924 }
5925 cut.truncate(idx);
5926 }
5927 Some(Value::text(cut))
5928 }
5929 (Value::Text(s), DataType::Varchar(max)) => {
5930 if max == 0 || u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
5931 Some(Value::text(s))
5932 } else {
5933 let excess_all_blanks = s.chars().skip(max as usize).all(|c| c == ' ');
5938 if excess_all_blanks {
5939 Some(Value::text(
5940 s.chars()
5941 .take(max as usize)
5942 .collect::<alloc::string::String>(),
5943 ))
5944 } else {
5945 return Err(EngineError::Unsupported(alloc::format!(
5946 "value too long for type character varying({max})"
5947 )));
5948 }
5949 }
5950 }
5951 (
5959 Value::Vector(v),
5960 DataType::Vector {
5961 dim,
5962 encoding: VecEncoding::Sq8,
5963 },
5964 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
5965 (
5970 Value::Vector(v),
5971 DataType::Vector {
5972 dim,
5973 encoding: VecEncoding::F16,
5974 },
5975 ) if v.len() == dim as usize => Some(Value::HalfVector(
5976 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
5977 )),
5978 (Value::Text(s), DataType::Char(size)) => {
5982 if size == 0 {
5986 return Ok(Value::BpChar(alloc::borrow::Cow::Owned(
5987 s.trim_end_matches(' ').to_string(),
5988 )));
5989 }
5990 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
5991 let body = if len > size {
5992 let trimmed = s.trim_end_matches(' ');
5993 let tlen = u32::try_from(trimmed.chars().count()).unwrap_or(u32::MAX);
5994 if tlen > size {
5995 return Err(EngineError::Unsupported(alloc::format!(
5996 "value too long for type character({size})"
5997 )));
5998 }
5999 trimmed.to_string()
6000 } else {
6001 s.into_owned()
6002 };
6003 let need = (size as usize) - body.chars().count();
6004 let mut padded = body;
6005 padded.reserve(need);
6006 for _ in 0..need {
6007 padded.push(' ');
6008 }
6009 Some(Value::BpChar(alloc::borrow::Cow::Owned(padded)))
6013 }
6014 _ => None,
6015 };
6016 coerced.ok_or_else(|| {
6017 EngineError::Storage(StorageError::TypeMismatch {
6018 column: col_name.into(),
6019 expected,
6020 actual,
6021 position,
6022 })
6023 })
6024}
6025
6026pub(crate) fn big_literal_to_value(s: &str) -> Value<'static> {
6029 let b = spg_storage::bignum::BigNumeric::from_decimal_str(s).expect("lexer-validated decimal");
6030 match b.to_i128() {
6031 Some(scaled) => Value::Numeric {
6032 scaled,
6033 scale: b.scale(),
6034 kind: spg_storage::NumericKind::Finite,
6035 },
6036 None => Value::NumericBig(alloc::boxed::Box::new(b)),
6037 }
6038}
6039
6040pub(crate) fn types_unify(a: DataType, b: DataType) -> bool {
6049 fn category(t: DataType) -> Option<u8> {
6050 Some(match t {
6051 DataType::SmallInt
6052 | DataType::Int
6053 | DataType::BigInt
6054 | DataType::Numeric { .. }
6055 | DataType::Real
6056 | DataType::Float => 1,
6057 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => 2,
6058 DataType::Date | DataType::Timestamp | DataType::Timestamptz => 3,
6059 _ => return None,
6060 })
6061 }
6062 if a == b {
6063 return true;
6064 }
6065 match (category(a), category(b)) {
6066 (Some(x), Some(y)) => x == y,
6067 _ => false,
6070 }
6071}
6072
6073pub(crate) fn pg_type_name_for_error_opt(t: Option<DataType>) -> alloc::string::String {
6087 match t {
6088 Some(t) => pg_type_name_for_error(t),
6089 None => alloc::string::String::from("unknown"),
6090 }
6091}
6092
6093pub(crate) fn pg_type_name_for_error(t: DataType) -> alloc::string::String {
6094 use spg_storage::DataType as D;
6095 let elem = match t {
6096 D::TextArray => Some(D::Text),
6097 D::IntArray => Some(D::Int),
6098 D::BigIntArray => Some(D::BigInt),
6099 D::SmallIntArray => Some(D::SmallInt),
6100 D::FloatArray => Some(D::Float),
6101 D::NumericArray => Some(D::Numeric {
6102 precision: 0,
6103 scale: 0,
6104 }),
6105 D::BoolArray => Some(D::Bool),
6106 D::DateArray => Some(D::Date),
6107 D::TimestampArray => Some(D::Timestamp),
6108 D::TimestamptzArray => Some(D::Timestamptz),
6109 D::IntervalArray => Some(D::Interval),
6110 D::UuidArray => Some(D::Uuid),
6111 D::JsonArray | D::JsonbArray => Some(D::Jsonb),
6112 D::BytesArray => Some(D::Bytes),
6113 D::MoneyArray => Some(D::Money),
6114 _ => None,
6115 };
6116 match elem {
6117 Some(e) => alloc::format!("{}[]", crate::system_catalog::pg_data_type_text(e)),
6118 None => crate::system_catalog::pg_data_type_text(t),
6119 }
6120}