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