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_error(
3089 before: &Value<'_>,
3090 after: &Value<'_>,
3091 schema: &ColumnSchema,
3092 row: usize,
3093 omitted: bool,
3094) -> Option<(u16, &'static str, alloc::string::String)> {
3095 let w = mysql_fit_warning(before, after, schema, row, omitted)?;
3096 let col = &schema.name;
3097 Some(match w.code {
3098 1265 if matches!(
3101 schema.ty,
3102 DataType::Varchar(_) | DataType::Char(_) | DataType::Text
3103 ) =>
3104 {
3105 (
3106 1406,
3107 "22001",
3108 alloc::format!("Data too long for column '{col}' at row {row}"),
3109 )
3110 }
3111 1265 => (1265, "01000", w.message),
3112 1264 => (1264, "22003", w.message),
3113 1366 => (1366, "HY000", w.message),
3114 1364 => (1364, "HY000", w.message),
3115 other => (other, "HY000", w.message),
3116 })
3117}
3118
3119pub(crate) fn mysql_fit_warning(
3120 before: &Value<'_>,
3121 after: &Value<'_>,
3122 schema: &ColumnSchema,
3123 row: usize,
3124 omitted: bool,
3125) -> Option<crate::MysqlWarning> {
3126 if before == after {
3127 return None;
3128 }
3129 let col = &schema.name;
3130 if omitted || before.is_null() {
3133 return Some(crate::MysqlWarning {
3134 level: "Warning",
3135 code: 1364,
3136 message: alloc::format!("Field '{col}' doesn't have a default value"),
3137 });
3138 }
3139 let numeric_col = matches!(
3143 schema.ty,
3144 DataType::SmallInt
3145 | DataType::Int
3146 | DataType::BigInt
3147 | DataType::Float
3148 | DataType::Real
3149 | DataType::Numeric { .. }
3150 );
3151 if numeric_col {
3152 let noun = match schema.ty {
3164 DataType::Numeric { .. } => "decimal",
3165 DataType::Real => "FLOAT",
3166 DataType::Float => "DOUBLE",
3167 _ => "integer",
3168 };
3169 return Some(if matches!(before, Value::Text(_) | Value::BpChar(_)) {
3172 crate::MysqlWarning {
3173 level: "Warning",
3174 code: 1366,
3175 message: alloc::format!(
3176 "Incorrect {noun} value: '{}' for column '{col}' at row {row}",
3177 crate::eval::value_to_text(before)
3178 ),
3179 }
3180 } else {
3181 crate::MysqlWarning {
3182 level: "Warning",
3183 code: 1264,
3184 message: alloc::format!("Out of range value for column '{col}' at row {row}"),
3185 }
3186 });
3187 }
3188 Some(crate::MysqlWarning {
3189 level: "Warning",
3190 code: 1265,
3191 message: alloc::format!("Data truncated for column '{col}' at row {row}"),
3192 })
3193}
3194
3195fn numeric_untouched(v: Value<'static>, _schema: &ColumnSchema) -> Value<'static> {
3198 v
3199}
3200
3201fn restate_scaled(scaled: i128, from: u16, to: u16) -> i128 {
3205 if from == to {
3206 return scaled;
3207 }
3208 if to > from {
3209 let f = 10i128.checked_pow(u32::from(to - from)).unwrap_or(1);
3210 return scaled.saturating_mul(f);
3211 }
3212 let f = 10i128.checked_pow(u32::from(from - to)).unwrap_or(1);
3213 if f == 0 {
3214 return scaled;
3215 }
3216 let half = f / 2;
3217 if scaled >= 0 {
3218 (scaled + half) / f
3219 } else {
3220 (scaled - half) / f
3221 }
3222}
3223
3224pub(crate) fn mysql_ignore_fit(v: Value<'static>, schema: &ColumnSchema) -> Value<'static> {
3225 if v.is_null() {
3226 if schema.nullable {
3227 return v;
3228 }
3229 return match schema.ty {
3231 DataType::SmallInt | DataType::Int | DataType::BigInt => Value::BigInt(0),
3232 DataType::Float | DataType::Real => Value::Float(0.0),
3233 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(""),
3234 _ => v,
3235 };
3236 }
3237 if let Value::Text(ref s) = v
3240 && matches!(
3241 schema.ty,
3242 DataType::SmallInt | DataType::Int | DataType::BigInt
3243 )
3244 && s.trim().parse::<i64>().is_err()
3245 {
3246 return Value::BigInt(leading_numeric_prefix(s));
3247 }
3248 if let DataType::Numeric { precision, scale } = schema.ty
3266 && precision != 0
3267 && scale >= 0
3268 {
3269 let col_scale = u16::try_from(scale).unwrap_or(0);
3270 let (scaled, val_scale) = match v {
3271 Value::Numeric {
3272 scaled,
3273 scale: vs,
3274 kind: spg_storage::NumericKind::Finite,
3275 } => (scaled, vs),
3276 Value::SmallInt(n) => (i128::from(n), 0),
3277 Value::Int(n) => (i128::from(n), 0),
3278 Value::BigInt(n) => (i128::from(n), 0),
3279 _ => return numeric_untouched(v, schema),
3280 };
3281 let restated = restate_scaled(scaled, val_scale, col_scale);
3295 let limit = 10i128
3296 .checked_pow(u32::from(precision))
3297 .map_or(i128::MAX, |p| p - 1);
3298 if restated < -limit || restated > limit {
3299 return Value::numeric(restated.clamp(-limit, limit), col_scale);
3300 }
3301 return numeric_untouched(v, schema);
3302 }
3303 let as_int = match v {
3305 Value::SmallInt(n) => Some(i128::from(n)),
3306 Value::Int(n) => Some(i128::from(n)),
3307 Value::BigInt(n) => Some(i128::from(n)),
3308 Value::Numeric {
3310 scaled, scale: 0, ..
3311 } => Some(scaled),
3312 _ => None,
3313 };
3314 if let Some(n) = as_int
3315 && let Some((lo, hi)) = column_int_bounds(schema)
3316 && (n < lo || n > hi)
3317 {
3318 return int_value_for_column(n.clamp(lo, hi));
3319 }
3320 if let Value::Text(ref s) = v {
3322 let max = match schema.ty {
3323 DataType::Varchar(m) | DataType::Char(m) if m > 0 => m as usize,
3324 _ => return v,
3325 };
3326 if s.chars().count() > max {
3327 return Value::text(s.chars().take(max).collect::<alloc::string::String>());
3328 }
3329 }
3330 v
3331}
3332
3333fn leading_numeric_prefix(s: &str) -> i64 {
3342 let t = s.trim_start();
3343 let b = t.as_bytes();
3344 let mut i = 0;
3345 if i < b.len() && (b[i] == b'-' || b[i] == b'+') {
3346 i += 1;
3347 }
3348 let int_start = i;
3349 while i < b.len() && b[i].is_ascii_digit() {
3350 i += 1;
3351 }
3352 let mut end = i;
3353 if i < b.len() && b[i] == b'.' {
3354 i += 1;
3355 while i < b.len() && b[i].is_ascii_digit() {
3356 i += 1;
3357 }
3358 if i > int_start + 1 {
3361 end = i;
3362 }
3363 }
3364 if end > int_start && i < b.len() && (b[i] == b'e' || b[i] == b'E') {
3366 let mut j = i + 1;
3367 if j < b.len() && (b[j] == b'-' || b[j] == b'+') {
3368 j += 1;
3369 }
3370 let digits_start = j;
3371 while j < b.len() && b[j].is_ascii_digit() {
3372 j += 1;
3373 }
3374 if j > digits_start {
3375 end = j;
3376 }
3377 }
3378 let Ok(f) = t[..end].parse::<f64>() else {
3379 return 0;
3380 };
3381 let r = f.round();
3383 if r >= i64::MAX as f64 {
3384 i64::MAX
3385 } else if r <= i64::MIN as f64 {
3386 i64::MIN
3387 } else {
3388 r as i64
3389 }
3390}
3391
3392fn int_value_for_column(n: i128) -> Value<'static> {
3396 match i64::try_from(n) {
3397 Ok(v) => Value::BigInt(v),
3398 Err(_) => Value::numeric(n, 0),
3399 }
3400}
3401
3402pub(crate) fn check_unsigned_range(
3403 v: &Value,
3404 schema: &ColumnSchema,
3405 position: usize,
3406) -> Result<(), EngineError> {
3407 let n: i128 = match v {
3408 Value::SmallInt(x) => i128::from(*x),
3409 Value::Int(x) => i128::from(*x),
3410 Value::BigInt(x) => i128::from(*x),
3411 Value::Numeric { scaled, scale, .. } if *scale == 0 => *scaled,
3414 _ => return Ok(()), };
3416 if let Some(width) = schema.mysql_int_width {
3420 let _ = width;
3426 let (lo, hi) = column_int_bounds(schema).unwrap_or((i128::MIN, i128::MAX));
3427 if n < lo || n > hi {
3428 return Err(EngineError::Unsupported(alloc::format!(
3431 "Out of range value for column '{}'",
3432 schema.name
3433 )));
3434 }
3435 return Ok(());
3436 }
3437 if schema.is_unsigned && n < 0 {
3439 return Err(EngineError::Unsupported(alloc::format!(
3440 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
3441 schema.name
3442 )));
3443 }
3444 Ok(())
3445}
3446
3447fn coerce_text_array_to(
3453 items: alloc::vec::Vec<Option<alloc::string::String>>,
3454 target: DataType,
3455 col: &str,
3456) -> Result<Option<Value<'static>>, EngineError> {
3457 let elem_dt = match target {
3458 DataType::BoolArray => DataType::Bool,
3459 DataType::NumericArray => DataType::Numeric {
3460 precision: 0,
3461 scale: 0,
3462 },
3463 DataType::DateArray => DataType::Date,
3464 DataType::TimestampArray => DataType::Timestamp,
3465 DataType::TimestamptzArray => DataType::Timestamptz,
3466 DataType::UuidArray => DataType::Uuid,
3467 DataType::IntervalArray => DataType::Interval,
3470 _ => return Ok(None),
3471 };
3472 let mut scal: alloc::vec::Vec<Option<Value<'static>>> =
3473 alloc::vec::Vec::with_capacity(items.len());
3474 for item in items {
3475 match item {
3476 None => scal.push(None),
3477 Some(s) => scal.push(Some(coerce_value(Value::text(s), elem_dt, col, 0)?)),
3478 }
3479 }
3480 let out = match target {
3481 DataType::BoolArray => Value::BoolArray(
3482 scal.into_iter()
3483 .map(|o| o.map(|v| matches!(v, Value::Bool(true))))
3484 .collect(),
3485 ),
3486 DataType::NumericArray => Value::NumericArray(
3487 scal.into_iter()
3488 .map(|o| {
3489 o.map(|v| match v {
3490 Value::Numeric { scaled, scale, .. } => (scaled, scale),
3491 _ => (0, 0),
3492 })
3493 })
3494 .collect(),
3495 ),
3496 DataType::DateArray => Value::DateArray(
3497 scal.into_iter()
3498 .map(|o| {
3499 o.map(|v| match v {
3500 Value::Date(d) => d,
3501 _ => 0,
3502 })
3503 })
3504 .collect(),
3505 ),
3506 DataType::TimestampArray => Value::TimestampArray(
3507 scal.into_iter()
3508 .map(|o| {
3509 o.map(|v| match v {
3510 Value::Timestamp(t) => t,
3511 _ => 0,
3512 })
3513 })
3514 .collect(),
3515 ),
3516 DataType::TimestamptzArray => Value::TimestamptzArray(
3517 scal.into_iter()
3518 .map(|o| {
3519 o.map(|v| match v {
3520 Value::Timestamp(t) => t,
3521 _ => 0,
3522 })
3523 })
3524 .collect(),
3525 ),
3526 DataType::UuidArray => Value::UuidArray(
3527 scal.into_iter()
3528 .map(|o| {
3529 o.map(|v| match v {
3530 Value::Uuid(u) => u,
3531 _ => [0u8; 16],
3532 })
3533 })
3534 .collect(),
3535 ),
3536 DataType::IntervalArray => Value::IntervalArray(
3537 scal.into_iter()
3538 .map(|o| {
3539 o.and_then(|v| match v {
3540 Value::Interval {
3541 months,
3542 days,
3543 micros,
3544 kind,
3545 } => Some(spg_storage::IntervalSpan {
3546 months,
3547 days,
3548 micros,
3549 kind,
3550 }),
3551 _ => None,
3552 })
3553 })
3554 .collect(),
3555 ),
3556 _ => return Ok(None),
3557 };
3558 Ok(Some(out))
3559}
3560
3561pub(crate) fn array_oid_element(oid: i64) -> Option<i64> {
3572 Some(match oid {
3573 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,
3607 })
3608}
3609
3610pub(crate) fn regtype_oid_to_name_owned(oid: i64) -> Option<alloc::string::String> {
3617 if let Some(scalar) = regtype_oid_to_name(oid) {
3618 return Some(alloc::string::String::from(scalar));
3619 }
3620 let (_, _, elem) = crate::system_catalog::ARRAY_TYPE_OIDS
3621 .iter()
3622 .find(|(arr, _, _)| *arr == oid)?;
3623 Some(alloc::format!("{}[]", regtype_oid_to_name(*elem)?))
3624}
3625
3626pub(crate) fn array_oid_for_element(elem: i64) -> Option<i64> {
3628 crate::system_catalog::ARRAY_TYPE_OIDS
3629 .iter()
3630 .find(|(_, _, e)| *e == elem)
3631 .map(|(arr, _, _)| *arr)
3632}
3633
3634pub(crate) fn regtype_oid_to_name(oid: i64) -> Option<&'static str> {
3635 Some(match oid {
3636 4600 => "pg_brin_bloom_summary",
3637 16 => "boolean",
3638 17 => "bytea",
3639 18 => "\"char\"",
3640 19 => "name",
3641 20 => "bigint",
3642 21 => "smallint",
3643 23 => "integer",
3644 25 => "text",
3645 26 => "oid",
3646 27 => "tid",
3648 28 => "xid",
3649 29 => "cid",
3650 5069 => "xid8",
3651 114 => "json",
3652 142 => "xml",
3653 650 => "cidr",
3654 700 => "real",
3655 701 => "double precision",
3656 774 => "macaddr8",
3657 790 => "money",
3658 829 => "macaddr",
3659 869 => "inet",
3660 1042 => "character",
3661 1043 => "character varying",
3662 1082 => "date",
3663 1083 => "time without time zone",
3664 1114 => "timestamp without time zone",
3665 1184 => "timestamp with time zone",
3666 1186 => "interval",
3667 1266 => "time with time zone",
3668 1560 => "bit",
3669 1562 => "bit varying",
3670 1700 => "numeric",
3671 2950 => "uuid",
3672 3614 => "tsvector",
3673 3615 => "tsquery",
3674 3802 => "jsonb",
3675 3904 => "int4range",
3676 3906 => "numrange",
3677 3908 => "tsrange",
3678 3910 => "tstzrange",
3679 3912 => "daterange",
3680 3926 => "int8range",
3681 _ => return None,
3682 })
3683}
3684
3685pub(crate) fn parse_pg_int(s: &str) -> Option<i64> {
3686 let s = s.trim();
3687 let (neg, rest) = if let Some(r) = s.strip_prefix('-') {
3688 (true, r)
3689 } else if let Some(r) = s.strip_prefix('+') {
3690 (false, r)
3691 } else {
3692 (false, s)
3693 };
3694 let (radix, digits, has_prefix) =
3699 if let Some(h) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
3700 (16u32, h, true)
3701 } else if let Some(o) = rest.strip_prefix("0o").or_else(|| rest.strip_prefix("0O")) {
3702 (8, o, true)
3703 } else if let Some(b) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) {
3704 (2, b, true)
3705 } else {
3706 (10, rest, false)
3707 };
3708 let db = digits.as_bytes();
3709 if db.last() == Some(&b'_')
3713 || digits.contains("__")
3714 || (!has_prefix && db.first() == Some(&b'_'))
3715 {
3716 return None;
3717 }
3718 let cleaned: alloc::string::String = digits.chars().filter(|&c| c != '_').collect();
3719 if cleaned.is_empty() {
3720 return None;
3721 }
3722 let mag = i64::from_str_radix(&cleaned, radix).ok()?;
3723 Some(if neg { mag.checked_neg()? } else { mag })
3724}
3725
3726fn xml_content_is_well_formed(s: &str) -> bool {
3735 let b = s.as_bytes();
3736 let is_name =
3737 |c: u8| c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_' | b'.' | b':') || c >= 0x80;
3738 let mut stack: alloc::vec::Vec<&[u8]> = alloc::vec::Vec::new();
3739 let mut i = 0;
3740 while i < b.len() {
3741 if b[i] != b'<' {
3742 i += 1;
3743 continue;
3744 }
3745 let rest = &s[i..];
3746 if rest.starts_with("<!--") {
3747 match rest.find("-->") {
3748 Some(p) => i += p + 3,
3749 None => return false,
3750 }
3751 } else if rest.starts_with("<![CDATA[") {
3752 match rest.find("]]>") {
3753 Some(p) => i += p + 3,
3754 None => return false,
3755 }
3756 } else if rest.starts_with("<?") {
3757 match rest.find("?>") {
3758 Some(p) => i += p + 2,
3759 None => return false,
3760 }
3761 } else if rest.starts_with("<!") {
3762 match rest.find('>') {
3763 Some(p) => i += p + 1,
3764 None => return false,
3765 }
3766 } else {
3767 let close = i + 1 < b.len() && b[i + 1] == b'/';
3769 let name_start = if close { i + 2 } else { i + 1 };
3770 let mut j = name_start;
3771 while j < b.len() && is_name(b[j]) {
3772 j += 1;
3773 }
3774 if j == name_start {
3775 return false; }
3777 let name = &b[name_start..j];
3778 let mut k = j;
3780 let mut quote = 0u8;
3781 let mut prev = 0u8;
3782 loop {
3783 if k >= b.len() {
3784 return false; }
3786 let c = b[k];
3787 if quote != 0 {
3788 if c == quote {
3789 quote = 0;
3790 }
3791 } else if c == b'"' || c == b'\'' {
3792 quote = c;
3793 } else if c == b'>' {
3794 break;
3795 }
3796 prev = c;
3797 k += 1;
3798 }
3799 let self_closing = prev == b'/';
3800 i = k + 1;
3801 if close {
3802 match stack.pop() {
3803 Some(top) if top == name => {}
3804 _ => return false,
3805 }
3806 } else if !self_closing {
3807 stack.push(name);
3808 }
3809 }
3810 }
3811 stack.is_empty()
3812}
3813
3814pub(crate) fn parse_float8(s: &str) -> Option<f64> {
3820 let t = s.trim();
3821 let parsed = t.parse::<f64>().ok()?;
3822 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3823 let numeric_looking = body
3824 .bytes()
3825 .next()
3826 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
3827 if numeric_looking {
3828 if parsed.is_infinite() {
3829 return None; }
3831 if parsed == 0.0 {
3832 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
3834 if mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0') {
3835 return None;
3836 }
3837 }
3838 }
3839 Some(parsed)
3840}
3841
3842fn decode_array_elems(
3846 s: &str,
3847 elem: DataType,
3848 col_name: &str,
3849 position: usize,
3850) -> Result<Vec<Option<Value<'static>>>, EngineError> {
3851 let raw = decode_text_array_literal(s).map_err(|_| {
3857 EngineError::Eval(EvalError::TypeMismatch {
3858 detail: malformed_array_literal(s),
3859 })
3860 })?;
3861 let mut out = Vec::with_capacity(raw.len());
3862 for e in raw {
3863 match e {
3864 None => out.push(None),
3865 Some(t) => out.push(Some(coerce_value(
3866 Value::text(t),
3867 elem,
3868 col_name,
3869 position,
3870 )?)),
3871 }
3872 }
3873 Ok(out)
3874}
3875
3876fn coerce_untyped_value(
3880 v: Value<'static>,
3881 expected: DataType,
3882 col_name: &str,
3883 position: usize,
3884) -> Result<Value<'static>, EngineError> {
3885 match (&v, expected) {
3886 (
3897 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3898 DataType::BigInt | DataType::Oid,
3899 ) => Ok(Value::BigInt(*oid)),
3900 (
3901 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3902 DataType::Int,
3903 ) => Ok(Value::Int(i32::try_from(*oid).unwrap_or(i32::MAX))),
3904 (
3905 Value::RegClass(_, name) | Value::RegProc(_, name) | Value::RegType(_, name),
3906 DataType::Text,
3907 ) => Ok(Value::text(alloc::string::String::from(name.as_ref()))),
3908 (Value::Composite(fields), DataType::Jsonb | DataType::Json) => {
3914 let mut obj = alloc::string::String::from("{");
3915 for (i, (name, val)) in fields.iter().enumerate() {
3916 if i > 0 {
3917 obj.push(',');
3918 }
3919 obj.push_str(&crate::json::value_to_json_text(&Value::text(
3921 alloc::string::String::from(name.as_str()),
3922 )));
3923 obj.push(':');
3924 obj.push_str(&crate::json::value_to_json_text(val));
3925 }
3926 obj.push('}');
3927 Ok(Value::Json(alloc::borrow::Cow::Owned(obj)))
3928 }
3929 (Value::Composite(_), DataType::Text) => Ok(Value::text(crate::eval::value_to_text(&v))),
3931 _ => Err(EngineError::Unsupported(alloc::format!(
3932 "cannot coerce {:?} to {expected:?} for column {col_name:?} (position {position})",
3933 v
3934 ))),
3935 }
3936}
3937
3938fn invalid_input_syntax(ty: &str, value: &str) -> EngineError {
3942 EngineError::Eval(EvalError::TypeMismatch {
3943 detail: alloc::format!("invalid input syntax for type {ty}: \"{value}\""),
3944 })
3945}
3946
3947fn real_out_of_range(value: &str) -> EngineError {
3950 float_out_of_range(value, "real")
3951}
3952
3953fn float_out_of_range(value: &str, ty: &str) -> EngineError {
3955 EngineError::Eval(EvalError::TypeMismatch {
3956 detail: alloc::format!("\"{value}\" is out of range for type {ty}"),
3957 })
3958}
3959
3960fn float_text_error(s: &str, ty: &str) -> EngineError {
3966 let t = s.trim();
3967 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3968 let numeric_looking = body
3969 .bytes()
3970 .next()
3971 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
3972 if numeric_looking && t.parse::<f64>().is_ok() {
3973 float_out_of_range(t, ty)
3974 } else {
3975 invalid_input_syntax(ty, s)
3976 }
3977}
3978
3979fn float_text_is_nonzero(t: &str) -> bool {
3983 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3984 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
3985 mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0')
3986}
3987
3988fn text_is_explicit_infinity(t: &str) -> bool {
3991 let t = t.trim_start_matches(['+', '-']);
3992 t.eq_ignore_ascii_case("inf") || t.eq_ignore_ascii_case("infinity")
3993}
3994
3995fn datetime_parse_error(ty: &str, s: &str) -> EngineError {
4004 let t = s.trim();
4005 let date_shaped = t.chars().any(|c| c.is_ascii_digit())
4006 && t.chars().all(|c| {
4007 c.is_ascii_digit() || matches!(c, '-' | '/' | ':' | '.' | ' ' | '+' | 'T' | 't')
4008 });
4009 let detail = if date_shaped {
4010 alloc::format!("date/time field value out of range: \"{t}\"")
4011 } else {
4012 alloc::format!("invalid input syntax for type {ty}: \"{t}\"")
4013 };
4014 EngineError::Eval(EvalError::TypeMismatch { detail })
4015}
4016
4017pub(crate) enum JsonbScalar {
4023 Numeric(Value<'static>),
4024 Bool(bool),
4025 Null,
4026}
4027
4028pub(crate) fn jsonb_cast_type_error(kind: &str, target: &str) -> EvalError {
4030 EvalError::TypeMismatch {
4031 detail: alloc::format!("cannot cast jsonb {kind} to type {target}"),
4032 }
4033}
4034
4035pub(crate) fn jsonb_scalar_for_cast(s: &str, target: &str) -> Result<JsonbScalar, EvalError> {
4038 use crate::json::JsonValue;
4039 match crate::json::parse(s) {
4040 Ok(JsonValue::Null) => Ok(JsonbScalar::Null),
4041 Ok(JsonValue::Bool(b)) => Ok(JsonbScalar::Bool(b)),
4042 Ok(JsonValue::Number(x)) => {
4046 let num = coerce_value(
4047 Value::text(alloc::format!("{x}")),
4048 DataType::Numeric {
4049 precision: 0,
4050 scale: 0,
4051 },
4052 "",
4053 0,
4054 )
4055 .map_err(|e| match e {
4056 EngineError::Eval(ev) => ev,
4057 _ => jsonb_cast_type_error("numeric", target),
4058 })?;
4059 Ok(JsonbScalar::Numeric(num))
4060 }
4061 Ok(JsonValue::NumberText(text)) => {
4062 let num = coerce_value(
4063 Value::text(text),
4064 DataType::Numeric {
4065 precision: 0,
4066 scale: 0,
4067 },
4068 "",
4069 0,
4070 )
4071 .map_err(|e| match e {
4072 EngineError::Eval(ev) => ev,
4073 _ => jsonb_cast_type_error("numeric", target),
4074 })?;
4075 Ok(JsonbScalar::Numeric(num))
4076 }
4077 Ok(JsonValue::String(_)) => Err(jsonb_cast_type_error("string", target)),
4078 Ok(JsonValue::Array(_)) => Err(jsonb_cast_type_error("array", target)),
4079 Ok(JsonValue::Object(_)) => Err(jsonb_cast_type_error("object", target)),
4080 Err(_) => Err(jsonb_cast_type_error("value", target)),
4081 }
4082}
4083pub(crate) fn normalize_composite_for_column(
4101 v: Value<'static>,
4102 col: &ColumnSchema,
4103 catalog: Option<&spg_storage::Catalog>,
4104) -> Result<Value<'static>, EngineError> {
4105 let Some(tname) = col.user_composite_type.as_deref() else {
4106 return Ok(v);
4107 };
4108 if matches!(v, Value::Null) {
4109 return Ok(v);
4110 }
4111 let Some(def) = catalog.and_then(|c| c.composite_types().get(tname)) else {
4114 return Ok(v);
4115 };
4116 if matches!(v, Value::Json(_)) {
4119 return Ok(v);
4120 }
4121 crate::eval::apply_composite_cast_pub(v, def, catalog).map_err(EngineError::Eval)
4122}
4123
4124fn try_coerce_json_scalar(
4128 s: &str,
4129 expected: DataType,
4130 col_name: &str,
4131 position: usize,
4132) -> Option<Result<Value<'static>, EngineError>> {
4133 let target = match expected {
4134 DataType::Int => "integer",
4135 DataType::BigInt => "bigint",
4136 DataType::SmallInt => "smallint",
4137 DataType::Numeric { .. } => "numeric",
4138 DataType::Real => "real",
4139 DataType::Float => "double precision",
4140 DataType::Bool => "boolean",
4141 _ => return None,
4142 };
4143 Some(
4144 (|| match jsonb_scalar_for_cast(s, target).map_err(EngineError::Eval)? {
4145 JsonbScalar::Null => Ok(Value::Null),
4146 JsonbScalar::Bool(b) => {
4147 if matches!(expected, DataType::Bool) {
4148 Ok(Value::Bool(b))
4149 } else {
4150 Err(EngineError::Eval(jsonb_cast_type_error("boolean", target)))
4151 }
4152 }
4153 JsonbScalar::Numeric(n) => {
4154 if matches!(expected, DataType::Bool) {
4155 Err(EngineError::Eval(jsonb_cast_type_error("numeric", target)))
4156 } else {
4157 coerce_value(n, expected, col_name, position)
4158 }
4159 }
4160 })(),
4161 )
4162}
4163
4164pub(crate) fn mysql_bytes_for_column(
4174 v: Value<'static>,
4175 expected: DataType,
4176 mysql: bool,
4177) -> Value<'static> {
4178 if !mysql {
4179 return v;
4180 }
4181 let Value::Bytes(ref b) = v else {
4182 return v;
4183 };
4184 match expected {
4185 DataType::SmallInt
4186 | DataType::Int
4187 | DataType::BigInt
4188 | DataType::Float
4189 | DataType::Real
4190 | DataType::Numeric { .. } => {
4191 let start = b.len().saturating_sub(16);
4192 let acc = b[start..]
4193 .iter()
4194 .fold(0u128, |a, &x| (a << 8) | u128::from(x));
4195 if acc <= i64::MAX as u128 {
4196 #[allow(clippy::cast_possible_truncation)]
4197 Value::BigInt(acc as i64)
4198 } else {
4199 big_literal_to_value(&alloc::format!("{acc}"))
4200 }
4201 }
4202 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(
4203 b.iter()
4204 .map(|&x| x as char)
4205 .collect::<alloc::string::String>(),
4206 ),
4207 _ => v,
4208 }
4209}
4210
4211fn try_coerce_time_family(
4230 v: &Value<'static>,
4231 expected: DataType,
4232) -> Option<Result<Value<'static>, EngineError>> {
4233 const DAY_US: i64 = 86_400_000_000;
4234 if expected != DataType::Time {
4235 return None;
4236 }
4237 match v {
4238 Value::TimeTz { us, .. } => Some(Ok(Value::Time(*us))),
4239 Value::Interval { micros, .. } => Some(Ok(Value::Time(micros.rem_euclid(DAY_US)))),
4240 _ => None,
4241 }
4242}
4243
4244pub(crate) fn coerce_to_oid(v: &Value<'_>) -> Result<Option<Value<'static>>, EvalError> {
4254 let as_i64 = match v {
4255 Value::Null => return Ok(Some(Value::Null)),
4256 Value::SmallInt(n) => i64::from(*n),
4257 Value::Int(n) => i64::from(*n),
4258 Value::BigInt(n) => *n,
4259 Value::Text(t) => match t.trim().parse::<i64>() {
4260 Ok(n) => n,
4261 Err(_) => {
4262 return Err(EvalError::TypeMismatch {
4263 detail: alloc::format!("invalid input syntax for type oid: {:?}", t.trim()),
4264 });
4265 }
4266 },
4267 _ => return Ok(None),
4268 };
4269 if (-(1i64 << 31)..0).contains(&as_i64) {
4271 return Ok(Some(Value::BigInt(as_i64 + (1i64 << 32))));
4272 }
4273 if !(0..=i64::from(u32::MAX)).contains(&as_i64) {
4274 return Err(EvalError::TypeMismatch {
4275 detail: "OID out of range".into(),
4276 });
4277 }
4278 Ok(Some(Value::BigInt(as_i64)))
4279}
4280
4281pub(crate) fn coerce_value(
4282 v: Value<'static>,
4283 expected: DataType,
4284 col_name: &str,
4285 position: usize,
4286) -> Result<Value<'static>, EngineError> {
4287 if v.is_null() {
4288 return Ok(Value::Null);
4289 }
4290 if let Value::Json(ref s) = v {
4295 if let Some(res) = try_coerce_json_scalar(s, expected, col_name, position) {
4296 return res;
4297 }
4298 }
4299 if let Some(res) = try_coerce_time_family(&v, expected) {
4303 return res;
4304 }
4305 if let Value::Numeric { kind, .. } = v
4321 && kind != spg_storage::NumericKind::Finite
4322 {
4323 use spg_storage::NumericKind as K;
4324 let as_f64 = match kind {
4325 K::NaN => f64::NAN,
4326 K::PosInf => f64::INFINITY,
4327 K::NegInf => f64::NEG_INFINITY,
4328 K::Finite => unreachable!("checked above"),
4329 };
4330 let what = if kind == K::NaN { "NaN" } else { "infinity" };
4332 let int_err = |target: &str| {
4333 Err(EngineError::Eval(EvalError::TypeMismatch {
4334 detail: alloc::format!("cannot convert {what} to {target}"),
4335 }))
4336 };
4337 match expected {
4338 DataType::Float => return Ok(Value::Float(as_f64)),
4339 #[allow(clippy::cast_possible_truncation)]
4340 DataType::Real => return Ok(Value::Real(as_f64 as f32)),
4341 DataType::Int => return int_err("integer"),
4342 DataType::BigInt => return int_err("bigint"),
4343 DataType::SmallInt => return int_err("smallint"),
4344 DataType::Numeric { precision, scale } => {
4345 if precision != 0 && kind != K::NaN {
4349 return Err(EngineError::Eval(EvalError::TypeMismatch {
4350 detail: alloc::string::String::from("numeric field overflow"),
4351 }));
4352 }
4353 let _ = scale;
4354 return Ok(v);
4355 }
4356 _ => {}
4357 }
4358 }
4359 if let DataType::Numeric { precision, .. } = expected {
4363 let f = match v {
4364 Value::Float(f) if !f.is_finite() => Some(f),
4365 #[allow(clippy::cast_lossless)]
4366 Value::Real(f) if !f.is_finite() => Some(f as f64),
4367 _ => None,
4368 };
4369 if let Some(f) = f {
4370 use spg_storage::NumericKind as K;
4371 if f.is_nan() {
4372 return Ok(Value::numeric_special(K::NaN));
4373 }
4374 if precision != 0 {
4375 return Err(EngineError::Eval(EvalError::TypeMismatch {
4376 detail: alloc::string::String::from("numeric field overflow"),
4377 }));
4378 }
4379 return Ok(Value::numeric_special(if f > 0.0 {
4380 K::PosInf
4381 } else {
4382 K::NegInf
4383 }));
4384 }
4385 }
4386 let Some(actual) = v.data_type() else {
4387 return coerce_untyped_value(v, expected, col_name, position);
4388 };
4389 if actual == expected {
4390 return Ok(v);
4391 }
4392 if matches!(expected, DataType::Json | DataType::Jsonb)
4416 && let Value::Text(ref s) | Value::Json(ref s) = v
4417 {
4418 let bad = || {
4419 EngineError::Eval(crate::eval::EvalError::TypeMismatch {
4420 detail: alloc::string::String::from("invalid input syntax for type json"),
4421 })
4422 };
4423 return if expected == DataType::Jsonb {
4424 crate::json::canonicalize_jsonb(s.as_ref())
4425 .map(Value::json)
4426 .map_err(|_| bad())
4427 } else {
4428 crate::json::parse(s.as_ref())
4429 .map_err(|_| bad())
4430 .map(|_| Value::json(s.clone()))
4431 };
4432 }
4433 let coerced: Option<Value<'static>> = match (v, expected) {
4434 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4435 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4436 (Value::Int(n), DataType::SmallInt) => match i16::try_from(n) {
4439 Ok(v) => Some(Value::SmallInt(v)),
4440 Err(_) => {
4441 return Err(EngineError::Eval(EvalError::TypeMismatch {
4442 detail: "smallint out of range".into(),
4443 }));
4444 }
4445 },
4446 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4447 i128::from(n),
4448 precision,
4449 scale,
4450 col_name,
4451 )?),
4452 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
4453 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4454 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4455 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4456 i128::from(n),
4457 precision,
4458 scale,
4459 col_name,
4460 )?),
4461 (Value::BigInt(n), DataType::Int) => match i32::try_from(n) {
4462 Ok(v) => Some(Value::Int(v)),
4463 Err(_) => {
4464 return Err(EngineError::Eval(EvalError::TypeMismatch {
4465 detail: "integer out of range".into(),
4466 }));
4467 }
4468 },
4469 (Value::BigInt(n), DataType::SmallInt) => match i16::try_from(n) {
4470 Ok(v) => Some(Value::SmallInt(v)),
4471 Err(_) => {
4472 return Err(EngineError::Eval(EvalError::TypeMismatch {
4473 detail: "smallint out of range".into(),
4474 }));
4475 }
4476 },
4477 #[allow(clippy::cast_precision_loss)]
4478 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
4479 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4480 i128::from(n),
4481 precision,
4482 scale,
4483 col_name,
4484 )?),
4485 (Value::Float(x), DataType::Numeric { precision, scale }) => {
4486 if precision == 0 && scale == 0 && x.is_finite() {
4492 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{x}")) {
4493 Some(Value::Numeric {
4494 scaled: mantissa,
4495 scale: src_scale,
4496 kind: spg_storage::NumericKind::Finite,
4497 })
4498 } else {
4499 Some(numeric_from_float(x, precision, scale, col_name)?)
4500 }
4501 } else {
4502 Some(numeric_from_float(x, precision, scale, col_name)?)
4503 }
4504 }
4505 (Value::Real(x), DataType::Numeric { precision, scale }) => {
4511 if precision == 0 && scale == 0 && x.is_finite() {
4512 let six = alloc::format!("{:.5e}", x);
4526 let six: f64 = six.parse().unwrap_or_else(|_| f64::from(x));
4527 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{six}")) {
4528 Some(Value::Numeric {
4529 scaled: mantissa,
4530 scale: src_scale,
4531 kind: spg_storage::NumericKind::Finite,
4532 })
4533 } else {
4534 Some(numeric_from_float(
4535 f64::from(x),
4536 precision,
4537 scale,
4538 col_name,
4539 )?)
4540 }
4541 } else {
4542 Some(numeric_from_float(
4543 f64::from(x),
4544 precision,
4545 scale,
4546 col_name,
4547 )?)
4548 }
4549 }
4550 (Value::Text(s), DataType::Numeric { precision, scale }) => {
4561 if let Some(kind) = crate::numeric::parse_numeric_special(&s) {
4564 return Ok(Value::numeric_special(kind));
4565 }
4566 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
4567 match spg_sql::parser::expand_scientific_literal(&s) {
4572 spg_sql::parser::SciExpanded::Expanded(plain) => {
4573 return coerce_value(
4574 Value::Text(plain.into()),
4575 DataType::Numeric { precision, scale },
4576 col_name,
4577 position,
4578 );
4579 }
4580 spg_sql::parser::SciExpanded::Overflow => {
4581 return Err(EngineError::Eval(EvalError::TypeMismatch {
4582 detail: "value overflows numeric format".into(),
4583 }));
4584 }
4585 spg_sql::parser::SciExpanded::NotScientific => {}
4586 }
4587 if precision == 0 && scale == 0 {
4590 if let Some(b) = spg_storage::bignum::BigNumeric::from_decimal_str(&s) {
4591 return Ok(Value::NumericBig(alloc::boxed::Box::new(b)));
4592 }
4593 }
4594 return Err(EngineError::Eval(EvalError::TypeMismatch {
4595 detail: alloc::format!("invalid input syntax for type numeric: \"{s}\""),
4596 }));
4597 };
4598 if precision == 0 && scale == 0 {
4600 Some(Value::Numeric {
4601 scaled: mantissa,
4602 scale: src_scale,
4603 kind: spg_storage::NumericKind::Finite,
4604 })
4605 } else {
4606 Some(numeric_rescale(
4607 mantissa, src_scale, precision, scale, col_name,
4608 )?)
4609 }
4610 }
4611 (Value::Text(s), DataType::Date) => {
4613 let d = eval::parse_date_literal(&s)
4620 .or_else(|| {
4621 eval::parse_timestamp_literal(&s)
4622 .and_then(|t| i32::try_from(t.div_euclid(86_400_000_000)).ok())
4623 })
4624 .ok_or_else(|| datetime_parse_error("date", &s))?;
4625 Some(Value::Date(d))
4626 }
4627 (Value::Text(s), DataType::SmallInt) => Some(Value::SmallInt(
4642 parse_pg_int(&s)
4643 .and_then(|n| i16::try_from(n).ok())
4644 .ok_or_else(|| invalid_input_syntax("smallint", &s))?,
4645 )),
4646 (Value::Text(s), DataType::Int) => Some(Value::Int(
4647 parse_pg_int(&s)
4648 .and_then(|n| i32::try_from(n).ok())
4649 .ok_or_else(|| invalid_input_syntax("integer", &s))?,
4650 )),
4651 (Value::Text(s), DataType::BigInt) => Some(Value::BigInt(
4652 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("bigint", &s))?,
4653 )),
4654 (Value::Text(s), DataType::Xid) => Some(Value::Xid(
4660 s.parse::<u32>()
4661 .map_err(|_| invalid_input_syntax("xid", &s))?,
4662 )),
4663 (Value::Xid(x), DataType::Xid) => Some(Value::Xid(x)),
4664 (Value::Text(s), DataType::Xid8) => Some(Value::BigInt(
4665 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("xid8", &s))?,
4666 )),
4667 (Value::BigInt(n), DataType::Xid8) => Some(Value::BigInt(n)),
4674 (ref other, DataType::Oid) => coerce_to_oid(other)?,
4678 (Value::Text(s), DataType::Float) => {
4679 Some(Value::Float(
4683 parse_float8(&s).ok_or_else(|| float_text_error(&s, "double precision"))?,
4684 ))
4685 }
4686 (Value::Int(n), DataType::Real) => Some(Value::Real(n as f32)),
4688 (Value::SmallInt(n), DataType::Real) => Some(Value::Real(f32::from(n))),
4689 (Value::BigInt(n), DataType::Real) => Some(Value::Real(n as f32)),
4690 (Value::Float(x), DataType::Real) => {
4691 let narrowed = x as f32;
4695 if narrowed.is_infinite() && x.is_finite() {
4696 return Err(EngineError::Eval(EvalError::TypeMismatch {
4697 detail: "value out of range: overflow".into(),
4698 }));
4699 }
4700 if narrowed == 0.0 && x != 0.0 {
4702 return Err(EngineError::Eval(EvalError::TypeMismatch {
4703 detail: "value out of range: underflow".into(),
4704 }));
4705 }
4706 Some(Value::Real(narrowed))
4707 }
4708 (
4709 Value::Numeric {
4710 scaled,
4711 scale,
4712 kind,
4713 },
4714 DataType::Real,
4715 ) => Some(Value::Real(match kind {
4716 spg_storage::NumericKind::NaN => f32::NAN,
4717 spg_storage::NumericKind::PosInf => f32::INFINITY,
4718 spg_storage::NumericKind::NegInf => f32::NEG_INFINITY,
4719 spg_storage::NumericKind::Finite => {
4720 let mut div = 1.0f64;
4721 for _ in 0..scale {
4722 div *= 10.0;
4723 }
4724 let x = (scaled as f64 / div) as f32;
4725 if x == 0.0 && scaled != 0 {
4728 return Err(real_out_of_range(&crate::eval::format_numeric(
4729 scaled, scale,
4730 )));
4731 }
4732 x
4733 }
4734 })),
4735 (Value::Real(x), DataType::Float) => Some(Value::Float(f64::from(x))),
4736 (Value::Text(s), DataType::Real) => {
4743 let t = s.trim();
4744 let x = t
4745 .parse::<f32>()
4746 .ok()
4747 .ok_or_else(|| invalid_input_syntax("real", &s))?;
4748 if x.is_infinite() && !text_is_explicit_infinity(t) {
4749 return Err(real_out_of_range(t));
4750 }
4751 if x == 0.0 && float_text_is_nonzero(t) {
4754 return Err(real_out_of_range(t));
4755 }
4756 Some(Value::Real(x))
4757 }
4758 (Value::Text(s), DataType::Bool) => match s.trim().to_ascii_lowercase().as_str() {
4762 "0" | "f" | "fa" | "fal" | "fals" | "false" | "n" | "no" | "of" | "off" => {
4763 Some(Value::Bool(false))
4764 }
4765 "1" | "t" | "tr" | "tru" | "true" | "y" | "ye" | "yes" | "on" => {
4766 Some(Value::Bool(true))
4767 }
4768 _ => return Err(invalid_input_syntax("boolean", &s)),
4769 },
4770 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4779 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4780 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4781 (Value::Json(s), DataType::Text) => Some(Value::text(s)),
4803 (Value::Json(s), DataType::Json) => Some(Value::json(s)),
4811 (Value::Json(s), DataType::Jsonb) => Some(Value::json(
4812 crate::json::canonicalize_jsonb(s.as_ref()).unwrap_or_else(|_| s.into_owned()),
4813 )),
4814 (Value::Text(s), DataType::Bytes) => {
4821 let bytes = decode_bytea_literal(&s)
4822 .map_err(|e| EngineError::Eval(EvalError::TypeMismatch { detail: e }))?;
4823 Some(Value::bytes(bytes))
4824 }
4825 (Value::Bytes(b), DataType::Text) => Some(Value::text(encode_bytea_hex(&b))),
4829 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
4837 Some(b) => Some(Value::Uuid(b)),
4838 None => {
4839 return Err(EngineError::Eval(EvalError::TypeMismatch {
4840 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4841 }));
4842 }
4843 },
4844 (Value::Uuid(b), DataType::Text) => Some(Value::text(spg_storage::format_uuid(&b))),
4849 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
4855 Some(us) => Some(Value::Time(us)),
4856 None => {
4857 let time_shaped = {
4863 let core = s.trim().split('.').next().unwrap_or("");
4864 !core.is_empty()
4865 && core.split(':').count() >= 2
4866 && core
4867 .split(':')
4868 .all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()))
4869 };
4870 let detail = if time_shaped {
4871 alloc::format!("date/time field value out of range: {s:?}")
4872 } else {
4873 alloc::format!("invalid input syntax for type time: {s:?}")
4874 };
4875 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
4876 }
4877 },
4878 (Value::Time(us), DataType::Text) => Some(Value::text(eval::format_time(us))),
4880 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4885 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4886 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
4887 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
4891 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
4892 Err(_) => {
4893 return Err(EngineError::Eval(EvalError::TypeMismatch {
4894 detail: alloc::format!("invalid input syntax for type year: {s:?}"),
4895 }));
4896 }
4897 },
4898 (Value::Year(y), DataType::Text) => Some(Value::text(alloc::format!("{y:04}"))),
4900 (Value::Time(t), DataType::TimeTz) => Some(Value::TimeTz {
4914 us: t,
4915 offset_secs: 0,
4916 }),
4917 (Value::Timestamp(t), DataType::TimeTz) => Some(Value::TimeTz {
4918 us: t.rem_euclid(86_400_000_000),
4919 offset_secs: 0,
4920 }),
4921 (Value::Text(s), DataType::TimeTz) => {
4922 match parse_timetz_str(&s).or_else(|| parse_time_str(s.trim()).map(|us| (us, 0))) {
4923 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
4924 None => {
4925 return Err(EngineError::Eval(EvalError::TypeMismatch {
4926 detail: alloc::format!(
4927 "invalid input syntax for type time with time zone: \
4928 {s:?}"
4929 ),
4930 }));
4931 }
4932 }
4933 }
4934 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
4936 Some(Value::text(eval::format_timetz(us, offset_secs)))
4937 }
4938 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
4942 Some(c) => Some(Value::Money(c)),
4943 None => {
4944 return Err(EngineError::Eval(EvalError::TypeMismatch {
4945 detail: alloc::format!("invalid input syntax for type money: {s:?}"),
4946 }));
4947 }
4948 },
4949 (Value::SmallInt(n), DataType::Money) => {
4953 Some(Value::Money(i64::from(n).saturating_mul(100)))
4954 }
4955 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
4956 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
4957 (Value::Float(x), DataType::Money) => {
4958 let scaled = x * 100.0;
4961 let cents = if scaled >= 0.0 {
4962 (scaled + 0.5) as i64
4963 } else {
4964 (scaled - 0.5) as i64
4965 };
4966 Some(Value::Money(cents))
4967 }
4968 (Value::Numeric { scaled, scale, .. }, DataType::Money) => {
4969 let cents = if scale == 2 {
4972 scaled
4973 } else if scale < 2 {
4974 let mult = 10_i128.pow(u32::from(2 - scale));
4975 scaled.saturating_mul(mult)
4976 } else {
4977 let div = 10_i128.pow(u32::from(scale - 2));
4978 let half = div / 2;
4979 let bias = if scaled >= 0 { half } else { -half };
4980 (scaled + bias) / div
4981 };
4982 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
4983 }
4984 (Value::Money(c), DataType::Text) => Some(Value::text(eval::format_money(c))),
4986 (Value::Money(c), DataType::Numeric { .. }) => Some(Value::Numeric {
4988 scaled: i128::from(c),
4989 scale: 2,
4990 kind: spg_storage::NumericKind::Finite,
4991 }),
4992 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
4996 Ok(v) => Some(v),
4997 Err(RangeParseError::Misordered) => {
4999 return Err(EngineError::Eval(EvalError::TypeMismatch {
5000 detail: alloc::string::String::from(
5001 "range lower bound must be less than or equal to range upper bound",
5002 ),
5003 }));
5004 }
5005 Err(RangeParseError::Malformed) => {
5006 return Err(EngineError::Eval(EvalError::TypeMismatch {
5007 detail: alloc::format!("malformed range literal: \"{s}\""),
5008 }));
5009 }
5010 Err(RangeParseError::BadElement(bad)) => {
5011 return Err(EngineError::Eval(EvalError::TypeMismatch {
5012 detail: alloc::format!(
5013 "invalid input syntax for type {}: \"{bad}\"",
5014 range_element_type_name(kind)
5015 ),
5016 }));
5017 }
5018 },
5019 (v @ Value::Range { .. }, DataType::Text) => Some(Value::text(format_range_str(&v))),
5021 (Value::Text(s), DataType::Inet) => match parse_inet_text(&s) {
5023 Some((family, bits, addr)) => Some(Value::Inet { family, bits, addr }),
5024 None => {
5025 return Err(EngineError::Eval(EvalError::TypeMismatch {
5029 detail: alloc::format!("invalid input syntax for type inet: {s:?}"),
5030 }));
5031 }
5032 },
5033 (Value::Inet { family, bits, addr }, DataType::Cidr) => {
5040 let full = if family == 6 { 128 } else { 32 };
5041 let bits = if bits > full { full } else { bits };
5042 let mut masked = addr;
5043 for i in 0..16usize {
5044 let bit_start = i * 8;
5045 if bit_start >= usize::from(bits) {
5046 masked[i] = 0;
5047 } else if bit_start + 8 > usize::from(bits) {
5048 let keep = usize::from(bits) - bit_start;
5049 masked[i] &= 0xffu8 << (8 - keep);
5050 }
5051 }
5052 Some(Value::Cidr {
5053 family,
5054 bits,
5055 addr: masked,
5056 })
5057 }
5058 (Value::Cidr { family, bits, addr }, DataType::Inet) => {
5059 Some(Value::Inet { family, bits, addr })
5060 }
5061 (Value::Text(s), DataType::Cidr) => match parse_cidr_text(&s) {
5062 Ok(Some((family, bits, addr))) => Some(Value::Cidr { family, bits, addr }),
5063 Err(()) => {
5064 return Err(EngineError::Eval(EvalError::TypeMismatch {
5065 detail: alloc::format!(
5066 "invalid cidr value: {s:?} DETAIL: Value has bits set to right of mask."
5067 ),
5068 }));
5069 }
5070 Ok(None) => {
5071 return Err(EngineError::Eval(EvalError::TypeMismatch {
5072 detail: alloc::format!("invalid input syntax for type cidr: {s:?}"),
5073 }));
5074 }
5075 },
5076 (Value::Text(s), DataType::Interval) => match spg_sql::parser::parse_interval_text(&s) {
5079 Some((months, days, micros)) => Some(Value::Interval {
5080 months,
5081 days,
5082 micros,
5083 kind: spg_storage::IntervalKind::from_fields(months, days, micros),
5084 }),
5085 None => {
5086 return Err(EngineError::Eval(EvalError::TypeMismatch {
5087 detail: alloc::format!("invalid input syntax for type interval: {s:?}"),
5088 }));
5089 }
5090 },
5091 (Value::Text(s), DataType::Macaddr) => match parse_macaddr_text(&s) {
5092 Some(m) => Some(Value::Macaddr(m)),
5093 None => {
5094 return Err(EngineError::Eval(EvalError::TypeMismatch {
5095 detail: alloc::format!("invalid input syntax for type macaddr: {s:?}"),
5096 }));
5097 }
5098 },
5099 (Value::Text(s), DataType::PgLsn) => match parse_pg_lsn_text(&s) {
5101 Some(l) => Some(Value::PgLsn(l)),
5102 None => {
5103 return Err(EngineError::Eval(EvalError::TypeMismatch {
5104 detail: alloc::format!("invalid input syntax for type pg_lsn: \"{s}\""),
5105 }));
5106 }
5107 },
5108 (Value::Text(s), DataType::Macaddr8) => match parse_macaddr8_text(&s) {
5109 Some(m) => Some(Value::Macaddr8(m)),
5110 None => {
5111 return Err(EngineError::Eval(EvalError::TypeMismatch {
5112 detail: alloc::format!("invalid input syntax for type macaddr8: {s:?}"),
5113 }));
5114 }
5115 },
5116 (Value::BitString { nbits, bytes }, DataType::Bit(n)) => {
5128 let want = if n == 0 { 1 } else { n };
5130 if nbits != want {
5131 return Err(EngineError::Unsupported(alloc::format!(
5132 "bit string length {nbits} does not match type bit({want})"
5133 )));
5134 }
5135 Some(Value::BitString { nbits, bytes })
5136 }
5137 (Value::BitString { nbits, bytes }, DataType::BitVarying(n)) => {
5138 if n != 0 && nbits > n {
5139 return Err(EngineError::Unsupported(alloc::format!(
5140 "bit string too long for type bit varying({n})"
5141 )));
5142 }
5143 Some(Value::BitString { nbits, bytes })
5144 }
5145 (Value::Text(s), bit_ty @ (DataType::Bit(_) | DataType::BitVarying(_))) => {
5146 match parse_bit_string_text(&s) {
5147 Some((nbits, bytes)) => {
5148 match bit_ty {
5158 DataType::Bit(n) => {
5160 let want = if n == 0 { 1 } else { n };
5161 if nbits != want {
5162 return Err(EngineError::Unsupported(alloc::format!(
5163 "bit string length {nbits} does not match type bit({want})"
5164 )));
5165 }
5166 }
5167 DataType::BitVarying(n) if n != 0 && nbits > n => {
5168 return Err(EngineError::Unsupported(alloc::format!(
5169 "bit string too long for type bit varying({n})"
5170 )));
5171 }
5172 _ => {}
5173 }
5174 Some(Value::bit_string(nbits, bytes))
5175 }
5176 None => {
5177 let bad = s.chars().find(|c| *c != '0' && *c != '1');
5179 return Err(EngineError::Eval(EvalError::TypeMismatch {
5180 detail: match bad {
5181 Some(c) => {
5182 alloc::format!("\"{c}\" is not a valid binary digit")
5183 }
5184 None => alloc::format!("invalid input syntax for BIT: {s:?}"),
5185 },
5186 }));
5187 }
5188 }
5189 }
5190 (Value::Text(s), DataType::Xml) => {
5191 if !xml_content_is_well_formed(&s) {
5196 return Err(EngineError::Eval(EvalError::TypeMismatch {
5197 detail: alloc::format!("invalid XML content: {s:?}"),
5198 }));
5199 }
5200 Some(Value::xml(s))
5201 }
5202 (Value::BpChar(s), DataType::Char1) => {
5209 Some(Value::Char1(s.as_bytes().first().copied().unwrap_or(0)))
5210 }
5211 (Value::BpChar(s), DataType::Xml) => {
5212 let stripped = s.trim_end_matches(' ');
5213 if !xml_content_is_well_formed(stripped) {
5214 return Err(EngineError::Eval(EvalError::TypeMismatch {
5215 detail: alloc::format!("invalid XML content: {stripped:?}"),
5216 }));
5217 }
5218 Some(Value::xml(alloc::string::String::from(stripped)))
5219 }
5220 (Value::Bytes(b), DataType::SmallInt | DataType::Int | DataType::BigInt) => {
5226 let mut acc: i128 = 0;
5227 for byte in b.iter() {
5228 acc = acc.saturating_mul(256).saturating_add(i128::from(*byte));
5229 }
5230 let (fits, made) = match expected {
5231 DataType::SmallInt => (
5232 i16::try_from(acc).is_ok(),
5233 i16::try_from(acc).map(Value::SmallInt).ok(),
5234 ),
5235 DataType::Int => (
5236 i32::try_from(acc).is_ok(),
5237 i32::try_from(acc).map(Value::Int).ok(),
5238 ),
5239 _ => (
5240 i64::try_from(acc).is_ok(),
5241 i64::try_from(acc).map(Value::BigInt).ok(),
5242 ),
5243 };
5244 if !fits {
5245 return Err(EngineError::Eval(EvalError::TypeMismatch {
5246 detail: alloc::format!("{} out of range", pg_type_name_for_error(expected)),
5247 }));
5248 }
5249 made
5250 }
5251 (Value::Int(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
5254 (Value::SmallInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
5255 (Value::BigInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
5256 (Value::Text(s), DataType::Char1) => {
5257 let bytes = s.as_bytes();
5263 if bytes.len() == 4
5264 && bytes[0] == b'\\'
5265 && bytes[1..].iter().all(|b| (b'0'..=b'7').contains(b))
5266 {
5267 let v = ((bytes[1] - b'0') << 6) | ((bytes[2] - b'0') << 3) | (bytes[3] - b'0');
5268 Some(Value::Char1(v))
5269 } else {
5270 let b = s.bytes().next().unwrap_or(0);
5271 Some(Value::Char1(b))
5272 }
5273 }
5274 (Value::Inet { family, bits, addr }, DataType::Text) => {
5276 let base = format_inet(family, bits, &addr);
5280 Some(Value::text(if base.contains('/') {
5281 base
5282 } else {
5283 alloc::format!("{base}/{bits}")
5284 }))
5285 }
5286 (Value::Cidr { family, bits, addr }, DataType::Text) => {
5287 Some(Value::text(format_inet(family, bits, &addr)))
5288 }
5289 (Value::Macaddr(m), DataType::Text) => Some(Value::text(format_macaddr(&m))),
5290 (Value::Macaddr8(m), DataType::Text) => Some(Value::text(format_macaddr8(&m))),
5291 (Value::PgLsn(l), DataType::Text) => Some(Value::text(format_pg_lsn(l))),
5292 (Value::Macaddr(m), DataType::Macaddr8) => Some(Value::Macaddr8([
5295 m[0], m[1], m[2], 0xff, 0xfe, m[3], m[4], m[5],
5296 ])),
5297 (Value::BitString { nbits, bytes }, DataType::Text) => {
5298 Some(Value::text(format_bit_string(nbits, &bytes)))
5299 }
5300 #[allow(clippy::cast_possible_truncation)]
5302 (Value::BitString { nbits, bytes }, DataType::SmallInt) => {
5303 Some(Value::SmallInt(bit_string_to_i64(nbits, &bytes) as i16))
5304 }
5305 #[allow(clippy::cast_possible_truncation)]
5306 (Value::BitString { nbits, bytes }, DataType::Int) => {
5307 Some(Value::Int(bit_string_to_i64(nbits, &bytes) as i32))
5308 }
5309 (Value::BitString { nbits, bytes }, DataType::BigInt) => {
5310 Some(Value::BigInt(bit_string_to_i64(nbits, &bytes)))
5311 }
5312 (Value::Xml(s), DataType::Text) => Some(Value::text(s)),
5313 (Value::Char1(b), DataType::Text) => Some(Value::text((b as char).to_string())),
5314 (Value::Text(s), DataType::Point) => match parse_point(&s) {
5318 Some(p) => Some(Value::Point(p)),
5319 None => {
5320 return Err(EngineError::Eval(EvalError::TypeMismatch {
5321 detail: alloc::format!("invalid input syntax for type point: {s:?}"),
5322 }));
5323 }
5324 },
5325 (Value::Text(s), DataType::Lseg) => match parse_lseg_text(&s) {
5326 Some((p1, p2)) => Some(Value::Lseg(p1, p2)),
5327 None => {
5328 return Err(EngineError::Eval(EvalError::TypeMismatch {
5329 detail: alloc::format!("invalid input syntax for type lseg: {s:?}"),
5330 }));
5331 }
5332 },
5333 (Value::Text(s), DataType::PgBox) => match parse_box_text(&s) {
5334 Some((ur, ll)) => Some(Value::PgBox(ur, ll)),
5335 None => {
5336 return Err(EngineError::Eval(EvalError::TypeMismatch {
5337 detail: alloc::format!("invalid input syntax for type box: {s:?}"),
5338 }));
5339 }
5340 },
5341 (Value::Text(s), DataType::Line) => match parse_line_text(&s) {
5342 Some((a, b, c)) => Some(Value::Line { a, b, c }),
5343 None => {
5344 let zero_ab = s
5348 .trim()
5349 .strip_prefix('{')
5350 .and_then(|x| x.strip_suffix('}'))
5351 .map(|inner| inner.split(',').collect::<alloc::vec::Vec<_>>())
5352 .is_some_and(|parts| {
5353 parts.len() == 3
5354 && parts[0].trim().parse::<f64>() == Ok(0.0)
5355 && parts[1].trim().parse::<f64>() == Ok(0.0)
5356 && parts[2].trim().parse::<f64>().is_ok()
5357 });
5358 let detail = if zero_ab {
5359 alloc::string::String::from(
5360 "invalid line specification: A and B cannot both be zero",
5361 )
5362 } else {
5363 alloc::format!("invalid input syntax for type line: {s:?}")
5364 };
5365 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
5366 }
5367 },
5368 (Value::Text(s), DataType::Circle) => match parse_circle_text(&s) {
5369 Some((center, radius)) => Some(Value::Circle { center, radius }),
5370 None => {
5371 return Err(EngineError::Eval(EvalError::TypeMismatch {
5372 detail: alloc::format!("invalid input syntax for type circle: {s:?}"),
5373 }));
5374 }
5375 },
5376 (Value::Text(s), DataType::Path) => match parse_path_text(&s) {
5377 Some((points, closed)) => Some(Value::Path { points, closed }),
5378 None => {
5379 return Err(EngineError::Eval(EvalError::TypeMismatch {
5380 detail: alloc::format!("invalid input syntax for type path: {s:?}"),
5381 }));
5382 }
5383 },
5384 (Value::PgBox(a, b), DataType::Polygon) => {
5387 let (hx, hy) = (a.x.max(b.x), a.y.max(b.y));
5388 let (lx, ly) = (a.x.min(b.x), a.y.min(b.y));
5389 let p = |x: f64, y: f64| spg_storage::Point2D { x, y };
5390 Some(Value::Polygon(alloc::vec![
5391 p(lx, ly),
5392 p(lx, hy),
5393 p(hx, hy),
5394 p(hx, ly),
5395 ]))
5396 }
5397 (Value::Text(s), DataType::Polygon) => match parse_polygon_text(&s) {
5398 Some(points) => Some(Value::Polygon(points)),
5399 None => {
5400 return Err(EngineError::Eval(EvalError::TypeMismatch {
5401 detail: alloc::format!("invalid input syntax for type polygon: {s:?}"),
5402 }));
5403 }
5404 },
5405 (Value::Point(p), DataType::Text) => Some(Value::text(format_point(p))),
5407 (Value::Lseg(p1, p2), DataType::Text) => Some(Value::text(format_lseg(p1, p2))),
5408 (Value::PgBox(ur, ll), DataType::Text) => Some(Value::text(format_pg_box(ur, ll))),
5409 (Value::Line { a, b, c }, DataType::Text) => Some(Value::text(format_line(a, b, c))),
5410 (Value::Circle { center, radius }, DataType::Text) => {
5411 Some(Value::text(format_circle(center, radius)))
5412 }
5413 (Value::Path { points, closed }, DataType::Text) => {
5414 Some(Value::text(format_path(&points, closed)))
5415 }
5416 (Value::Polygon(points), DataType::Text) => Some(Value::text(format_polygon(&points))),
5417 (ref rv @ Value::Range { kind: rk, .. }, DataType::Multirange(kind)) => {
5424 if rk != kind {
5425 return Err(EngineError::Eval(EvalError::TypeMismatch {
5426 detail: alloc::format!(
5427 "cannot cast type {} to {}",
5428 DataType::Range(rk),
5429 DataType::Multirange(kind)
5430 ),
5431 }));
5432 }
5433 crate::eval::binop::range_as_multirange(rv)
5434 }
5435 (Value::Text(s), DataType::Multirange(kind)) => match parse_multirange_str(&s, kind) {
5436 Some(ranges) => Some(Value::Multirange {
5442 kind,
5443 ranges: crate::eval::binop::normalize_multirange_spans(kind, &ranges),
5444 }),
5445 None => {
5446 return Err(EngineError::Eval(EvalError::TypeMismatch {
5447 detail: alloc::format!("invalid input syntax for multirange type: {s:?}"),
5448 }));
5449 }
5450 },
5451 (Value::Multirange { ranges, .. }, DataType::Text) => {
5453 Some(Value::text(format_multirange(&ranges)))
5454 }
5455 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
5457 Some(pairs) => Some(Value::Hstore(pairs)),
5458 None => {
5459 return Err(EngineError::Eval(EvalError::TypeMismatch {
5460 detail: alloc::format!("invalid input syntax for type hstore: {s:?}"),
5461 }));
5462 }
5463 },
5464 (Value::Hstore(pairs), DataType::Text) => Some(Value::text(format_hstore_str(&pairs))),
5466 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
5469 Ok(m) => Some(Value::IntArray2D(m)),
5470 Err(e) => {
5471 return Err(EngineError::Eval(EvalError::TypeMismatch {
5472 detail: alloc::format!("invalid input syntax for INT[][]: {s:?}: {e}"),
5473 }));
5474 }
5475 },
5476 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
5477 Ok(m) => Some(Value::BigIntArray2D(m)),
5478 Err(e) => {
5479 return Err(EngineError::Eval(EvalError::TypeMismatch {
5480 detail: alloc::format!("invalid input syntax for BIGINT[][]: {s:?}: {e}"),
5481 }));
5482 }
5483 },
5484 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
5485 Ok(m) => Some(Value::TextArray2D(m)),
5486 Err(e) => {
5487 return Err(EngineError::Eval(EvalError::TypeMismatch {
5488 detail: alloc::format!("invalid input syntax for TEXT[][]: {s:?}: {e}"),
5489 }));
5490 }
5491 },
5492 (Value::IntArray2D(rows), DataType::Text) => Some(Value::text(format_int_2d_text(&rows))),
5494 (Value::BigIntArray2D(rows), DataType::Text) => {
5495 Some(Value::text(format_bigint_2d_text(&rows)))
5496 }
5497 (Value::TextArray2D(rows), DataType::Text) => Some(Value::text(format_text_2d_text(&rows))),
5498 (Value::Text(s), DataType::TextArray) => {
5503 let arr = decode_text_array_literal(&s).map_err(|_| {
5507 EngineError::Eval(EvalError::TypeMismatch {
5508 detail: malformed_array_literal(&s),
5509 })
5510 })?;
5511 Some(Value::TextArray(arr))
5512 }
5513 (Value::Text(s), DataType::IntArray) => {
5519 let arr = decode_text_array_literal(&s).map_err(|_| {
5523 EngineError::Eval(EvalError::TypeMismatch {
5524 detail: malformed_array_literal(&s),
5525 })
5526 })?;
5527 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
5528 for elem in arr {
5529 match elem {
5530 None => out.push(None),
5531 Some(t) => {
5532 let n: i32 = t.parse().map_err(|_| {
5533 EngineError::Eval(EvalError::TypeMismatch {
5534 detail: alloc::format!(
5535 "invalid input syntax for type integer: {t:?}"
5536 ),
5537 })
5538 })?;
5539 out.push(Some(n));
5540 }
5541 }
5542 }
5543 Some(Value::IntArray(out))
5544 }
5545 (Value::Text(s), DataType::SmallIntArray) => Some(Value::SmallIntArray(
5549 decode_array_elems(&s, DataType::SmallInt, col_name, position)?
5550 .into_iter()
5551 .map(|o| match o {
5552 Some(Value::SmallInt(n)) => Some(n),
5553 _ => None,
5554 })
5555 .collect(),
5556 )),
5557 (Value::Text(s), DataType::BoolArray) => {
5558 if let Some(rows) = crate::eval::values::split_2d_rows(&s) {
5563 let mut row_vals: Vec<Value<'static>> = Vec::with_capacity(rows.len());
5564 for r in &rows {
5565 let bools: Vec<Option<bool>> =
5566 decode_array_elems(r, DataType::Bool, col_name, position)?
5567 .into_iter()
5568 .map(|o| match o {
5569 Some(Value::Bool(b)) => Some(b),
5570 _ => None,
5571 })
5572 .collect();
5573 row_vals.push(Value::BoolArray(bools));
5574 }
5575 return crate::eval::values::build_2d_from_rows(&row_vals).ok_or_else(|| {
5576 EngineError::Eval(EvalError::TypeMismatch {
5577 detail: malformed_array_literal(&s),
5578 })
5579 });
5580 }
5581 Some(Value::BoolArray(
5582 decode_array_elems(&s, DataType::Bool, col_name, position)?
5583 .into_iter()
5584 .map(|o| match o {
5585 Some(Value::Bool(b)) => Some(b),
5586 _ => None,
5587 })
5588 .collect(),
5589 ))
5590 }
5591 (Value::Text(s), DataType::FloatArray) => Some(Value::FloatArray(
5592 decode_array_elems(&s, DataType::Float, col_name, position)?
5593 .into_iter()
5594 .map(|o| match o {
5595 Some(Value::Float(f)) => Some(f),
5596 _ => None,
5597 })
5598 .collect(),
5599 )),
5600 (Value::Text(s), DataType::NumericArray) => Some(Value::NumericArray(
5601 decode_array_elems(
5602 &s,
5603 DataType::Numeric {
5604 precision: 0,
5605 scale: 0,
5606 },
5607 col_name,
5608 position,
5609 )?
5610 .into_iter()
5611 .map(|o| match o {
5612 Some(Value::Numeric { scaled, scale, .. }) => Some((scaled, scale)),
5613 _ => None,
5614 })
5615 .collect(),
5616 )),
5617 (Value::Text(s), DataType::DateArray) => Some(Value::DateArray(
5618 decode_array_elems(&s, DataType::Date, col_name, position)?
5619 .into_iter()
5620 .map(|o| match o {
5621 Some(Value::Date(d)) => Some(d),
5622 _ => None,
5623 })
5624 .collect(),
5625 )),
5626 (Value::Text(s), DataType::UuidArray) => Some(Value::UuidArray(
5627 decode_array_elems(&s, DataType::Uuid, col_name, position)?
5628 .into_iter()
5629 .map(|o| match o {
5630 Some(Value::Uuid(u)) => Some(u),
5631 _ => None,
5632 })
5633 .collect(),
5634 )),
5635 (Value::Text(s), DataType::BigIntArray | DataType::OidArray) => {
5642 let arr = decode_text_array_literal(&s).map_err(|_| {
5646 EngineError::Eval(EvalError::TypeMismatch {
5647 detail: malformed_array_literal(&s),
5648 })
5649 })?;
5650 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
5651 for elem in arr {
5652 match elem {
5653 None => out.push(None),
5654 Some(t) => {
5655 let n: i64 = t.parse().map_err(|_| {
5656 EngineError::Eval(EvalError::TypeMismatch {
5657 detail: alloc::format!(
5658 "invalid input syntax for type bigint: {t:?}"
5659 ),
5660 })
5661 })?;
5662 out.push(Some(n));
5663 }
5664 }
5665 }
5666 Some(Value::BigIntArray(out))
5667 }
5668 (Value::TextArray(items), DataType::Text) => Some(Value::text(encode_text_array(&items))),
5672 (Value::TextArray(items), DataType::BoolArray) if items.is_empty() => {
5680 Some(Value::BoolArray(alloc::vec::Vec::new()))
5681 }
5682 (Value::TextArray(items), DataType::SmallIntArray) if items.is_empty() => {
5683 Some(Value::SmallIntArray(alloc::vec::Vec::new()))
5684 }
5685 (Value::TextArray(items), DataType::IntArray) if items.is_empty() => {
5686 Some(Value::IntArray(alloc::vec::Vec::new()))
5687 }
5688 (Value::TextArray(items), DataType::BigIntArray) if items.is_empty() => {
5689 Some(Value::BigIntArray(alloc::vec::Vec::new()))
5690 }
5691 (Value::TextArray(items), DataType::FloatArray) if items.is_empty() => {
5692 Some(Value::FloatArray(alloc::vec::Vec::new()))
5693 }
5694 (Value::TextArray(items), DataType::FloatArray) => {
5697 let mut out = alloc::vec::Vec::with_capacity(items.len());
5698 let mut ok = true;
5699 for item in items {
5700 match item {
5701 None => out.push(None),
5702 Some(s) => match s.trim().parse::<f64>() {
5703 Ok(x) => out.push(Some(x)),
5704 Err(_) => {
5705 ok = false;
5706 break;
5707 }
5708 },
5709 }
5710 }
5711 if ok {
5712 Some(Value::FloatArray(out))
5713 } else {
5714 None
5715 }
5716 }
5717 (Value::FloatArray(items), DataType::FloatArray) => Some(Value::FloatArray(items)),
5720 #[allow(clippy::cast_precision_loss)]
5721 (Value::IntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5722 items.into_iter().map(|o| o.map(|n| f64::from(n))).collect(),
5723 )),
5724 #[allow(clippy::cast_precision_loss)]
5725 (Value::BigIntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5726 items.into_iter().map(|o| o.map(|n| n as f64)).collect(),
5727 )),
5728 #[allow(clippy::cast_precision_loss)]
5732 (Value::NumericArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5733 items
5734 .into_iter()
5735 .map(|o| {
5736 o.map(|(scaled, scale)| {
5737 crate::eval::format_numeric(scaled, scale)
5738 .parse()
5739 .unwrap_or(f64::NAN)
5740 })
5741 })
5742 .collect(),
5743 )),
5744 (Value::IntArray(items), DataType::BigIntArray) => Some(Value::BigIntArray(
5749 items.into_iter().map(|o| o.map(i64::from)).collect(),
5750 )),
5751 (Value::BigIntArray(items), DataType::IntArray) => {
5752 let mut out = alloc::vec::Vec::with_capacity(items.len());
5753 let mut ok = true;
5754 for o in items {
5755 match o {
5756 None => out.push(None),
5757 Some(n) => match i32::try_from(n) {
5758 Ok(v) => out.push(Some(v)),
5759 Err(_) => {
5760 ok = false;
5761 break;
5762 }
5763 },
5764 }
5765 }
5766 if ok { Some(Value::IntArray(out)) } else { None }
5767 }
5768 (Value::IntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5769 items
5770 .into_iter()
5771 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5772 .collect(),
5773 )),
5774 (Value::BigIntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5775 items
5776 .into_iter()
5777 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5778 .collect(),
5779 )),
5780 (Value::FloatArray(items), DataType::NumericArray) => {
5781 let mut out = alloc::vec::Vec::with_capacity(items.len());
5782 let mut ok = true;
5783 for o in items {
5784 match o {
5785 None => out.push(None),
5786 Some(x) => match parse_numeric_text(&alloc::format!("{x}")) {
5787 Some((mantissa, scale)) => out.push(Some((mantissa, scale))),
5788 None => {
5789 ok = false;
5790 break;
5791 }
5792 },
5793 }
5794 }
5795 if ok {
5796 Some(Value::NumericArray(out))
5797 } else {
5798 None
5799 }
5800 }
5801 (Value::NumericArray(items), DataType::IntArray) => {
5805 let mut out = alloc::vec::Vec::with_capacity(items.len());
5806 let mut ok = true;
5807 for o in items {
5808 match o {
5809 None => out.push(None),
5810 Some((scaled, scale)) => {
5811 match i32::try_from(numeric_round_to_integer(scaled, scale)) {
5812 Ok(v) => out.push(Some(v)),
5813 Err(_) => {
5814 ok = false;
5815 break;
5816 }
5817 }
5818 }
5819 }
5820 }
5821 if ok { Some(Value::IntArray(out)) } else { None }
5822 }
5823 (Value::NumericArray(items), DataType::BigIntArray) => {
5824 let mut out = alloc::vec::Vec::with_capacity(items.len());
5825 let mut ok = true;
5826 for o in items {
5827 match o {
5828 None => out.push(None),
5829 Some((scaled, scale)) => {
5830 match i64::try_from(numeric_round_to_integer(scaled, scale)) {
5831 Ok(v) => out.push(Some(v)),
5832 Err(_) => {
5833 ok = false;
5834 break;
5835 }
5836 }
5837 }
5838 }
5839 }
5840 if ok {
5841 Some(Value::BigIntArray(out))
5842 } else {
5843 None
5844 }
5845 }
5846 #[allow(clippy::cast_possible_truncation)]
5850 (Value::FloatArray(items), DataType::IntArray) => {
5851 let mut out = alloc::vec::Vec::with_capacity(items.len());
5852 let mut ok = true;
5853 for o in items {
5854 match o {
5855 None => out.push(None),
5856 Some(x) if x.is_finite() => {
5857 let r = crate::eval::math::f64_round_half_even(x);
5858 if r >= f64::from(i32::MIN) && r <= f64::from(i32::MAX) {
5859 out.push(Some(r as i32));
5860 } else {
5861 ok = false;
5862 break;
5863 }
5864 }
5865 Some(_) => {
5866 ok = false;
5867 break;
5868 }
5869 }
5870 }
5871 if ok { Some(Value::IntArray(out)) } else { None }
5872 }
5873 #[allow(clippy::cast_possible_truncation)]
5874 (Value::FloatArray(items), DataType::BigIntArray) => {
5875 let mut out = alloc::vec::Vec::with_capacity(items.len());
5876 let mut ok = true;
5877 for o in items {
5878 match o {
5879 None => out.push(None),
5880 Some(x) if x.is_finite() => {
5881 out.push(Some(crate::eval::math::f64_round_half_even(x) as i64));
5882 }
5883 Some(_) => {
5884 ok = false;
5885 break;
5886 }
5887 }
5888 }
5889 if ok {
5890 Some(Value::BigIntArray(out))
5891 } else {
5892 None
5893 }
5894 }
5895 (Value::TextArray(items), DataType::NumericArray) if items.is_empty() => {
5896 Some(Value::NumericArray(alloc::vec::Vec::new()))
5897 }
5898 (Value::TextArray(items), DataType::DateArray) if items.is_empty() => {
5899 Some(Value::DateArray(alloc::vec::Vec::new()))
5900 }
5901 (Value::TextArray(items), DataType::TimestampArray) if items.is_empty() => {
5902 Some(Value::TimestampArray(alloc::vec::Vec::new()))
5903 }
5904 (Value::TextArray(items), DataType::TimestamptzArray) if items.is_empty() => {
5905 Some(Value::TimestamptzArray(alloc::vec::Vec::new()))
5906 }
5907 (Value::TextArray(items), DataType::UuidArray) if items.is_empty() => {
5908 Some(Value::UuidArray(alloc::vec::Vec::new()))
5909 }
5910 (Value::TextArray(items), DataType::JsonArray) if items.is_empty() => {
5911 Some(Value::JsonArray(alloc::vec::Vec::new()))
5912 }
5913 (Value::TextArray(items), DataType::JsonbArray) if items.is_empty() => {
5914 Some(Value::JsonbArray(alloc::vec::Vec::new()))
5915 }
5916 (Value::TextArray(items), DataType::BytesArray) if items.is_empty() => {
5917 Some(Value::BytesArray(alloc::vec::Vec::new()))
5918 }
5919 (Value::TextArray(items), DataType::IntervalArray) if items.is_empty() => {
5920 Some(Value::IntervalArray(alloc::vec::Vec::new()))
5921 }
5922 (
5926 Value::TextArray(items),
5927 dt @ (DataType::BoolArray
5928 | DataType::NumericArray
5929 | DataType::DateArray
5930 | DataType::TimestampArray
5931 | DataType::TimestamptzArray
5932 | DataType::IntervalArray
5933 | DataType::UuidArray),
5934 ) => coerce_text_array_to(items, dt, col_name)?,
5935 (
5941 Value::Text(s),
5942 dt @ (DataType::TimestampArray | DataType::TimestamptzArray | DataType::IntervalArray),
5943 ) => {
5944 let items = decode_text_array_literal(&s).map_err(|_| {
5945 EngineError::Eval(EvalError::TypeMismatch {
5946 detail: malformed_array_literal(&s),
5947 })
5948 })?;
5949 coerce_text_array_to(items, dt, col_name)?
5950 }
5951 (Value::TextArray(items), DataType::MoneyArray) if items.is_empty() => {
5952 Some(Value::MoneyArray(alloc::vec::Vec::new()))
5953 }
5954 (Value::IntArray(items), DataType::SmallIntArray) => {
5959 let mut out = alloc::vec::Vec::with_capacity(items.len());
5960 let mut ok = true;
5961 for item in items {
5962 match item {
5963 None => out.push(None),
5964 Some(n) => match i16::try_from(n) {
5965 Ok(x) => out.push(Some(x)),
5966 Err(_) => {
5967 ok = false;
5968 break;
5969 }
5970 },
5971 }
5972 }
5973 if ok {
5974 Some(Value::SmallIntArray(out))
5975 } else {
5976 None
5977 }
5978 }
5979 (Value::Text(s), DataType::Vector { dim, encoding }) => {
5988 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
5989 EngineError::Eval(EvalError::TypeMismatch {
5990 detail: alloc::format!("cannot parse {s:?} as VECTOR"),
5991 })
5992 })?;
5993 if parsed.len() != dim as usize {
5994 return Err(EngineError::Eval(EvalError::TypeMismatch {
5995 detail: alloc::format!(
5996 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
5997 parsed.len()
5998 ),
5999 }));
6000 }
6001 Some(match encoding {
6002 VecEncoding::F32 => Value::vector(parsed),
6003 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
6004 VecEncoding::F16 => {
6005 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
6006 }
6007 })
6008 }
6009 (Value::Text(s), DataType::TsVector) => {
6019 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
6020 EngineError::Eval(EvalError::TypeMismatch {
6021 detail: alloc::format!("cannot parse {s:?} as TSVECTOR: {e}"),
6022 })
6023 })?;
6024 Some(Value::TsVector(lexs))
6025 }
6026 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
6027 let t = eval::parse_timestamp_literal(&s)
6028 .ok_or_else(|| datetime_parse_error("timestamp", &s))?;
6029 Some(Value::Timestamp(t))
6030 }
6031 (Value::Date(i32::MAX), DataType::Timestamp | DataType::Timestamptz) => {
6034 Some(Value::Timestamp(i64::MAX))
6035 }
6036 (Value::Date(i32::MIN), DataType::Timestamp | DataType::Timestamptz) => {
6037 Some(Value::Timestamp(i64::MIN))
6038 }
6039 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
6040 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
6041 }
6042 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
6046 (Value::Timestamp(t), DataType::Date) => {
6047 let days = t.div_euclid(86_400_000_000);
6048 i32::try_from(days).ok().map(Value::Date)
6049 }
6050 (Value::Timestamp(t), DataType::Time) => Some(Value::Time(t.rem_euclid(86_400_000_000))),
6059 (
6063 Value::NumericBig(b),
6064 DataType::Numeric {
6065 precision: 0,
6066 scale: 0,
6067 },
6068 ) => Some(Value::NumericBig(b)),
6069 (
6070 Value::Numeric {
6071 scaled,
6072 scale: src_scale,
6073 ..
6074 },
6075 DataType::Numeric { precision, scale },
6076 ) => {
6077 if precision == 0 && scale == 0 {
6083 Some(Value::Numeric {
6084 scaled,
6085 scale: src_scale,
6086 kind: spg_storage::NumericKind::Finite,
6087 })
6088 } else {
6089 Some(numeric_rescale(
6090 scaled, src_scale, precision, scale, col_name,
6091 )?)
6092 }
6093 }
6094 (Value::NumericBig(b), DataType::Numeric { precision, scale }) => {
6099 if precision == 0 && scale == 0 {
6100 Some(Value::NumericBig(b))
6101 } else {
6102 #[allow(clippy::cast_sign_loss)]
6103 let rounded = if scale < 0 {
6104 b.round_to(0)
6106 } else {
6107 b.round_to(scale as u16)
6108 };
6109 let out = crate::eval::binop::bignum_to_value(rounded);
6110 crate::numeric::check_precision_text(&out, precision, scale, col_name)?;
6113 Some(out)
6114 }
6115 }
6116 #[allow(clippy::cast_precision_loss)]
6117 (Value::Numeric { scaled, scale, .. }, DataType::Float) => {
6118 let text = crate::eval::format_numeric(scaled, scale);
6125 let x: f64 = text.parse().unwrap_or(f64::NAN);
6126 if x == 0.0 && scaled != 0 {
6130 return Err(float_out_of_range(
6131 &crate::eval::format_numeric(scaled, scale),
6132 "double precision",
6133 ));
6134 }
6135 Some(Value::Float(x))
6136 }
6137 (Value::NumericBig(b), DataType::Real) => {
6145 let text = b.to_decimal_str();
6146 let x: f32 = text.parse().map_err(|_| real_out_of_range(&text))?;
6147 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
6148 return Err(real_out_of_range(&text));
6149 }
6150 Some(Value::Real(x))
6151 }
6152 (Value::NumericBig(b), DataType::Float) => {
6153 let text = b.to_decimal_str();
6157 let x: f64 = text
6158 .parse()
6159 .map_err(|_| float_out_of_range(&text, "double precision"))?;
6160 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
6161 return Err(float_out_of_range(&text, "double precision"));
6162 }
6163 Some(Value::Float(x))
6164 }
6165 (Value::Float(x), DataType::Int) => {
6173 let r = crate::eval::math::f64_round_half_even(x);
6174 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
6175 return Err(EngineError::Eval(EvalError::TypeMismatch {
6176 detail: "integer out of range".into(),
6177 }));
6178 }
6179 #[allow(clippy::cast_possible_truncation)]
6180 Some(Value::Int(r as i32))
6181 }
6182 (Value::Float(x), DataType::BigInt) => {
6183 let r = crate::eval::math::f64_round_half_even(x);
6184 if !r.is_finite()
6185 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
6186 {
6187 return Err(EngineError::Eval(EvalError::TypeMismatch {
6188 detail: "bigint out of range".into(),
6189 }));
6190 }
6191 #[allow(clippy::cast_possible_truncation)]
6192 Some(Value::BigInt(r as i64))
6193 }
6194 (Value::Float(x), DataType::SmallInt) => {
6195 let r = crate::eval::math::f64_round_half_even(x);
6196 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
6197 return Err(EngineError::Eval(EvalError::TypeMismatch {
6198 detail: "smallint out of range".into(),
6199 }));
6200 }
6201 #[allow(clippy::cast_possible_truncation)]
6202 Some(Value::SmallInt(r as i16))
6203 }
6204 (Value::Real(x), DataType::Int) => {
6208 let r = crate::eval::math::f64_round_half_even(f64::from(x));
6209 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
6210 return Err(EngineError::Eval(EvalError::TypeMismatch {
6211 detail: "integer out of range".into(),
6212 }));
6213 }
6214 #[allow(clippy::cast_possible_truncation)]
6215 Some(Value::Int(r as i32))
6216 }
6217 (Value::Real(x), DataType::BigInt) => {
6218 let r = crate::eval::math::f64_round_half_even(f64::from(x));
6219 if !r.is_finite()
6220 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
6221 {
6222 return Err(EngineError::Eval(EvalError::TypeMismatch {
6223 detail: "bigint out of range".into(),
6224 }));
6225 }
6226 #[allow(clippy::cast_possible_truncation)]
6227 Some(Value::BigInt(r as i64))
6228 }
6229 (Value::Real(x), DataType::SmallInt) => {
6230 let r = crate::eval::math::f64_round_half_even(f64::from(x));
6231 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
6232 return Err(EngineError::Eval(EvalError::TypeMismatch {
6233 detail: "smallint out of range".into(),
6234 }));
6235 }
6236 #[allow(clippy::cast_possible_truncation)]
6237 Some(Value::SmallInt(r as i16))
6238 }
6239 (Value::Numeric { scaled, scale, .. }, DataType::Int) => {
6240 let rounded = numeric_round_to_integer(scaled, scale);
6241 i32::try_from(rounded).ok().map(Value::Int)
6242 }
6243 (Value::Numeric { scaled, scale, .. }, DataType::BigInt) => {
6244 let rounded = numeric_round_to_integer(scaled, scale);
6245 i64::try_from(rounded).ok().map(Value::BigInt)
6246 }
6247 (Value::Numeric { scaled, scale, .. }, DataType::SmallInt) => {
6248 let rounded = numeric_round_to_integer(scaled, scale);
6249 i16::try_from(rounded).ok().map(Value::SmallInt)
6250 }
6251 (Value::Text(s), DataType::Name) => {
6258 let mut cut = s.into_owned();
6259 if cut.len() > 63 {
6260 let mut idx = 63;
6261 while !cut.is_char_boundary(idx) {
6262 idx -= 1;
6263 }
6264 cut.truncate(idx);
6265 }
6266 Some(Value::text(cut))
6267 }
6268 (Value::Text(s), DataType::Varchar(max)) => {
6269 if max == 0 || u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
6270 Some(Value::text(s))
6271 } else {
6272 let excess_all_blanks = s.chars().skip(max as usize).all(|c| c == ' ');
6277 if excess_all_blanks {
6278 Some(Value::text(
6279 s.chars()
6280 .take(max as usize)
6281 .collect::<alloc::string::String>(),
6282 ))
6283 } else {
6284 return Err(EngineError::Unsupported(alloc::format!(
6285 "value too long for type character varying({max})"
6286 )));
6287 }
6288 }
6289 }
6290 (
6298 Value::Vector(v),
6299 DataType::Vector {
6300 dim,
6301 encoding: VecEncoding::Sq8,
6302 },
6303 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
6304 (
6309 Value::Vector(v),
6310 DataType::Vector {
6311 dim,
6312 encoding: VecEncoding::F16,
6313 },
6314 ) if v.len() == dim as usize => Some(Value::HalfVector(
6315 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
6316 )),
6317 (Value::Text(s), DataType::Char(size)) => {
6321 if size == 0 {
6325 return Ok(Value::BpChar(alloc::borrow::Cow::Owned(
6326 s.trim_end_matches(' ').to_string(),
6327 )));
6328 }
6329 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
6330 let body = if len > size {
6331 let trimmed = s.trim_end_matches(' ');
6332 let tlen = u32::try_from(trimmed.chars().count()).unwrap_or(u32::MAX);
6333 if tlen > size {
6334 return Err(EngineError::Unsupported(alloc::format!(
6335 "value too long for type character({size})"
6336 )));
6337 }
6338 trimmed.to_string()
6339 } else {
6340 s.into_owned()
6341 };
6342 let need = (size as usize) - body.chars().count();
6343 let mut padded = body;
6344 padded.reserve(need);
6345 for _ in 0..need {
6346 padded.push(' ');
6347 }
6348 Some(Value::BpChar(alloc::borrow::Cow::Owned(padded)))
6352 }
6353 _ => None,
6354 };
6355 coerced.ok_or_else(|| {
6356 EngineError::Storage(StorageError::TypeMismatch {
6357 column: col_name.into(),
6358 expected,
6359 actual,
6360 position,
6361 })
6362 })
6363}
6364
6365pub(crate) fn big_literal_to_value(s: &str) -> Value<'static> {
6368 let b = spg_storage::bignum::BigNumeric::from_decimal_str(s).expect("lexer-validated decimal");
6369 match b.to_i128() {
6370 Some(scaled) => Value::Numeric {
6371 scaled,
6372 scale: b.scale(),
6373 kind: spg_storage::NumericKind::Finite,
6374 },
6375 None => Value::NumericBig(alloc::boxed::Box::new(b)),
6376 }
6377}
6378
6379pub(crate) fn types_unify(a: DataType, b: DataType) -> bool {
6388 fn category(t: DataType) -> Option<u8> {
6389 Some(match t {
6390 DataType::SmallInt
6391 | DataType::Int
6392 | DataType::BigInt
6393 | DataType::Numeric { .. }
6394 | DataType::Real
6395 | DataType::Float => 1,
6396 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => 2,
6397 DataType::Date | DataType::Timestamp | DataType::Timestamptz => 3,
6398 _ => return None,
6399 })
6400 }
6401 if a == b {
6402 return true;
6403 }
6404 match (category(a), category(b)) {
6405 (Some(x), Some(y)) => x == y,
6406 _ => false,
6409 }
6410}
6411
6412pub(crate) fn pg_type_name_for_error_opt(t: Option<DataType>) -> alloc::string::String {
6426 match t {
6427 Some(t) => pg_type_name_for_error(t),
6428 None => alloc::string::String::from("unknown"),
6429 }
6430}
6431
6432pub(crate) fn pg_type_name_for_error(t: DataType) -> alloc::string::String {
6433 use spg_storage::DataType as D;
6434 let elem = match t {
6435 D::TextArray => Some(D::Text),
6436 D::IntArray => Some(D::Int),
6437 D::BigIntArray => Some(D::BigInt),
6438 D::SmallIntArray => Some(D::SmallInt),
6439 D::FloatArray => Some(D::Float),
6440 D::NumericArray => Some(D::Numeric {
6441 precision: 0,
6442 scale: 0,
6443 }),
6444 D::BoolArray => Some(D::Bool),
6445 D::DateArray => Some(D::Date),
6446 D::TimestampArray => Some(D::Timestamp),
6447 D::TimestamptzArray => Some(D::Timestamptz),
6448 D::IntervalArray => Some(D::Interval),
6449 D::UuidArray => Some(D::Uuid),
6450 D::JsonArray | D::JsonbArray => Some(D::Jsonb),
6451 D::BytesArray => Some(D::Bytes),
6452 D::MoneyArray => Some(D::Money),
6453 _ => None,
6454 };
6455 match elem {
6456 Some(e) => alloc::format!("{}[]", crate::system_catalog::pg_data_type_text(e)),
6457 None => crate::system_catalog::pg_data_type_text(t),
6458 }
6459}