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 pseudo_type(name: &str) -> Option<&'static str> {
43 const NAMES: &[&str] = &[
44 "anyarray",
45 "anycompatible",
46 "anycompatiblearray",
47 "anycompatiblemultirange",
48 "anycompatiblenonarray",
49 "anycompatiblerange",
50 "anyelement",
51 "anyenum",
52 "anymultirange",
53 "anynonarray",
54 "anyrange",
55 "cstring",
56 "event_trigger",
57 "fdw_handler",
58 "index_am_handler",
59 "internal",
60 "language_handler",
61 "pg_ddl_command",
62 "record",
63 "table_am_handler",
64 "trigger",
65 "tsm_handler",
66 "unknown",
67 "void",
68 ];
69 NAMES.iter().find(|n| n.eq_ignore_ascii_case(name)).copied()
70}
71
72pub(crate) fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, alloc::string::String> {
73 let s = s.trim();
74 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
75 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
77 if cleaned.len() % 2 != 0 {
78 return Err(alloc::string::String::from(
79 "invalid hexadecimal data: odd number of digits",
80 ));
81 }
82 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
83 let cleaned_bytes = cleaned.as_bytes();
84 for i in (0..cleaned_bytes.len()).step_by(2) {
85 let hi = hex_nibble(cleaned_bytes[i]).map_err(|()| bad_hex_digit(cleaned_bytes[i]))?;
86 let lo = hex_nibble(cleaned_bytes[i + 1])
87 .map_err(|()| bad_hex_digit(cleaned_bytes[i + 1]))?;
88 out.push((hi << 4) | lo);
89 }
90 return Ok(out);
91 }
92 let bytes = s.as_bytes();
95 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
96 let mut i = 0;
97 while i < bytes.len() {
98 let b = bytes[i];
99 if b == b'\\' && i + 1 < bytes.len() {
100 let n = bytes[i + 1];
101 if n == b'\\' {
102 out.push(b'\\');
103 i += 2;
104 continue;
105 }
106 if n.is_ascii_digit()
107 && i + 3 < bytes.len()
108 && bytes[i + 2].is_ascii_digit()
109 && bytes[i + 3].is_ascii_digit()
110 {
111 let oct = |x: u8| (x - b'0') as u32;
112 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
113 if v <= 0xFF {
114 out.push(v as u8);
115 i += 4;
116 continue;
117 }
118 }
119 }
120 out.push(b);
121 i += 1;
122 }
123 Ok(out)
124}
125
126pub(crate) fn hex_nibble(b: u8) -> Result<u8, ()> {
127 match b {
128 b'0'..=b'9' => Ok(b - b'0'),
129 b'a'..=b'f' => Ok(b - b'a' + 10),
130 b'A'..=b'F' => Ok(b - b'A' + 10),
131 _ => Err(()),
132 }
133}
134
135fn bad_hex_digit(b: u8) -> alloc::string::String {
137 alloc::format!("invalid hexadecimal digit: \"{}\"", b as char)
138}
139
140#[derive(Clone, Copy)]
145enum UniformArrayKind {
146 Bool,
147 Float,
148 Numeric,
149 Date,
150 Timestamp,
151 Uuid,
152 Bytes,
153 Interval,
154 Money,
155}
156
157impl UniformArrayKind {
158 fn build(self, items: alloc::vec::Vec<Value<'static>>) -> Value<'static> {
159 match self {
160 Self::Bool => Value::BoolArray(
161 items
162 .into_iter()
163 .map(|v| match v {
164 Value::Null => None,
165 Value::Bool(b) => Some(b),
166 _ => unreachable!("uniform Bool"),
167 })
168 .collect(),
169 ),
170 Self::Float => Value::FloatArray(
171 items
172 .into_iter()
173 .map(|v| match v {
174 Value::Null => None,
175 Value::Float(x) => Some(x),
176 _ => unreachable!("uniform Float"),
177 })
178 .collect(),
179 ),
180 Self::Numeric => Value::NumericArray(
181 items
182 .into_iter()
183 .map(|v| match v {
184 Value::Null => None,
185 Value::Numeric { scaled, scale, .. } => Some((scaled, scale)),
186 _ => unreachable!("uniform Numeric"),
187 })
188 .collect(),
189 ),
190 Self::Date => Value::DateArray(
191 items
192 .into_iter()
193 .map(|v| match v {
194 Value::Null => None,
195 Value::Date(d) => Some(d),
196 _ => unreachable!("uniform Date"),
197 })
198 .collect(),
199 ),
200 Self::Timestamp => Value::TimestampArray(
201 items
202 .into_iter()
203 .map(|v| match v {
204 Value::Null => None,
205 Value::Timestamp(t) => Some(t),
206 _ => unreachable!("uniform Timestamp"),
207 })
208 .collect(),
209 ),
210 Self::Uuid => Value::UuidArray(
211 items
212 .into_iter()
213 .map(|v| match v {
214 Value::Null => None,
215 Value::Uuid(b) => Some(b),
216 _ => unreachable!("uniform Uuid"),
217 })
218 .collect(),
219 ),
220 Self::Bytes => Value::BytesArray(
221 items
222 .into_iter()
223 .map(|v| match v {
224 Value::Null => None,
225 Value::Bytes(b) => Some(b.into_owned()),
226 _ => unreachable!("uniform Bytes"),
227 })
228 .collect(),
229 ),
230 Self::Interval => Value::IntervalArray(
231 items
232 .into_iter()
233 .map(|v| match v {
234 Value::Null => None,
235 Value::Interval {
236 months,
237 days,
238 micros,
239 kind,
240 } => Some(spg_storage::IntervalSpan {
241 months,
242 days,
243 micros,
244 kind,
245 }),
246 _ => unreachable!("uniform Interval"),
247 })
248 .collect(),
249 ),
250 Self::Money => Value::MoneyArray(
251 items
252 .into_iter()
253 .map(|v| match v {
254 Value::Null => None,
255 Value::Money(c) => Some(c),
256 _ => unreachable!("uniform Money"),
257 })
258 .collect(),
259 ),
260 }
261 }
262}
263
264fn widen_uniform_typed(items: &[Value<'static>]) -> Option<UniformArrayKind> {
265 let mut kind: Option<UniformArrayKind> = None;
266 let mut saw_non_null = false;
267 for v in items {
268 let this = match v {
269 Value::Null => continue,
270 Value::Bool(_) => UniformArrayKind::Bool,
271 Value::Float(_) => UniformArrayKind::Float,
272 Value::Numeric { .. } => UniformArrayKind::Numeric,
273 Value::Date(_) => UniformArrayKind::Date,
274 Value::Timestamp(_) => UniformArrayKind::Timestamp,
275 Value::Uuid(_) => UniformArrayKind::Uuid,
276 Value::Bytes(_) => UniformArrayKind::Bytes,
277 Value::Interval { .. } => UniformArrayKind::Interval,
278 Value::Money(_) => UniformArrayKind::Money,
279 _ => return None,
283 };
284 match kind {
285 None => kind = Some(this),
286 Some(prev) if discriminant_eq(prev, this) => {}
287 Some(_) => return None,
288 }
289 saw_non_null = true;
290 }
291 if saw_non_null { kind } else { None }
292}
293
294fn discriminant_eq(a: UniformArrayKind, b: UniformArrayKind) -> bool {
295 matches!(
296 (a, b),
297 (UniformArrayKind::Bool, UniformArrayKind::Bool)
298 | (UniformArrayKind::Float, UniformArrayKind::Float)
299 | (UniformArrayKind::Numeric, UniformArrayKind::Numeric)
300 | (UniformArrayKind::Date, UniformArrayKind::Date)
301 | (UniformArrayKind::Timestamp, UniformArrayKind::Timestamp)
302 | (UniformArrayKind::Uuid, UniformArrayKind::Uuid)
303 | (UniformArrayKind::Bytes, UniformArrayKind::Bytes)
304 | (UniformArrayKind::Interval, UniformArrayKind::Interval)
305 | (UniformArrayKind::Money, UniformArrayKind::Money)
306 )
307}
308
309pub(crate) fn array_literal_widen(items: alloc::vec::Vec<Value<'static>>) -> Value<'static> {
323 if let Some(m) = crate::eval::values::build_2d_from_rows(&items) {
334 return m;
335 }
336 if let Some(arr) = widen_uniform_typed(&items) {
337 return arr.build(items);
338 }
339 let mut has_text = false;
340 let mut has_bigint = false;
341 let mut has_int = false;
342 for v in &items {
343 match v {
344 Value::Null => {}
345 Value::Text(_) | Value::Json(_) => has_text = true,
346 Value::BigInt(_) => has_bigint = true,
347 Value::Int(_) | Value::SmallInt(_) => has_int = true,
348 _ => has_text = true,
349 }
350 }
351 if has_text || (!has_bigint && !has_int) {
352 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
353 .into_iter()
354 .map(|v| match v {
355 Value::Null => None,
356 Value::Text(s) | Value::Json(s) => Some(s.into_owned()),
357 other => Some(alloc::format!("{other:?}")),
358 })
359 .collect();
360 return Value::TextArray(out);
361 }
362 if has_bigint {
363 let out: alloc::vec::Vec<Option<i64>> = items
364 .into_iter()
365 .map(|v| match v {
366 Value::Null => None,
367 Value::Int(n) => Some(i64::from(n)),
368 Value::SmallInt(n) => Some(i64::from(n)),
369 Value::BigInt(n) => Some(n),
370 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
371 })
372 .collect();
373 return Value::BigIntArray(out);
374 }
375 let out: alloc::vec::Vec<Option<i32>> = items
376 .into_iter()
377 .map(|v| match v {
378 Value::Null => None,
379 Value::Int(n) => Some(n),
380 Value::SmallInt(n) => Some(i32::from(n)),
381 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
382 })
383 .collect();
384 Value::IntArray(out)
385}
386
387#[must_use]
403pub(crate) fn malformed_array_literal(text: &str) -> alloc::string::String {
404 let t = text.trim();
405 let detail = if !t.starts_with('{') {
406 "Array value must start with \"{\" or dimension information."
407 } else {
408 match first_unquoted_close_brace(&t[1..]) {
412 None => "Unexpected end of input.",
413 Some(close) => {
414 let inner = &t[1..1 + close];
415 if !t[1 + close + 1..].trim().is_empty() {
416 "Junk after closing right brace."
417 } else if inner.trim_end().ends_with(',') {
418 "Unexpected \"}\" character."
419 } else {
420 "Unexpected end of input."
421 }
422 }
423 }
424 };
425 alloc::format!("malformed array literal: \"{text}\" DETAIL: {detail}")
426}
427
428fn first_unquoted_close_brace(body: &str) -> Option<usize> {
430 let bs = body.as_bytes();
431 let mut in_quote = false;
432 let mut k = 0;
433 while k < bs.len() {
434 match bs[k] {
435 b'\\' if in_quote => k += 1,
436 b'"' => in_quote = !in_quote,
437 b'}' if !in_quote => return Some(k),
438 _ => {}
439 }
440 k += 1;
441 }
442 None
443}
444
445pub(crate) fn decode_text_array_literal(
446 s: &str,
447) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
448 let trimmed = s.trim();
449 let body = trimmed
455 .strip_prefix('{')
456 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
457 let close =
458 first_unquoted_close_brace(body).ok_or("TEXT[] literal must be enclosed in '{...}'")?;
459 if !body[close + 1..].trim().is_empty() {
460 return Err("junk after closing right brace");
461 }
462 let inner = &body[..close];
463 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
464 if inner.trim().is_empty() {
465 return Ok(out);
466 }
467 let bytes = inner.as_bytes();
468 let mut i = 0;
469 while i <= bytes.len() {
470 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
472 i += 1;
473 }
474 if i < bytes.len() && bytes[i] == b'"' {
476 i += 1; let mut buf = alloc::string::String::new();
478 while i < bytes.len() && bytes[i] != b'"' {
479 if bytes[i] == b'\\' && i + 1 < bytes.len() {
480 buf.push(bytes[i + 1] as char);
481 i += 2;
482 } else {
483 buf.push(bytes[i] as char);
484 i += 1;
485 }
486 }
487 if i >= bytes.len() {
488 return Err("unterminated quoted element");
489 }
490 i += 1; out.push(Some(buf));
492 } else {
493 let start = i;
495 while i < bytes.len() && bytes[i] != b',' {
496 i += 1;
497 }
498 let raw = inner[start..i].trim();
499 if raw.is_empty() {
504 return Err("empty array element");
505 }
506 if raw.eq_ignore_ascii_case("NULL") {
507 out.push(None);
508 } else {
509 out.push(Some(alloc::string::ToString::to_string(raw)));
510 }
511 }
512 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
514 i += 1;
515 }
516 if i >= bytes.len() {
517 break;
518 }
519 if bytes[i] != b',' {
520 return Err("expected ',' between TEXT[] elements");
521 }
522 i += 1;
523 }
524 Ok(out)
525}
526
527pub(crate) fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
532 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
533 out.push('{');
534 for (i, item) in items.iter().enumerate() {
535 if i > 0 {
536 out.push(',');
537 }
538 match item {
539 None => out.push_str("NULL"),
540 Some(s) => {
541 let needs_quote = s.is_empty()
542 || s.eq_ignore_ascii_case("NULL")
543 || s.chars()
544 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
545 if needs_quote {
546 out.push('"');
547 for c in s.chars() {
548 if c == '"' || c == '\\' {
549 out.push('\\');
550 }
551 out.push(c);
552 }
553 out.push('"');
554 } else {
555 out.push_str(s);
556 }
557 }
558 }
559 }
560 out.push('}');
561 out
562}
563
564pub(crate) fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
568 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
569 out.push_str("\\x");
570 for byte in b {
571 let hi = byte >> 4;
572 let lo = byte & 0x0F;
573 out.push(hex_digit(hi));
574 out.push(hex_digit(lo));
575 }
576 out
577}
578
579pub(crate) const fn hex_digit(n: u8) -> char {
580 match n {
581 0..=9 => (b'0' + n) as char,
582 10..=15 => (b'a' + n - 10) as char,
583 _ => '?',
584 }
585}
586
587pub(crate) fn parse_hstore_str(
600 s: &str,
601) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
602 let bytes = s.as_bytes();
603 let mut i = 0;
604 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
605 let skip_ws = |bytes: &[u8], i: &mut usize| {
606 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
607 *i += 1;
608 }
609 };
610 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
611 if *i >= bytes.len() {
612 return None;
613 }
614 if bytes[*i] == b'"' {
615 *i += 1;
616 let mut out = alloc::string::String::new();
617 while *i < bytes.len() {
618 match bytes[*i] {
619 b'"' => {
620 *i += 1;
621 return Some(out);
622 }
623 b'\\' if *i + 1 < bytes.len() => {
624 out.push(bytes[*i + 1] as char);
625 *i += 2;
626 }
627 c => {
628 out.push(c as char);
629 *i += 1;
630 }
631 }
632 }
633 None
634 } else {
635 let start = *i;
636 while *i < bytes.len()
637 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
638 {
639 *i += 1;
640 }
641 if *i == start {
642 return None;
643 }
644 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
645 }
646 };
647 skip_ws(bytes, &mut i);
648 while i < bytes.len() {
649 let key = parse_token(bytes, &mut i)?;
650 skip_ws(bytes, &mut i);
651 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
652 return None;
653 }
654 i += 2;
655 skip_ws(bytes, &mut i);
656 let val_token = if i + 4 <= bytes.len()
658 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
659 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
660 {
661 i += 4;
662 None
663 } else {
664 Some(parse_token(bytes, &mut i)?)
665 };
666 if out.iter().any(|(k, _)| k == &key) {
671 } else {
673 out.push((key, val_token));
674 }
675 skip_ws(bytes, &mut i);
676 if i >= bytes.len() {
677 break;
678 }
679 if bytes[i] == b',' {
680 i += 1;
681 skip_ws(bytes, &mut i);
682 continue;
683 }
684 return None;
685 }
686 Some(out)
687}
688
689pub(crate) fn format_hstore_str(
693 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
694) -> alloc::string::String {
695 let mut out = alloc::string::String::new();
696 for (i, (k, v)) in pairs.iter().enumerate() {
697 if i > 0 {
698 out.push_str(", ");
699 }
700 out.push('"');
701 out.push_str(k);
702 out.push_str("\"=>");
703 match v {
704 None => out.push_str("NULL"),
705 Some(val) => {
706 out.push('"');
707 out.push_str(val);
708 out.push('"');
709 }
710 }
711 }
712 out
713}
714
715pub fn format_hstore_text(
718 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
719) -> alloc::string::String {
720 format_hstore_str(pairs)
721}
722
723pub(crate) fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
728 let s = s.trim();
729 let outer = s
730 .strip_prefix('{')
731 .and_then(|x| x.strip_suffix('}'))
732 .ok_or("missing outer '{...}' braces")?;
733 let trimmed = outer.trim();
734 if trimmed.is_empty() {
735 return Ok(Vec::new());
736 }
737 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
738 let mut i = 0;
739 let bytes = trimmed.as_bytes();
740 while i < bytes.len() {
741 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
742 i += 1;
743 }
744 if i >= bytes.len() {
745 break;
746 }
747 if bytes[i] != b'{' {
748 return Err("expected '{' opening a row");
749 }
750 i += 1;
751 let row_start = i;
752 let mut depth = 1;
753 while i < bytes.len() && depth > 0 {
754 match bytes[i] {
755 b'{' => depth += 1,
756 b'}' => depth -= 1,
757 _ => {}
758 }
759 if depth > 0 {
760 i += 1;
761 }
762 }
763 if depth != 0 {
764 return Err("unbalanced '{...}' in row");
765 }
766 let row_text = &trimmed[row_start..i];
767 i += 1;
768 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
769 Vec::new()
770 } else {
771 row_text.split(',').map(|t| t.trim().to_string()).collect()
772 };
773 rows.push(cells);
774 }
775 if let Some(first) = rows.first() {
776 let cols = first.len();
777 for r in &rows {
778 if r.len() != cols {
779 return Err("ragged 2D array (rows have different column counts)");
780 }
781 }
782 }
783 Ok(rows)
784}
785
786pub(crate) fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
787 let raw = split_2d_literal(s)?;
788 raw.into_iter()
789 .map(|row| {
790 row.into_iter()
791 .map(|cell| {
792 if cell.eq_ignore_ascii_case("NULL") {
793 Ok(None)
794 } else {
795 cell.parse::<i32>()
796 .map(Some)
797 .map_err(|_| "invalid int element")
798 }
799 })
800 .collect()
801 })
802 .collect()
803}
804
805pub(crate) fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
806 let raw = split_2d_literal(s)?;
807 raw.into_iter()
808 .map(|row| {
809 row.into_iter()
810 .map(|cell| {
811 if cell.eq_ignore_ascii_case("NULL") {
812 Ok(None)
813 } else {
814 cell.parse::<i64>()
815 .map(Some)
816 .map_err(|_| "invalid bigint element")
817 }
818 })
819 .collect()
820 })
821 .collect()
822}
823
824pub(crate) fn parse_text_2d_literal(
825 s: &str,
826) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
827 let raw = split_2d_literal(s)?;
828 Ok(raw
829 .into_iter()
830 .map(|row| {
831 row.into_iter()
832 .map(|cell| {
833 if cell.eq_ignore_ascii_case("NULL") {
834 None
835 } else {
836 Some(cell.trim_matches('"').to_string())
837 }
838 })
839 .collect()
840 })
841 .collect())
842}
843
844pub(crate) fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
845 let mut out = alloc::string::String::from("{");
846 for (i, row) in rows.iter().enumerate() {
847 if i > 0 {
848 out.push(',');
849 }
850 out.push('{');
851 for (j, cell) in row.iter().enumerate() {
852 if j > 0 {
853 out.push(',');
854 }
855 match cell {
856 None => out.push_str("NULL"),
857 Some(n) => out.push_str(&alloc::format!("{n}")),
858 }
859 }
860 out.push('}');
861 }
862 out.push('}');
863 out
864}
865
866pub(crate) fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
867 let mut out = alloc::string::String::from("{");
868 for (i, row) in rows.iter().enumerate() {
869 if i > 0 {
870 out.push(',');
871 }
872 out.push('{');
873 for (j, cell) in row.iter().enumerate() {
874 if j > 0 {
875 out.push(',');
876 }
877 match cell {
878 None => out.push_str("NULL"),
879 Some(n) => out.push_str(&alloc::format!("{n}")),
880 }
881 }
882 out.push('}');
883 }
884 out.push('}');
885 out
886}
887
888pub(crate) fn format_text_2d_text(
889 rows: &[Vec<Option<alloc::string::String>>],
890) -> alloc::string::String {
891 let mut out = alloc::string::String::from("{");
892 for (i, row) in rows.iter().enumerate() {
893 if i > 0 {
894 out.push(',');
895 }
896 out.push('{');
897 for (j, cell) in row.iter().enumerate() {
898 if j > 0 {
899 out.push(',');
900 }
901 match cell {
902 None => out.push_str("NULL"),
903 Some(s) => out.push_str(s),
904 }
905 }
906 out.push('}');
907 }
908 out.push('}');
909 out
910}
911
912pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
915 format_int_2d_text(rows)
916}
917pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
918 format_bigint_2d_text(rows)
919}
920pub fn format_text_2d_text_pub(
921 rows: &[Vec<Option<alloc::string::String>>],
922) -> alloc::string::String {
923 format_text_2d_text(rows)
924}
925
926#[must_use]
930pub fn format_bool_2d_text_pub(rows: &[Vec<Option<bool>>]) -> alloc::string::String {
931 use core::fmt::Write as _;
932 let mut out = alloc::string::String::from("{");
933 for (i, row) in rows.iter().enumerate() {
934 if i > 0 {
935 out.push(',');
936 }
937 out.push('{');
938 for (j, cell) in row.iter().enumerate() {
939 if j > 0 {
940 out.push(',');
941 }
942 let _ = match cell {
943 None => write!(out, "NULL"),
944 Some(true) => write!(out, "t"),
945 Some(false) => write!(out, "f"),
946 };
947 }
948 out.push('}');
949 }
950 out.push('}');
951 out
952}
953
954pub(crate) type CanonRangeBounds = (
969 Option<Value<'static>>,
970 Option<Value<'static>>,
971 bool,
972 bool,
973 bool,
974);
975
976pub(crate) fn canonicalize_range_bounds(
978 kind: spg_storage::RangeKind,
979 lower: Option<Value<'static>>,
980 upper: Option<Value<'static>>,
981 lower_inc: bool,
982 upper_inc: bool,
983) -> Option<CanonRangeBounds> {
984 use spg_storage::RangeKind as K;
985 let mut lower_inc = lower.is_some() && lower_inc;
987 let mut upper_inc = upper.is_some() && upper_inc;
988 let mut lower = lower;
989 let mut upper = upper;
990 if matches!(kind, K::Int4 | K::Int8 | K::Date) {
991 fn succ(v: Value<'static>) -> Option<Value<'static>> {
992 Some(match v {
993 Value::Int(n) => Value::Int(n.checked_add(1)?),
994 Value::BigInt(n) => Value::BigInt(n.checked_add(1)?),
995 Value::Date(d) => Value::Date(d.checked_add(1)?),
996 other => other,
997 })
998 }
999 if let Some(l) = lower {
1000 lower = Some(if lower_inc { l } else { succ(l)? });
1001 lower_inc = true;
1002 }
1003 if let Some(u) = upper {
1004 upper = Some(if upper_inc { succ(u)? } else { u });
1005 upper_inc = false;
1006 }
1007 }
1008 let empty = match (&lower, &upper) {
1010 (Some(l), Some(u)) => l == u && !(lower_inc && upper_inc),
1011 _ => false,
1012 };
1013 Some((lower, upper, lower_inc, upper_inc, empty))
1014}
1015
1016pub(crate) enum RangeParseError {
1020 Malformed,
1021 Misordered,
1022 BadElement(alloc::string::String),
1028}
1029
1030fn range_element_type_name(kind: spg_storage::RangeKind) -> &'static str {
1033 match kind {
1034 spg_storage::RangeKind::Int4 => "integer",
1035 spg_storage::RangeKind::Int8 => "bigint",
1036 spg_storage::RangeKind::Num => "numeric",
1037 spg_storage::RangeKind::Ts => "timestamp",
1038 spg_storage::RangeKind::TsTz => "timestamp with time zone",
1039 spg_storage::RangeKind::Date => "date",
1040 }
1041}
1042
1043pub(crate) fn range_bounds_misordered(
1046 lower: &Option<Value<'static>>,
1047 upper: &Option<Value<'static>>,
1048) -> bool {
1049 match (lower, upper) {
1050 (Some(l), Some(u)) => crate::orderby::value_cmp(l, u) == core::cmp::Ordering::Greater,
1051 _ => false,
1052 }
1053}
1054
1055pub(crate) fn parse_range_str(
1056 s: &str,
1057 kind: spg_storage::RangeKind,
1058) -> Result<Value<'static>, RangeParseError> {
1059 let s = s.trim();
1060 if s.eq_ignore_ascii_case("empty") {
1061 return Ok(Value::Range {
1062 kind,
1063 lower: None,
1064 upper: None,
1065 lower_inc: false,
1066 upper_inc: false,
1067 empty: true,
1068 });
1069 }
1070 let bytes = s.as_bytes();
1071 if bytes.len() < 3 {
1072 return Err(RangeParseError::Malformed);
1073 }
1074 let lower_inc = match bytes[0] {
1075 b'[' => true,
1076 b'(' => false,
1077 _ => return Err(RangeParseError::Malformed),
1078 };
1079 let upper_inc = match bytes[bytes.len() - 1] {
1080 b']' => true,
1081 b')' => false,
1082 _ => return Err(RangeParseError::Malformed),
1083 };
1084 let inner = &s[1..s.len() - 1];
1085 let (lo_text, up_text) = inner.split_once(',').ok_or(RangeParseError::Malformed)?;
1086 let lower = if lo_text.is_empty() {
1087 None
1088 } else {
1089 Some(
1090 parse_range_element(lo_text, kind)
1091 .ok_or_else(|| RangeParseError::BadElement(lo_text.trim().into()))?,
1092 )
1093 };
1094 let upper = if up_text.is_empty() {
1095 None
1096 } else {
1097 Some(
1098 parse_range_element(up_text, kind)
1099 .ok_or_else(|| RangeParseError::BadElement(up_text.trim().into()))?,
1100 )
1101 };
1102 if range_bounds_misordered(&lower, &upper) {
1105 return Err(RangeParseError::Misordered);
1106 }
1107 let (lower, upper, lower_inc, upper_inc, empty) =
1110 canonicalize_range_bounds(kind, lower, upper, lower_inc, upper_inc)
1111 .ok_or(RangeParseError::Malformed)?;
1112 Ok(Value::Range {
1113 kind,
1114 lower: lower.map(alloc::boxed::Box::new),
1115 upper: upper.map(alloc::boxed::Box::new),
1116 lower_inc,
1117 upper_inc,
1118 empty,
1119 })
1120}
1121
1122pub(crate) fn parse_multirange_str(
1129 s: &str,
1130 kind: spg_storage::RangeKind,
1131) -> Option<Vec<spg_storage::RangeSpan>> {
1132 let s = s.trim();
1133 let inner = s.strip_prefix('{').and_then(|x| x.strip_suffix('}'))?;
1134 let inner = inner.trim();
1135 if inner.is_empty() {
1136 return Some(Vec::new());
1137 }
1138 let mut spans: Vec<spg_storage::RangeSpan> = Vec::new();
1142 let bytes = inner.as_bytes();
1143 let mut depth: i32 = 0;
1144 let mut start = 0usize;
1145 for i in 0..=bytes.len() {
1146 let cut = i == bytes.len() || (depth == 0 && bytes[i] == b',');
1147 if !cut {
1148 match bytes.get(i) {
1149 Some(b'[') | Some(b'(') => depth += 1,
1150 Some(b']') | Some(b')') => depth -= 1,
1151 _ => {}
1152 }
1153 continue;
1154 }
1155 let piece = inner[start..i].trim();
1156 if piece.is_empty() {
1157 return None;
1158 }
1159 let r = parse_range_str(piece, kind).ok()?;
1160 let Value::Range {
1161 lower,
1162 upper,
1163 lower_inc,
1164 upper_inc,
1165 empty,
1166 ..
1167 } = r
1168 else {
1169 return None;
1170 };
1171 spans.push(spg_storage::RangeSpan {
1172 lower,
1173 upper,
1174 lower_inc,
1175 upper_inc,
1176 empty,
1177 });
1178 start = i + 1;
1179 }
1180 Some(spans)
1181}
1182
1183fn parse_hhmm_offset_secs(off: &str) -> Option<i32> {
1187 let (h, m) = match off.split_once(':') {
1188 Some((h, m)) => (h, m),
1189 None => (off, "0"),
1190 };
1191 let h: i32 = h.parse().ok()?;
1192 let m: i32 = m.parse().ok()?;
1193 if !(0..=15).contains(&h) || !(0..60).contains(&m) {
1194 return None;
1195 }
1196 Some(h * 3600 + m * 60)
1197}
1198
1199pub(crate) fn regtype_name_to_oid(name: &str) -> Option<i64> {
1203 if let Some(base) = name.trim().strip_suffix("[]") {
1207 return array_oid_for_element(regtype_name_to_oid(base)?);
1208 }
1209 Some(match name.trim() {
1210 "bool" | "boolean" => 16,
1211 "bytea" => 17,
1212 "name" => 19,
1213 "int8" | "bigint" => 20,
1214 "int2" | "smallint" => 21,
1215 "int4" | "int" | "integer" => 23,
1216 "text" => 25,
1217 "oid" => 26,
1218 "json" => 114,
1219 "xml" => 142,
1220 "float4" | "real" => 700,
1221 "float8" | "double precision" => 701,
1222 "cidr" => 650,
1223 "inet" => 869,
1224 "macaddr" => 829,
1225 "macaddr8" => 774,
1226 "money" => 790,
1227 "bpchar" | "char" | "character" => 1042,
1228 "varchar" | "character varying" => 1043,
1229 "date" => 1082,
1230 "time" | "time without time zone" => 1083,
1231 "timestamp" | "timestamp without time zone" => 1114,
1232 "timestamptz" | "timestamp with time zone" => 1184,
1233 "interval" => 1186,
1234 "timetz" | "time with time zone" => 1266,
1235 "numeric" | "decimal" => 1700,
1236 "uuid" => 2950,
1237 "jsonb" => 3802,
1238 "tsvector" => 3614,
1239 "tsquery" => 3615,
1240 "pg_lsn" => 3220,
1241 "regtype" => 2206,
1242 "regclass" => 2205,
1243 "regproc" => 24,
1244 "xid" => 28,
1249 "xid8" => 5069,
1250 "tid" => 27,
1251 "cid" => 29,
1252 _ => return None,
1253 })
1254}
1255
1256pub(crate) fn regtype_canonical_name(name: &str) -> Option<alloc::string::String> {
1260 let t = name.trim();
1261 if let Some(base) = t.strip_suffix("[]") {
1262 let inner = regtype_canonical_name(base)?;
1263 return Some(alloc::format!("{inner}[]"));
1264 }
1265 if let Some(base) = t.strip_prefix('_') {
1267 let inner = regtype_canonical_name(base)?;
1268 return Some(alloc::format!("{inner}[]"));
1269 }
1270 let oid = regtype_name_to_oid(&t.to_lowercase())?;
1271 regtype_oid_to_name(oid).map(alloc::string::String::from)
1272}
1273
1274pub(crate) fn parse_range_element(
1275 text: &str,
1276 kind: spg_storage::RangeKind,
1277) -> Option<Value<'static>> {
1278 let text = text.trim().trim_matches('"');
1279 use spg_storage::RangeKind as K;
1280 match kind {
1281 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
1282 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
1283 K::Num => {
1284 let dot = text.find('.');
1287 let scale: u16 = dot.map_or(0, |p| (text.len() - p - 1) as u16);
1288 let digits: alloc::string::String = text
1289 .chars()
1290 .filter(|c| *c == '-' || c.is_ascii_digit())
1291 .collect();
1292 let scaled: i128 = digits.parse().ok()?;
1293 Some(Value::Numeric {
1294 scaled,
1295 scale,
1296 kind: spg_storage::NumericKind::Finite,
1297 })
1298 }
1299 K::Ts | K::TsTz => {
1300 crate::eval::parse_timestamp_literal(text)
1304 .or_else(|| {
1305 let (date_part, off) = text.split_once(['+'])?;
1306 if !off.chars().all(|c| c.is_ascii_digit() || c == ':') {
1307 return None;
1308 }
1309 let d = crate::eval::parse_date_literal(date_part.trim())?;
1310 let mut t = i64::from(d) * 86_400_000_000;
1311 let secs = parse_hhmm_offset_secs(off)?;
1313 t -= i64::from(secs) * 1_000_000;
1314 Some(t)
1315 })
1316 .map(Value::Timestamp)
1317 }
1318 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
1319 }
1320}
1321
1322pub fn format_range_text(v: &Value) -> alloc::string::String {
1326 format_range_str(v)
1327}
1328
1329pub(crate) fn format_range_str(v: &Value) -> alloc::string::String {
1330 let Value::Range {
1331 kind,
1332 lower,
1333 upper,
1334 lower_inc,
1335 upper_inc,
1336 empty,
1337 } = v
1338 else {
1339 return alloc::string::String::new();
1340 };
1341 if *empty {
1342 return "empty".into();
1343 }
1344 let elem = |v: &Value| -> alloc::string::String {
1349 let base = format_range_element(v);
1350 if matches!(kind, spg_storage::RangeKind::TsTz) && matches!(v, Value::Timestamp(_)) {
1351 alloc::format!("{base}+00")
1352 } else {
1353 base
1354 }
1355 };
1356 let mut out = alloc::string::String::new();
1357 out.push(if *lower_inc { '[' } else { '(' });
1358 if let Some(l) = lower {
1359 out.push_str("e_range_bound(&elem(l)));
1360 }
1361 out.push(',');
1362 if let Some(u) = upper {
1363 out.push_str("e_range_bound(&elem(u)));
1364 }
1365 out.push(if *upper_inc { ']' } else { ')' });
1366 out
1367}
1368
1369fn quote_range_bound(s: &str) -> alloc::string::String {
1376 let needs_quote = s.is_empty()
1377 || s.chars()
1378 .any(|c| matches!(c, '"' | '\\' | '(' | ')' | '[' | ']' | ',') || c.is_whitespace());
1379 if !needs_quote {
1380 return s.into();
1381 }
1382 let mut out = alloc::string::String::with_capacity(s.len() + 2);
1383 out.push('"');
1384 for c in s.chars() {
1385 if c == '"' || c == '\\' {
1386 out.push('\\');
1387 }
1388 out.push(c);
1389 }
1390 out.push('"');
1391 out
1392}
1393
1394pub fn format_point(p: spg_storage::Point2D) -> alloc::string::String {
1396 alloc::format!("({},{})", p.x, p.y)
1397}
1398
1399pub fn format_lseg(p1: spg_storage::Point2D, p2: spg_storage::Point2D) -> alloc::string::String {
1401 alloc::format!("[({},{}),({},{})]", p1.x, p1.y, p2.x, p2.y)
1402}
1403
1404pub fn format_pg_box(ur: spg_storage::Point2D, ll: spg_storage::Point2D) -> alloc::string::String {
1409 alloc::format!("({},{}),({},{})", ur.x, ur.y, ll.x, ll.y)
1410}
1411
1412pub fn format_line(a: f64, b: f64, c: f64) -> alloc::string::String {
1414 alloc::format!("{{{},{},{}}}", a, b, c)
1415}
1416
1417pub fn format_circle(center: spg_storage::Point2D, radius: f64) -> alloc::string::String {
1419 alloc::format!("<({},{}),{}>", center.x, center.y, radius)
1420}
1421
1422pub fn format_path(points: &[spg_storage::Point2D], closed: bool) -> alloc::string::String {
1425 let (open, close) = if closed { ('(', ')') } else { ('[', ']') };
1426 let mut out = alloc::string::String::new();
1427 out.push(open);
1428 for (i, p) in points.iter().enumerate() {
1429 if i > 0 {
1430 out.push(',');
1431 }
1432 out.push_str(&alloc::format!("({},{})", p.x, p.y));
1433 }
1434 out.push(close);
1435 out
1436}
1437
1438pub fn format_polygon(points: &[spg_storage::Point2D]) -> alloc::string::String {
1440 let mut out = alloc::string::String::new();
1441 out.push('(');
1442 for (i, p) in points.iter().enumerate() {
1443 if i > 0 {
1444 out.push(',');
1445 }
1446 out.push_str(&alloc::format!("({},{})", p.x, p.y));
1447 }
1448 out.push(')');
1449 out
1450}
1451
1452fn parse_point(s: &str) -> Option<spg_storage::Point2D> {
1455 let s = s.trim();
1456 let inner = s
1457 .strip_prefix('(')
1458 .and_then(|x| x.strip_suffix(')'))
1459 .unwrap_or(s);
1460 let (xs, ys) = inner.split_once(',')?;
1461 let x: f64 = xs.trim().parse().ok()?;
1462 let y: f64 = ys.trim().parse().ok()?;
1463 Some(spg_storage::Point2D { x, y })
1464}
1465
1466fn parse_point_list(s: &str) -> Option<Vec<spg_storage::Point2D>> {
1471 let bytes = s.as_bytes();
1472 let mut out: Vec<spg_storage::Point2D> = Vec::new();
1473 let mut depth: i32 = 0;
1474 let mut start = 0usize;
1475 for i in 0..=bytes.len() {
1476 let cut = i == bytes.len() || (depth == 0 && bytes[i] == b',');
1477 if !cut {
1478 match bytes.get(i) {
1479 Some(b'(') | Some(b'[') | Some(b'<') => depth += 1,
1480 Some(b')') | Some(b']') | Some(b'>') => depth -= 1,
1481 _ => {}
1482 }
1483 continue;
1484 }
1485 let piece = s[start..i].trim();
1486 if !piece.is_empty() {
1487 out.push(parse_point(piece)?);
1488 }
1489 start = i + 1;
1490 }
1491 Some(out)
1492}
1493
1494pub fn parse_lseg_text(s: &str) -> Option<(spg_storage::Point2D, spg_storage::Point2D)> {
1496 let s = s.trim();
1497 let inner = s
1500 .strip_prefix('[')
1501 .and_then(|x| x.strip_suffix(']'))
1502 .unwrap_or(s);
1503 let two_points = |v: Option<alloc::vec::Vec<spg_storage::Point2D>>| v.filter(|p| p.len() == 2);
1504 let pts = if let Some(p) = two_points(parse_point_list(inner)) {
1505 p
1506 } else {
1507 inner
1508 .strip_prefix('(')
1509 .and_then(|x| x.strip_suffix(')'))
1510 .and_then(|w| two_points(parse_point_list(w)))?
1511 };
1512 Some((pts[0], pts[1]))
1513}
1514
1515pub fn parse_box_text(s: &str) -> Option<(spg_storage::Point2D, spg_storage::Point2D)> {
1519 let s = s.trim();
1523 let two_points = |v: Option<alloc::vec::Vec<spg_storage::Point2D>>| v.filter(|p| p.len() == 2);
1524 let pts = if let Some(p) = two_points(parse_point_list(s)) {
1525 p
1526 } else if let Some(p) = s
1527 .strip_prefix('(')
1528 .and_then(|x| x.strip_suffix(')'))
1529 .and_then(|inner| two_points(parse_point_list(inner)))
1530 {
1531 p
1532 } else {
1533 let nums: Option<alloc::vec::Vec<f64>> =
1534 s.split(',').map(|t| t.trim().parse::<f64>().ok()).collect();
1535 let nums = nums?;
1536 if nums.len() != 4 {
1537 return None;
1538 }
1539 alloc::vec![
1540 spg_storage::Point2D {
1541 x: nums[0],
1542 y: nums[1]
1543 },
1544 spg_storage::Point2D {
1545 x: nums[2],
1546 y: nums[3]
1547 },
1548 ]
1549 };
1550 if pts.len() != 2 {
1551 return None;
1552 }
1553 let (a, b) = (pts[0], pts[1]);
1554 let ur = spg_storage::Point2D {
1556 x: a.x.max(b.x),
1557 y: a.y.max(b.y),
1558 };
1559 let ll = spg_storage::Point2D {
1560 x: a.x.min(b.x),
1561 y: a.y.min(b.y),
1562 };
1563 Some((ur, ll))
1564}
1565
1566pub fn parse_line_text(s: &str) -> Option<(f64, f64, f64)> {
1568 let s = s.trim();
1569 if let Some(inner) = s.strip_prefix('{').and_then(|x| x.strip_suffix('}')) {
1570 let parts: Vec<&str> = inner.split(',').collect();
1571 if parts.len() != 3 {
1572 return None;
1573 }
1574 let a: f64 = parts[0].trim().parse().ok()?;
1575 let b: f64 = parts[1].trim().parse().ok()?;
1576 if a == 0.0 && b == 0.0 {
1578 return None;
1579 }
1580 let c: f64 = parts[2].trim().parse().ok()?;
1581 return Some((a, b, c));
1582 }
1583 let (p1, p2) = parse_lseg_text(s)?;
1588 if p1.x == p2.x && p1.y == p2.y {
1589 return None;
1590 }
1591 Some(line_from_points(p1, p2))
1592}
1593
1594pub fn line_from_points(p1: spg_storage::Point2D, p2: spg_storage::Point2D) -> (f64, f64, f64) {
1596 if p1.x == p2.x {
1597 (-1.0, 0.0, p1.x)
1598 } else if p1.y == p2.y {
1599 (0.0, -1.0, p1.y)
1600 } else {
1601 let m = (p1.y - p2.y) / (p1.x - p2.x);
1602 let c = p1.y - m * p1.x;
1603 (m, -1.0, if c == 0.0 { 0.0 } else { c })
1604 }
1605}
1606
1607pub fn parse_circle_text(s: &str) -> Option<(spg_storage::Point2D, f64)> {
1609 let s = s.trim();
1610 let inner = if let Some(i) = s.strip_prefix('<').and_then(|x| x.strip_suffix('>')) {
1612 i
1613 } else if let Some(i) = s.strip_prefix('(').and_then(|x| x.strip_suffix(')')) {
1614 i
1615 } else {
1616 s
1617 };
1618 let bytes = inner.as_bytes();
1620 let mut depth = 0i32;
1621 let mut split_at: Option<usize> = None;
1622 for (i, &b) in bytes.iter().enumerate() {
1623 match b {
1624 b'(' | b'[' | b'<' => depth += 1,
1625 b')' | b']' | b'>' => depth -= 1,
1626 b',' if depth == 0 => split_at = Some(i),
1627 _ => {}
1628 }
1629 }
1630 let i = split_at?;
1631 let center = parse_point(&inner[..i])?;
1632 let radius: f64 = inner[i + 1..].trim().parse().ok()?;
1633 Some((center, radius))
1634}
1635
1636pub fn parse_path_text(s: &str) -> Option<(Vec<spg_storage::Point2D>, bool)> {
1639 let s = s.trim();
1640 if let Some(i) = s.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
1645 if let Some(pts) = parse_point_list(i) {
1646 return Some((pts, false));
1647 }
1648 }
1649 if let Some(i) = s.strip_prefix('(').and_then(|x| x.strip_suffix(')')) {
1650 if let Some(pts) = parse_point_list(i) {
1651 return Some((pts, true));
1652 }
1653 }
1654 parse_point_list(s).map(|pts| (pts, true))
1655}
1656
1657pub fn parse_polygon_text(s: &str) -> Option<Vec<spg_storage::Point2D>> {
1659 let s = s.trim();
1660 if let Some(inner) = s.strip_prefix('(').and_then(|x| x.strip_suffix(')')) {
1664 if let Some(pts) = parse_point_list(inner) {
1665 return Some(pts);
1666 }
1667 }
1668 parse_point_list(s)
1669}
1670
1671pub fn format_inet_full(family: u8, bits: u8, addr: &[u8; 16]) -> alloc::string::String {
1679 let max = if family == 4 { 32 } else { 128 };
1680 let base = format_inet(family, max, addr);
1681 alloc::format!("{base}/{bits}")
1682}
1683
1684pub fn format_inet(family: u8, bits: u8, addr: &[u8; 16]) -> alloc::string::String {
1685 match family {
1686 4 => {
1687 let s = alloc::format!("{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3]);
1688 if bits == 32 {
1689 s
1690 } else {
1691 alloc::format!("{s}/{bits}")
1692 }
1693 }
1694 6 => {
1695 let mut groups = [0u16; 8];
1699 for (i, g) in groups.iter_mut().enumerate() {
1700 *g = (u16::from(addr[i * 2]) << 8) | u16::from(addr[i * 2 + 1]);
1701 }
1702 if groups[..5].iter().all(|&g| g == 0) && groups[5] == 0xffff {
1706 let s =
1707 alloc::format!("::ffff:{}.{}.{}.{}", addr[12], addr[13], addr[14], addr[15]);
1708 return if bits == 128 {
1709 s
1710 } else {
1711 alloc::format!("{s}/{bits}")
1712 };
1713 }
1714 let (mut best_start, mut best_len) = (usize::MAX, 0usize);
1715 let mut i = 0;
1716 while i < 8 {
1717 if groups[i] == 0 {
1718 let start = i;
1719 while i < 8 && groups[i] == 0 {
1720 i += 1;
1721 }
1722 if i - start > best_len {
1723 best_start = start;
1724 best_len = i - start;
1725 }
1726 } else {
1727 i += 1;
1728 }
1729 }
1730 let mut out = alloc::string::String::new();
1731 if best_len >= 2 {
1732 for (idx, g) in groups.iter().enumerate().take(best_start) {
1733 if idx > 0 {
1734 out.push(':');
1735 }
1736 out.push_str(&alloc::format!("{g:x}"));
1737 }
1738 out.push_str("::");
1739 for (idx, g) in groups.iter().enumerate().skip(best_start + best_len) {
1740 if idx > best_start + best_len {
1741 out.push(':');
1742 }
1743 out.push_str(&alloc::format!("{g:x}"));
1744 }
1745 } else {
1746 for (idx, g) in groups.iter().enumerate() {
1747 if idx > 0 {
1748 out.push(':');
1749 }
1750 out.push_str(&alloc::format!("{g:x}"));
1751 }
1752 }
1753 if bits == 128 {
1754 out
1755 } else {
1756 alloc::format!("{out}/{bits}")
1757 }
1758 }
1759 _ => alloc::format!("?invalid-inet-family-{family}"),
1760 }
1761}
1762
1763pub fn format_macaddr(m: &[u8; 6]) -> alloc::string::String {
1765 alloc::format!(
1766 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
1767 m[0],
1768 m[1],
1769 m[2],
1770 m[3],
1771 m[4],
1772 m[5]
1773 )
1774}
1775
1776pub fn format_macaddr8(m: &[u8; 8]) -> alloc::string::String {
1778 alloc::format!(
1779 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
1780 m[0],
1781 m[1],
1782 m[2],
1783 m[3],
1784 m[4],
1785 m[5],
1786 m[6],
1787 m[7]
1788 )
1789}
1790
1791pub fn format_bit_string(nbits: u32, bytes: &[u8]) -> alloc::string::String {
1796 let mut out = alloc::string::String::with_capacity(nbits as usize);
1797 for i in 0..nbits as usize {
1798 let byte = bytes[i / 8];
1799 let bit = (byte >> (7 - (i % 8))) & 1;
1800 out.push(if bit == 1 { '1' } else { '0' });
1801 }
1802 out
1803}
1804
1805pub fn bit_string_to_i64(nbits: u32, bytes: &[u8]) -> i64 {
1807 let mut val: i64 = 0;
1808 for i in 0..nbits as usize {
1809 let byte = bytes.get(i / 8).copied().unwrap_or(0);
1810 val = (val << 1) | i64::from((byte >> (7 - (i % 8))) & 1);
1811 }
1812 val
1813}
1814
1815pub fn format_money_array(items: &[Option<i64>]) -> alloc::string::String {
1819 let mut out = alloc::string::String::new();
1820 out.push('{');
1821 for (i, item) in items.iter().enumerate() {
1822 if i > 0 {
1823 out.push(',');
1824 }
1825 match item {
1826 None => out.push_str("NULL"),
1827 Some(c) => out.push_str(&crate::eval::format_money(*c)),
1828 }
1829 }
1830 out.push('}');
1831 out
1832}
1833
1834pub fn parse_inet_text(s: &str) -> Option<(u8, u8, [u8; 16])> {
1839 let s = s.trim();
1840 let (addr_s, bits_s) = match s.split_once('/') {
1841 Some((a, b)) => (a, Some(b)),
1842 None => (s, None),
1843 };
1844 if addr_s.contains(':') {
1845 let (head, tail) = match addr_s.find("::") {
1850 Some(idx) => (&addr_s[..idx], Some(&addr_s[idx + 2..])),
1851 None => (addr_s, None),
1852 };
1853 let mut head_groups: alloc::vec::Vec<&str> = if head.is_empty() {
1854 alloc::vec::Vec::new()
1855 } else {
1856 head.split(':').collect()
1857 };
1858 let mut tail_groups: alloc::vec::Vec<&str> = match tail {
1859 Some(t) if !t.is_empty() => t.split(':').collect(),
1860 _ => alloc::vec::Vec::new(),
1861 };
1862 let mut dotted_words: Option<[u16; 2]> = None;
1866 if let Some(g) = tail_groups.last().or_else(|| head_groups.last()) {
1867 if g.contains('.') {
1868 let oct: alloc::vec::Vec<&str> = g.split('.').collect();
1869 if oct.len() != 4 {
1870 return None;
1871 }
1872 let mut b = [0u8; 4];
1873 for (i, o) in oct.iter().enumerate() {
1874 b[i] = o.parse::<u8>().ok()?;
1875 }
1876 dotted_words = Some([
1877 (u16::from(b[0]) << 8) | u16::from(b[1]),
1878 (u16::from(b[2]) << 8) | u16::from(b[3]),
1879 ]);
1880 if !tail_groups.is_empty() {
1881 tail_groups.pop();
1882 } else {
1883 head_groups.pop();
1884 }
1885 }
1886 }
1887 let dq = if dotted_words.is_some() { 2 } else { 0 };
1888 let head_len = head_groups.len();
1889 let tail_len = tail_groups.len();
1890 if tail.is_none() {
1891 if head_len + dq != 8 {
1892 return None;
1893 }
1894 } else if head_len + tail_len + dq > 7 {
1895 return None;
1896 }
1897 let mut words = [0u16; 8];
1898 for (i, g) in head_groups.iter().enumerate() {
1899 words[i] = u16::from_str_radix(g, 16).ok()?;
1900 }
1901 let trailing_start = 8 - dq - tail_len;
1904 for (i, g) in tail_groups.iter().enumerate() {
1905 words[trailing_start + i] = u16::from_str_radix(g, 16).ok()?;
1906 }
1907 if let Some(dw) = dotted_words {
1908 words[6] = dw[0];
1909 words[7] = dw[1];
1910 }
1911 let mut addr = [0u8; 16];
1912 for (i, w) in words.iter().enumerate() {
1913 addr[i * 2] = (w >> 8) as u8;
1914 addr[i * 2 + 1] = (w & 0xff) as u8;
1915 }
1916 let bits = match bits_s {
1917 Some(b) => b.parse::<u8>().ok().filter(|&n| n <= 128)?,
1918 None => 128,
1919 };
1920 Some((6, bits, addr))
1921 } else {
1922 let parts: alloc::vec::Vec<&str> = addr_s.split('.').collect();
1924 if parts.len() != 4 {
1925 return None;
1926 }
1927 let mut addr = [0u8; 16];
1928 for (i, p) in parts.iter().enumerate() {
1929 addr[i] = p.parse::<u8>().ok()?;
1930 }
1931 let bits = match bits_s {
1932 Some(b) => b.parse::<u8>().ok().filter(|&n| n <= 32)?,
1933 None => 32,
1934 };
1935 Some((4, bits, addr))
1936 }
1937}
1938
1939pub fn parse_cidr_text(s: &str) -> Result<Option<(u8, u8, [u8; 16])>, ()> {
1946 let s = s.trim();
1947 let parsed = if !s.contains(':') {
1948 let (addr_s, bits_s) = match s.split_once('/') {
1949 Some((a, b)) => (a, Some(b)),
1950 None => (s, None),
1951 };
1952 let parts: alloc::vec::Vec<&str> = addr_s.split('.').collect();
1953 if parts.is_empty() || parts.len() > 4 || parts.iter().any(|p| p.is_empty()) {
1954 return Ok(None);
1955 }
1956 let mut addr = [0u8; 16];
1957 for (i, p) in parts.iter().enumerate() {
1958 match p.parse::<u8>() {
1959 Ok(v) => addr[i] = v,
1960 Err(_) => return Ok(None),
1961 }
1962 }
1963 let bits = match bits_s {
1964 Some(b) => match b.parse::<u8>() {
1965 Ok(n) if n <= 32 => n,
1966 _ => return Ok(None),
1967 },
1968 None => (parts.len() as u8) * 8,
1969 };
1970 Some((4u8, bits, addr))
1971 } else {
1972 parse_inet_text(s).map(|(f, b, a)| {
1973 (f, if s.contains('/') { b } else { 128 }, a)
1975 })
1976 };
1977 let Some((family, bits, addr)) = parsed else {
1978 return Ok(None);
1979 };
1980 let total = if family == 4 { 32u16 } else { 128 };
1982 let nbytes = if family == 4 { 4 } else { 16 };
1983 for byte in 0..nbytes {
1984 let bit_base = (byte as u16) * 8;
1985 let keep = (u16::from(bits)).saturating_sub(bit_base).min(8) as u8;
1986 let mask: u8 = if keep == 0 { 0 } else { 0xffu8 << (8 - keep) };
1987 if addr[byte] & !mask != 0 {
1988 return Err(());
1989 }
1990 if bit_base >= total {
1991 break;
1992 }
1993 }
1994 Ok(Some((family, bits, addr)))
1995}
1996
1997pub fn parse_macaddr_text(s: &str) -> Option<[u8; 6]> {
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 {
2003 return None;
2004 }
2005 let mut out = [0u8; 6];
2006 for i in 0..6 {
2007 out[i] = u8::from_str_radix(&cleaned[i * 2..i * 2 + 2], 16).ok()?;
2008 }
2009 Some(out)
2010}
2011
2012#[must_use]
2019pub fn date_days_to_micros(d: i32) -> i64 {
2020 match d {
2021 i32::MAX => i64::MAX,
2022 i32::MIN => i64::MIN,
2023 _ => i64::from(d) * 86_400_000_000,
2024 }
2025}
2026
2027pub fn parse_pg_lsn_text(s: &str) -> Option<u64> {
2028 let t = s.trim();
2029 let (hi, lo) = t.split_once('/')?;
2030 if hi.is_empty() || lo.is_empty() || hi.len() > 8 || lo.len() > 8 {
2031 return None;
2032 }
2033 let hi = u32::from_str_radix(hi, 16).ok()?;
2034 let lo = u32::from_str_radix(lo, 16).ok()?;
2035 Some((u64::from(hi) << 32) | u64::from(lo))
2036}
2037
2038#[must_use]
2040pub fn format_pg_lsn(l: u64) -> alloc::string::String {
2041 alloc::format!("{:X}/{:X}", l >> 32, l & 0xFFFF_FFFF)
2042}
2043
2044pub fn parse_macaddr8_text(s: &str) -> Option<[u8; 8]> {
2045 let s = s.trim();
2046 let cleaned: alloc::string::String = s.chars().filter(|c| c.is_ascii_hexdigit()).collect();
2047 if cleaned.len() == 12 {
2050 let mut six = [0u8; 6];
2051 for i in 0..6 {
2052 six[i] = u8::from_str_radix(&cleaned[i * 2..i * 2 + 2], 16).ok()?;
2053 }
2054 return Some([six[0], six[1], six[2], 0xff, 0xfe, six[3], six[4], six[5]]);
2055 }
2056 if cleaned.len() != 16 {
2057 return None;
2058 }
2059 let mut out = [0u8; 8];
2060 for i in 0..8 {
2061 out[i] = u8::from_str_radix(&cleaned[i * 2..i * 2 + 2], 16).ok()?;
2062 }
2063 Some(out)
2064}
2065
2066pub fn parse_bit_string_text(s: &str) -> Option<(u32, alloc::vec::Vec<u8>)> {
2070 let s = s.trim();
2071 let nbits = u32::try_from(s.len()).ok()?;
2072 let nbytes = (s.len()).div_ceil(8);
2073 let mut bytes = alloc::vec![0u8; nbytes];
2074 for (i, c) in s.chars().enumerate() {
2075 let bit = match c {
2076 '0' => 0u8,
2077 '1' => 1u8,
2078 _ => return None,
2079 };
2080 if bit == 1 {
2081 bytes[i / 8] |= 1 << (7 - (i % 8));
2082 }
2083 }
2084 Some((nbits, bytes))
2085}
2086
2087pub fn format_multirange(ranges: &[spg_storage::RangeSpan]) -> alloc::string::String {
2094 let mut out = alloc::string::String::new();
2095 out.push('{');
2096 for (i, r) in ranges.iter().enumerate() {
2097 if i > 0 {
2098 out.push(',');
2099 }
2100 if r.empty {
2101 out.push_str("empty");
2102 continue;
2103 }
2104 out.push(if r.lower_inc { '[' } else { '(' });
2105 if let Some(l) = &r.lower {
2106 out.push_str("e_range_bound(&format_range_element(l)));
2107 }
2108 out.push(',');
2109 if let Some(u) = &r.upper {
2110 out.push_str("e_range_bound(&format_range_element(u)));
2111 }
2112 out.push(if r.upper_inc { ']' } else { ')' });
2113 }
2114 out.push('}');
2115 out
2116}
2117
2118pub(crate) fn format_range_element(v: &Value) -> alloc::string::String {
2119 match v {
2120 Value::Int(n) => alloc::format!("{n}"),
2121 Value::BigInt(n) => alloc::format!("{n}"),
2122 Value::Date(d) => crate::eval::format_date(*d),
2123 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
2124 Value::Numeric {
2125 scaled,
2126 scale,
2127 kind,
2128 } => crate::eval::format_numeric_kind(*kind, *scaled, *scale),
2129 other => alloc::format!("{other:?}"),
2130 }
2131}
2132
2133pub(crate) fn parse_money_str(s: &str) -> Option<i64> {
2144 let mut rest = s.trim();
2149 let mut neg = false;
2150 loop {
2153 let before = rest;
2154 rest = rest.trim_start();
2155 if let Some(r) = rest.strip_prefix('$') {
2156 rest = r;
2157 } else if let Some(r) = rest.strip_prefix('-') {
2158 neg = true;
2159 rest = r;
2160 } else if let Some(r) = rest.strip_prefix('(') {
2161 neg = true;
2162 rest = r;
2163 } else if let Some(r) = rest.strip_prefix('+') {
2164 rest = r;
2165 }
2166 if rest == before {
2167 break;
2168 }
2169 }
2170 let (int_part, tail) = {
2171 let end = rest
2172 .find(|c: char| !(c.is_ascii_digit() || c == ','))
2173 .unwrap_or(rest.len());
2174 (&rest[..end], &rest[end..])
2175 };
2176 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
2178 for b in int_part.bytes() {
2179 match b {
2180 b',' => {}
2181 b'0'..=b'9' => int_digits.push(b as char),
2182 _ => return None,
2183 }
2184 }
2185 if int_digits.is_empty() {
2186 return None;
2187 }
2188 let dollars: i64 = int_digits.parse().ok()?;
2189 let (mut cents, tail) = match tail.strip_prefix('.') {
2191 None => (0i64, tail),
2192 Some(f) => {
2193 let end = f.find(|c: char| !c.is_ascii_digit()).unwrap_or(f.len());
2194 let (digits, rest_tail) = (&f[..end], &f[end..]);
2195 if digits.is_empty() {
2196 return None;
2197 }
2198 let b = digits.as_bytes();
2199 let mut c = i64::from(b[0] - b'0') * 10;
2200 if b.len() >= 2 {
2201 c += i64::from(b[1] - b'0');
2202 }
2203 if b.len() >= 3 && b[2] >= b'5' {
2204 c += 1;
2205 }
2206 (c, rest_tail)
2207 }
2208 };
2209 let mut tail = tail;
2211 while !tail.is_empty() {
2212 let t = tail.trim_start();
2213 if let Some(r) = t.strip_prefix(')') {
2214 tail = r;
2215 } else if let Some(r) = t.strip_prefix('-') {
2216 neg = true;
2217 tail = r;
2218 } else if let Some(r) = t.strip_prefix('+') {
2219 tail = r;
2220 } else if let Some(r) = t.strip_prefix('$') {
2221 tail = r;
2222 } else if t.is_empty() {
2223 break;
2224 } else {
2225 return None;
2226 }
2227 }
2228 let carry = cents / 100;
2230 cents %= 100;
2231 let total = dollars
2232 .checked_add(carry)?
2233 .checked_mul(100)?
2234 .checked_add(cents)?;
2235 Some(if neg { -total } else { total })
2236}
2237
2238pub(crate) fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
2249 let s = s.trim();
2250 let bytes = s.as_bytes();
2254 let sign_pos = bytes
2255 .iter()
2256 .enumerate()
2257 .rev()
2258 .find(|&(_, &b)| b == b'+' || b == b'-')
2259 .map(|(i, _)| i)?;
2260 if sign_pos == 0 {
2261 return None; }
2263 let time_part = &s[..sign_pos];
2264 let offset_part = &s[sign_pos..];
2265 let us = parse_time_str(time_part)?;
2266 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
2267 let offset_body = &offset_part[1..];
2268 let (hh_str, mm_str) = match offset_body.split_once(':') {
2271 Some((h, m)) => (h, m),
2272 None if offset_body.len() == 4 => offset_body.split_at(2),
2273 None if offset_body.len() == 3 => offset_body.split_at(1),
2274 None => (offset_body, "0"),
2275 };
2276 let hh: i32 = hh_str.parse().ok()?;
2277 let mm: i32 = mm_str.parse().ok()?;
2278 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
2279 return None;
2280 }
2281 let total = sign * (hh * 3600 + mm * 60);
2282 if total.abs() > 50_400 {
2283 return None;
2284 }
2285 Some((us, total))
2286}
2287
2288pub(crate) fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value<'static>, EngineError> {
2293 if n == 0 || (1901..=2155).contains(&n) {
2294 return Ok(Value::Year(n as u16));
2297 }
2298 Err(EngineError::Eval(EvalError::TypeMismatch {
2299 detail: alloc::format!(
2300 "year value out of range: {n} (column `{col_name}`; \
2301 MySQL accepts 0 or 1901..=2155)"
2302 ),
2303 }))
2304}
2305
2306pub(crate) fn parse_time_str(s: &str) -> Option<i64> {
2319 let s = s.trim();
2320 if s.eq_ignore_ascii_case("allballs") {
2322 return Some(0);
2323 }
2324 let (hms, frac) = match s.split_once('.') {
2325 Some((h, f)) => (h, Some(f)),
2326 None => (s, None),
2327 };
2328 let mut parts = hms.split(':');
2329 let hh: u32 = parts.next()?.parse().ok()?;
2330 let mm: u32 = parts.next()?.parse().ok()?;
2331 let ss: u32 = match parts.next() {
2334 Some(x) => x.parse().ok()?,
2335 None => 0,
2336 };
2337 if parts.next().is_some() {
2338 return None;
2339 }
2340 if hh > 24 || mm > 59 || ss > 59 || (hh == 24 && (mm != 0 || ss != 0)) {
2342 return None;
2343 }
2344 let frac_us: i64 = match frac {
2345 None => 0,
2346 Some(f) => {
2347 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
2348 return None;
2349 }
2350 let mut padded = alloc::string::String::with_capacity(6);
2352 padded.push_str(f);
2353 while padded.len() < 6 {
2354 padded.push('0');
2355 }
2356 padded.parse().ok()?
2357 }
2358 };
2359 if hh == 24 && frac_us != 0 {
2360 return None;
2361 }
2362 Some(
2363 i64::from(hh) * 3_600_000_000
2364 + i64::from(mm) * 60_000_000
2365 + i64::from(ss) * 1_000_000
2366 + frac_us,
2367 )
2368}
2369
2370pub(crate) fn numeric_typmod_in_range(precision: u16, scale: i16) -> bool {
2374 (1..=1000).contains(&precision) && (-1000..=1000).contains(&scale)
2375}
2376
2377pub(crate) fn numeric_typmod_error(name: &str) -> Option<alloc::string::String> {
2381 let lower = name.trim().to_ascii_lowercase();
2382 let (head, rest) = lower.split_once('(')?;
2383 if !matches!(head.trim(), "numeric" | "decimal") {
2384 return None;
2385 }
2386 let args = rest.strip_suffix(')')?;
2387 let mut it = args.split(',').map(str::trim);
2388 let p: i64 = it.next()?.parse().ok()?;
2389 if !(1..=1000).contains(&p) {
2390 return Some(alloc::format!(
2391 "NUMERIC precision {p} must be between 1 and 1000"
2392 ));
2393 }
2394 if let Some(s) = it.next() {
2395 let s: i64 = s.parse().ok()?;
2396 if !(-1000..=1000).contains(&s) {
2397 return Some(alloc::format!(
2398 "NUMERIC scale {s} must be between -1000 and 1000"
2399 ));
2400 }
2401 }
2402 None
2403}
2404
2405pub(crate) fn type_name_to_data_type(name: &str) -> Option<DataType> {
2412 with_lower_name(name.trim(), type_name_to_data_type_lower)
2413}
2414
2415pub(crate) fn with_lower_name<R>(name: &str, f: impl FnOnce(&str) -> R) -> R {
2428 const CAP: usize = 64;
2429 if name.len() <= CAP {
2430 let mut buf = [0u8; CAP];
2431 buf[..name.len()].copy_from_slice(name.as_bytes());
2432 buf[..name.len()].make_ascii_lowercase();
2433 if let Ok(s) = core::str::from_utf8(&buf[..name.len()]) {
2434 return f(s);
2435 }
2436 }
2437 f(&name.to_ascii_lowercase())
2438}
2439
2440fn type_name_to_data_type_lower(n: &str) -> Option<DataType> {
2441 if let Some((head, paren)) = n.split_once('(')
2444 && let Some(args) = paren.strip_suffix(')')
2445 {
2446 let mut wide: [Option<i32>; 2] = [None, None];
2454 for (slot, s) in wide.iter_mut().zip(args.split(',')) {
2455 *slot = s.trim().parse::<i32>().ok();
2456 }
2457 let nums: [u8; 2] = [
2458 wide[0].and_then(|v| u8::try_from(v).ok()).unwrap_or(0),
2459 wide[1].and_then(|v| u8::try_from(v).ok()).unwrap_or(0),
2460 ];
2461 match head {
2462 "bit" => {
2464 return Some(DataType::Bit(
2465 u32::try_from(wide.first().copied().flatten()?).ok()?,
2466 ));
2467 }
2468 "varbit" | "bit varying" => {
2469 return Some(DataType::BitVarying(
2470 u32::try_from(wide.first().copied().flatten()?).ok()?,
2471 ));
2472 }
2473 "numeric" | "decimal" => {
2474 let precision = u16::try_from(wide.first().copied().flatten()?).ok()?;
2475 let scale = i16::try_from(wide.get(1).copied().flatten().unwrap_or(0)).ok()?;
2477 if !numeric_typmod_in_range(precision, scale) {
2478 return None;
2479 }
2480 return Some(DataType::Numeric { precision, scale });
2481 }
2482 "varchar" => {
2488 return Some(DataType::Varchar(nums.first().copied().unwrap_or(0).into()));
2489 }
2490 "char" | "character" => {
2491 return Some(DataType::Char(nums.first().copied().unwrap_or(0).into()));
2492 }
2493 _ => {}
2494 }
2495 }
2496 Some(match n {
2497 "smallint" | "int2" => DataType::SmallInt,
2498 "numeric" | "decimal" => DataType::Numeric {
2499 precision: 0,
2500 scale: 0,
2501 },
2502 "inet" => DataType::Inet,
2505 "cidr" => DataType::Cidr,
2506 "macaddr" => DataType::Macaddr,
2507 "macaddr8" => DataType::Macaddr8,
2508 "pg_lsn" => DataType::PgLsn,
2509 "__bit_literal" => DataType::BitVarying(0),
2511 "xid" => DataType::Xid,
2516 "xid8" => DataType::Xid8,
2517 "bit" => DataType::Bit(0),
2518 "varbit" | "bit varying" => DataType::BitVarying(0),
2519 "xml" => DataType::Xml,
2520 "tsvector" => DataType::TsVector,
2530 "tsquery" => DataType::TsQuery,
2531 "money" => DataType::Money,
2538 "char1" => DataType::Char1,
2539 "point" => DataType::Point,
2541 "lseg" => DataType::Lseg,
2542 "path" => DataType::Path,
2543 "box" => DataType::PgBox,
2544 "polygon" => DataType::Polygon,
2545 "line" => DataType::Line,
2546 "circle" => DataType::Circle,
2547 "int4multirange" => DataType::Multirange(spg_storage::RangeKind::Int4),
2549 "int8multirange" => DataType::Multirange(spg_storage::RangeKind::Int8),
2550 "nummultirange" => DataType::Multirange(spg_storage::RangeKind::Num),
2551 "tsmultirange" => DataType::Multirange(spg_storage::RangeKind::Ts),
2552 "tstzmultirange" => DataType::Multirange(spg_storage::RangeKind::TsTz),
2553 "datemultirange" => DataType::Multirange(spg_storage::RangeKind::Date),
2554 "int4range" => DataType::Range(spg_storage::RangeKind::Int4),
2556 "int8range" => DataType::Range(spg_storage::RangeKind::Int8),
2557 "numrange" => DataType::Range(spg_storage::RangeKind::Num),
2558 "tsrange" => DataType::Range(spg_storage::RangeKind::Ts),
2559 "tstzrange" => DataType::Range(spg_storage::RangeKind::TsTz),
2560 "daterange" => DataType::Range(spg_storage::RangeKind::Date),
2561 "bool_array" | "boolean_array" => DataType::BoolArray,
2565 "smallint_array" | "int2_array" => DataType::SmallIntArray,
2566 "int_array" | "integer_array" | "int4_array" => DataType::IntArray,
2567 "bigint_array" | "int8_array" => DataType::BigIntArray,
2568 "float_array" | "double_array" | "real_array" | "float8_array" | "float4_array" => {
2569 DataType::FloatArray
2570 }
2571 "float4" | "real" => DataType::Real,
2574 "float8" | "double precision" | "float" => DataType::Float,
2575 "oid" => DataType::Oid,
2580 "oid_array" => DataType::OidArray,
2594 "name_array" | "regtype_array" | "regclass_array" | "regproc_array" => DataType::TextArray,
2595 "time" | "time without time zone" => DataType::Time,
2598 "timetz" | "time with time zone" => DataType::TimeTz,
2599 "hstore" => DataType::Hstore,
2606 "numeric_array" | "decimal_array" => DataType::NumericArray,
2607 "varchar_array" | "character varying_array" | "char_array" | "bpchar_array" => {
2608 DataType::TextArray
2609 }
2610 "text_array" => DataType::TextArray,
2611 "date_array" => DataType::DateArray,
2612 "timestamp_array" => DataType::TimestampArray,
2613 "timestamptz_array" => DataType::TimestamptzArray,
2614 "uuid_array" => DataType::UuidArray,
2615 "json_array" => DataType::JsonArray,
2616 "jsonb_array" => DataType::JsonbArray,
2617 "bytea_array" => DataType::BytesArray,
2618 "interval_array" => DataType::IntervalArray,
2619 "money_array" => DataType::MoneyArray,
2620 "int" | "int4" | "integer" => DataType::Int,
2625 "bigint" | "int8" => DataType::BigInt,
2626 "text" => DataType::Text,
2627 "name" => DataType::Name,
2631 "varchar" | "character varying" => DataType::Varchar(0),
2632 "char" | "character" => DataType::Char(1),
2636 "bpchar" => DataType::Char(0),
2637 "bool" | "boolean" => DataType::Bool,
2638 "date" => DataType::Date,
2639 "timestamp" | "timestamp without time zone" => DataType::Timestamp,
2640 "timestamptz" | "timestamp with time zone" => DataType::Timestamptz,
2641 "uuid" => DataType::Uuid,
2642 "json" => DataType::Json,
2643 "jsonb" => DataType::Jsonb,
2644 "bytea" => DataType::Bytes,
2645 "interval" => DataType::Interval,
2646 _ => return None,
2647 })
2648}
2649
2650pub(crate) const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
2651 match t {
2652 ColumnTypeName::SmallInt => DataType::SmallInt,
2653 ColumnTypeName::Int => DataType::Int,
2654 ColumnTypeName::BigInt => DataType::BigInt,
2655 ColumnTypeName::Float => DataType::Float,
2656 ColumnTypeName::Real => DataType::Real,
2657 ColumnTypeName::Text => DataType::Text,
2658 ColumnTypeName::Name => DataType::Name,
2659 ColumnTypeName::Xid => DataType::Xid,
2660 ColumnTypeName::Xid8 => DataType::Xid8,
2661 ColumnTypeName::Oid => DataType::Oid,
2662 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
2663 ColumnTypeName::Char(n) => DataType::Char(n),
2664 ColumnTypeName::Bool => DataType::Bool,
2665 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
2666 dim,
2667 encoding: match encoding {
2668 SqlVecEncoding::F32 => VecEncoding::F32,
2669 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
2670 SqlVecEncoding::F16 => VecEncoding::F16,
2671 },
2672 },
2673 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
2674 ColumnTypeName::Date => DataType::Date,
2675 ColumnTypeName::Timestamp => DataType::Timestamp,
2676 ColumnTypeName::Timestamptz => DataType::Timestamptz,
2677 ColumnTypeName::Json => DataType::Json,
2678 ColumnTypeName::Jsonb => DataType::Jsonb,
2679 ColumnTypeName::Bytes => DataType::Bytes,
2680 ColumnTypeName::TextArray => DataType::TextArray,
2681 ColumnTypeName::IntArray => DataType::IntArray,
2682 ColumnTypeName::BigIntArray => DataType::BigIntArray,
2683 ColumnTypeName::TsVector => DataType::TsVector,
2684 ColumnTypeName::TsQuery => DataType::TsQuery,
2685 ColumnTypeName::Uuid => DataType::Uuid,
2686 ColumnTypeName::Time => DataType::Time,
2687 ColumnTypeName::Year => DataType::Year,
2688 ColumnTypeName::TimeTz => DataType::TimeTz,
2689 ColumnTypeName::Money => DataType::Money,
2690 ColumnTypeName::Range(k) => DataType::Range(match k {
2691 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
2692 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
2693 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
2694 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
2695 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
2696 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
2697 }),
2698 ColumnTypeName::Hstore => DataType::Hstore,
2699 ColumnTypeName::IntArray2D => DataType::IntArray2D,
2700 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
2701 ColumnTypeName::TextArray2D => DataType::TextArray2D,
2702 ColumnTypeName::BoolArray2D => DataType::BoolArray2D,
2703 ColumnTypeName::Interval => DataType::Interval,
2704 ColumnTypeName::IntervalArray => DataType::IntervalArray,
2705 ColumnTypeName::BoolArray => DataType::BoolArray,
2706 ColumnTypeName::SmallIntArray => DataType::SmallIntArray,
2707 ColumnTypeName::FloatArray => DataType::FloatArray,
2708 ColumnTypeName::NumericArray => DataType::NumericArray,
2709 ColumnTypeName::DateArray => DataType::DateArray,
2710 ColumnTypeName::TimestampArray => DataType::TimestampArray,
2711 ColumnTypeName::TimestamptzArray => DataType::TimestamptzArray,
2712 ColumnTypeName::UuidArray => DataType::UuidArray,
2713 ColumnTypeName::JsonArray => DataType::JsonArray,
2714 ColumnTypeName::JsonbArray => DataType::JsonbArray,
2715 ColumnTypeName::BytesArray => DataType::BytesArray,
2716 ColumnTypeName::VarcharArray => DataType::VarcharArray,
2717 ColumnTypeName::CharArray => DataType::CharArray,
2718 ColumnTypeName::Multirange(k) => DataType::Multirange(match k {
2719 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
2720 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
2721 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
2722 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
2723 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
2724 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
2725 }),
2726 ColumnTypeName::Point => DataType::Point,
2727 ColumnTypeName::Lseg => DataType::Lseg,
2728 ColumnTypeName::Path => DataType::Path,
2729 ColumnTypeName::PgBox => DataType::PgBox,
2730 ColumnTypeName::Polygon => DataType::Polygon,
2731 ColumnTypeName::Line => DataType::Line,
2732 ColumnTypeName::Circle => DataType::Circle,
2733 ColumnTypeName::Inet => DataType::Inet,
2734 ColumnTypeName::Cidr => DataType::Cidr,
2735 ColumnTypeName::Macaddr => DataType::Macaddr,
2736 ColumnTypeName::Macaddr8 => DataType::Macaddr8,
2737 ColumnTypeName::Bit(n) => DataType::Bit(n),
2738 ColumnTypeName::BitVarying(n) => DataType::BitVarying(n),
2739 ColumnTypeName::Xml => DataType::Xml,
2740 ColumnTypeName::Char1 => DataType::Char1,
2741 ColumnTypeName::MoneyArray => DataType::MoneyArray,
2742 }
2743}
2744
2745pub(crate) fn literal_expr_to_value(expr: Expr) -> Result<Value<'static>, EngineError> {
2749 literal_expr_to_value_in(expr, None)
2750}
2751
2752pub(crate) fn literal_expr_to_value_in(
2760 expr: Expr,
2761 catalog: Option<&spg_storage::Catalog>,
2762) -> Result<Value<'static>, EngineError> {
2763 match expr {
2764 Expr::Literal(l) => Ok(literal_to_value(l)),
2765 Expr::Cast { expr, target } => {
2766 if catalog.is_some()
2769 && matches!(
2770 target,
2771 spg_sql::ast::CastTarget::Named(_) | spg_sql::ast::CastTarget::RegClass
2772 )
2773 {
2774 return eval_expr_with_catalog(Expr::Cast { expr, target }, catalog);
2775 }
2776 let inner_value = literal_expr_to_value_in(*expr, catalog)?;
2777 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
2778 }
2779 Expr::Unary {
2780 op: UnOp::Neg,
2781 expr,
2782 } => match *expr {
2783 Expr::Literal(Literal::Integer(n)) => {
2784 let neg = n.checked_neg().ok_or_else(|| {
2787 EngineError::Unsupported("integer literal overflow on negation".into())
2788 })?;
2789 Ok(int_value_for(neg))
2790 }
2791 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
2792 Expr::Literal(Literal::Numeric { unscaled, scale }) => Ok(Value::Numeric {
2794 scaled: -unscaled,
2795 scale,
2796 kind: spg_storage::NumericKind::Finite,
2797 }),
2798 Expr::Literal(Literal::NumericBig(ref s)) => {
2801 let flipped = if let Some(rest) = s.strip_prefix('-') {
2802 rest.to_string()
2803 } else {
2804 alloc::format!("-{s}")
2805 };
2806 Ok(big_literal_to_value(&flipped))
2807 }
2808 Expr::Cast {
2814 expr: inner,
2815 target,
2816 } => {
2817 let negated_inner = match *inner {
2818 Expr::Literal(Literal::Integer(n)) => {
2819 let neg = n.checked_neg().ok_or_else(|| {
2820 EngineError::Unsupported("integer literal overflow on negation".into())
2821 })?;
2822 Expr::Literal(Literal::Integer(neg))
2823 }
2824 Expr::Literal(Literal::Float(x)) => Expr::Literal(Literal::Float(-x)),
2825 Expr::Literal(Literal::Numeric { unscaled, scale }) => {
2826 Expr::Literal(Literal::Numeric {
2827 unscaled: -unscaled,
2828 scale,
2829 })
2830 }
2831 Expr::Literal(Literal::NumericBig(ref s)) => {
2834 let flipped = if let Some(rest) = s.strip_prefix('-') {
2835 rest.to_string()
2836 } else {
2837 alloc::format!("-{s}")
2838 };
2839 Expr::Literal(Literal::NumericBig(flipped))
2840 }
2841 other => Expr::Unary {
2842 op: spg_sql::ast::UnOp::Neg,
2843 expr: alloc::boxed::Box::new(other),
2844 },
2845 };
2846 literal_expr_to_value_in(
2847 Expr::Cast {
2848 expr: alloc::boxed::Box::new(negated_inner),
2849 target,
2850 },
2851 catalog,
2852 )
2853 }
2854 other => Err(EngineError::Unsupported(alloc::format!(
2855 "unary minus over non-literal expression: {other:?}"
2856 ))),
2857 },
2858 Expr::Array(items) => {
2866 let mut materialised: alloc::vec::Vec<Value<'static>> =
2867 alloc::vec::Vec::with_capacity(items.len());
2868 for elem in &items {
2869 materialised.push(literal_expr_to_value_in(elem.clone(), catalog)?);
2870 }
2871 Ok(crate::describe::upgrade_timestamptz_array(
2872 array_literal_widen(materialised),
2873 &items,
2874 &[],
2875 ))
2876 }
2877 other => eval_expr_with_catalog(other, catalog),
2890 }
2891}
2892
2893fn eval_expr_with_catalog(
2896 expr: Expr,
2897 catalog: Option<&spg_storage::Catalog>,
2898) -> Result<Value<'static>, EngineError> {
2899 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
2900 let mut ctx = EvalContext::new(&empty_schema, None);
2901 if let Some(cat) = catalog {
2902 ctx = ctx.with_catalog(cat);
2903 }
2904 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
2905 crate::eval::eval_expr(&expr, &empty_row, &ctx).map_err(EngineError::Eval)
2906}
2907
2908pub(crate) fn literal_to_value(l: Literal) -> Value<'static> {
2909 match l {
2910 Literal::Integer(n) => int_value_for(n),
2911 Literal::Float(x) => Value::Float(x),
2912 Literal::Numeric { unscaled, scale } => Value::Numeric {
2913 scaled: unscaled,
2914 scale,
2915 kind: spg_storage::NumericKind::Finite,
2916 },
2917 Literal::NumericBig(s) => big_literal_to_value(&s),
2918 Literal::Timestamp { micros, .. } => Value::Timestamp(micros),
2919 Literal::Date { days, .. } => Value::Date(days),
2920 Literal::String(s) => Value::text(s),
2921 Literal::Bool(b) => Value::Bool(b),
2922 Literal::Null => Value::Null,
2923 Literal::Vector(v) => Value::vector(v),
2924 Literal::TextArray(items) => Value::TextArray(items),
2925 Literal::IntArray(items) => Value::IntArray(items),
2926 Literal::BigIntArray(items) => Value::BigIntArray(items),
2927 Literal::Interval {
2928 months,
2929 days,
2930 micros,
2931 ..
2932 } => Value::Interval {
2933 months,
2934 days,
2935 micros,
2936 kind: spg_storage::IntervalKind::Finite,
2937 },
2938 }
2939}
2940
2941pub(crate) fn int_value_for(n: i64) -> Value<'static> {
2945 if let Ok(small) = i32::try_from(n) {
2946 Value::Int(small)
2947 } else {
2948 Value::BigInt(n)
2949 }
2950}
2951
2952pub(crate) fn truncate_to_column_fsp(v: Value<'static>, schema: &ColumnSchema) -> Value<'static> {
2974 let Some(fsp) = schema.mysql_fsp else {
2975 return v;
2976 };
2977 if fsp >= 6 {
2978 return v;
2979 }
2980 let scale = 10i64.pow(u32::from(6 - fsp));
2981 let cut = |micros: i64| (micros / scale) * scale;
2983 match v {
2984 Value::Timestamp(m) => Value::Timestamp(cut(m)),
2985 Value::Time(m) => Value::Time(cut(m)),
2986 other => other,
2987 }
2988}
2989
2990fn column_int_bounds(schema: &ColumnSchema) -> Option<(i128, i128)> {
2995 if let Some(width) = schema.mysql_int_width {
2996 return Some(match (width, schema.is_unsigned) {
2997 (spg_storage::MysqlIntWidth::Tiny, false) => (-128, 127),
2998 (spg_storage::MysqlIntWidth::Tiny, true) => (0, 255),
2999 (spg_storage::MysqlIntWidth::Small, false) => (-32_768, 32_767),
3000 (spg_storage::MysqlIntWidth::Small, true) => (0, 65_535),
3001 (spg_storage::MysqlIntWidth::Medium, false) => (-8_388_608, 8_388_607),
3002 (spg_storage::MysqlIntWidth::Medium, true) => (0, 16_777_215),
3003 (spg_storage::MysqlIntWidth::Int, false) => (-2_147_483_648, 2_147_483_647),
3004 (spg_storage::MysqlIntWidth::Int, true) => (0, 4_294_967_295),
3005 (spg_storage::MysqlIntWidth::Big, false) => {
3008 (i128::from(i64::MIN), i128::from(i64::MAX))
3009 }
3010 (spg_storage::MysqlIntWidth::Big, true) => (0, i128::from(u64::MAX)),
3011 });
3012 }
3013 let (lo, hi) = match schema.ty {
3014 DataType::SmallInt => (i128::from(i16::MIN), i128::from(i16::MAX)),
3015 DataType::Int => (i128::from(i32::MIN), i128::from(i32::MAX)),
3016 DataType::BigInt => (i128::from(i64::MIN), i128::from(i64::MAX)),
3017 _ => return None,
3018 };
3019 Some(if schema.is_unsigned {
3020 (0, hi)
3021 } else {
3022 (lo, hi)
3023 })
3024}
3025
3026pub(crate) fn mysql_fit_warning(
3061 before: &Value<'_>,
3062 after: &Value<'_>,
3063 schema: &ColumnSchema,
3064 row: usize,
3065 omitted: bool,
3066) -> Option<crate::MysqlWarning> {
3067 if before == after {
3068 return None;
3069 }
3070 let col = &schema.name;
3071 if omitted || before.is_null() {
3074 return Some(crate::MysqlWarning {
3075 level: "Warning",
3076 code: 1364,
3077 message: alloc::format!("Field '{col}' doesn't have a default value"),
3078 });
3079 }
3080 let numeric_col = matches!(
3081 schema.ty,
3082 DataType::SmallInt | DataType::Int | DataType::BigInt | DataType::Float | DataType::Real
3083 );
3084 if numeric_col {
3085 return Some(if matches!(before, Value::Text(_) | Value::BpChar(_)) {
3088 crate::MysqlWarning {
3089 level: "Warning",
3090 code: 1366,
3091 message: alloc::format!(
3092 "Incorrect integer value: '{}' for column '{col}' at row {row}",
3093 crate::eval::value_to_text(before)
3094 ),
3095 }
3096 } else {
3097 crate::MysqlWarning {
3098 level: "Warning",
3099 code: 1264,
3100 message: alloc::format!("Out of range value for column '{col}' at row {row}"),
3101 }
3102 });
3103 }
3104 Some(crate::MysqlWarning {
3105 level: "Warning",
3106 code: 1265,
3107 message: alloc::format!("Data truncated for column '{col}' at row {row}"),
3108 })
3109}
3110
3111pub(crate) fn mysql_ignore_fit(v: Value<'static>, schema: &ColumnSchema) -> Value<'static> {
3112 if v.is_null() {
3113 if schema.nullable {
3114 return v;
3115 }
3116 return match schema.ty {
3118 DataType::SmallInt | DataType::Int | DataType::BigInt => Value::BigInt(0),
3119 DataType::Float | DataType::Real => Value::Float(0.0),
3120 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(""),
3121 _ => v,
3122 };
3123 }
3124 if let Value::Text(ref s) = v
3127 && matches!(
3128 schema.ty,
3129 DataType::SmallInt | DataType::Int | DataType::BigInt
3130 )
3131 && s.trim().parse::<i64>().is_err()
3132 {
3133 return Value::BigInt(leading_numeric_prefix(s));
3134 }
3135 let as_int = match v {
3137 Value::SmallInt(n) => Some(i128::from(n)),
3138 Value::Int(n) => Some(i128::from(n)),
3139 Value::BigInt(n) => Some(i128::from(n)),
3140 Value::Numeric {
3142 scaled, scale: 0, ..
3143 } => Some(scaled),
3144 _ => None,
3145 };
3146 if let Some(n) = as_int
3147 && let Some((lo, hi)) = column_int_bounds(schema)
3148 && (n < lo || n > hi)
3149 {
3150 return int_value_for_column(n.clamp(lo, hi));
3151 }
3152 if let Value::Text(ref s) = v {
3154 let max = match schema.ty {
3155 DataType::Varchar(m) | DataType::Char(m) if m > 0 => m as usize,
3156 _ => return v,
3157 };
3158 if s.chars().count() > max {
3159 return Value::text(s.chars().take(max).collect::<alloc::string::String>());
3160 }
3161 }
3162 v
3163}
3164
3165fn leading_numeric_prefix(s: &str) -> i64 {
3174 let t = s.trim_start();
3175 let b = t.as_bytes();
3176 let mut i = 0;
3177 if i < b.len() && (b[i] == b'-' || b[i] == b'+') {
3178 i += 1;
3179 }
3180 let int_start = i;
3181 while i < b.len() && b[i].is_ascii_digit() {
3182 i += 1;
3183 }
3184 let mut end = i;
3185 if i < b.len() && b[i] == b'.' {
3186 i += 1;
3187 while i < b.len() && b[i].is_ascii_digit() {
3188 i += 1;
3189 }
3190 if i > int_start + 1 {
3193 end = i;
3194 }
3195 }
3196 if end > int_start && i < b.len() && (b[i] == b'e' || b[i] == b'E') {
3198 let mut j = i + 1;
3199 if j < b.len() && (b[j] == b'-' || b[j] == b'+') {
3200 j += 1;
3201 }
3202 let digits_start = j;
3203 while j < b.len() && b[j].is_ascii_digit() {
3204 j += 1;
3205 }
3206 if j > digits_start {
3207 end = j;
3208 }
3209 }
3210 let Ok(f) = t[..end].parse::<f64>() else {
3211 return 0;
3212 };
3213 let r = f.round();
3215 if r >= i64::MAX as f64 {
3216 i64::MAX
3217 } else if r <= i64::MIN as f64 {
3218 i64::MIN
3219 } else {
3220 r as i64
3221 }
3222}
3223
3224fn int_value_for_column(n: i128) -> Value<'static> {
3228 match i64::try_from(n) {
3229 Ok(v) => Value::BigInt(v),
3230 Err(_) => Value::numeric(n, 0),
3231 }
3232}
3233
3234pub(crate) fn check_unsigned_range(
3235 v: &Value,
3236 schema: &ColumnSchema,
3237 position: usize,
3238) -> Result<(), EngineError> {
3239 let n: i128 = match v {
3240 Value::SmallInt(x) => i128::from(*x),
3241 Value::Int(x) => i128::from(*x),
3242 Value::BigInt(x) => i128::from(*x),
3243 Value::Numeric { scaled, scale, .. } if *scale == 0 => *scaled,
3246 _ => return Ok(()), };
3248 if let Some(width) = schema.mysql_int_width {
3252 let _ = width;
3258 let (lo, hi) = column_int_bounds(schema).unwrap_or((i128::MIN, i128::MAX));
3259 if n < lo || n > hi {
3260 return Err(EngineError::Unsupported(alloc::format!(
3263 "Out of range value for column '{}'",
3264 schema.name
3265 )));
3266 }
3267 return Ok(());
3268 }
3269 if schema.is_unsigned && n < 0 {
3271 return Err(EngineError::Unsupported(alloc::format!(
3272 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
3273 schema.name
3274 )));
3275 }
3276 Ok(())
3277}
3278
3279fn coerce_text_array_to(
3285 items: alloc::vec::Vec<Option<alloc::string::String>>,
3286 target: DataType,
3287 col: &str,
3288) -> Result<Option<Value<'static>>, EngineError> {
3289 let elem_dt = match target {
3290 DataType::BoolArray => DataType::Bool,
3291 DataType::NumericArray => DataType::Numeric {
3292 precision: 0,
3293 scale: 0,
3294 },
3295 DataType::DateArray => DataType::Date,
3296 DataType::TimestampArray => DataType::Timestamp,
3297 DataType::TimestamptzArray => DataType::Timestamptz,
3298 DataType::UuidArray => DataType::Uuid,
3299 DataType::IntervalArray => DataType::Interval,
3302 _ => return Ok(None),
3303 };
3304 let mut scal: alloc::vec::Vec<Option<Value<'static>>> =
3305 alloc::vec::Vec::with_capacity(items.len());
3306 for item in items {
3307 match item {
3308 None => scal.push(None),
3309 Some(s) => scal.push(Some(coerce_value(Value::text(s), elem_dt, col, 0)?)),
3310 }
3311 }
3312 let out = match target {
3313 DataType::BoolArray => Value::BoolArray(
3314 scal.into_iter()
3315 .map(|o| o.map(|v| matches!(v, Value::Bool(true))))
3316 .collect(),
3317 ),
3318 DataType::NumericArray => Value::NumericArray(
3319 scal.into_iter()
3320 .map(|o| {
3321 o.map(|v| match v {
3322 Value::Numeric { scaled, scale, .. } => (scaled, scale),
3323 _ => (0, 0),
3324 })
3325 })
3326 .collect(),
3327 ),
3328 DataType::DateArray => Value::DateArray(
3329 scal.into_iter()
3330 .map(|o| {
3331 o.map(|v| match v {
3332 Value::Date(d) => d,
3333 _ => 0,
3334 })
3335 })
3336 .collect(),
3337 ),
3338 DataType::TimestampArray => Value::TimestampArray(
3339 scal.into_iter()
3340 .map(|o| {
3341 o.map(|v| match v {
3342 Value::Timestamp(t) => t,
3343 _ => 0,
3344 })
3345 })
3346 .collect(),
3347 ),
3348 DataType::TimestamptzArray => Value::TimestamptzArray(
3349 scal.into_iter()
3350 .map(|o| {
3351 o.map(|v| match v {
3352 Value::Timestamp(t) => t,
3353 _ => 0,
3354 })
3355 })
3356 .collect(),
3357 ),
3358 DataType::UuidArray => Value::UuidArray(
3359 scal.into_iter()
3360 .map(|o| {
3361 o.map(|v| match v {
3362 Value::Uuid(u) => u,
3363 _ => [0u8; 16],
3364 })
3365 })
3366 .collect(),
3367 ),
3368 DataType::IntervalArray => Value::IntervalArray(
3369 scal.into_iter()
3370 .map(|o| {
3371 o.and_then(|v| match v {
3372 Value::Interval {
3373 months,
3374 days,
3375 micros,
3376 kind,
3377 } => Some(spg_storage::IntervalSpan {
3378 months,
3379 days,
3380 micros,
3381 kind,
3382 }),
3383 _ => None,
3384 })
3385 })
3386 .collect(),
3387 ),
3388 _ => return Ok(None),
3389 };
3390 Ok(Some(out))
3391}
3392
3393pub(crate) fn array_oid_element(oid: i64) -> Option<i64> {
3404 Some(match oid {
3405 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,
3439 })
3440}
3441
3442pub(crate) fn regtype_oid_to_name_owned(oid: i64) -> Option<alloc::string::String> {
3449 if let Some(scalar) = regtype_oid_to_name(oid) {
3450 return Some(alloc::string::String::from(scalar));
3451 }
3452 let (_, _, elem) = crate::system_catalog::ARRAY_TYPE_OIDS
3453 .iter()
3454 .find(|(arr, _, _)| *arr == oid)?;
3455 Some(alloc::format!("{}[]", regtype_oid_to_name(*elem)?))
3456}
3457
3458pub(crate) fn array_oid_for_element(elem: i64) -> Option<i64> {
3460 crate::system_catalog::ARRAY_TYPE_OIDS
3461 .iter()
3462 .find(|(_, _, e)| *e == elem)
3463 .map(|(arr, _, _)| *arr)
3464}
3465
3466pub(crate) fn regtype_oid_to_name(oid: i64) -> Option<&'static str> {
3467 Some(match oid {
3468 4600 => "pg_brin_bloom_summary",
3469 16 => "boolean",
3470 17 => "bytea",
3471 18 => "\"char\"",
3472 19 => "name",
3473 20 => "bigint",
3474 21 => "smallint",
3475 23 => "integer",
3476 25 => "text",
3477 26 => "oid",
3478 27 => "tid",
3480 28 => "xid",
3481 29 => "cid",
3482 5069 => "xid8",
3483 114 => "json",
3484 142 => "xml",
3485 650 => "cidr",
3486 700 => "real",
3487 701 => "double precision",
3488 774 => "macaddr8",
3489 790 => "money",
3490 829 => "macaddr",
3491 869 => "inet",
3492 1042 => "character",
3493 1043 => "character varying",
3494 1082 => "date",
3495 1083 => "time without time zone",
3496 1114 => "timestamp without time zone",
3497 1184 => "timestamp with time zone",
3498 1186 => "interval",
3499 1266 => "time with time zone",
3500 1560 => "bit",
3501 1562 => "bit varying",
3502 1700 => "numeric",
3503 2950 => "uuid",
3504 3614 => "tsvector",
3505 3615 => "tsquery",
3506 3802 => "jsonb",
3507 3904 => "int4range",
3508 3906 => "numrange",
3509 3908 => "tsrange",
3510 3910 => "tstzrange",
3511 3912 => "daterange",
3512 3926 => "int8range",
3513 _ => return None,
3514 })
3515}
3516
3517pub(crate) fn parse_pg_int(s: &str) -> Option<i64> {
3518 let s = s.trim();
3519 let (neg, rest) = if let Some(r) = s.strip_prefix('-') {
3520 (true, r)
3521 } else if let Some(r) = s.strip_prefix('+') {
3522 (false, r)
3523 } else {
3524 (false, s)
3525 };
3526 let (radix, digits, has_prefix) =
3531 if let Some(h) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
3532 (16u32, h, true)
3533 } else if let Some(o) = rest.strip_prefix("0o").or_else(|| rest.strip_prefix("0O")) {
3534 (8, o, true)
3535 } else if let Some(b) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) {
3536 (2, b, true)
3537 } else {
3538 (10, rest, false)
3539 };
3540 let db = digits.as_bytes();
3541 if db.last() == Some(&b'_')
3545 || digits.contains("__")
3546 || (!has_prefix && db.first() == Some(&b'_'))
3547 {
3548 return None;
3549 }
3550 let cleaned: alloc::string::String = digits.chars().filter(|&c| c != '_').collect();
3551 if cleaned.is_empty() {
3552 return None;
3553 }
3554 let mag = i64::from_str_radix(&cleaned, radix).ok()?;
3555 Some(if neg { mag.checked_neg()? } else { mag })
3556}
3557
3558fn xml_content_is_well_formed(s: &str) -> bool {
3567 let b = s.as_bytes();
3568 let is_name =
3569 |c: u8| c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_' | b'.' | b':') || c >= 0x80;
3570 let mut stack: alloc::vec::Vec<&[u8]> = alloc::vec::Vec::new();
3571 let mut i = 0;
3572 while i < b.len() {
3573 if b[i] != b'<' {
3574 i += 1;
3575 continue;
3576 }
3577 let rest = &s[i..];
3578 if rest.starts_with("<!--") {
3579 match rest.find("-->") {
3580 Some(p) => i += p + 3,
3581 None => return false,
3582 }
3583 } else if rest.starts_with("<![CDATA[") {
3584 match rest.find("]]>") {
3585 Some(p) => i += p + 3,
3586 None => return false,
3587 }
3588 } else if rest.starts_with("<?") {
3589 match rest.find("?>") {
3590 Some(p) => i += p + 2,
3591 None => return false,
3592 }
3593 } else if rest.starts_with("<!") {
3594 match rest.find('>') {
3595 Some(p) => i += p + 1,
3596 None => return false,
3597 }
3598 } else {
3599 let close = i + 1 < b.len() && b[i + 1] == b'/';
3601 let name_start = if close { i + 2 } else { i + 1 };
3602 let mut j = name_start;
3603 while j < b.len() && is_name(b[j]) {
3604 j += 1;
3605 }
3606 if j == name_start {
3607 return false; }
3609 let name = &b[name_start..j];
3610 let mut k = j;
3612 let mut quote = 0u8;
3613 let mut prev = 0u8;
3614 loop {
3615 if k >= b.len() {
3616 return false; }
3618 let c = b[k];
3619 if quote != 0 {
3620 if c == quote {
3621 quote = 0;
3622 }
3623 } else if c == b'"' || c == b'\'' {
3624 quote = c;
3625 } else if c == b'>' {
3626 break;
3627 }
3628 prev = c;
3629 k += 1;
3630 }
3631 let self_closing = prev == b'/';
3632 i = k + 1;
3633 if close {
3634 match stack.pop() {
3635 Some(top) if top == name => {}
3636 _ => return false,
3637 }
3638 } else if !self_closing {
3639 stack.push(name);
3640 }
3641 }
3642 }
3643 stack.is_empty()
3644}
3645
3646pub(crate) fn parse_float8(s: &str) -> Option<f64> {
3652 let t = s.trim();
3653 let parsed = t.parse::<f64>().ok()?;
3654 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3655 let numeric_looking = body
3656 .bytes()
3657 .next()
3658 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
3659 if numeric_looking {
3660 if parsed.is_infinite() {
3661 return None; }
3663 if parsed == 0.0 {
3664 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
3666 if mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0') {
3667 return None;
3668 }
3669 }
3670 }
3671 Some(parsed)
3672}
3673
3674fn decode_array_elems(
3678 s: &str,
3679 elem: DataType,
3680 col_name: &str,
3681 position: usize,
3682) -> Result<Vec<Option<Value<'static>>>, EngineError> {
3683 let raw = decode_text_array_literal(s).map_err(|_| {
3689 EngineError::Eval(EvalError::TypeMismatch {
3690 detail: malformed_array_literal(s),
3691 })
3692 })?;
3693 let mut out = Vec::with_capacity(raw.len());
3694 for e in raw {
3695 match e {
3696 None => out.push(None),
3697 Some(t) => out.push(Some(coerce_value(
3698 Value::text(t),
3699 elem,
3700 col_name,
3701 position,
3702 )?)),
3703 }
3704 }
3705 Ok(out)
3706}
3707
3708fn coerce_untyped_value(
3712 v: Value<'static>,
3713 expected: DataType,
3714 col_name: &str,
3715 position: usize,
3716) -> Result<Value<'static>, EngineError> {
3717 match (&v, expected) {
3718 (
3729 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3730 DataType::BigInt | DataType::Oid,
3731 ) => Ok(Value::BigInt(*oid)),
3732 (
3733 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3734 DataType::Int,
3735 ) => Ok(Value::Int(i32::try_from(*oid).unwrap_or(i32::MAX))),
3736 (
3737 Value::RegClass(_, name) | Value::RegProc(_, name) | Value::RegType(_, name),
3738 DataType::Text,
3739 ) => Ok(Value::text(alloc::string::String::from(name.as_ref()))),
3740 (Value::Composite(fields), DataType::Jsonb | DataType::Json) => {
3746 let mut obj = alloc::string::String::from("{");
3747 for (i, (name, val)) in fields.iter().enumerate() {
3748 if i > 0 {
3749 obj.push(',');
3750 }
3751 obj.push_str(&crate::json::value_to_json_text(&Value::text(
3753 alloc::string::String::from(name.as_str()),
3754 )));
3755 obj.push(':');
3756 obj.push_str(&crate::json::value_to_json_text(val));
3757 }
3758 obj.push('}');
3759 Ok(Value::Json(alloc::borrow::Cow::Owned(obj)))
3760 }
3761 (Value::Composite(_), DataType::Text) => Ok(Value::text(crate::eval::value_to_text(&v))),
3763 _ => Err(EngineError::Unsupported(alloc::format!(
3764 "cannot coerce {:?} to {expected:?} for column {col_name:?} (position {position})",
3765 v
3766 ))),
3767 }
3768}
3769
3770fn invalid_input_syntax(ty: &str, value: &str) -> EngineError {
3774 EngineError::Eval(EvalError::TypeMismatch {
3775 detail: alloc::format!("invalid input syntax for type {ty}: \"{value}\""),
3776 })
3777}
3778
3779fn real_out_of_range(value: &str) -> EngineError {
3782 float_out_of_range(value, "real")
3783}
3784
3785fn float_out_of_range(value: &str, ty: &str) -> EngineError {
3787 EngineError::Eval(EvalError::TypeMismatch {
3788 detail: alloc::format!("\"{value}\" is out of range for type {ty}"),
3789 })
3790}
3791
3792fn float_text_error(s: &str, ty: &str) -> EngineError {
3798 let t = s.trim();
3799 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3800 let numeric_looking = body
3801 .bytes()
3802 .next()
3803 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
3804 if numeric_looking && t.parse::<f64>().is_ok() {
3805 float_out_of_range(t, ty)
3806 } else {
3807 invalid_input_syntax(ty, s)
3808 }
3809}
3810
3811fn float_text_is_nonzero(t: &str) -> bool {
3815 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3816 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
3817 mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0')
3818}
3819
3820fn text_is_explicit_infinity(t: &str) -> bool {
3823 let t = t.trim_start_matches(['+', '-']);
3824 t.eq_ignore_ascii_case("inf") || t.eq_ignore_ascii_case("infinity")
3825}
3826
3827fn datetime_parse_error(ty: &str, s: &str) -> EngineError {
3836 let t = s.trim();
3837 let date_shaped = t.chars().any(|c| c.is_ascii_digit())
3838 && t.chars().all(|c| {
3839 c.is_ascii_digit() || matches!(c, '-' | '/' | ':' | '.' | ' ' | '+' | 'T' | 't')
3840 });
3841 let detail = if date_shaped {
3842 alloc::format!("date/time field value out of range: \"{t}\"")
3843 } else {
3844 alloc::format!("invalid input syntax for type {ty}: \"{t}\"")
3845 };
3846 EngineError::Eval(EvalError::TypeMismatch { detail })
3847}
3848
3849pub(crate) enum JsonbScalar {
3855 Numeric(Value<'static>),
3856 Bool(bool),
3857 Null,
3858}
3859
3860pub(crate) fn jsonb_cast_type_error(kind: &str, target: &str) -> EvalError {
3862 EvalError::TypeMismatch {
3863 detail: alloc::format!("cannot cast jsonb {kind} to type {target}"),
3864 }
3865}
3866
3867pub(crate) fn jsonb_scalar_for_cast(s: &str, target: &str) -> Result<JsonbScalar, EvalError> {
3870 use crate::json::JsonValue;
3871 match crate::json::parse(s) {
3872 Ok(JsonValue::Null) => Ok(JsonbScalar::Null),
3873 Ok(JsonValue::Bool(b)) => Ok(JsonbScalar::Bool(b)),
3874 Ok(JsonValue::Number(x)) => {
3878 let num = coerce_value(
3879 Value::text(alloc::format!("{x}")),
3880 DataType::Numeric {
3881 precision: 0,
3882 scale: 0,
3883 },
3884 "",
3885 0,
3886 )
3887 .map_err(|e| match e {
3888 EngineError::Eval(ev) => ev,
3889 _ => jsonb_cast_type_error("numeric", target),
3890 })?;
3891 Ok(JsonbScalar::Numeric(num))
3892 }
3893 Ok(JsonValue::NumberText(text)) => {
3894 let num = coerce_value(
3895 Value::text(text),
3896 DataType::Numeric {
3897 precision: 0,
3898 scale: 0,
3899 },
3900 "",
3901 0,
3902 )
3903 .map_err(|e| match e {
3904 EngineError::Eval(ev) => ev,
3905 _ => jsonb_cast_type_error("numeric", target),
3906 })?;
3907 Ok(JsonbScalar::Numeric(num))
3908 }
3909 Ok(JsonValue::String(_)) => Err(jsonb_cast_type_error("string", target)),
3910 Ok(JsonValue::Array(_)) => Err(jsonb_cast_type_error("array", target)),
3911 Ok(JsonValue::Object(_)) => Err(jsonb_cast_type_error("object", target)),
3912 Err(_) => Err(jsonb_cast_type_error("value", target)),
3913 }
3914}
3915pub(crate) fn normalize_composite_for_column(
3933 v: Value<'static>,
3934 col: &ColumnSchema,
3935 catalog: Option<&spg_storage::Catalog>,
3936) -> Result<Value<'static>, EngineError> {
3937 let Some(tname) = col.user_composite_type.as_deref() else {
3938 return Ok(v);
3939 };
3940 if matches!(v, Value::Null) {
3941 return Ok(v);
3942 }
3943 let Some(def) = catalog.and_then(|c| c.composite_types().get(tname)) else {
3946 return Ok(v);
3947 };
3948 if matches!(v, Value::Json(_)) {
3951 return Ok(v);
3952 }
3953 crate::eval::apply_composite_cast_pub(v, def, catalog).map_err(EngineError::Eval)
3954}
3955
3956fn try_coerce_json_scalar(
3960 s: &str,
3961 expected: DataType,
3962 col_name: &str,
3963 position: usize,
3964) -> Option<Result<Value<'static>, EngineError>> {
3965 let target = match expected {
3966 DataType::Int => "integer",
3967 DataType::BigInt => "bigint",
3968 DataType::SmallInt => "smallint",
3969 DataType::Numeric { .. } => "numeric",
3970 DataType::Real => "real",
3971 DataType::Float => "double precision",
3972 DataType::Bool => "boolean",
3973 _ => return None,
3974 };
3975 Some(
3976 (|| match jsonb_scalar_for_cast(s, target).map_err(EngineError::Eval)? {
3977 JsonbScalar::Null => Ok(Value::Null),
3978 JsonbScalar::Bool(b) => {
3979 if matches!(expected, DataType::Bool) {
3980 Ok(Value::Bool(b))
3981 } else {
3982 Err(EngineError::Eval(jsonb_cast_type_error("boolean", target)))
3983 }
3984 }
3985 JsonbScalar::Numeric(n) => {
3986 if matches!(expected, DataType::Bool) {
3987 Err(EngineError::Eval(jsonb_cast_type_error("numeric", target)))
3988 } else {
3989 coerce_value(n, expected, col_name, position)
3990 }
3991 }
3992 })(),
3993 )
3994}
3995
3996pub(crate) fn mysql_bytes_for_column(
4006 v: Value<'static>,
4007 expected: DataType,
4008 mysql: bool,
4009) -> Value<'static> {
4010 if !mysql {
4011 return v;
4012 }
4013 let Value::Bytes(ref b) = v else {
4014 return v;
4015 };
4016 match expected {
4017 DataType::SmallInt
4018 | DataType::Int
4019 | DataType::BigInt
4020 | DataType::Float
4021 | DataType::Real
4022 | DataType::Numeric { .. } => {
4023 let start = b.len().saturating_sub(16);
4024 let acc = b[start..]
4025 .iter()
4026 .fold(0u128, |a, &x| (a << 8) | u128::from(x));
4027 if acc <= i64::MAX as u128 {
4028 #[allow(clippy::cast_possible_truncation)]
4029 Value::BigInt(acc as i64)
4030 } else {
4031 big_literal_to_value(&alloc::format!("{acc}"))
4032 }
4033 }
4034 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(
4035 b.iter()
4036 .map(|&x| x as char)
4037 .collect::<alloc::string::String>(),
4038 ),
4039 _ => v,
4040 }
4041}
4042
4043fn try_coerce_time_family(
4062 v: &Value<'static>,
4063 expected: DataType,
4064) -> Option<Result<Value<'static>, EngineError>> {
4065 const DAY_US: i64 = 86_400_000_000;
4066 if expected != DataType::Time {
4067 return None;
4068 }
4069 match v {
4070 Value::TimeTz { us, .. } => Some(Ok(Value::Time(*us))),
4071 Value::Interval { micros, .. } => Some(Ok(Value::Time(micros.rem_euclid(DAY_US)))),
4072 _ => None,
4073 }
4074}
4075
4076pub(crate) fn coerce_to_oid(v: &Value<'_>) -> Result<Option<Value<'static>>, EvalError> {
4086 let as_i64 = match v {
4087 Value::Null => return Ok(Some(Value::Null)),
4088 Value::SmallInt(n) => i64::from(*n),
4089 Value::Int(n) => i64::from(*n),
4090 Value::BigInt(n) => *n,
4091 Value::Text(t) => match t.trim().parse::<i64>() {
4092 Ok(n) => n,
4093 Err(_) => {
4094 return Err(EvalError::TypeMismatch {
4095 detail: alloc::format!("invalid input syntax for type oid: {:?}", t.trim()),
4096 });
4097 }
4098 },
4099 _ => return Ok(None),
4100 };
4101 if (-(1i64 << 31)..0).contains(&as_i64) {
4103 return Ok(Some(Value::BigInt(as_i64 + (1i64 << 32))));
4104 }
4105 if !(0..=i64::from(u32::MAX)).contains(&as_i64) {
4106 return Err(EvalError::TypeMismatch {
4107 detail: "OID out of range".into(),
4108 });
4109 }
4110 Ok(Some(Value::BigInt(as_i64)))
4111}
4112
4113pub(crate) fn coerce_value(
4114 v: Value<'static>,
4115 expected: DataType,
4116 col_name: &str,
4117 position: usize,
4118) -> Result<Value<'static>, EngineError> {
4119 if v.is_null() {
4120 return Ok(Value::Null);
4121 }
4122 if let Value::Json(ref s) = v {
4127 if let Some(res) = try_coerce_json_scalar(s, expected, col_name, position) {
4128 return res;
4129 }
4130 }
4131 if let Some(res) = try_coerce_time_family(&v, expected) {
4135 return res;
4136 }
4137 if let Value::Numeric { kind, .. } = v
4153 && kind != spg_storage::NumericKind::Finite
4154 {
4155 use spg_storage::NumericKind as K;
4156 let as_f64 = match kind {
4157 K::NaN => f64::NAN,
4158 K::PosInf => f64::INFINITY,
4159 K::NegInf => f64::NEG_INFINITY,
4160 K::Finite => unreachable!("checked above"),
4161 };
4162 let what = if kind == K::NaN { "NaN" } else { "infinity" };
4164 let int_err = |target: &str| {
4165 Err(EngineError::Eval(EvalError::TypeMismatch {
4166 detail: alloc::format!("cannot convert {what} to {target}"),
4167 }))
4168 };
4169 match expected {
4170 DataType::Float => return Ok(Value::Float(as_f64)),
4171 #[allow(clippy::cast_possible_truncation)]
4172 DataType::Real => return Ok(Value::Real(as_f64 as f32)),
4173 DataType::Int => return int_err("integer"),
4174 DataType::BigInt => return int_err("bigint"),
4175 DataType::SmallInt => return int_err("smallint"),
4176 DataType::Numeric { precision, scale } => {
4177 if precision != 0 && kind != K::NaN {
4181 return Err(EngineError::Eval(EvalError::TypeMismatch {
4182 detail: alloc::string::String::from("numeric field overflow"),
4183 }));
4184 }
4185 let _ = scale;
4186 return Ok(v);
4187 }
4188 _ => {}
4189 }
4190 }
4191 if let DataType::Numeric { precision, .. } = expected {
4195 let f = match v {
4196 Value::Float(f) if !f.is_finite() => Some(f),
4197 #[allow(clippy::cast_lossless)]
4198 Value::Real(f) if !f.is_finite() => Some(f as f64),
4199 _ => None,
4200 };
4201 if let Some(f) = f {
4202 use spg_storage::NumericKind as K;
4203 if f.is_nan() {
4204 return Ok(Value::numeric_special(K::NaN));
4205 }
4206 if precision != 0 {
4207 return Err(EngineError::Eval(EvalError::TypeMismatch {
4208 detail: alloc::string::String::from("numeric field overflow"),
4209 }));
4210 }
4211 return Ok(Value::numeric_special(if f > 0.0 {
4212 K::PosInf
4213 } else {
4214 K::NegInf
4215 }));
4216 }
4217 }
4218 let Some(actual) = v.data_type() else {
4219 return coerce_untyped_value(v, expected, col_name, position);
4220 };
4221 if actual == expected {
4222 return Ok(v);
4223 }
4224 if matches!(expected, DataType::Json | DataType::Jsonb)
4248 && let Value::Text(ref s) | Value::Json(ref s) = v
4249 {
4250 let bad = || {
4251 EngineError::Eval(crate::eval::EvalError::TypeMismatch {
4252 detail: alloc::string::String::from("invalid input syntax for type json"),
4253 })
4254 };
4255 return if expected == DataType::Jsonb {
4256 crate::json::canonicalize_jsonb(s.as_ref())
4257 .map(Value::json)
4258 .map_err(|_| bad())
4259 } else {
4260 crate::json::parse(s.as_ref())
4261 .map_err(|_| bad())
4262 .map(|_| Value::json(s.clone()))
4263 };
4264 }
4265 let coerced: Option<Value<'static>> = match (v, expected) {
4266 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4267 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4268 (Value::Int(n), DataType::SmallInt) => match i16::try_from(n) {
4271 Ok(v) => Some(Value::SmallInt(v)),
4272 Err(_) => {
4273 return Err(EngineError::Eval(EvalError::TypeMismatch {
4274 detail: "smallint out of range".into(),
4275 }));
4276 }
4277 },
4278 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4279 i128::from(n),
4280 precision,
4281 scale,
4282 col_name,
4283 )?),
4284 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
4285 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4286 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4287 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4288 i128::from(n),
4289 precision,
4290 scale,
4291 col_name,
4292 )?),
4293 (Value::BigInt(n), DataType::Int) => match i32::try_from(n) {
4294 Ok(v) => Some(Value::Int(v)),
4295 Err(_) => {
4296 return Err(EngineError::Eval(EvalError::TypeMismatch {
4297 detail: "integer out of range".into(),
4298 }));
4299 }
4300 },
4301 (Value::BigInt(n), DataType::SmallInt) => match i16::try_from(n) {
4302 Ok(v) => Some(Value::SmallInt(v)),
4303 Err(_) => {
4304 return Err(EngineError::Eval(EvalError::TypeMismatch {
4305 detail: "smallint out of range".into(),
4306 }));
4307 }
4308 },
4309 #[allow(clippy::cast_precision_loss)]
4310 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
4311 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4312 i128::from(n),
4313 precision,
4314 scale,
4315 col_name,
4316 )?),
4317 (Value::Float(x), DataType::Numeric { precision, scale }) => {
4318 if precision == 0 && scale == 0 && x.is_finite() {
4324 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{x}")) {
4325 Some(Value::Numeric {
4326 scaled: mantissa,
4327 scale: src_scale,
4328 kind: spg_storage::NumericKind::Finite,
4329 })
4330 } else {
4331 Some(numeric_from_float(x, precision, scale, col_name)?)
4332 }
4333 } else {
4334 Some(numeric_from_float(x, precision, scale, col_name)?)
4335 }
4336 }
4337 (Value::Real(x), DataType::Numeric { precision, scale }) => {
4343 if precision == 0 && scale == 0 && x.is_finite() {
4344 let six = alloc::format!("{:.5e}", x);
4358 let six: f64 = six.parse().unwrap_or_else(|_| f64::from(x));
4359 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{six}")) {
4360 Some(Value::Numeric {
4361 scaled: mantissa,
4362 scale: src_scale,
4363 kind: spg_storage::NumericKind::Finite,
4364 })
4365 } else {
4366 Some(numeric_from_float(
4367 f64::from(x),
4368 precision,
4369 scale,
4370 col_name,
4371 )?)
4372 }
4373 } else {
4374 Some(numeric_from_float(
4375 f64::from(x),
4376 precision,
4377 scale,
4378 col_name,
4379 )?)
4380 }
4381 }
4382 (Value::Text(s), DataType::Numeric { precision, scale }) => {
4393 if let Some(kind) = crate::numeric::parse_numeric_special(&s) {
4396 return Ok(Value::numeric_special(kind));
4397 }
4398 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
4399 match spg_sql::parser::expand_scientific_literal(&s) {
4404 spg_sql::parser::SciExpanded::Expanded(plain) => {
4405 return coerce_value(
4406 Value::Text(plain.into()),
4407 DataType::Numeric { precision, scale },
4408 col_name,
4409 position,
4410 );
4411 }
4412 spg_sql::parser::SciExpanded::Overflow => {
4413 return Err(EngineError::Eval(EvalError::TypeMismatch {
4414 detail: "value overflows numeric format".into(),
4415 }));
4416 }
4417 spg_sql::parser::SciExpanded::NotScientific => {}
4418 }
4419 if precision == 0 && scale == 0 {
4422 if let Some(b) = spg_storage::bignum::BigNumeric::from_decimal_str(&s) {
4423 return Ok(Value::NumericBig(alloc::boxed::Box::new(b)));
4424 }
4425 }
4426 return Err(EngineError::Eval(EvalError::TypeMismatch {
4427 detail: alloc::format!("invalid input syntax for type numeric: \"{s}\""),
4428 }));
4429 };
4430 if precision == 0 && scale == 0 {
4432 Some(Value::Numeric {
4433 scaled: mantissa,
4434 scale: src_scale,
4435 kind: spg_storage::NumericKind::Finite,
4436 })
4437 } else {
4438 Some(numeric_rescale(
4439 mantissa, src_scale, precision, scale, col_name,
4440 )?)
4441 }
4442 }
4443 (Value::Text(s), DataType::Date) => {
4445 let d = eval::parse_date_literal(&s)
4452 .or_else(|| {
4453 eval::parse_timestamp_literal(&s)
4454 .and_then(|t| i32::try_from(t.div_euclid(86_400_000_000)).ok())
4455 })
4456 .ok_or_else(|| datetime_parse_error("date", &s))?;
4457 Some(Value::Date(d))
4458 }
4459 (Value::Text(s), DataType::SmallInt) => Some(Value::SmallInt(
4474 parse_pg_int(&s)
4475 .and_then(|n| i16::try_from(n).ok())
4476 .ok_or_else(|| invalid_input_syntax("smallint", &s))?,
4477 )),
4478 (Value::Text(s), DataType::Int) => Some(Value::Int(
4479 parse_pg_int(&s)
4480 .and_then(|n| i32::try_from(n).ok())
4481 .ok_or_else(|| invalid_input_syntax("integer", &s))?,
4482 )),
4483 (Value::Text(s), DataType::BigInt) => Some(Value::BigInt(
4484 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("bigint", &s))?,
4485 )),
4486 (Value::Text(s), DataType::Xid) => Some(Value::Xid(
4492 s.parse::<u32>()
4493 .map_err(|_| invalid_input_syntax("xid", &s))?,
4494 )),
4495 (Value::Xid(x), DataType::Xid) => Some(Value::Xid(x)),
4496 (Value::Text(s), DataType::Xid8) => Some(Value::BigInt(
4497 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("xid8", &s))?,
4498 )),
4499 (Value::BigInt(n), DataType::Xid8) => Some(Value::BigInt(n)),
4506 (ref other, DataType::Oid) => coerce_to_oid(other)?,
4510 (Value::Text(s), DataType::Float) => {
4511 Some(Value::Float(
4515 parse_float8(&s).ok_or_else(|| float_text_error(&s, "double precision"))?,
4516 ))
4517 }
4518 (Value::Int(n), DataType::Real) => Some(Value::Real(n as f32)),
4520 (Value::SmallInt(n), DataType::Real) => Some(Value::Real(f32::from(n))),
4521 (Value::BigInt(n), DataType::Real) => Some(Value::Real(n as f32)),
4522 (Value::Float(x), DataType::Real) => {
4523 let narrowed = x as f32;
4527 if narrowed.is_infinite() && x.is_finite() {
4528 return Err(EngineError::Eval(EvalError::TypeMismatch {
4529 detail: "value out of range: overflow".into(),
4530 }));
4531 }
4532 if narrowed == 0.0 && x != 0.0 {
4534 return Err(EngineError::Eval(EvalError::TypeMismatch {
4535 detail: "value out of range: underflow".into(),
4536 }));
4537 }
4538 Some(Value::Real(narrowed))
4539 }
4540 (
4541 Value::Numeric {
4542 scaled,
4543 scale,
4544 kind,
4545 },
4546 DataType::Real,
4547 ) => Some(Value::Real(match kind {
4548 spg_storage::NumericKind::NaN => f32::NAN,
4549 spg_storage::NumericKind::PosInf => f32::INFINITY,
4550 spg_storage::NumericKind::NegInf => f32::NEG_INFINITY,
4551 spg_storage::NumericKind::Finite => {
4552 let mut div = 1.0f64;
4553 for _ in 0..scale {
4554 div *= 10.0;
4555 }
4556 let x = (scaled as f64 / div) as f32;
4557 if x == 0.0 && scaled != 0 {
4560 return Err(real_out_of_range(&crate::eval::format_numeric(
4561 scaled, scale,
4562 )));
4563 }
4564 x
4565 }
4566 })),
4567 (Value::Real(x), DataType::Float) => Some(Value::Float(f64::from(x))),
4568 (Value::Text(s), DataType::Real) => {
4575 let t = s.trim();
4576 let x = t
4577 .parse::<f32>()
4578 .ok()
4579 .ok_or_else(|| invalid_input_syntax("real", &s))?;
4580 if x.is_infinite() && !text_is_explicit_infinity(t) {
4581 return Err(real_out_of_range(t));
4582 }
4583 if x == 0.0 && float_text_is_nonzero(t) {
4586 return Err(real_out_of_range(t));
4587 }
4588 Some(Value::Real(x))
4589 }
4590 (Value::Text(s), DataType::Bool) => match s.trim().to_ascii_lowercase().as_str() {
4594 "0" | "f" | "fa" | "fal" | "fals" | "false" | "n" | "no" | "of" | "off" => {
4595 Some(Value::Bool(false))
4596 }
4597 "1" | "t" | "tr" | "tru" | "true" | "y" | "ye" | "yes" | "on" => {
4598 Some(Value::Bool(true))
4599 }
4600 _ => return Err(invalid_input_syntax("boolean", &s)),
4601 },
4602 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4611 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4612 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4613 (Value::Json(s), DataType::Text) => Some(Value::text(s)),
4635 (Value::Json(s), DataType::Json) => Some(Value::json(s)),
4643 (Value::Json(s), DataType::Jsonb) => Some(Value::json(
4644 crate::json::canonicalize_jsonb(s.as_ref()).unwrap_or_else(|_| s.into_owned()),
4645 )),
4646 (Value::Text(s), DataType::Bytes) => {
4653 let bytes = decode_bytea_literal(&s)
4654 .map_err(|e| EngineError::Eval(EvalError::TypeMismatch { detail: e }))?;
4655 Some(Value::bytes(bytes))
4656 }
4657 (Value::Bytes(b), DataType::Text) => Some(Value::text(encode_bytea_hex(&b))),
4661 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
4669 Some(b) => Some(Value::Uuid(b)),
4670 None => {
4671 return Err(EngineError::Eval(EvalError::TypeMismatch {
4672 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4673 }));
4674 }
4675 },
4676 (Value::Uuid(b), DataType::Text) => Some(Value::text(spg_storage::format_uuid(&b))),
4681 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
4687 Some(us) => Some(Value::Time(us)),
4688 None => {
4689 let time_shaped = {
4695 let core = s.trim().split('.').next().unwrap_or("");
4696 !core.is_empty()
4697 && core.split(':').count() >= 2
4698 && core
4699 .split(':')
4700 .all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()))
4701 };
4702 let detail = if time_shaped {
4703 alloc::format!("date/time field value out of range: {s:?}")
4704 } else {
4705 alloc::format!("invalid input syntax for type time: {s:?}")
4706 };
4707 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
4708 }
4709 },
4710 (Value::Time(us), DataType::Text) => Some(Value::text(eval::format_time(us))),
4712 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4717 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4718 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
4719 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
4723 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
4724 Err(_) => {
4725 return Err(EngineError::Eval(EvalError::TypeMismatch {
4726 detail: alloc::format!("invalid input syntax for type year: {s:?}"),
4727 }));
4728 }
4729 },
4730 (Value::Year(y), DataType::Text) => Some(Value::text(alloc::format!("{y:04}"))),
4732 (Value::Time(t), DataType::TimeTz) => Some(Value::TimeTz {
4746 us: t,
4747 offset_secs: 0,
4748 }),
4749 (Value::Timestamp(t), DataType::TimeTz) => Some(Value::TimeTz {
4750 us: t.rem_euclid(86_400_000_000),
4751 offset_secs: 0,
4752 }),
4753 (Value::Text(s), DataType::TimeTz) => {
4754 match parse_timetz_str(&s).or_else(|| parse_time_str(s.trim()).map(|us| (us, 0))) {
4755 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
4756 None => {
4757 return Err(EngineError::Eval(EvalError::TypeMismatch {
4758 detail: alloc::format!(
4759 "invalid input syntax for type time with time zone: \
4760 {s:?}"
4761 ),
4762 }));
4763 }
4764 }
4765 }
4766 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
4768 Some(Value::text(eval::format_timetz(us, offset_secs)))
4769 }
4770 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
4774 Some(c) => Some(Value::Money(c)),
4775 None => {
4776 return Err(EngineError::Eval(EvalError::TypeMismatch {
4777 detail: alloc::format!("invalid input syntax for type money: {s:?}"),
4778 }));
4779 }
4780 },
4781 (Value::SmallInt(n), DataType::Money) => {
4785 Some(Value::Money(i64::from(n).saturating_mul(100)))
4786 }
4787 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
4788 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
4789 (Value::Float(x), DataType::Money) => {
4790 let scaled = x * 100.0;
4793 let cents = if scaled >= 0.0 {
4794 (scaled + 0.5) as i64
4795 } else {
4796 (scaled - 0.5) as i64
4797 };
4798 Some(Value::Money(cents))
4799 }
4800 (Value::Numeric { scaled, scale, .. }, DataType::Money) => {
4801 let cents = if scale == 2 {
4804 scaled
4805 } else if scale < 2 {
4806 let mult = 10_i128.pow(u32::from(2 - scale));
4807 scaled.saturating_mul(mult)
4808 } else {
4809 let div = 10_i128.pow(u32::from(scale - 2));
4810 let half = div / 2;
4811 let bias = if scaled >= 0 { half } else { -half };
4812 (scaled + bias) / div
4813 };
4814 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
4815 }
4816 (Value::Money(c), DataType::Text) => Some(Value::text(eval::format_money(c))),
4818 (Value::Money(c), DataType::Numeric { .. }) => Some(Value::Numeric {
4820 scaled: i128::from(c),
4821 scale: 2,
4822 kind: spg_storage::NumericKind::Finite,
4823 }),
4824 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
4828 Ok(v) => Some(v),
4829 Err(RangeParseError::Misordered) => {
4831 return Err(EngineError::Eval(EvalError::TypeMismatch {
4832 detail: alloc::string::String::from(
4833 "range lower bound must be less than or equal to range upper bound",
4834 ),
4835 }));
4836 }
4837 Err(RangeParseError::Malformed) => {
4838 return Err(EngineError::Eval(EvalError::TypeMismatch {
4839 detail: alloc::format!("malformed range literal: \"{s}\""),
4840 }));
4841 }
4842 Err(RangeParseError::BadElement(bad)) => {
4843 return Err(EngineError::Eval(EvalError::TypeMismatch {
4844 detail: alloc::format!(
4845 "invalid input syntax for type {}: \"{bad}\"",
4846 range_element_type_name(kind)
4847 ),
4848 }));
4849 }
4850 },
4851 (v @ Value::Range { .. }, DataType::Text) => Some(Value::text(format_range_str(&v))),
4853 (Value::Text(s), DataType::Inet) => match parse_inet_text(&s) {
4855 Some((family, bits, addr)) => Some(Value::Inet { family, bits, addr }),
4856 None => {
4857 return Err(EngineError::Eval(EvalError::TypeMismatch {
4861 detail: alloc::format!("invalid input syntax for type inet: {s:?}"),
4862 }));
4863 }
4864 },
4865 (Value::Inet { family, bits, addr }, DataType::Cidr) => {
4872 let full = if family == 6 { 128 } else { 32 };
4873 let bits = if bits > full { full } else { bits };
4874 let mut masked = addr;
4875 for i in 0..16usize {
4876 let bit_start = i * 8;
4877 if bit_start >= usize::from(bits) {
4878 masked[i] = 0;
4879 } else if bit_start + 8 > usize::from(bits) {
4880 let keep = usize::from(bits) - bit_start;
4881 masked[i] &= 0xffu8 << (8 - keep);
4882 }
4883 }
4884 Some(Value::Cidr {
4885 family,
4886 bits,
4887 addr: masked,
4888 })
4889 }
4890 (Value::Cidr { family, bits, addr }, DataType::Inet) => {
4891 Some(Value::Inet { family, bits, addr })
4892 }
4893 (Value::Text(s), DataType::Cidr) => match parse_cidr_text(&s) {
4894 Ok(Some((family, bits, addr))) => Some(Value::Cidr { family, bits, addr }),
4895 Err(()) => {
4896 return Err(EngineError::Eval(EvalError::TypeMismatch {
4897 detail: alloc::format!(
4898 "invalid cidr value: {s:?} DETAIL: Value has bits set to right of mask."
4899 ),
4900 }));
4901 }
4902 Ok(None) => {
4903 return Err(EngineError::Eval(EvalError::TypeMismatch {
4904 detail: alloc::format!("invalid input syntax for type cidr: {s:?}"),
4905 }));
4906 }
4907 },
4908 (Value::Text(s), DataType::Interval) => match spg_sql::parser::parse_interval_text(&s) {
4911 Some((months, days, micros)) => Some(Value::Interval {
4912 months,
4913 days,
4914 micros,
4915 kind: spg_storage::IntervalKind::from_fields(months, days, micros),
4916 }),
4917 None => {
4918 return Err(EngineError::Eval(EvalError::TypeMismatch {
4919 detail: alloc::format!("invalid input syntax for type interval: {s:?}"),
4920 }));
4921 }
4922 },
4923 (Value::Text(s), DataType::Macaddr) => match parse_macaddr_text(&s) {
4924 Some(m) => Some(Value::Macaddr(m)),
4925 None => {
4926 return Err(EngineError::Eval(EvalError::TypeMismatch {
4927 detail: alloc::format!("invalid input syntax for type macaddr: {s:?}"),
4928 }));
4929 }
4930 },
4931 (Value::Text(s), DataType::PgLsn) => match parse_pg_lsn_text(&s) {
4933 Some(l) => Some(Value::PgLsn(l)),
4934 None => {
4935 return Err(EngineError::Eval(EvalError::TypeMismatch {
4936 detail: alloc::format!("invalid input syntax for type pg_lsn: \"{s}\""),
4937 }));
4938 }
4939 },
4940 (Value::Text(s), DataType::Macaddr8) => match parse_macaddr8_text(&s) {
4941 Some(m) => Some(Value::Macaddr8(m)),
4942 None => {
4943 return Err(EngineError::Eval(EvalError::TypeMismatch {
4944 detail: alloc::format!("invalid input syntax for type macaddr8: {s:?}"),
4945 }));
4946 }
4947 },
4948 (Value::BitString { nbits, bytes }, DataType::Bit(n)) => {
4960 let want = if n == 0 { 1 } else { n };
4962 if nbits != want {
4963 return Err(EngineError::Unsupported(alloc::format!(
4964 "bit string length {nbits} does not match type bit({want})"
4965 )));
4966 }
4967 Some(Value::BitString { nbits, bytes })
4968 }
4969 (Value::BitString { nbits, bytes }, DataType::BitVarying(n)) => {
4970 if n != 0 && nbits > n {
4971 return Err(EngineError::Unsupported(alloc::format!(
4972 "bit string too long for type bit varying({n})"
4973 )));
4974 }
4975 Some(Value::BitString { nbits, bytes })
4976 }
4977 (Value::Text(s), bit_ty @ (DataType::Bit(_) | DataType::BitVarying(_))) => {
4978 match parse_bit_string_text(&s) {
4979 Some((nbits, bytes)) => {
4980 match bit_ty {
4990 DataType::Bit(n) => {
4992 let want = if n == 0 { 1 } else { n };
4993 if nbits != want {
4994 return Err(EngineError::Unsupported(alloc::format!(
4995 "bit string length {nbits} does not match type bit({want})"
4996 )));
4997 }
4998 }
4999 DataType::BitVarying(n) if n != 0 && nbits > n => {
5000 return Err(EngineError::Unsupported(alloc::format!(
5001 "bit string too long for type bit varying({n})"
5002 )));
5003 }
5004 _ => {}
5005 }
5006 Some(Value::bit_string(nbits, bytes))
5007 }
5008 None => {
5009 let bad = s.chars().find(|c| *c != '0' && *c != '1');
5011 return Err(EngineError::Eval(EvalError::TypeMismatch {
5012 detail: match bad {
5013 Some(c) => {
5014 alloc::format!("\"{c}\" is not a valid binary digit")
5015 }
5016 None => alloc::format!("invalid input syntax for BIT: {s:?}"),
5017 },
5018 }));
5019 }
5020 }
5021 }
5022 (Value::Text(s), DataType::Xml) => {
5023 if !xml_content_is_well_formed(&s) {
5028 return Err(EngineError::Eval(EvalError::TypeMismatch {
5029 detail: alloc::format!("invalid XML content: {s:?}"),
5030 }));
5031 }
5032 Some(Value::xml(s))
5033 }
5034 (Value::BpChar(s), DataType::Char1) => {
5041 Some(Value::Char1(s.as_bytes().first().copied().unwrap_or(0)))
5042 }
5043 (Value::BpChar(s), DataType::Xml) => {
5044 let stripped = s.trim_end_matches(' ');
5045 if !xml_content_is_well_formed(stripped) {
5046 return Err(EngineError::Eval(EvalError::TypeMismatch {
5047 detail: alloc::format!("invalid XML content: {stripped:?}"),
5048 }));
5049 }
5050 Some(Value::xml(alloc::string::String::from(stripped)))
5051 }
5052 (Value::Bytes(b), DataType::SmallInt | DataType::Int | DataType::BigInt) => {
5058 let mut acc: i128 = 0;
5059 for byte in b.iter() {
5060 acc = acc.saturating_mul(256).saturating_add(i128::from(*byte));
5061 }
5062 let (fits, made) = match expected {
5063 DataType::SmallInt => (
5064 i16::try_from(acc).is_ok(),
5065 i16::try_from(acc).map(Value::SmallInt).ok(),
5066 ),
5067 DataType::Int => (
5068 i32::try_from(acc).is_ok(),
5069 i32::try_from(acc).map(Value::Int).ok(),
5070 ),
5071 _ => (
5072 i64::try_from(acc).is_ok(),
5073 i64::try_from(acc).map(Value::BigInt).ok(),
5074 ),
5075 };
5076 if !fits {
5077 return Err(EngineError::Eval(EvalError::TypeMismatch {
5078 detail: alloc::format!("{} out of range", pg_type_name_for_error(expected)),
5079 }));
5080 }
5081 made
5082 }
5083 (Value::Int(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
5086 (Value::SmallInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
5087 (Value::BigInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
5088 (Value::Text(s), DataType::Char1) => {
5089 let bytes = s.as_bytes();
5095 if bytes.len() == 4
5096 && bytes[0] == b'\\'
5097 && bytes[1..].iter().all(|b| (b'0'..=b'7').contains(b))
5098 {
5099 let v = ((bytes[1] - b'0') << 6) | ((bytes[2] - b'0') << 3) | (bytes[3] - b'0');
5100 Some(Value::Char1(v))
5101 } else {
5102 let b = s.bytes().next().unwrap_or(0);
5103 Some(Value::Char1(b))
5104 }
5105 }
5106 (Value::Inet { family, bits, addr }, DataType::Text) => {
5108 let base = format_inet(family, bits, &addr);
5112 Some(Value::text(if base.contains('/') {
5113 base
5114 } else {
5115 alloc::format!("{base}/{bits}")
5116 }))
5117 }
5118 (Value::Cidr { family, bits, addr }, DataType::Text) => {
5119 Some(Value::text(format_inet(family, bits, &addr)))
5120 }
5121 (Value::Macaddr(m), DataType::Text) => Some(Value::text(format_macaddr(&m))),
5122 (Value::Macaddr8(m), DataType::Text) => Some(Value::text(format_macaddr8(&m))),
5123 (Value::PgLsn(l), DataType::Text) => Some(Value::text(format_pg_lsn(l))),
5124 (Value::Macaddr(m), DataType::Macaddr8) => Some(Value::Macaddr8([
5127 m[0], m[1], m[2], 0xff, 0xfe, m[3], m[4], m[5],
5128 ])),
5129 (Value::BitString { nbits, bytes }, DataType::Text) => {
5130 Some(Value::text(format_bit_string(nbits, &bytes)))
5131 }
5132 #[allow(clippy::cast_possible_truncation)]
5134 (Value::BitString { nbits, bytes }, DataType::SmallInt) => {
5135 Some(Value::SmallInt(bit_string_to_i64(nbits, &bytes) as i16))
5136 }
5137 #[allow(clippy::cast_possible_truncation)]
5138 (Value::BitString { nbits, bytes }, DataType::Int) => {
5139 Some(Value::Int(bit_string_to_i64(nbits, &bytes) as i32))
5140 }
5141 (Value::BitString { nbits, bytes }, DataType::BigInt) => {
5142 Some(Value::BigInt(bit_string_to_i64(nbits, &bytes)))
5143 }
5144 (Value::Xml(s), DataType::Text) => Some(Value::text(s)),
5145 (Value::Char1(b), DataType::Text) => Some(Value::text((b as char).to_string())),
5146 (Value::Text(s), DataType::Point) => match parse_point(&s) {
5150 Some(p) => Some(Value::Point(p)),
5151 None => {
5152 return Err(EngineError::Eval(EvalError::TypeMismatch {
5153 detail: alloc::format!("invalid input syntax for type point: {s:?}"),
5154 }));
5155 }
5156 },
5157 (Value::Text(s), DataType::Lseg) => match parse_lseg_text(&s) {
5158 Some((p1, p2)) => Some(Value::Lseg(p1, p2)),
5159 None => {
5160 return Err(EngineError::Eval(EvalError::TypeMismatch {
5161 detail: alloc::format!("invalid input syntax for type lseg: {s:?}"),
5162 }));
5163 }
5164 },
5165 (Value::Text(s), DataType::PgBox) => match parse_box_text(&s) {
5166 Some((ur, ll)) => Some(Value::PgBox(ur, ll)),
5167 None => {
5168 return Err(EngineError::Eval(EvalError::TypeMismatch {
5169 detail: alloc::format!("invalid input syntax for type box: {s:?}"),
5170 }));
5171 }
5172 },
5173 (Value::Text(s), DataType::Line) => match parse_line_text(&s) {
5174 Some((a, b, c)) => Some(Value::Line { a, b, c }),
5175 None => {
5176 let zero_ab = s
5180 .trim()
5181 .strip_prefix('{')
5182 .and_then(|x| x.strip_suffix('}'))
5183 .map(|inner| inner.split(',').collect::<alloc::vec::Vec<_>>())
5184 .is_some_and(|parts| {
5185 parts.len() == 3
5186 && parts[0].trim().parse::<f64>() == Ok(0.0)
5187 && parts[1].trim().parse::<f64>() == Ok(0.0)
5188 && parts[2].trim().parse::<f64>().is_ok()
5189 });
5190 let detail = if zero_ab {
5191 alloc::string::String::from(
5192 "invalid line specification: A and B cannot both be zero",
5193 )
5194 } else {
5195 alloc::format!("invalid input syntax for type line: {s:?}")
5196 };
5197 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
5198 }
5199 },
5200 (Value::Text(s), DataType::Circle) => match parse_circle_text(&s) {
5201 Some((center, radius)) => Some(Value::Circle { center, radius }),
5202 None => {
5203 return Err(EngineError::Eval(EvalError::TypeMismatch {
5204 detail: alloc::format!("invalid input syntax for type circle: {s:?}"),
5205 }));
5206 }
5207 },
5208 (Value::Text(s), DataType::Path) => match parse_path_text(&s) {
5209 Some((points, closed)) => Some(Value::Path { points, closed }),
5210 None => {
5211 return Err(EngineError::Eval(EvalError::TypeMismatch {
5212 detail: alloc::format!("invalid input syntax for type path: {s:?}"),
5213 }));
5214 }
5215 },
5216 (Value::PgBox(a, b), DataType::Polygon) => {
5219 let (hx, hy) = (a.x.max(b.x), a.y.max(b.y));
5220 let (lx, ly) = (a.x.min(b.x), a.y.min(b.y));
5221 let p = |x: f64, y: f64| spg_storage::Point2D { x, y };
5222 Some(Value::Polygon(alloc::vec![
5223 p(lx, ly),
5224 p(lx, hy),
5225 p(hx, hy),
5226 p(hx, ly),
5227 ]))
5228 }
5229 (Value::Text(s), DataType::Polygon) => match parse_polygon_text(&s) {
5230 Some(points) => Some(Value::Polygon(points)),
5231 None => {
5232 return Err(EngineError::Eval(EvalError::TypeMismatch {
5233 detail: alloc::format!("invalid input syntax for type polygon: {s:?}"),
5234 }));
5235 }
5236 },
5237 (Value::Point(p), DataType::Text) => Some(Value::text(format_point(p))),
5239 (Value::Lseg(p1, p2), DataType::Text) => Some(Value::text(format_lseg(p1, p2))),
5240 (Value::PgBox(ur, ll), DataType::Text) => Some(Value::text(format_pg_box(ur, ll))),
5241 (Value::Line { a, b, c }, DataType::Text) => Some(Value::text(format_line(a, b, c))),
5242 (Value::Circle { center, radius }, DataType::Text) => {
5243 Some(Value::text(format_circle(center, radius)))
5244 }
5245 (Value::Path { points, closed }, DataType::Text) => {
5246 Some(Value::text(format_path(&points, closed)))
5247 }
5248 (Value::Polygon(points), DataType::Text) => Some(Value::text(format_polygon(&points))),
5249 (ref rv @ Value::Range { kind: rk, .. }, DataType::Multirange(kind)) => {
5256 if rk != kind {
5257 return Err(EngineError::Eval(EvalError::TypeMismatch {
5258 detail: alloc::format!(
5259 "cannot cast type {} to {}",
5260 DataType::Range(rk),
5261 DataType::Multirange(kind)
5262 ),
5263 }));
5264 }
5265 crate::eval::binop::range_as_multirange(rv)
5266 }
5267 (Value::Text(s), DataType::Multirange(kind)) => match parse_multirange_str(&s, kind) {
5268 Some(ranges) => Some(Value::Multirange {
5274 kind,
5275 ranges: crate::eval::binop::normalize_multirange_spans(kind, &ranges),
5276 }),
5277 None => {
5278 return Err(EngineError::Eval(EvalError::TypeMismatch {
5279 detail: alloc::format!("invalid input syntax for multirange type: {s:?}"),
5280 }));
5281 }
5282 },
5283 (Value::Multirange { ranges, .. }, DataType::Text) => {
5285 Some(Value::text(format_multirange(&ranges)))
5286 }
5287 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
5289 Some(pairs) => Some(Value::Hstore(pairs)),
5290 None => {
5291 return Err(EngineError::Eval(EvalError::TypeMismatch {
5292 detail: alloc::format!("invalid input syntax for type hstore: {s:?}"),
5293 }));
5294 }
5295 },
5296 (Value::Hstore(pairs), DataType::Text) => Some(Value::text(format_hstore_str(&pairs))),
5298 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
5301 Ok(m) => Some(Value::IntArray2D(m)),
5302 Err(e) => {
5303 return Err(EngineError::Eval(EvalError::TypeMismatch {
5304 detail: alloc::format!("invalid input syntax for INT[][]: {s:?}: {e}"),
5305 }));
5306 }
5307 },
5308 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
5309 Ok(m) => Some(Value::BigIntArray2D(m)),
5310 Err(e) => {
5311 return Err(EngineError::Eval(EvalError::TypeMismatch {
5312 detail: alloc::format!("invalid input syntax for BIGINT[][]: {s:?}: {e}"),
5313 }));
5314 }
5315 },
5316 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
5317 Ok(m) => Some(Value::TextArray2D(m)),
5318 Err(e) => {
5319 return Err(EngineError::Eval(EvalError::TypeMismatch {
5320 detail: alloc::format!("invalid input syntax for TEXT[][]: {s:?}: {e}"),
5321 }));
5322 }
5323 },
5324 (Value::IntArray2D(rows), DataType::Text) => Some(Value::text(format_int_2d_text(&rows))),
5326 (Value::BigIntArray2D(rows), DataType::Text) => {
5327 Some(Value::text(format_bigint_2d_text(&rows)))
5328 }
5329 (Value::TextArray2D(rows), DataType::Text) => Some(Value::text(format_text_2d_text(&rows))),
5330 (Value::Text(s), DataType::TextArray) => {
5335 let arr = decode_text_array_literal(&s).map_err(|_| {
5339 EngineError::Eval(EvalError::TypeMismatch {
5340 detail: malformed_array_literal(&s),
5341 })
5342 })?;
5343 Some(Value::TextArray(arr))
5344 }
5345 (Value::Text(s), DataType::IntArray) => {
5351 let arr = decode_text_array_literal(&s).map_err(|_| {
5355 EngineError::Eval(EvalError::TypeMismatch {
5356 detail: malformed_array_literal(&s),
5357 })
5358 })?;
5359 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
5360 for elem in arr {
5361 match elem {
5362 None => out.push(None),
5363 Some(t) => {
5364 let n: i32 = t.parse().map_err(|_| {
5365 EngineError::Eval(EvalError::TypeMismatch {
5366 detail: alloc::format!(
5367 "invalid input syntax for type integer: {t:?}"
5368 ),
5369 })
5370 })?;
5371 out.push(Some(n));
5372 }
5373 }
5374 }
5375 Some(Value::IntArray(out))
5376 }
5377 (Value::Text(s), DataType::SmallIntArray) => Some(Value::SmallIntArray(
5381 decode_array_elems(&s, DataType::SmallInt, col_name, position)?
5382 .into_iter()
5383 .map(|o| match o {
5384 Some(Value::SmallInt(n)) => Some(n),
5385 _ => None,
5386 })
5387 .collect(),
5388 )),
5389 (Value::Text(s), DataType::BoolArray) => {
5390 if let Some(rows) = crate::eval::values::split_2d_rows(&s) {
5395 let mut row_vals: Vec<Value<'static>> = Vec::with_capacity(rows.len());
5396 for r in &rows {
5397 let bools: Vec<Option<bool>> =
5398 decode_array_elems(r, DataType::Bool, col_name, position)?
5399 .into_iter()
5400 .map(|o| match o {
5401 Some(Value::Bool(b)) => Some(b),
5402 _ => None,
5403 })
5404 .collect();
5405 row_vals.push(Value::BoolArray(bools));
5406 }
5407 return crate::eval::values::build_2d_from_rows(&row_vals).ok_or_else(|| {
5408 EngineError::Eval(EvalError::TypeMismatch {
5409 detail: malformed_array_literal(&s),
5410 })
5411 });
5412 }
5413 Some(Value::BoolArray(
5414 decode_array_elems(&s, DataType::Bool, col_name, position)?
5415 .into_iter()
5416 .map(|o| match o {
5417 Some(Value::Bool(b)) => Some(b),
5418 _ => None,
5419 })
5420 .collect(),
5421 ))
5422 }
5423 (Value::Text(s), DataType::FloatArray) => Some(Value::FloatArray(
5424 decode_array_elems(&s, DataType::Float, col_name, position)?
5425 .into_iter()
5426 .map(|o| match o {
5427 Some(Value::Float(f)) => Some(f),
5428 _ => None,
5429 })
5430 .collect(),
5431 )),
5432 (Value::Text(s), DataType::NumericArray) => Some(Value::NumericArray(
5433 decode_array_elems(
5434 &s,
5435 DataType::Numeric {
5436 precision: 0,
5437 scale: 0,
5438 },
5439 col_name,
5440 position,
5441 )?
5442 .into_iter()
5443 .map(|o| match o {
5444 Some(Value::Numeric { scaled, scale, .. }) => Some((scaled, scale)),
5445 _ => None,
5446 })
5447 .collect(),
5448 )),
5449 (Value::Text(s), DataType::DateArray) => Some(Value::DateArray(
5450 decode_array_elems(&s, DataType::Date, col_name, position)?
5451 .into_iter()
5452 .map(|o| match o {
5453 Some(Value::Date(d)) => Some(d),
5454 _ => None,
5455 })
5456 .collect(),
5457 )),
5458 (Value::Text(s), DataType::UuidArray) => Some(Value::UuidArray(
5459 decode_array_elems(&s, DataType::Uuid, col_name, position)?
5460 .into_iter()
5461 .map(|o| match o {
5462 Some(Value::Uuid(u)) => Some(u),
5463 _ => None,
5464 })
5465 .collect(),
5466 )),
5467 (Value::Text(s), DataType::BigIntArray | DataType::OidArray) => {
5474 let arr = decode_text_array_literal(&s).map_err(|_| {
5478 EngineError::Eval(EvalError::TypeMismatch {
5479 detail: malformed_array_literal(&s),
5480 })
5481 })?;
5482 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
5483 for elem in arr {
5484 match elem {
5485 None => out.push(None),
5486 Some(t) => {
5487 let n: i64 = t.parse().map_err(|_| {
5488 EngineError::Eval(EvalError::TypeMismatch {
5489 detail: alloc::format!(
5490 "invalid input syntax for type bigint: {t:?}"
5491 ),
5492 })
5493 })?;
5494 out.push(Some(n));
5495 }
5496 }
5497 }
5498 Some(Value::BigIntArray(out))
5499 }
5500 (Value::TextArray(items), DataType::Text) => Some(Value::text(encode_text_array(&items))),
5504 (Value::TextArray(items), DataType::BoolArray) if items.is_empty() => {
5512 Some(Value::BoolArray(alloc::vec::Vec::new()))
5513 }
5514 (Value::TextArray(items), DataType::SmallIntArray) if items.is_empty() => {
5515 Some(Value::SmallIntArray(alloc::vec::Vec::new()))
5516 }
5517 (Value::TextArray(items), DataType::IntArray) if items.is_empty() => {
5518 Some(Value::IntArray(alloc::vec::Vec::new()))
5519 }
5520 (Value::TextArray(items), DataType::BigIntArray) if items.is_empty() => {
5521 Some(Value::BigIntArray(alloc::vec::Vec::new()))
5522 }
5523 (Value::TextArray(items), DataType::FloatArray) if items.is_empty() => {
5524 Some(Value::FloatArray(alloc::vec::Vec::new()))
5525 }
5526 (Value::TextArray(items), DataType::FloatArray) => {
5529 let mut out = alloc::vec::Vec::with_capacity(items.len());
5530 let mut ok = true;
5531 for item in items {
5532 match item {
5533 None => out.push(None),
5534 Some(s) => match s.trim().parse::<f64>() {
5535 Ok(x) => out.push(Some(x)),
5536 Err(_) => {
5537 ok = false;
5538 break;
5539 }
5540 },
5541 }
5542 }
5543 if ok {
5544 Some(Value::FloatArray(out))
5545 } else {
5546 None
5547 }
5548 }
5549 (Value::FloatArray(items), DataType::FloatArray) => Some(Value::FloatArray(items)),
5552 #[allow(clippy::cast_precision_loss)]
5553 (Value::IntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5554 items.into_iter().map(|o| o.map(|n| f64::from(n))).collect(),
5555 )),
5556 #[allow(clippy::cast_precision_loss)]
5557 (Value::BigIntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5558 items.into_iter().map(|o| o.map(|n| n as f64)).collect(),
5559 )),
5560 #[allow(clippy::cast_precision_loss)]
5564 (Value::NumericArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5565 items
5566 .into_iter()
5567 .map(|o| {
5568 o.map(|(scaled, scale)| {
5569 crate::eval::format_numeric(scaled, scale)
5570 .parse()
5571 .unwrap_or(f64::NAN)
5572 })
5573 })
5574 .collect(),
5575 )),
5576 (Value::IntArray(items), DataType::BigIntArray) => Some(Value::BigIntArray(
5581 items.into_iter().map(|o| o.map(i64::from)).collect(),
5582 )),
5583 (Value::BigIntArray(items), DataType::IntArray) => {
5584 let mut out = alloc::vec::Vec::with_capacity(items.len());
5585 let mut ok = true;
5586 for o in items {
5587 match o {
5588 None => out.push(None),
5589 Some(n) => match i32::try_from(n) {
5590 Ok(v) => out.push(Some(v)),
5591 Err(_) => {
5592 ok = false;
5593 break;
5594 }
5595 },
5596 }
5597 }
5598 if ok { Some(Value::IntArray(out)) } else { None }
5599 }
5600 (Value::IntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5601 items
5602 .into_iter()
5603 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5604 .collect(),
5605 )),
5606 (Value::BigIntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5607 items
5608 .into_iter()
5609 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5610 .collect(),
5611 )),
5612 (Value::FloatArray(items), DataType::NumericArray) => {
5613 let mut out = alloc::vec::Vec::with_capacity(items.len());
5614 let mut ok = true;
5615 for o in items {
5616 match o {
5617 None => out.push(None),
5618 Some(x) => match parse_numeric_text(&alloc::format!("{x}")) {
5619 Some((mantissa, scale)) => out.push(Some((mantissa, scale))),
5620 None => {
5621 ok = false;
5622 break;
5623 }
5624 },
5625 }
5626 }
5627 if ok {
5628 Some(Value::NumericArray(out))
5629 } else {
5630 None
5631 }
5632 }
5633 (Value::NumericArray(items), DataType::IntArray) => {
5637 let mut out = alloc::vec::Vec::with_capacity(items.len());
5638 let mut ok = true;
5639 for o in items {
5640 match o {
5641 None => out.push(None),
5642 Some((scaled, scale)) => {
5643 match i32::try_from(numeric_round_to_integer(scaled, scale)) {
5644 Ok(v) => out.push(Some(v)),
5645 Err(_) => {
5646 ok = false;
5647 break;
5648 }
5649 }
5650 }
5651 }
5652 }
5653 if ok { Some(Value::IntArray(out)) } else { None }
5654 }
5655 (Value::NumericArray(items), DataType::BigIntArray) => {
5656 let mut out = alloc::vec::Vec::with_capacity(items.len());
5657 let mut ok = true;
5658 for o in items {
5659 match o {
5660 None => out.push(None),
5661 Some((scaled, scale)) => {
5662 match i64::try_from(numeric_round_to_integer(scaled, scale)) {
5663 Ok(v) => out.push(Some(v)),
5664 Err(_) => {
5665 ok = false;
5666 break;
5667 }
5668 }
5669 }
5670 }
5671 }
5672 if ok {
5673 Some(Value::BigIntArray(out))
5674 } else {
5675 None
5676 }
5677 }
5678 #[allow(clippy::cast_possible_truncation)]
5682 (Value::FloatArray(items), DataType::IntArray) => {
5683 let mut out = alloc::vec::Vec::with_capacity(items.len());
5684 let mut ok = true;
5685 for o in items {
5686 match o {
5687 None => out.push(None),
5688 Some(x) if x.is_finite() => {
5689 let r = crate::eval::math::f64_round_half_even(x);
5690 if r >= f64::from(i32::MIN) && r <= f64::from(i32::MAX) {
5691 out.push(Some(r as i32));
5692 } else {
5693 ok = false;
5694 break;
5695 }
5696 }
5697 Some(_) => {
5698 ok = false;
5699 break;
5700 }
5701 }
5702 }
5703 if ok { Some(Value::IntArray(out)) } else { None }
5704 }
5705 #[allow(clippy::cast_possible_truncation)]
5706 (Value::FloatArray(items), DataType::BigIntArray) => {
5707 let mut out = alloc::vec::Vec::with_capacity(items.len());
5708 let mut ok = true;
5709 for o in items {
5710 match o {
5711 None => out.push(None),
5712 Some(x) if x.is_finite() => {
5713 out.push(Some(crate::eval::math::f64_round_half_even(x) as i64));
5714 }
5715 Some(_) => {
5716 ok = false;
5717 break;
5718 }
5719 }
5720 }
5721 if ok {
5722 Some(Value::BigIntArray(out))
5723 } else {
5724 None
5725 }
5726 }
5727 (Value::TextArray(items), DataType::NumericArray) if items.is_empty() => {
5728 Some(Value::NumericArray(alloc::vec::Vec::new()))
5729 }
5730 (Value::TextArray(items), DataType::DateArray) if items.is_empty() => {
5731 Some(Value::DateArray(alloc::vec::Vec::new()))
5732 }
5733 (Value::TextArray(items), DataType::TimestampArray) if items.is_empty() => {
5734 Some(Value::TimestampArray(alloc::vec::Vec::new()))
5735 }
5736 (Value::TextArray(items), DataType::TimestamptzArray) if items.is_empty() => {
5737 Some(Value::TimestamptzArray(alloc::vec::Vec::new()))
5738 }
5739 (Value::TextArray(items), DataType::UuidArray) if items.is_empty() => {
5740 Some(Value::UuidArray(alloc::vec::Vec::new()))
5741 }
5742 (Value::TextArray(items), DataType::JsonArray) if items.is_empty() => {
5743 Some(Value::JsonArray(alloc::vec::Vec::new()))
5744 }
5745 (Value::TextArray(items), DataType::JsonbArray) if items.is_empty() => {
5746 Some(Value::JsonbArray(alloc::vec::Vec::new()))
5747 }
5748 (Value::TextArray(items), DataType::BytesArray) if items.is_empty() => {
5749 Some(Value::BytesArray(alloc::vec::Vec::new()))
5750 }
5751 (Value::TextArray(items), DataType::IntervalArray) if items.is_empty() => {
5752 Some(Value::IntervalArray(alloc::vec::Vec::new()))
5753 }
5754 (
5758 Value::TextArray(items),
5759 dt @ (DataType::BoolArray
5760 | DataType::NumericArray
5761 | DataType::DateArray
5762 | DataType::TimestampArray
5763 | DataType::TimestamptzArray
5764 | DataType::IntervalArray
5765 | DataType::UuidArray),
5766 ) => coerce_text_array_to(items, dt, col_name)?,
5767 (
5773 Value::Text(s),
5774 dt @ (DataType::TimestampArray | DataType::TimestamptzArray | DataType::IntervalArray),
5775 ) => {
5776 let items = decode_text_array_literal(&s).map_err(|_| {
5777 EngineError::Eval(EvalError::TypeMismatch {
5778 detail: malformed_array_literal(&s),
5779 })
5780 })?;
5781 coerce_text_array_to(items, dt, col_name)?
5782 }
5783 (Value::TextArray(items), DataType::MoneyArray) if items.is_empty() => {
5784 Some(Value::MoneyArray(alloc::vec::Vec::new()))
5785 }
5786 (Value::IntArray(items), DataType::SmallIntArray) => {
5791 let mut out = alloc::vec::Vec::with_capacity(items.len());
5792 let mut ok = true;
5793 for item in items {
5794 match item {
5795 None => out.push(None),
5796 Some(n) => match i16::try_from(n) {
5797 Ok(x) => out.push(Some(x)),
5798 Err(_) => {
5799 ok = false;
5800 break;
5801 }
5802 },
5803 }
5804 }
5805 if ok {
5806 Some(Value::SmallIntArray(out))
5807 } else {
5808 None
5809 }
5810 }
5811 (Value::Text(s), DataType::Vector { dim, encoding }) => {
5820 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
5821 EngineError::Eval(EvalError::TypeMismatch {
5822 detail: alloc::format!("cannot parse {s:?} as VECTOR"),
5823 })
5824 })?;
5825 if parsed.len() != dim as usize {
5826 return Err(EngineError::Eval(EvalError::TypeMismatch {
5827 detail: alloc::format!(
5828 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
5829 parsed.len()
5830 ),
5831 }));
5832 }
5833 Some(match encoding {
5834 VecEncoding::F32 => Value::vector(parsed),
5835 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
5836 VecEncoding::F16 => {
5837 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
5838 }
5839 })
5840 }
5841 (Value::Text(s), DataType::TsVector) => {
5851 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
5852 EngineError::Eval(EvalError::TypeMismatch {
5853 detail: alloc::format!("cannot parse {s:?} as TSVECTOR: {e}"),
5854 })
5855 })?;
5856 Some(Value::TsVector(lexs))
5857 }
5858 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
5859 let t = eval::parse_timestamp_literal(&s)
5860 .ok_or_else(|| datetime_parse_error("timestamp", &s))?;
5861 Some(Value::Timestamp(t))
5862 }
5863 (Value::Date(i32::MAX), DataType::Timestamp | DataType::Timestamptz) => {
5866 Some(Value::Timestamp(i64::MAX))
5867 }
5868 (Value::Date(i32::MIN), DataType::Timestamp | DataType::Timestamptz) => {
5869 Some(Value::Timestamp(i64::MIN))
5870 }
5871 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
5872 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
5873 }
5874 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
5878 (Value::Timestamp(t), DataType::Date) => {
5879 let days = t.div_euclid(86_400_000_000);
5880 i32::try_from(days).ok().map(Value::Date)
5881 }
5882 (Value::Timestamp(t), DataType::Time) => Some(Value::Time(t.rem_euclid(86_400_000_000))),
5891 (
5895 Value::NumericBig(b),
5896 DataType::Numeric {
5897 precision: 0,
5898 scale: 0,
5899 },
5900 ) => Some(Value::NumericBig(b)),
5901 (
5902 Value::Numeric {
5903 scaled,
5904 scale: src_scale,
5905 ..
5906 },
5907 DataType::Numeric { precision, scale },
5908 ) => {
5909 if precision == 0 && scale == 0 {
5915 Some(Value::Numeric {
5916 scaled,
5917 scale: src_scale,
5918 kind: spg_storage::NumericKind::Finite,
5919 })
5920 } else {
5921 Some(numeric_rescale(
5922 scaled, src_scale, precision, scale, col_name,
5923 )?)
5924 }
5925 }
5926 (Value::NumericBig(b), DataType::Numeric { precision, scale }) => {
5931 if precision == 0 && scale == 0 {
5932 Some(Value::NumericBig(b))
5933 } else {
5934 #[allow(clippy::cast_sign_loss)]
5935 let rounded = if scale < 0 {
5936 b.round_to(0)
5938 } else {
5939 b.round_to(scale as u16)
5940 };
5941 let out = crate::eval::binop::bignum_to_value(rounded);
5942 crate::numeric::check_precision_text(&out, precision, scale, col_name)?;
5945 Some(out)
5946 }
5947 }
5948 #[allow(clippy::cast_precision_loss)]
5949 (Value::Numeric { scaled, scale, .. }, DataType::Float) => {
5950 let text = crate::eval::format_numeric(scaled, scale);
5957 let x: f64 = text.parse().unwrap_or(f64::NAN);
5958 if x == 0.0 && scaled != 0 {
5962 return Err(float_out_of_range(
5963 &crate::eval::format_numeric(scaled, scale),
5964 "double precision",
5965 ));
5966 }
5967 Some(Value::Float(x))
5968 }
5969 (Value::NumericBig(b), DataType::Real) => {
5977 let text = b.to_decimal_str();
5978 let x: f32 = text.parse().map_err(|_| real_out_of_range(&text))?;
5979 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
5980 return Err(real_out_of_range(&text));
5981 }
5982 Some(Value::Real(x))
5983 }
5984 (Value::NumericBig(b), DataType::Float) => {
5985 let text = b.to_decimal_str();
5989 let x: f64 = text
5990 .parse()
5991 .map_err(|_| float_out_of_range(&text, "double precision"))?;
5992 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
5993 return Err(float_out_of_range(&text, "double precision"));
5994 }
5995 Some(Value::Float(x))
5996 }
5997 (Value::Float(x), DataType::Int) => {
6005 let r = crate::eval::math::f64_round_half_even(x);
6006 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
6007 return Err(EngineError::Eval(EvalError::TypeMismatch {
6008 detail: "integer out of range".into(),
6009 }));
6010 }
6011 #[allow(clippy::cast_possible_truncation)]
6012 Some(Value::Int(r as i32))
6013 }
6014 (Value::Float(x), DataType::BigInt) => {
6015 let r = crate::eval::math::f64_round_half_even(x);
6016 if !r.is_finite()
6017 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
6018 {
6019 return Err(EngineError::Eval(EvalError::TypeMismatch {
6020 detail: "bigint out of range".into(),
6021 }));
6022 }
6023 #[allow(clippy::cast_possible_truncation)]
6024 Some(Value::BigInt(r as i64))
6025 }
6026 (Value::Float(x), DataType::SmallInt) => {
6027 let r = crate::eval::math::f64_round_half_even(x);
6028 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
6029 return Err(EngineError::Eval(EvalError::TypeMismatch {
6030 detail: "smallint out of range".into(),
6031 }));
6032 }
6033 #[allow(clippy::cast_possible_truncation)]
6034 Some(Value::SmallInt(r as i16))
6035 }
6036 (Value::Real(x), DataType::Int) => {
6040 let r = crate::eval::math::f64_round_half_even(f64::from(x));
6041 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
6042 return Err(EngineError::Eval(EvalError::TypeMismatch {
6043 detail: "integer out of range".into(),
6044 }));
6045 }
6046 #[allow(clippy::cast_possible_truncation)]
6047 Some(Value::Int(r as i32))
6048 }
6049 (Value::Real(x), DataType::BigInt) => {
6050 let r = crate::eval::math::f64_round_half_even(f64::from(x));
6051 if !r.is_finite()
6052 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
6053 {
6054 return Err(EngineError::Eval(EvalError::TypeMismatch {
6055 detail: "bigint out of range".into(),
6056 }));
6057 }
6058 #[allow(clippy::cast_possible_truncation)]
6059 Some(Value::BigInt(r as i64))
6060 }
6061 (Value::Real(x), DataType::SmallInt) => {
6062 let r = crate::eval::math::f64_round_half_even(f64::from(x));
6063 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
6064 return Err(EngineError::Eval(EvalError::TypeMismatch {
6065 detail: "smallint out of range".into(),
6066 }));
6067 }
6068 #[allow(clippy::cast_possible_truncation)]
6069 Some(Value::SmallInt(r as i16))
6070 }
6071 (Value::Numeric { scaled, scale, .. }, DataType::Int) => {
6072 let rounded = numeric_round_to_integer(scaled, scale);
6073 i32::try_from(rounded).ok().map(Value::Int)
6074 }
6075 (Value::Numeric { scaled, scale, .. }, DataType::BigInt) => {
6076 let rounded = numeric_round_to_integer(scaled, scale);
6077 i64::try_from(rounded).ok().map(Value::BigInt)
6078 }
6079 (Value::Numeric { scaled, scale, .. }, DataType::SmallInt) => {
6080 let rounded = numeric_round_to_integer(scaled, scale);
6081 i16::try_from(rounded).ok().map(Value::SmallInt)
6082 }
6083 (Value::Text(s), DataType::Name) => {
6090 let mut cut = s.into_owned();
6091 if cut.len() > 63 {
6092 let mut idx = 63;
6093 while !cut.is_char_boundary(idx) {
6094 idx -= 1;
6095 }
6096 cut.truncate(idx);
6097 }
6098 Some(Value::text(cut))
6099 }
6100 (Value::Text(s), DataType::Varchar(max)) => {
6101 if max == 0 || u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
6102 Some(Value::text(s))
6103 } else {
6104 let excess_all_blanks = s.chars().skip(max as usize).all(|c| c == ' ');
6109 if excess_all_blanks {
6110 Some(Value::text(
6111 s.chars()
6112 .take(max as usize)
6113 .collect::<alloc::string::String>(),
6114 ))
6115 } else {
6116 return Err(EngineError::Unsupported(alloc::format!(
6117 "value too long for type character varying({max})"
6118 )));
6119 }
6120 }
6121 }
6122 (
6130 Value::Vector(v),
6131 DataType::Vector {
6132 dim,
6133 encoding: VecEncoding::Sq8,
6134 },
6135 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
6136 (
6141 Value::Vector(v),
6142 DataType::Vector {
6143 dim,
6144 encoding: VecEncoding::F16,
6145 },
6146 ) if v.len() == dim as usize => Some(Value::HalfVector(
6147 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
6148 )),
6149 (Value::Text(s), DataType::Char(size)) => {
6153 if size == 0 {
6157 return Ok(Value::BpChar(alloc::borrow::Cow::Owned(
6158 s.trim_end_matches(' ').to_string(),
6159 )));
6160 }
6161 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
6162 let body = if len > size {
6163 let trimmed = s.trim_end_matches(' ');
6164 let tlen = u32::try_from(trimmed.chars().count()).unwrap_or(u32::MAX);
6165 if tlen > size {
6166 return Err(EngineError::Unsupported(alloc::format!(
6167 "value too long for type character({size})"
6168 )));
6169 }
6170 trimmed.to_string()
6171 } else {
6172 s.into_owned()
6173 };
6174 let need = (size as usize) - body.chars().count();
6175 let mut padded = body;
6176 padded.reserve(need);
6177 for _ in 0..need {
6178 padded.push(' ');
6179 }
6180 Some(Value::BpChar(alloc::borrow::Cow::Owned(padded)))
6184 }
6185 _ => None,
6186 };
6187 coerced.ok_or_else(|| {
6188 EngineError::Storage(StorageError::TypeMismatch {
6189 column: col_name.into(),
6190 expected,
6191 actual,
6192 position,
6193 })
6194 })
6195}
6196
6197pub(crate) fn big_literal_to_value(s: &str) -> Value<'static> {
6200 let b = spg_storage::bignum::BigNumeric::from_decimal_str(s).expect("lexer-validated decimal");
6201 match b.to_i128() {
6202 Some(scaled) => Value::Numeric {
6203 scaled,
6204 scale: b.scale(),
6205 kind: spg_storage::NumericKind::Finite,
6206 },
6207 None => Value::NumericBig(alloc::boxed::Box::new(b)),
6208 }
6209}
6210
6211pub(crate) fn types_unify(a: DataType, b: DataType) -> bool {
6220 fn category(t: DataType) -> Option<u8> {
6221 Some(match t {
6222 DataType::SmallInt
6223 | DataType::Int
6224 | DataType::BigInt
6225 | DataType::Numeric { .. }
6226 | DataType::Real
6227 | DataType::Float => 1,
6228 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => 2,
6229 DataType::Date | DataType::Timestamp | DataType::Timestamptz => 3,
6230 _ => return None,
6231 })
6232 }
6233 if a == b {
6234 return true;
6235 }
6236 match (category(a), category(b)) {
6237 (Some(x), Some(y)) => x == y,
6238 _ => false,
6241 }
6242}
6243
6244pub(crate) fn pg_type_name_for_error_opt(t: Option<DataType>) -> alloc::string::String {
6258 match t {
6259 Some(t) => pg_type_name_for_error(t),
6260 None => alloc::string::String::from("unknown"),
6261 }
6262}
6263
6264pub(crate) fn pg_type_name_for_error(t: DataType) -> alloc::string::String {
6265 use spg_storage::DataType as D;
6266 let elem = match t {
6267 D::TextArray => Some(D::Text),
6268 D::IntArray => Some(D::Int),
6269 D::BigIntArray => Some(D::BigInt),
6270 D::SmallIntArray => Some(D::SmallInt),
6271 D::FloatArray => Some(D::Float),
6272 D::NumericArray => Some(D::Numeric {
6273 precision: 0,
6274 scale: 0,
6275 }),
6276 D::BoolArray => Some(D::Bool),
6277 D::DateArray => Some(D::Date),
6278 D::TimestampArray => Some(D::Timestamp),
6279 D::TimestamptzArray => Some(D::Timestamptz),
6280 D::IntervalArray => Some(D::Interval),
6281 D::UuidArray => Some(D::Uuid),
6282 D::JsonArray | D::JsonbArray => Some(D::Jsonb),
6283 D::BytesArray => Some(D::Bytes),
6284 D::MoneyArray => Some(D::Money),
6285 _ => None,
6286 };
6287 match elem {
6288 Some(e) => alloc::format!("{}[]", crate::system_catalog::pg_data_type_text(e)),
6289 None => crate::system_catalog::pg_data_type_text(t),
6290 }
6291}