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
2990pub(crate) fn round_to_column_float_md(
3010 v: Value<'static>,
3011 schema: &ColumnSchema,
3012) -> Result<Value<'static>, EvalError> {
3013 let Some((m, d)) = schema.mysql_float_md else {
3014 return Ok(v);
3015 };
3016 let round = |x: f64| -> f64 {
3017 if d == 0 {
3018 let f = x.floor();
3020 return if x - f == 0.5 { f } else { x.round() };
3021 }
3022 alloc::format!("{x:.*}", usize::from(d))
3029 .parse::<f64>()
3030 .unwrap_or(x)
3031 };
3032 let limit = 10f64.powi(i32::from(m.saturating_sub(d)));
3036 let checked = |x: f64| -> Result<f64, EvalError> {
3037 let r = round(x);
3038 if r.abs() >= limit {
3039 return Err(EvalError::TypeMismatch {
3040 detail: alloc::format!("Out of range value for column '{}' at row 1", schema.name),
3041 });
3042 }
3043 Ok(r)
3044 };
3045 match v {
3046 Value::Float(x) => Ok(Value::Float(checked(x)?)),
3047 #[allow(clippy::cast_possible_truncation)]
3048 Value::Real(x) => Ok(Value::Real(checked(f64::from(x))? as f32)),
3049 other => Ok(other),
3050 }
3051}
3052
3053fn column_int_bounds(schema: &ColumnSchema) -> Option<(i128, i128)> {
3058 if let Some(width) = schema.mysql_int_width {
3059 return Some(match (width, schema.is_unsigned) {
3060 (spg_storage::MysqlIntWidth::Tiny, false) => (-128, 127),
3061 (spg_storage::MysqlIntWidth::Tiny, true) => (0, 255),
3062 (spg_storage::MysqlIntWidth::Small, false) => (-32_768, 32_767),
3063 (spg_storage::MysqlIntWidth::Small, true) => (0, 65_535),
3064 (spg_storage::MysqlIntWidth::Medium, false) => (-8_388_608, 8_388_607),
3065 (spg_storage::MysqlIntWidth::Medium, true) => (0, 16_777_215),
3066 (spg_storage::MysqlIntWidth::Int, false) => (-2_147_483_648, 2_147_483_647),
3067 (spg_storage::MysqlIntWidth::Int, true) => (0, 4_294_967_295),
3068 (spg_storage::MysqlIntWidth::Big, false) => {
3071 (i128::from(i64::MIN), i128::from(i64::MAX))
3072 }
3073 (spg_storage::MysqlIntWidth::Big, true) => (0, i128::from(u64::MAX)),
3074 });
3075 }
3076 let (lo, hi) = match schema.ty {
3077 DataType::SmallInt => (i128::from(i16::MIN), i128::from(i16::MAX)),
3078 DataType::Int => (i128::from(i32::MIN), i128::from(i32::MAX)),
3079 DataType::BigInt => (i128::from(i64::MIN), i128::from(i64::MAX)),
3080 _ => return None,
3081 };
3082 Some(if schema.is_unsigned {
3083 (0, hi)
3084 } else {
3085 (lo, hi)
3086 })
3087}
3088
3089pub(crate) fn mysql_fit_error(
3152 before: &Value<'_>,
3153 after: &Value<'_>,
3154 schema: &ColumnSchema,
3155 row: usize,
3156 omitted: bool,
3157) -> Option<(u16, &'static str, alloc::string::String)> {
3158 let w = mysql_fit_warning(before, after, schema, row, omitted)?;
3159 let col = &schema.name;
3160 Some(match w.code {
3161 1265 if matches!(
3164 schema.ty,
3165 DataType::Varchar(_) | DataType::Char(_) | DataType::Text
3166 ) =>
3167 {
3168 (
3169 1406,
3170 "22001",
3171 alloc::format!("Data too long for column '{col}' at row {row}"),
3172 )
3173 }
3174 1265 => (1265, "01000", w.message),
3175 1264 => (1264, "22003", w.message),
3176 1366 => (1366, "HY000", w.message),
3177 1364 => (1364, "HY000", w.message),
3178 other => (other, "HY000", w.message),
3179 })
3180}
3181
3182pub(crate) fn mysql_fit_warning(
3183 before: &Value<'_>,
3184 after: &Value<'_>,
3185 schema: &ColumnSchema,
3186 row: usize,
3187 omitted: bool,
3188) -> Option<crate::MysqlWarning> {
3189 if before == after {
3190 return None;
3191 }
3192 let col = &schema.name;
3193 if omitted || before.is_null() {
3196 return Some(crate::MysqlWarning {
3197 level: "Warning",
3198 code: 1364,
3199 message: alloc::format!("Field '{col}' doesn't have a default value"),
3200 });
3201 }
3202 let numeric_col = matches!(
3206 schema.ty,
3207 DataType::SmallInt
3208 | DataType::Int
3209 | DataType::BigInt
3210 | DataType::Float
3211 | DataType::Real
3212 | DataType::Numeric { .. }
3213 );
3214 if numeric_col {
3215 let noun = match schema.ty {
3227 DataType::Numeric { .. } => "decimal",
3228 DataType::Real => "FLOAT",
3229 DataType::Float => "DOUBLE",
3230 _ => "integer",
3231 };
3232 return Some(if matches!(before, Value::Text(_) | Value::BpChar(_)) {
3235 crate::MysqlWarning {
3236 level: "Warning",
3237 code: 1366,
3238 message: alloc::format!(
3239 "Incorrect {noun} value: '{}' for column '{col}' at row {row}",
3240 crate::eval::value_to_text(before)
3241 ),
3242 }
3243 } else {
3244 crate::MysqlWarning {
3245 level: "Warning",
3246 code: 1264,
3247 message: alloc::format!("Out of range value for column '{col}' at row {row}"),
3248 }
3249 });
3250 }
3251 Some(crate::MysqlWarning {
3252 level: "Warning",
3253 code: 1265,
3254 message: alloc::format!("Data truncated for column '{col}' at row {row}"),
3255 })
3256}
3257
3258fn numeric_untouched(v: Value<'static>, _schema: &ColumnSchema) -> Value<'static> {
3261 v
3262}
3263
3264fn restate_scaled(scaled: i128, from: u16, to: u16) -> i128 {
3268 if from == to {
3269 return scaled;
3270 }
3271 if to > from {
3272 let f = 10i128.checked_pow(u32::from(to - from)).unwrap_or(1);
3273 return scaled.saturating_mul(f);
3274 }
3275 let f = 10i128.checked_pow(u32::from(from - to)).unwrap_or(1);
3276 if f == 0 {
3277 return scaled;
3278 }
3279 let half = f / 2;
3280 if scaled >= 0 {
3281 (scaled + half) / f
3282 } else {
3283 (scaled - half) / f
3284 }
3285}
3286
3287pub(crate) fn mysql_ignore_fit(v: Value<'static>, schema: &ColumnSchema) -> Value<'static> {
3288 if v.is_null() {
3289 if schema.nullable {
3290 return v;
3291 }
3292 return match schema.ty {
3294 DataType::SmallInt | DataType::Int | DataType::BigInt => Value::BigInt(0),
3295 DataType::Float | DataType::Real => Value::Float(0.0),
3296 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(""),
3297 _ => v,
3298 };
3299 }
3300 if let Value::Text(ref s) = v
3303 && matches!(
3304 schema.ty,
3305 DataType::SmallInt | DataType::Int | DataType::BigInt
3306 )
3307 && s.trim().parse::<i64>().is_err()
3308 {
3309 return Value::BigInt(leading_numeric_prefix(s));
3310 }
3311 if let DataType::Numeric { precision, scale } = schema.ty
3329 && precision != 0
3330 && scale >= 0
3331 {
3332 let col_scale = u16::try_from(scale).unwrap_or(0);
3333 let (scaled, val_scale) = match v {
3334 Value::Numeric {
3335 scaled,
3336 scale: vs,
3337 kind: spg_storage::NumericKind::Finite,
3338 } => (scaled, vs),
3339 Value::SmallInt(n) => (i128::from(n), 0),
3340 Value::Int(n) => (i128::from(n), 0),
3341 Value::BigInt(n) => (i128::from(n), 0),
3342 _ => return numeric_untouched(v, schema),
3343 };
3344 let restated = restate_scaled(scaled, val_scale, col_scale);
3358 let limit = 10i128
3359 .checked_pow(u32::from(precision))
3360 .map_or(i128::MAX, |p| p - 1);
3361 if restated < -limit || restated > limit {
3362 return Value::numeric(restated.clamp(-limit, limit), col_scale);
3363 }
3364 return numeric_untouched(v, schema);
3365 }
3366 let as_int = match v {
3368 Value::SmallInt(n) => Some(i128::from(n)),
3369 Value::Int(n) => Some(i128::from(n)),
3370 Value::BigInt(n) => Some(i128::from(n)),
3371 Value::Numeric {
3373 scaled, scale: 0, ..
3374 } => Some(scaled),
3375 _ => None,
3376 };
3377 if let Some(n) = as_int
3378 && let Some((lo, hi)) = column_int_bounds(schema)
3379 && (n < lo || n > hi)
3380 {
3381 return int_value_for_column(n.clamp(lo, hi));
3382 }
3383 if let Value::Text(ref s) = v {
3385 let max = match schema.ty {
3386 DataType::Varchar(m) | DataType::Char(m) if m > 0 => m as usize,
3387 _ => return v,
3388 };
3389 if s.chars().count() > max {
3390 return Value::text(s.chars().take(max).collect::<alloc::string::String>());
3391 }
3392 }
3393 v
3394}
3395
3396fn leading_numeric_prefix(s: &str) -> i64 {
3405 let t = s.trim_start();
3406 let b = t.as_bytes();
3407 let mut i = 0;
3408 if i < b.len() && (b[i] == b'-' || b[i] == b'+') {
3409 i += 1;
3410 }
3411 let int_start = i;
3412 while i < b.len() && b[i].is_ascii_digit() {
3413 i += 1;
3414 }
3415 let mut end = i;
3416 if i < b.len() && b[i] == b'.' {
3417 i += 1;
3418 while i < b.len() && b[i].is_ascii_digit() {
3419 i += 1;
3420 }
3421 if i > int_start + 1 {
3424 end = i;
3425 }
3426 }
3427 if end > int_start && i < b.len() && (b[i] == b'e' || b[i] == b'E') {
3429 let mut j = i + 1;
3430 if j < b.len() && (b[j] == b'-' || b[j] == b'+') {
3431 j += 1;
3432 }
3433 let digits_start = j;
3434 while j < b.len() && b[j].is_ascii_digit() {
3435 j += 1;
3436 }
3437 if j > digits_start {
3438 end = j;
3439 }
3440 }
3441 let Ok(f) = t[..end].parse::<f64>() else {
3442 return 0;
3443 };
3444 let r = f.round();
3446 if r >= i64::MAX as f64 {
3447 i64::MAX
3448 } else if r <= i64::MIN as f64 {
3449 i64::MIN
3450 } else {
3451 r as i64
3452 }
3453}
3454
3455fn int_value_for_column(n: i128) -> Value<'static> {
3459 match i64::try_from(n) {
3460 Ok(v) => Value::BigInt(v),
3461 Err(_) => Value::numeric(n, 0),
3462 }
3463}
3464
3465pub(crate) fn check_unsigned_range(
3466 v: &Value,
3467 schema: &ColumnSchema,
3468 position: usize,
3469) -> Result<(), EngineError> {
3470 let n: i128 = match v {
3471 Value::SmallInt(x) => i128::from(*x),
3472 Value::Int(x) => i128::from(*x),
3473 Value::BigInt(x) => i128::from(*x),
3474 Value::Numeric { scaled, scale, .. } if *scale == 0 => *scaled,
3477 _ => return Ok(()), };
3479 if let Some(width) = schema.mysql_int_width {
3483 let _ = width;
3489 let (lo, hi) = column_int_bounds(schema).unwrap_or((i128::MIN, i128::MAX));
3490 if n < lo || n > hi {
3491 return Err(EngineError::Unsupported(alloc::format!(
3494 "Out of range value for column '{}'",
3495 schema.name
3496 )));
3497 }
3498 return Ok(());
3499 }
3500 if schema.is_unsigned && n < 0 {
3502 return Err(EngineError::Unsupported(alloc::format!(
3503 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
3504 schema.name
3505 )));
3506 }
3507 Ok(())
3508}
3509
3510fn coerce_text_array_to(
3516 items: alloc::vec::Vec<Option<alloc::string::String>>,
3517 target: DataType,
3518 col: &str,
3519) -> Result<Option<Value<'static>>, EngineError> {
3520 let elem_dt = match target {
3521 DataType::BoolArray => DataType::Bool,
3522 DataType::NumericArray => DataType::Numeric {
3523 precision: 0,
3524 scale: 0,
3525 },
3526 DataType::DateArray => DataType::Date,
3527 DataType::TimestampArray => DataType::Timestamp,
3528 DataType::TimestamptzArray => DataType::Timestamptz,
3529 DataType::UuidArray => DataType::Uuid,
3530 DataType::IntervalArray => DataType::Interval,
3533 _ => return Ok(None),
3534 };
3535 let mut scal: alloc::vec::Vec<Option<Value<'static>>> =
3536 alloc::vec::Vec::with_capacity(items.len());
3537 for item in items {
3538 match item {
3539 None => scal.push(None),
3540 Some(s) => scal.push(Some(coerce_value(Value::text(s), elem_dt, col, 0)?)),
3541 }
3542 }
3543 let out = match target {
3544 DataType::BoolArray => Value::BoolArray(
3545 scal.into_iter()
3546 .map(|o| o.map(|v| matches!(v, Value::Bool(true))))
3547 .collect(),
3548 ),
3549 DataType::NumericArray => Value::NumericArray(
3550 scal.into_iter()
3551 .map(|o| {
3552 o.map(|v| match v {
3553 Value::Numeric { scaled, scale, .. } => (scaled, scale),
3554 _ => (0, 0),
3555 })
3556 })
3557 .collect(),
3558 ),
3559 DataType::DateArray => Value::DateArray(
3560 scal.into_iter()
3561 .map(|o| {
3562 o.map(|v| match v {
3563 Value::Date(d) => d,
3564 _ => 0,
3565 })
3566 })
3567 .collect(),
3568 ),
3569 DataType::TimestampArray => Value::TimestampArray(
3570 scal.into_iter()
3571 .map(|o| {
3572 o.map(|v| match v {
3573 Value::Timestamp(t) => t,
3574 _ => 0,
3575 })
3576 })
3577 .collect(),
3578 ),
3579 DataType::TimestamptzArray => Value::TimestamptzArray(
3580 scal.into_iter()
3581 .map(|o| {
3582 o.map(|v| match v {
3583 Value::Timestamp(t) => t,
3584 _ => 0,
3585 })
3586 })
3587 .collect(),
3588 ),
3589 DataType::UuidArray => Value::UuidArray(
3590 scal.into_iter()
3591 .map(|o| {
3592 o.map(|v| match v {
3593 Value::Uuid(u) => u,
3594 _ => [0u8; 16],
3595 })
3596 })
3597 .collect(),
3598 ),
3599 DataType::IntervalArray => Value::IntervalArray(
3600 scal.into_iter()
3601 .map(|o| {
3602 o.and_then(|v| match v {
3603 Value::Interval {
3604 months,
3605 days,
3606 micros,
3607 kind,
3608 } => Some(spg_storage::IntervalSpan {
3609 months,
3610 days,
3611 micros,
3612 kind,
3613 }),
3614 _ => None,
3615 })
3616 })
3617 .collect(),
3618 ),
3619 _ => return Ok(None),
3620 };
3621 Ok(Some(out))
3622}
3623
3624pub(crate) fn array_oid_element(oid: i64) -> Option<i64> {
3635 Some(match oid {
3636 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,
3670 })
3671}
3672
3673pub(crate) fn regtype_oid_to_name_owned(oid: i64) -> Option<alloc::string::String> {
3680 if let Some(scalar) = regtype_oid_to_name(oid) {
3681 return Some(alloc::string::String::from(scalar));
3682 }
3683 let (_, _, elem) = crate::system_catalog::ARRAY_TYPE_OIDS
3684 .iter()
3685 .find(|(arr, _, _)| *arr == oid)?;
3686 Some(alloc::format!("{}[]", regtype_oid_to_name(*elem)?))
3687}
3688
3689pub(crate) fn array_oid_for_element(elem: i64) -> Option<i64> {
3691 crate::system_catalog::ARRAY_TYPE_OIDS
3692 .iter()
3693 .find(|(_, _, e)| *e == elem)
3694 .map(|(arr, _, _)| *arr)
3695}
3696
3697pub(crate) fn regtype_oid_to_name(oid: i64) -> Option<&'static str> {
3698 Some(match oid {
3699 4600 => "pg_brin_bloom_summary",
3700 16 => "boolean",
3701 17 => "bytea",
3702 18 => "\"char\"",
3703 19 => "name",
3704 20 => "bigint",
3705 21 => "smallint",
3706 23 => "integer",
3707 25 => "text",
3708 26 => "oid",
3709 27 => "tid",
3711 28 => "xid",
3712 29 => "cid",
3713 5069 => "xid8",
3714 114 => "json",
3715 142 => "xml",
3716 650 => "cidr",
3717 700 => "real",
3718 701 => "double precision",
3719 774 => "macaddr8",
3720 790 => "money",
3721 829 => "macaddr",
3722 869 => "inet",
3723 1042 => "character",
3724 1043 => "character varying",
3725 1082 => "date",
3726 1083 => "time without time zone",
3727 1114 => "timestamp without time zone",
3728 1184 => "timestamp with time zone",
3729 1186 => "interval",
3730 1266 => "time with time zone",
3731 1560 => "bit",
3732 1562 => "bit varying",
3733 1700 => "numeric",
3734 2950 => "uuid",
3735 3614 => "tsvector",
3736 3615 => "tsquery",
3737 3802 => "jsonb",
3738 3904 => "int4range",
3739 3906 => "numrange",
3740 3908 => "tsrange",
3741 3910 => "tstzrange",
3742 3912 => "daterange",
3743 3926 => "int8range",
3744 _ => return None,
3745 })
3746}
3747
3748pub(crate) fn parse_pg_int(s: &str) -> Option<i64> {
3749 let s = s.trim();
3750 let (neg, rest) = if let Some(r) = s.strip_prefix('-') {
3751 (true, r)
3752 } else if let Some(r) = s.strip_prefix('+') {
3753 (false, r)
3754 } else {
3755 (false, s)
3756 };
3757 let (radix, digits, has_prefix) =
3762 if let Some(h) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
3763 (16u32, h, true)
3764 } else if let Some(o) = rest.strip_prefix("0o").or_else(|| rest.strip_prefix("0O")) {
3765 (8, o, true)
3766 } else if let Some(b) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) {
3767 (2, b, true)
3768 } else {
3769 (10, rest, false)
3770 };
3771 let db = digits.as_bytes();
3772 if db.last() == Some(&b'_')
3776 || digits.contains("__")
3777 || (!has_prefix && db.first() == Some(&b'_'))
3778 {
3779 return None;
3780 }
3781 let cleaned: alloc::string::String = digits.chars().filter(|&c| c != '_').collect();
3782 if cleaned.is_empty() {
3783 return None;
3784 }
3785 let mag = i64::from_str_radix(&cleaned, radix).ok()?;
3786 Some(if neg { mag.checked_neg()? } else { mag })
3787}
3788
3789fn xml_content_is_well_formed(s: &str) -> bool {
3798 let b = s.as_bytes();
3799 let is_name =
3800 |c: u8| c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_' | b'.' | b':') || c >= 0x80;
3801 let mut stack: alloc::vec::Vec<&[u8]> = alloc::vec::Vec::new();
3802 let mut i = 0;
3803 while i < b.len() {
3804 if b[i] != b'<' {
3805 i += 1;
3806 continue;
3807 }
3808 let rest = &s[i..];
3809 if rest.starts_with("<!--") {
3810 match rest.find("-->") {
3811 Some(p) => i += p + 3,
3812 None => return false,
3813 }
3814 } else if rest.starts_with("<![CDATA[") {
3815 match rest.find("]]>") {
3816 Some(p) => i += p + 3,
3817 None => return false,
3818 }
3819 } else if rest.starts_with("<?") {
3820 match rest.find("?>") {
3821 Some(p) => i += p + 2,
3822 None => return false,
3823 }
3824 } else if rest.starts_with("<!") {
3825 match rest.find('>') {
3826 Some(p) => i += p + 1,
3827 None => return false,
3828 }
3829 } else {
3830 let close = i + 1 < b.len() && b[i + 1] == b'/';
3832 let name_start = if close { i + 2 } else { i + 1 };
3833 let mut j = name_start;
3834 while j < b.len() && is_name(b[j]) {
3835 j += 1;
3836 }
3837 if j == name_start {
3838 return false; }
3840 let name = &b[name_start..j];
3841 let mut k = j;
3843 let mut quote = 0u8;
3844 let mut prev = 0u8;
3845 loop {
3846 if k >= b.len() {
3847 return false; }
3849 let c = b[k];
3850 if quote != 0 {
3851 if c == quote {
3852 quote = 0;
3853 }
3854 } else if c == b'"' || c == b'\'' {
3855 quote = c;
3856 } else if c == b'>' {
3857 break;
3858 }
3859 prev = c;
3860 k += 1;
3861 }
3862 let self_closing = prev == b'/';
3863 i = k + 1;
3864 if close {
3865 match stack.pop() {
3866 Some(top) if top == name => {}
3867 _ => return false,
3868 }
3869 } else if !self_closing {
3870 stack.push(name);
3871 }
3872 }
3873 }
3874 stack.is_empty()
3875}
3876
3877pub(crate) fn parse_float8(s: &str) -> Option<f64> {
3883 let t = s.trim();
3884 let parsed = t.parse::<f64>().ok()?;
3885 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
3886 let numeric_looking = body
3887 .bytes()
3888 .next()
3889 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
3890 if numeric_looking {
3891 if parsed.is_infinite() {
3892 return None; }
3894 if parsed == 0.0 {
3895 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
3897 if mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0') {
3898 return None;
3899 }
3900 }
3901 }
3902 Some(parsed)
3903}
3904
3905fn decode_array_elems(
3909 s: &str,
3910 elem: DataType,
3911 col_name: &str,
3912 position: usize,
3913) -> Result<Vec<Option<Value<'static>>>, EngineError> {
3914 let raw = decode_text_array_literal(s).map_err(|_| {
3920 EngineError::Eval(EvalError::TypeMismatch {
3921 detail: malformed_array_literal(s),
3922 })
3923 })?;
3924 let mut out = Vec::with_capacity(raw.len());
3925 for e in raw {
3926 match e {
3927 None => out.push(None),
3928 Some(t) => out.push(Some(coerce_value(
3929 Value::text(t),
3930 elem,
3931 col_name,
3932 position,
3933 )?)),
3934 }
3935 }
3936 Ok(out)
3937}
3938
3939fn coerce_untyped_value(
3943 v: Value<'static>,
3944 expected: DataType,
3945 col_name: &str,
3946 position: usize,
3947) -> Result<Value<'static>, EngineError> {
3948 match (&v, expected) {
3949 (
3960 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3961 DataType::BigInt | DataType::Oid,
3962 ) => Ok(Value::BigInt(*oid)),
3963 (
3964 Value::RegClass(oid, _) | Value::RegProc(oid, _) | Value::RegType(oid, _),
3965 DataType::Int,
3966 ) => Ok(Value::Int(i32::try_from(*oid).unwrap_or(i32::MAX))),
3967 (
3968 Value::RegClass(_, name) | Value::RegProc(_, name) | Value::RegType(_, name),
3969 DataType::Text,
3970 ) => Ok(Value::text(alloc::string::String::from(name.as_ref()))),
3971 (Value::Composite(fields), DataType::Jsonb | DataType::Json) => {
3977 let mut obj = alloc::string::String::from("{");
3978 for (i, (name, val)) in fields.iter().enumerate() {
3979 if i > 0 {
3980 obj.push(',');
3981 }
3982 obj.push_str(&crate::json::value_to_json_text(&Value::text(
3984 alloc::string::String::from(name.as_str()),
3985 )));
3986 obj.push(':');
3987 obj.push_str(&crate::json::value_to_json_text(val));
3988 }
3989 obj.push('}');
3990 Ok(Value::Json(alloc::borrow::Cow::Owned(obj)))
3991 }
3992 (Value::Composite(_), DataType::Text) => Ok(Value::text(crate::eval::value_to_text(&v))),
3994 _ => Err(EngineError::Unsupported(alloc::format!(
3995 "cannot coerce {:?} to {expected:?} for column {col_name:?} (position {position})",
3996 v
3997 ))),
3998 }
3999}
4000
4001fn invalid_input_syntax(ty: &str, value: &str) -> EngineError {
4005 EngineError::Eval(EvalError::TypeMismatch {
4006 detail: alloc::format!("invalid input syntax for type {ty}: \"{value}\""),
4007 })
4008}
4009
4010fn real_out_of_range(value: &str) -> EngineError {
4013 float_out_of_range(value, "real")
4014}
4015
4016fn float_out_of_range(value: &str, ty: &str) -> EngineError {
4018 EngineError::Eval(EvalError::TypeMismatch {
4019 detail: alloc::format!("\"{value}\" is out of range for type {ty}"),
4020 })
4021}
4022
4023fn float_text_error(s: &str, ty: &str) -> EngineError {
4029 let t = s.trim();
4030 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
4031 let numeric_looking = body
4032 .bytes()
4033 .next()
4034 .is_some_and(|c| c.is_ascii_digit() || c == b'.');
4035 if numeric_looking && t.parse::<f64>().is_ok() {
4036 float_out_of_range(t, ty)
4037 } else {
4038 invalid_input_syntax(ty, s)
4039 }
4040}
4041
4042fn float_text_is_nonzero(t: &str) -> bool {
4046 let body = t.strip_prefix(['+', '-']).unwrap_or(t);
4047 let mantissa = body.split(['e', 'E']).next().unwrap_or(body);
4048 mantissa.bytes().any(|c| c.is_ascii_digit() && c != b'0')
4049}
4050
4051fn text_is_explicit_infinity(t: &str) -> bool {
4054 let t = t.trim_start_matches(['+', '-']);
4055 t.eq_ignore_ascii_case("inf") || t.eq_ignore_ascii_case("infinity")
4056}
4057
4058fn datetime_parse_error(ty: &str, s: &str) -> EngineError {
4067 let t = s.trim();
4068 let date_shaped = t.chars().any(|c| c.is_ascii_digit())
4069 && t.chars().all(|c| {
4070 c.is_ascii_digit() || matches!(c, '-' | '/' | ':' | '.' | ' ' | '+' | 'T' | 't')
4071 });
4072 let detail = if date_shaped {
4073 alloc::format!("date/time field value out of range: \"{t}\"")
4074 } else {
4075 alloc::format!("invalid input syntax for type {ty}: \"{t}\"")
4076 };
4077 EngineError::Eval(EvalError::TypeMismatch { detail })
4078}
4079
4080pub(crate) enum JsonbScalar {
4086 Numeric(Value<'static>),
4087 Bool(bool),
4088 Null,
4089}
4090
4091pub(crate) fn jsonb_cast_type_error(kind: &str, target: &str) -> EvalError {
4093 EvalError::TypeMismatch {
4094 detail: alloc::format!("cannot cast jsonb {kind} to type {target}"),
4095 }
4096}
4097
4098pub(crate) fn jsonb_scalar_for_cast(s: &str, target: &str) -> Result<JsonbScalar, EvalError> {
4101 use crate::json::JsonValue;
4102 match crate::json::parse(s) {
4103 Ok(JsonValue::Null) => Ok(JsonbScalar::Null),
4104 Ok(JsonValue::Bool(b)) => Ok(JsonbScalar::Bool(b)),
4105 Ok(JsonValue::Number(x)) => {
4109 let num = coerce_value(
4110 Value::text(alloc::format!("{x}")),
4111 DataType::Numeric {
4112 precision: 0,
4113 scale: 0,
4114 },
4115 "",
4116 0,
4117 )
4118 .map_err(|e| match e {
4119 EngineError::Eval(ev) => ev,
4120 _ => jsonb_cast_type_error("numeric", target),
4121 })?;
4122 Ok(JsonbScalar::Numeric(num))
4123 }
4124 Ok(JsonValue::NumberText(text)) => {
4125 let num = coerce_value(
4126 Value::text(text),
4127 DataType::Numeric {
4128 precision: 0,
4129 scale: 0,
4130 },
4131 "",
4132 0,
4133 )
4134 .map_err(|e| match e {
4135 EngineError::Eval(ev) => ev,
4136 _ => jsonb_cast_type_error("numeric", target),
4137 })?;
4138 Ok(JsonbScalar::Numeric(num))
4139 }
4140 Ok(JsonValue::String(_)) => Err(jsonb_cast_type_error("string", target)),
4141 Ok(JsonValue::Array(_)) => Err(jsonb_cast_type_error("array", target)),
4142 Ok(JsonValue::Object(_)) => Err(jsonb_cast_type_error("object", target)),
4143 Err(_) => Err(jsonb_cast_type_error("value", target)),
4144 }
4145}
4146pub(crate) fn normalize_composite_for_column(
4164 v: Value<'static>,
4165 col: &ColumnSchema,
4166 catalog: Option<&spg_storage::Catalog>,
4167) -> Result<Value<'static>, EngineError> {
4168 let Some(tname) = col.user_composite_type.as_deref() else {
4169 return Ok(v);
4170 };
4171 if matches!(v, Value::Null) {
4172 return Ok(v);
4173 }
4174 let Some(def) = catalog.and_then(|c| c.composite_types().get(tname)) else {
4177 return Ok(v);
4178 };
4179 if matches!(v, Value::Json(_)) {
4182 return Ok(v);
4183 }
4184 crate::eval::apply_composite_cast_pub(v, def, catalog).map_err(EngineError::Eval)
4185}
4186
4187fn try_coerce_json_scalar(
4191 s: &str,
4192 expected: DataType,
4193 col_name: &str,
4194 position: usize,
4195) -> Option<Result<Value<'static>, EngineError>> {
4196 let target = match expected {
4197 DataType::Int => "integer",
4198 DataType::BigInt => "bigint",
4199 DataType::SmallInt => "smallint",
4200 DataType::Numeric { .. } => "numeric",
4201 DataType::Real => "real",
4202 DataType::Float => "double precision",
4203 DataType::Bool => "boolean",
4204 _ => return None,
4205 };
4206 Some(
4207 (|| match jsonb_scalar_for_cast(s, target).map_err(EngineError::Eval)? {
4208 JsonbScalar::Null => Ok(Value::Null),
4209 JsonbScalar::Bool(b) => {
4210 if matches!(expected, DataType::Bool) {
4211 Ok(Value::Bool(b))
4212 } else {
4213 Err(EngineError::Eval(jsonb_cast_type_error("boolean", target)))
4214 }
4215 }
4216 JsonbScalar::Numeric(n) => {
4217 if matches!(expected, DataType::Bool) {
4218 Err(EngineError::Eval(jsonb_cast_type_error("numeric", target)))
4219 } else {
4220 coerce_value(n, expected, col_name, position)
4221 }
4222 }
4223 })(),
4224 )
4225}
4226
4227pub(crate) fn mysql_bytes_for_column(
4237 v: Value<'static>,
4238 expected: DataType,
4239 mysql: bool,
4240) -> Value<'static> {
4241 if !mysql {
4242 return v;
4243 }
4244 let Value::Bytes(ref b) = v else {
4245 return v;
4246 };
4247 match expected {
4248 DataType::SmallInt
4249 | DataType::Int
4250 | DataType::BigInt
4251 | DataType::Float
4252 | DataType::Real
4253 | DataType::Numeric { .. } => {
4254 let start = b.len().saturating_sub(16);
4255 let acc = b[start..]
4256 .iter()
4257 .fold(0u128, |a, &x| (a << 8) | u128::from(x));
4258 if acc <= i64::MAX as u128 {
4259 #[allow(clippy::cast_possible_truncation)]
4260 Value::BigInt(acc as i64)
4261 } else {
4262 big_literal_to_value(&alloc::format!("{acc}"))
4263 }
4264 }
4265 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => Value::text(
4266 b.iter()
4267 .map(|&x| x as char)
4268 .collect::<alloc::string::String>(),
4269 ),
4270 _ => v,
4271 }
4272}
4273
4274fn try_coerce_time_family(
4293 v: &Value<'static>,
4294 expected: DataType,
4295) -> Option<Result<Value<'static>, EngineError>> {
4296 const DAY_US: i64 = 86_400_000_000;
4297 if expected != DataType::Time {
4298 return None;
4299 }
4300 match v {
4301 Value::TimeTz { us, .. } => Some(Ok(Value::Time(*us))),
4302 Value::Interval { micros, .. } => Some(Ok(Value::Time(micros.rem_euclid(DAY_US)))),
4303 _ => None,
4304 }
4305}
4306
4307pub(crate) fn coerce_to_oid(v: &Value<'_>) -> Result<Option<Value<'static>>, EvalError> {
4317 let as_i64 = match v {
4318 Value::Null => return Ok(Some(Value::Null)),
4319 Value::SmallInt(n) => i64::from(*n),
4320 Value::Int(n) => i64::from(*n),
4321 Value::BigInt(n) => *n,
4322 Value::Text(t) => match t.trim().parse::<i64>() {
4323 Ok(n) => n,
4324 Err(_) => {
4325 return Err(EvalError::TypeMismatch {
4326 detail: alloc::format!("invalid input syntax for type oid: {:?}", t.trim()),
4327 });
4328 }
4329 },
4330 _ => return Ok(None),
4331 };
4332 if (-(1i64 << 31)..0).contains(&as_i64) {
4334 return Ok(Some(Value::BigInt(as_i64 + (1i64 << 32))));
4335 }
4336 if !(0..=i64::from(u32::MAX)).contains(&as_i64) {
4337 return Err(EvalError::TypeMismatch {
4338 detail: "OID out of range".into(),
4339 });
4340 }
4341 Ok(Some(Value::BigInt(as_i64)))
4342}
4343
4344pub(crate) fn coerce_value(
4345 v: Value<'static>,
4346 expected: DataType,
4347 col_name: &str,
4348 position: usize,
4349) -> Result<Value<'static>, EngineError> {
4350 if v.is_null() {
4351 return Ok(Value::Null);
4352 }
4353 if let Value::Json(ref s) = v {
4358 if let Some(res) = try_coerce_json_scalar(s, expected, col_name, position) {
4359 return res;
4360 }
4361 }
4362 if let Some(res) = try_coerce_time_family(&v, expected) {
4366 return res;
4367 }
4368 if let Value::Numeric { kind, .. } = v
4384 && kind != spg_storage::NumericKind::Finite
4385 {
4386 use spg_storage::NumericKind as K;
4387 let as_f64 = match kind {
4388 K::NaN => f64::NAN,
4389 K::PosInf => f64::INFINITY,
4390 K::NegInf => f64::NEG_INFINITY,
4391 K::Finite => unreachable!("checked above"),
4392 };
4393 let what = if kind == K::NaN { "NaN" } else { "infinity" };
4395 let int_err = |target: &str| {
4396 Err(EngineError::Eval(EvalError::TypeMismatch {
4397 detail: alloc::format!("cannot convert {what} to {target}"),
4398 }))
4399 };
4400 match expected {
4401 DataType::Float => return Ok(Value::Float(as_f64)),
4402 #[allow(clippy::cast_possible_truncation)]
4403 DataType::Real => return Ok(Value::Real(as_f64 as f32)),
4404 DataType::Int => return int_err("integer"),
4405 DataType::BigInt => return int_err("bigint"),
4406 DataType::SmallInt => return int_err("smallint"),
4407 DataType::Numeric { precision, scale } => {
4408 if precision != 0 && kind != K::NaN {
4412 return Err(EngineError::Eval(EvalError::TypeMismatch {
4413 detail: alloc::string::String::from("numeric field overflow"),
4414 }));
4415 }
4416 let _ = scale;
4417 return Ok(v);
4418 }
4419 _ => {}
4420 }
4421 }
4422 if let DataType::Numeric { precision, .. } = expected {
4426 let f = match v {
4427 Value::Float(f) if !f.is_finite() => Some(f),
4428 #[allow(clippy::cast_lossless)]
4429 Value::Real(f) if !f.is_finite() => Some(f as f64),
4430 _ => None,
4431 };
4432 if let Some(f) = f {
4433 use spg_storage::NumericKind as K;
4434 if f.is_nan() {
4435 return Ok(Value::numeric_special(K::NaN));
4436 }
4437 if precision != 0 {
4438 return Err(EngineError::Eval(EvalError::TypeMismatch {
4439 detail: alloc::string::String::from("numeric field overflow"),
4440 }));
4441 }
4442 return Ok(Value::numeric_special(if f > 0.0 {
4443 K::PosInf
4444 } else {
4445 K::NegInf
4446 }));
4447 }
4448 }
4449 let Some(actual) = v.data_type() else {
4450 return coerce_untyped_value(v, expected, col_name, position);
4451 };
4452 if actual == expected {
4453 return Ok(v);
4454 }
4455 if matches!(expected, DataType::Json | DataType::Jsonb)
4479 && let Value::Text(ref s) | Value::Json(ref s) = v
4480 {
4481 let bad = || {
4482 EngineError::Eval(crate::eval::EvalError::TypeMismatch {
4483 detail: alloc::string::String::from("invalid input syntax for type json"),
4484 })
4485 };
4486 return if expected == DataType::Jsonb {
4487 crate::json::canonicalize_jsonb(s.as_ref())
4488 .map(Value::json)
4489 .map_err(|_| bad())
4490 } else {
4491 crate::json::parse(s.as_ref())
4492 .map_err(|_| bad())
4493 .map(|_| Value::json(s.clone()))
4494 };
4495 }
4496 let coerced: Option<Value<'static>> = match (v, expected) {
4497 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4498 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4499 (Value::Int(n), DataType::SmallInt) => match i16::try_from(n) {
4502 Ok(v) => Some(Value::SmallInt(v)),
4503 Err(_) => {
4504 return Err(EngineError::Eval(EvalError::TypeMismatch {
4505 detail: "smallint out of range".into(),
4506 }));
4507 }
4508 },
4509 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4510 i128::from(n),
4511 precision,
4512 scale,
4513 col_name,
4514 )?),
4515 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
4516 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
4517 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
4518 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4519 i128::from(n),
4520 precision,
4521 scale,
4522 col_name,
4523 )?),
4524 (Value::BigInt(n), DataType::Int) => match i32::try_from(n) {
4525 Ok(v) => Some(Value::Int(v)),
4526 Err(_) => {
4527 return Err(EngineError::Eval(EvalError::TypeMismatch {
4528 detail: "integer out of range".into(),
4529 }));
4530 }
4531 },
4532 (Value::BigInt(n), DataType::SmallInt) => match i16::try_from(n) {
4533 Ok(v) => Some(Value::SmallInt(v)),
4534 Err(_) => {
4535 return Err(EngineError::Eval(EvalError::TypeMismatch {
4536 detail: "smallint out of range".into(),
4537 }));
4538 }
4539 },
4540 #[allow(clippy::cast_precision_loss)]
4541 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
4542 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
4543 i128::from(n),
4544 precision,
4545 scale,
4546 col_name,
4547 )?),
4548 (Value::Float(x), DataType::Numeric { precision, scale }) => {
4549 if precision == 0 && scale == 0 && x.is_finite() {
4555 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{x}")) {
4556 Some(Value::Numeric {
4557 scaled: mantissa,
4558 scale: src_scale,
4559 kind: spg_storage::NumericKind::Finite,
4560 })
4561 } else {
4562 Some(numeric_from_float(x, precision, scale, col_name)?)
4563 }
4564 } else {
4565 Some(numeric_from_float(x, precision, scale, col_name)?)
4566 }
4567 }
4568 (Value::Real(x), DataType::Numeric { precision, scale }) => {
4574 if precision == 0 && scale == 0 && x.is_finite() {
4575 let six = alloc::format!("{:.5e}", x);
4589 let six: f64 = six.parse().unwrap_or_else(|_| f64::from(x));
4590 if let Some((mantissa, src_scale)) = parse_numeric_text(&alloc::format!("{six}")) {
4591 Some(Value::Numeric {
4592 scaled: mantissa,
4593 scale: src_scale,
4594 kind: spg_storage::NumericKind::Finite,
4595 })
4596 } else {
4597 Some(numeric_from_float(
4598 f64::from(x),
4599 precision,
4600 scale,
4601 col_name,
4602 )?)
4603 }
4604 } else {
4605 Some(numeric_from_float(
4606 f64::from(x),
4607 precision,
4608 scale,
4609 col_name,
4610 )?)
4611 }
4612 }
4613 (Value::Text(s), DataType::Numeric { precision, scale }) => {
4624 if let Some(kind) = crate::numeric::parse_numeric_special(&s) {
4627 return Ok(Value::numeric_special(kind));
4628 }
4629 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
4630 match spg_sql::parser::expand_scientific_literal(&s) {
4635 spg_sql::parser::SciExpanded::Expanded(plain) => {
4636 return coerce_value(
4637 Value::Text(plain.into()),
4638 DataType::Numeric { precision, scale },
4639 col_name,
4640 position,
4641 );
4642 }
4643 spg_sql::parser::SciExpanded::Overflow => {
4644 return Err(EngineError::Eval(EvalError::TypeMismatch {
4645 detail: "value overflows numeric format".into(),
4646 }));
4647 }
4648 spg_sql::parser::SciExpanded::NotScientific => {}
4649 }
4650 if precision == 0 && scale == 0 {
4653 if let Some(b) = spg_storage::bignum::BigNumeric::from_decimal_str(&s) {
4654 return Ok(Value::NumericBig(alloc::boxed::Box::new(b)));
4655 }
4656 }
4657 return Err(EngineError::Eval(EvalError::TypeMismatch {
4658 detail: alloc::format!("invalid input syntax for type numeric: \"{s}\""),
4659 }));
4660 };
4661 if precision == 0 && scale == 0 {
4663 Some(Value::Numeric {
4664 scaled: mantissa,
4665 scale: src_scale,
4666 kind: spg_storage::NumericKind::Finite,
4667 })
4668 } else {
4669 Some(numeric_rescale(
4670 mantissa, src_scale, precision, scale, col_name,
4671 )?)
4672 }
4673 }
4674 (Value::Text(s), DataType::Date) => {
4676 let d = eval::parse_date_literal(&s)
4683 .or_else(|| {
4684 eval::parse_timestamp_literal(&s)
4685 .and_then(|t| i32::try_from(t.div_euclid(86_400_000_000)).ok())
4686 })
4687 .ok_or_else(|| datetime_parse_error("date", &s))?;
4688 Some(Value::Date(d))
4689 }
4690 (Value::Text(s), DataType::SmallInt) => Some(Value::SmallInt(
4705 parse_pg_int(&s)
4706 .and_then(|n| i16::try_from(n).ok())
4707 .ok_or_else(|| invalid_input_syntax("smallint", &s))?,
4708 )),
4709 (Value::Text(s), DataType::Int) => Some(Value::Int(
4710 parse_pg_int(&s)
4711 .and_then(|n| i32::try_from(n).ok())
4712 .ok_or_else(|| invalid_input_syntax("integer", &s))?,
4713 )),
4714 (Value::Text(s), DataType::BigInt) => Some(Value::BigInt(
4715 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("bigint", &s))?,
4716 )),
4717 (Value::Text(s), DataType::Xid) => Some(Value::Xid(
4723 s.parse::<u32>()
4724 .map_err(|_| invalid_input_syntax("xid", &s))?,
4725 )),
4726 (Value::Xid(x), DataType::Xid) => Some(Value::Xid(x)),
4727 (Value::Text(s), DataType::Xid8) => Some(Value::BigInt(
4728 parse_pg_int(&s).ok_or_else(|| invalid_input_syntax("xid8", &s))?,
4729 )),
4730 (Value::BigInt(n), DataType::Xid8) => Some(Value::BigInt(n)),
4737 (ref other, DataType::Oid) => coerce_to_oid(other)?,
4741 (Value::Text(s), DataType::Float) => {
4742 Some(Value::Float(
4746 parse_float8(&s).ok_or_else(|| float_text_error(&s, "double precision"))?,
4747 ))
4748 }
4749 (Value::Int(n), DataType::Real) => Some(Value::Real(n as f32)),
4751 (Value::SmallInt(n), DataType::Real) => Some(Value::Real(f32::from(n))),
4752 (Value::BigInt(n), DataType::Real) => Some(Value::Real(n as f32)),
4753 (Value::Float(x), DataType::Real) => {
4754 let narrowed = x as f32;
4758 if narrowed.is_infinite() && x.is_finite() {
4759 return Err(EngineError::Eval(EvalError::TypeMismatch {
4760 detail: "value out of range: overflow".into(),
4761 }));
4762 }
4763 if narrowed == 0.0 && x != 0.0 {
4765 return Err(EngineError::Eval(EvalError::TypeMismatch {
4766 detail: "value out of range: underflow".into(),
4767 }));
4768 }
4769 Some(Value::Real(narrowed))
4770 }
4771 (
4772 Value::Numeric {
4773 scaled,
4774 scale,
4775 kind,
4776 },
4777 DataType::Real,
4778 ) => Some(Value::Real(match kind {
4779 spg_storage::NumericKind::NaN => f32::NAN,
4780 spg_storage::NumericKind::PosInf => f32::INFINITY,
4781 spg_storage::NumericKind::NegInf => f32::NEG_INFINITY,
4782 spg_storage::NumericKind::Finite => {
4783 let mut div = 1.0f64;
4784 for _ in 0..scale {
4785 div *= 10.0;
4786 }
4787 let x = (scaled as f64 / div) as f32;
4788 if x == 0.0 && scaled != 0 {
4791 return Err(real_out_of_range(&crate::eval::format_numeric(
4792 scaled, scale,
4793 )));
4794 }
4795 x
4796 }
4797 })),
4798 (Value::Real(x), DataType::Float) => Some(Value::Float(f64::from(x))),
4799 (Value::Text(s), DataType::Real) => {
4806 let t = s.trim();
4807 let x = t
4808 .parse::<f32>()
4809 .ok()
4810 .ok_or_else(|| invalid_input_syntax("real", &s))?;
4811 if x.is_infinite() && !text_is_explicit_infinity(t) {
4812 return Err(real_out_of_range(t));
4813 }
4814 if x == 0.0 && float_text_is_nonzero(t) {
4817 return Err(real_out_of_range(t));
4818 }
4819 Some(Value::Real(x))
4820 }
4821 (Value::Text(s), DataType::Bool) => match s.trim().to_ascii_lowercase().as_str() {
4825 "0" | "f" | "fa" | "fal" | "fals" | "false" | "n" | "no" | "of" | "off" => {
4826 Some(Value::Bool(false))
4827 }
4828 "1" | "t" | "tr" | "tru" | "true" | "y" | "ye" | "yes" | "on" => {
4829 Some(Value::Bool(true))
4830 }
4831 _ => return Err(invalid_input_syntax("boolean", &s)),
4832 },
4833 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4842 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4843 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
4844 (Value::Json(s), DataType::Text) => Some(Value::text(s)),
4866 (Value::Json(s), DataType::Json) => Some(Value::json(s)),
4874 (Value::Json(s), DataType::Jsonb) => Some(Value::json(
4875 crate::json::canonicalize_jsonb(s.as_ref()).unwrap_or_else(|_| s.into_owned()),
4876 )),
4877 (Value::Text(s), DataType::Bytes) => {
4884 let bytes = decode_bytea_literal(&s)
4885 .map_err(|e| EngineError::Eval(EvalError::TypeMismatch { detail: e }))?;
4886 Some(Value::bytes(bytes))
4887 }
4888 (Value::Bytes(b), DataType::Text) => Some(Value::text(encode_bytea_hex(&b))),
4892 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
4900 Some(b) => Some(Value::Uuid(b)),
4901 None => {
4902 return Err(EngineError::Eval(EvalError::TypeMismatch {
4903 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
4904 }));
4905 }
4906 },
4907 (Value::Uuid(b), DataType::Text) => Some(Value::text(spg_storage::format_uuid(&b))),
4912 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
4918 Some(us) => Some(Value::Time(us)),
4919 None => {
4920 let time_shaped = {
4926 let core = s.trim().split('.').next().unwrap_or("");
4927 !core.is_empty()
4928 && core.split(':').count() >= 2
4929 && core
4930 .split(':')
4931 .all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()))
4932 };
4933 let detail = if time_shaped {
4934 alloc::format!("date/time field value out of range: {s:?}")
4935 } else {
4936 alloc::format!("invalid input syntax for type time: {s:?}")
4937 };
4938 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
4939 }
4940 },
4941 (Value::Time(us), DataType::Text) => Some(Value::text(eval::format_time(us))),
4943 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4948 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
4949 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
4950 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
4954 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
4955 Err(_) => {
4956 return Err(EngineError::Eval(EvalError::TypeMismatch {
4957 detail: alloc::format!("invalid input syntax for type year: {s:?}"),
4958 }));
4959 }
4960 },
4961 (Value::Year(y), DataType::Text) => Some(Value::text(alloc::format!("{y:04}"))),
4963 (Value::Time(t), DataType::TimeTz) => Some(Value::TimeTz {
4977 us: t,
4978 offset_secs: 0,
4979 }),
4980 (Value::Timestamp(t), DataType::TimeTz) => Some(Value::TimeTz {
4981 us: t.rem_euclid(86_400_000_000),
4982 offset_secs: 0,
4983 }),
4984 (Value::Text(s), DataType::TimeTz) => {
4985 match parse_timetz_str(&s).or_else(|| parse_time_str(s.trim()).map(|us| (us, 0))) {
4986 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
4987 None => {
4988 return Err(EngineError::Eval(EvalError::TypeMismatch {
4989 detail: alloc::format!(
4990 "invalid input syntax for type time with time zone: \
4991 {s:?}"
4992 ),
4993 }));
4994 }
4995 }
4996 }
4997 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
4999 Some(Value::text(eval::format_timetz(us, offset_secs)))
5000 }
5001 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
5005 Some(c) => Some(Value::Money(c)),
5006 None => {
5007 return Err(EngineError::Eval(EvalError::TypeMismatch {
5008 detail: alloc::format!("invalid input syntax for type money: {s:?}"),
5009 }));
5010 }
5011 },
5012 (Value::SmallInt(n), DataType::Money) => {
5016 Some(Value::Money(i64::from(n).saturating_mul(100)))
5017 }
5018 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
5019 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
5020 (Value::Float(x), DataType::Money) => {
5021 let scaled = x * 100.0;
5024 let cents = if scaled >= 0.0 {
5025 (scaled + 0.5) as i64
5026 } else {
5027 (scaled - 0.5) as i64
5028 };
5029 Some(Value::Money(cents))
5030 }
5031 (Value::Numeric { scaled, scale, .. }, DataType::Money) => {
5032 let cents = if scale == 2 {
5035 scaled
5036 } else if scale < 2 {
5037 let mult = 10_i128.pow(u32::from(2 - scale));
5038 scaled.saturating_mul(mult)
5039 } else {
5040 let div = 10_i128.pow(u32::from(scale - 2));
5041 let half = div / 2;
5042 let bias = if scaled >= 0 { half } else { -half };
5043 (scaled + bias) / div
5044 };
5045 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
5046 }
5047 (Value::Money(c), DataType::Text) => Some(Value::text(eval::format_money(c))),
5049 (Value::Money(c), DataType::Numeric { .. }) => Some(Value::Numeric {
5051 scaled: i128::from(c),
5052 scale: 2,
5053 kind: spg_storage::NumericKind::Finite,
5054 }),
5055 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
5059 Ok(v) => Some(v),
5060 Err(RangeParseError::Misordered) => {
5062 return Err(EngineError::Eval(EvalError::TypeMismatch {
5063 detail: alloc::string::String::from(
5064 "range lower bound must be less than or equal to range upper bound",
5065 ),
5066 }));
5067 }
5068 Err(RangeParseError::Malformed) => {
5069 return Err(EngineError::Eval(EvalError::TypeMismatch {
5070 detail: alloc::format!("malformed range literal: \"{s}\""),
5071 }));
5072 }
5073 Err(RangeParseError::BadElement(bad)) => {
5074 return Err(EngineError::Eval(EvalError::TypeMismatch {
5075 detail: alloc::format!(
5076 "invalid input syntax for type {}: \"{bad}\"",
5077 range_element_type_name(kind)
5078 ),
5079 }));
5080 }
5081 },
5082 (v @ Value::Range { .. }, DataType::Text) => Some(Value::text(format_range_str(&v))),
5084 (Value::Text(s), DataType::Inet) => match parse_inet_text(&s) {
5086 Some((family, bits, addr)) => Some(Value::Inet { family, bits, addr }),
5087 None => {
5088 return Err(EngineError::Eval(EvalError::TypeMismatch {
5092 detail: alloc::format!("invalid input syntax for type inet: {s:?}"),
5093 }));
5094 }
5095 },
5096 (Value::Inet { family, bits, addr }, DataType::Cidr) => {
5103 let full = if family == 6 { 128 } else { 32 };
5104 let bits = if bits > full { full } else { bits };
5105 let mut masked = addr;
5106 for i in 0..16usize {
5107 let bit_start = i * 8;
5108 if bit_start >= usize::from(bits) {
5109 masked[i] = 0;
5110 } else if bit_start + 8 > usize::from(bits) {
5111 let keep = usize::from(bits) - bit_start;
5112 masked[i] &= 0xffu8 << (8 - keep);
5113 }
5114 }
5115 Some(Value::Cidr {
5116 family,
5117 bits,
5118 addr: masked,
5119 })
5120 }
5121 (Value::Cidr { family, bits, addr }, DataType::Inet) => {
5122 Some(Value::Inet { family, bits, addr })
5123 }
5124 (Value::Text(s), DataType::Cidr) => match parse_cidr_text(&s) {
5125 Ok(Some((family, bits, addr))) => Some(Value::Cidr { family, bits, addr }),
5126 Err(()) => {
5127 return Err(EngineError::Eval(EvalError::TypeMismatch {
5128 detail: alloc::format!(
5129 "invalid cidr value: {s:?} DETAIL: Value has bits set to right of mask."
5130 ),
5131 }));
5132 }
5133 Ok(None) => {
5134 return Err(EngineError::Eval(EvalError::TypeMismatch {
5135 detail: alloc::format!("invalid input syntax for type cidr: {s:?}"),
5136 }));
5137 }
5138 },
5139 (Value::Text(s), DataType::Interval) => match spg_sql::parser::parse_interval_text(&s) {
5142 Some((months, days, micros)) => Some(Value::Interval {
5143 months,
5144 days,
5145 micros,
5146 kind: spg_storage::IntervalKind::from_fields(months, days, micros),
5147 }),
5148 None => {
5149 return Err(EngineError::Eval(EvalError::TypeMismatch {
5150 detail: alloc::format!("invalid input syntax for type interval: {s:?}"),
5151 }));
5152 }
5153 },
5154 (Value::Text(s), DataType::Macaddr) => match parse_macaddr_text(&s) {
5155 Some(m) => Some(Value::Macaddr(m)),
5156 None => {
5157 return Err(EngineError::Eval(EvalError::TypeMismatch {
5158 detail: alloc::format!("invalid input syntax for type macaddr: {s:?}"),
5159 }));
5160 }
5161 },
5162 (Value::Text(s), DataType::PgLsn) => match parse_pg_lsn_text(&s) {
5164 Some(l) => Some(Value::PgLsn(l)),
5165 None => {
5166 return Err(EngineError::Eval(EvalError::TypeMismatch {
5167 detail: alloc::format!("invalid input syntax for type pg_lsn: \"{s}\""),
5168 }));
5169 }
5170 },
5171 (Value::Text(s), DataType::Macaddr8) => match parse_macaddr8_text(&s) {
5172 Some(m) => Some(Value::Macaddr8(m)),
5173 None => {
5174 return Err(EngineError::Eval(EvalError::TypeMismatch {
5175 detail: alloc::format!("invalid input syntax for type macaddr8: {s:?}"),
5176 }));
5177 }
5178 },
5179 (Value::BitString { nbits, bytes }, DataType::Bit(n)) => {
5191 let want = if n == 0 { 1 } else { n };
5193 if nbits != want {
5194 return Err(EngineError::Unsupported(alloc::format!(
5195 "bit string length {nbits} does not match type bit({want})"
5196 )));
5197 }
5198 Some(Value::BitString { nbits, bytes })
5199 }
5200 (Value::BitString { nbits, bytes }, DataType::BitVarying(n)) => {
5201 if n != 0 && nbits > n {
5202 return Err(EngineError::Unsupported(alloc::format!(
5203 "bit string too long for type bit varying({n})"
5204 )));
5205 }
5206 Some(Value::BitString { nbits, bytes })
5207 }
5208 (Value::Text(s), bit_ty @ (DataType::Bit(_) | DataType::BitVarying(_))) => {
5209 match parse_bit_string_text(&s) {
5210 Some((nbits, bytes)) => {
5211 match bit_ty {
5221 DataType::Bit(n) => {
5223 let want = if n == 0 { 1 } else { n };
5224 if nbits != want {
5225 return Err(EngineError::Unsupported(alloc::format!(
5226 "bit string length {nbits} does not match type bit({want})"
5227 )));
5228 }
5229 }
5230 DataType::BitVarying(n) if n != 0 && nbits > n => {
5231 return Err(EngineError::Unsupported(alloc::format!(
5232 "bit string too long for type bit varying({n})"
5233 )));
5234 }
5235 _ => {}
5236 }
5237 Some(Value::bit_string(nbits, bytes))
5238 }
5239 None => {
5240 let bad = s.chars().find(|c| *c != '0' && *c != '1');
5242 return Err(EngineError::Eval(EvalError::TypeMismatch {
5243 detail: match bad {
5244 Some(c) => {
5245 alloc::format!("\"{c}\" is not a valid binary digit")
5246 }
5247 None => alloc::format!("invalid input syntax for BIT: {s:?}"),
5248 },
5249 }));
5250 }
5251 }
5252 }
5253 (Value::Text(s), DataType::Xml) => {
5254 if !xml_content_is_well_formed(&s) {
5259 return Err(EngineError::Eval(EvalError::TypeMismatch {
5260 detail: alloc::format!("invalid XML content: {s:?}"),
5261 }));
5262 }
5263 Some(Value::xml(s))
5264 }
5265 (Value::BpChar(s), DataType::Char1) => {
5272 Some(Value::Char1(s.as_bytes().first().copied().unwrap_or(0)))
5273 }
5274 (Value::BpChar(s), DataType::Xml) => {
5275 let stripped = s.trim_end_matches(' ');
5276 if !xml_content_is_well_formed(stripped) {
5277 return Err(EngineError::Eval(EvalError::TypeMismatch {
5278 detail: alloc::format!("invalid XML content: {stripped:?}"),
5279 }));
5280 }
5281 Some(Value::xml(alloc::string::String::from(stripped)))
5282 }
5283 (Value::Bytes(b), DataType::SmallInt | DataType::Int | DataType::BigInt) => {
5289 let mut acc: i128 = 0;
5290 for byte in b.iter() {
5291 acc = acc.saturating_mul(256).saturating_add(i128::from(*byte));
5292 }
5293 let (fits, made) = match expected {
5294 DataType::SmallInt => (
5295 i16::try_from(acc).is_ok(),
5296 i16::try_from(acc).map(Value::SmallInt).ok(),
5297 ),
5298 DataType::Int => (
5299 i32::try_from(acc).is_ok(),
5300 i32::try_from(acc).map(Value::Int).ok(),
5301 ),
5302 _ => (
5303 i64::try_from(acc).is_ok(),
5304 i64::try_from(acc).map(Value::BigInt).ok(),
5305 ),
5306 };
5307 if !fits {
5308 return Err(EngineError::Eval(EvalError::TypeMismatch {
5309 detail: alloc::format!("{} out of range", pg_type_name_for_error(expected)),
5310 }));
5311 }
5312 made
5313 }
5314 (Value::Int(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
5317 (Value::SmallInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
5318 (Value::BigInt(n), DataType::Char1) => Some(Value::Char1((n & 0xff) as u8)),
5319 (Value::Text(s), DataType::Char1) => {
5320 let bytes = s.as_bytes();
5326 if bytes.len() == 4
5327 && bytes[0] == b'\\'
5328 && bytes[1..].iter().all(|b| (b'0'..=b'7').contains(b))
5329 {
5330 let v = ((bytes[1] - b'0') << 6) | ((bytes[2] - b'0') << 3) | (bytes[3] - b'0');
5331 Some(Value::Char1(v))
5332 } else {
5333 let b = s.bytes().next().unwrap_or(0);
5334 Some(Value::Char1(b))
5335 }
5336 }
5337 (Value::Inet { family, bits, addr }, DataType::Text) => {
5339 let base = format_inet(family, bits, &addr);
5343 Some(Value::text(if base.contains('/') {
5344 base
5345 } else {
5346 alloc::format!("{base}/{bits}")
5347 }))
5348 }
5349 (Value::Cidr { family, bits, addr }, DataType::Text) => {
5350 Some(Value::text(format_inet(family, bits, &addr)))
5351 }
5352 (Value::Macaddr(m), DataType::Text) => Some(Value::text(format_macaddr(&m))),
5353 (Value::Macaddr8(m), DataType::Text) => Some(Value::text(format_macaddr8(&m))),
5354 (Value::PgLsn(l), DataType::Text) => Some(Value::text(format_pg_lsn(l))),
5355 (Value::Macaddr(m), DataType::Macaddr8) => Some(Value::Macaddr8([
5358 m[0], m[1], m[2], 0xff, 0xfe, m[3], m[4], m[5],
5359 ])),
5360 (Value::BitString { nbits, bytes }, DataType::Text) => {
5361 Some(Value::text(format_bit_string(nbits, &bytes)))
5362 }
5363 #[allow(clippy::cast_possible_truncation)]
5365 (Value::BitString { nbits, bytes }, DataType::SmallInt) => {
5366 Some(Value::SmallInt(bit_string_to_i64(nbits, &bytes) as i16))
5367 }
5368 #[allow(clippy::cast_possible_truncation)]
5369 (Value::BitString { nbits, bytes }, DataType::Int) => {
5370 Some(Value::Int(bit_string_to_i64(nbits, &bytes) as i32))
5371 }
5372 (Value::BitString { nbits, bytes }, DataType::BigInt) => {
5373 Some(Value::BigInt(bit_string_to_i64(nbits, &bytes)))
5374 }
5375 (Value::Xml(s), DataType::Text) => Some(Value::text(s)),
5376 (Value::Char1(b), DataType::Text) => Some(Value::text((b as char).to_string())),
5377 (Value::Text(s), DataType::Point) => match parse_point(&s) {
5381 Some(p) => Some(Value::Point(p)),
5382 None => {
5383 return Err(EngineError::Eval(EvalError::TypeMismatch {
5384 detail: alloc::format!("invalid input syntax for type point: {s:?}"),
5385 }));
5386 }
5387 },
5388 (Value::Text(s), DataType::Lseg) => match parse_lseg_text(&s) {
5389 Some((p1, p2)) => Some(Value::Lseg(p1, p2)),
5390 None => {
5391 return Err(EngineError::Eval(EvalError::TypeMismatch {
5392 detail: alloc::format!("invalid input syntax for type lseg: {s:?}"),
5393 }));
5394 }
5395 },
5396 (Value::Text(s), DataType::PgBox) => match parse_box_text(&s) {
5397 Some((ur, ll)) => Some(Value::PgBox(ur, ll)),
5398 None => {
5399 return Err(EngineError::Eval(EvalError::TypeMismatch {
5400 detail: alloc::format!("invalid input syntax for type box: {s:?}"),
5401 }));
5402 }
5403 },
5404 (Value::Text(s), DataType::Line) => match parse_line_text(&s) {
5405 Some((a, b, c)) => Some(Value::Line { a, b, c }),
5406 None => {
5407 let zero_ab = s
5411 .trim()
5412 .strip_prefix('{')
5413 .and_then(|x| x.strip_suffix('}'))
5414 .map(|inner| inner.split(',').collect::<alloc::vec::Vec<_>>())
5415 .is_some_and(|parts| {
5416 parts.len() == 3
5417 && parts[0].trim().parse::<f64>() == Ok(0.0)
5418 && parts[1].trim().parse::<f64>() == Ok(0.0)
5419 && parts[2].trim().parse::<f64>().is_ok()
5420 });
5421 let detail = if zero_ab {
5422 alloc::string::String::from(
5423 "invalid line specification: A and B cannot both be zero",
5424 )
5425 } else {
5426 alloc::format!("invalid input syntax for type line: {s:?}")
5427 };
5428 return Err(EngineError::Eval(EvalError::TypeMismatch { detail }));
5429 }
5430 },
5431 (Value::Text(s), DataType::Circle) => match parse_circle_text(&s) {
5432 Some((center, radius)) => Some(Value::Circle { center, radius }),
5433 None => {
5434 return Err(EngineError::Eval(EvalError::TypeMismatch {
5435 detail: alloc::format!("invalid input syntax for type circle: {s:?}"),
5436 }));
5437 }
5438 },
5439 (Value::Text(s), DataType::Path) => match parse_path_text(&s) {
5440 Some((points, closed)) => Some(Value::Path { points, closed }),
5441 None => {
5442 return Err(EngineError::Eval(EvalError::TypeMismatch {
5443 detail: alloc::format!("invalid input syntax for type path: {s:?}"),
5444 }));
5445 }
5446 },
5447 (Value::PgBox(a, b), DataType::Polygon) => {
5450 let (hx, hy) = (a.x.max(b.x), a.y.max(b.y));
5451 let (lx, ly) = (a.x.min(b.x), a.y.min(b.y));
5452 let p = |x: f64, y: f64| spg_storage::Point2D { x, y };
5453 Some(Value::Polygon(alloc::vec![
5454 p(lx, ly),
5455 p(lx, hy),
5456 p(hx, hy),
5457 p(hx, ly),
5458 ]))
5459 }
5460 (Value::Text(s), DataType::Polygon) => match parse_polygon_text(&s) {
5461 Some(points) => Some(Value::Polygon(points)),
5462 None => {
5463 return Err(EngineError::Eval(EvalError::TypeMismatch {
5464 detail: alloc::format!("invalid input syntax for type polygon: {s:?}"),
5465 }));
5466 }
5467 },
5468 (Value::Point(p), DataType::Text) => Some(Value::text(format_point(p))),
5470 (Value::Lseg(p1, p2), DataType::Text) => Some(Value::text(format_lseg(p1, p2))),
5471 (Value::PgBox(ur, ll), DataType::Text) => Some(Value::text(format_pg_box(ur, ll))),
5472 (Value::Line { a, b, c }, DataType::Text) => Some(Value::text(format_line(a, b, c))),
5473 (Value::Circle { center, radius }, DataType::Text) => {
5474 Some(Value::text(format_circle(center, radius)))
5475 }
5476 (Value::Path { points, closed }, DataType::Text) => {
5477 Some(Value::text(format_path(&points, closed)))
5478 }
5479 (Value::Polygon(points), DataType::Text) => Some(Value::text(format_polygon(&points))),
5480 (ref rv @ Value::Range { kind: rk, .. }, DataType::Multirange(kind)) => {
5487 if rk != kind {
5488 return Err(EngineError::Eval(EvalError::TypeMismatch {
5489 detail: alloc::format!(
5490 "cannot cast type {} to {}",
5491 DataType::Range(rk),
5492 DataType::Multirange(kind)
5493 ),
5494 }));
5495 }
5496 crate::eval::binop::range_as_multirange(rv)
5497 }
5498 (Value::Text(s), DataType::Multirange(kind)) => match parse_multirange_str(&s, kind) {
5499 Some(ranges) => Some(Value::Multirange {
5505 kind,
5506 ranges: crate::eval::binop::normalize_multirange_spans(kind, &ranges),
5507 }),
5508 None => {
5509 return Err(EngineError::Eval(EvalError::TypeMismatch {
5510 detail: alloc::format!("invalid input syntax for multirange type: {s:?}"),
5511 }));
5512 }
5513 },
5514 (Value::Multirange { ranges, .. }, DataType::Text) => {
5516 Some(Value::text(format_multirange(&ranges)))
5517 }
5518 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
5520 Some(pairs) => Some(Value::Hstore(pairs)),
5521 None => {
5522 return Err(EngineError::Eval(EvalError::TypeMismatch {
5523 detail: alloc::format!("invalid input syntax for type hstore: {s:?}"),
5524 }));
5525 }
5526 },
5527 (Value::Hstore(pairs), DataType::Text) => Some(Value::text(format_hstore_str(&pairs))),
5529 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
5532 Ok(m) => Some(Value::IntArray2D(m)),
5533 Err(e) => {
5534 return Err(EngineError::Eval(EvalError::TypeMismatch {
5535 detail: alloc::format!("invalid input syntax for INT[][]: {s:?}: {e}"),
5536 }));
5537 }
5538 },
5539 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
5540 Ok(m) => Some(Value::BigIntArray2D(m)),
5541 Err(e) => {
5542 return Err(EngineError::Eval(EvalError::TypeMismatch {
5543 detail: alloc::format!("invalid input syntax for BIGINT[][]: {s:?}: {e}"),
5544 }));
5545 }
5546 },
5547 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
5548 Ok(m) => Some(Value::TextArray2D(m)),
5549 Err(e) => {
5550 return Err(EngineError::Eval(EvalError::TypeMismatch {
5551 detail: alloc::format!("invalid input syntax for TEXT[][]: {s:?}: {e}"),
5552 }));
5553 }
5554 },
5555 (Value::IntArray2D(rows), DataType::Text) => Some(Value::text(format_int_2d_text(&rows))),
5557 (Value::BigIntArray2D(rows), DataType::Text) => {
5558 Some(Value::text(format_bigint_2d_text(&rows)))
5559 }
5560 (Value::TextArray2D(rows), DataType::Text) => Some(Value::text(format_text_2d_text(&rows))),
5561 (Value::Text(s), DataType::TextArray) => {
5566 let arr = decode_text_array_literal(&s).map_err(|_| {
5570 EngineError::Eval(EvalError::TypeMismatch {
5571 detail: malformed_array_literal(&s),
5572 })
5573 })?;
5574 Some(Value::TextArray(arr))
5575 }
5576 (Value::Text(s), DataType::IntArray) => {
5582 let arr = decode_text_array_literal(&s).map_err(|_| {
5586 EngineError::Eval(EvalError::TypeMismatch {
5587 detail: malformed_array_literal(&s),
5588 })
5589 })?;
5590 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
5591 for elem in arr {
5592 match elem {
5593 None => out.push(None),
5594 Some(t) => {
5595 let n: i32 = t.parse().map_err(|_| {
5596 EngineError::Eval(EvalError::TypeMismatch {
5597 detail: alloc::format!(
5598 "invalid input syntax for type integer: {t:?}"
5599 ),
5600 })
5601 })?;
5602 out.push(Some(n));
5603 }
5604 }
5605 }
5606 Some(Value::IntArray(out))
5607 }
5608 (Value::Text(s), DataType::SmallIntArray) => Some(Value::SmallIntArray(
5612 decode_array_elems(&s, DataType::SmallInt, col_name, position)?
5613 .into_iter()
5614 .map(|o| match o {
5615 Some(Value::SmallInt(n)) => Some(n),
5616 _ => None,
5617 })
5618 .collect(),
5619 )),
5620 (Value::Text(s), DataType::BoolArray) => {
5621 if let Some(rows) = crate::eval::values::split_2d_rows(&s) {
5626 let mut row_vals: Vec<Value<'static>> = Vec::with_capacity(rows.len());
5627 for r in &rows {
5628 let bools: Vec<Option<bool>> =
5629 decode_array_elems(r, DataType::Bool, col_name, position)?
5630 .into_iter()
5631 .map(|o| match o {
5632 Some(Value::Bool(b)) => Some(b),
5633 _ => None,
5634 })
5635 .collect();
5636 row_vals.push(Value::BoolArray(bools));
5637 }
5638 return crate::eval::values::build_2d_from_rows(&row_vals).ok_or_else(|| {
5639 EngineError::Eval(EvalError::TypeMismatch {
5640 detail: malformed_array_literal(&s),
5641 })
5642 });
5643 }
5644 Some(Value::BoolArray(
5645 decode_array_elems(&s, DataType::Bool, col_name, position)?
5646 .into_iter()
5647 .map(|o| match o {
5648 Some(Value::Bool(b)) => Some(b),
5649 _ => None,
5650 })
5651 .collect(),
5652 ))
5653 }
5654 (Value::Text(s), DataType::FloatArray) => Some(Value::FloatArray(
5655 decode_array_elems(&s, DataType::Float, col_name, position)?
5656 .into_iter()
5657 .map(|o| match o {
5658 Some(Value::Float(f)) => Some(f),
5659 _ => None,
5660 })
5661 .collect(),
5662 )),
5663 (Value::Text(s), DataType::NumericArray) => Some(Value::NumericArray(
5664 decode_array_elems(
5665 &s,
5666 DataType::Numeric {
5667 precision: 0,
5668 scale: 0,
5669 },
5670 col_name,
5671 position,
5672 )?
5673 .into_iter()
5674 .map(|o| match o {
5675 Some(Value::Numeric { scaled, scale, .. }) => Some((scaled, scale)),
5676 _ => None,
5677 })
5678 .collect(),
5679 )),
5680 (Value::Text(s), DataType::DateArray) => Some(Value::DateArray(
5681 decode_array_elems(&s, DataType::Date, col_name, position)?
5682 .into_iter()
5683 .map(|o| match o {
5684 Some(Value::Date(d)) => Some(d),
5685 _ => None,
5686 })
5687 .collect(),
5688 )),
5689 (Value::Text(s), DataType::UuidArray) => Some(Value::UuidArray(
5690 decode_array_elems(&s, DataType::Uuid, col_name, position)?
5691 .into_iter()
5692 .map(|o| match o {
5693 Some(Value::Uuid(u)) => Some(u),
5694 _ => None,
5695 })
5696 .collect(),
5697 )),
5698 (Value::Text(s), DataType::BigIntArray | DataType::OidArray) => {
5705 let arr = decode_text_array_literal(&s).map_err(|_| {
5709 EngineError::Eval(EvalError::TypeMismatch {
5710 detail: malformed_array_literal(&s),
5711 })
5712 })?;
5713 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
5714 for elem in arr {
5715 match elem {
5716 None => out.push(None),
5717 Some(t) => {
5718 let n: i64 = t.parse().map_err(|_| {
5719 EngineError::Eval(EvalError::TypeMismatch {
5720 detail: alloc::format!(
5721 "invalid input syntax for type bigint: {t:?}"
5722 ),
5723 })
5724 })?;
5725 out.push(Some(n));
5726 }
5727 }
5728 }
5729 Some(Value::BigIntArray(out))
5730 }
5731 (Value::TextArray(items), DataType::Text) => Some(Value::text(encode_text_array(&items))),
5735 (Value::TextArray(items), DataType::BoolArray) if items.is_empty() => {
5743 Some(Value::BoolArray(alloc::vec::Vec::new()))
5744 }
5745 (Value::TextArray(items), DataType::SmallIntArray) if items.is_empty() => {
5746 Some(Value::SmallIntArray(alloc::vec::Vec::new()))
5747 }
5748 (Value::TextArray(items), DataType::IntArray) if items.is_empty() => {
5749 Some(Value::IntArray(alloc::vec::Vec::new()))
5750 }
5751 (Value::TextArray(items), DataType::BigIntArray) if items.is_empty() => {
5752 Some(Value::BigIntArray(alloc::vec::Vec::new()))
5753 }
5754 (Value::TextArray(items), DataType::FloatArray) if items.is_empty() => {
5755 Some(Value::FloatArray(alloc::vec::Vec::new()))
5756 }
5757 (Value::TextArray(items), DataType::FloatArray) => {
5760 let mut out = alloc::vec::Vec::with_capacity(items.len());
5761 let mut ok = true;
5762 for item in items {
5763 match item {
5764 None => out.push(None),
5765 Some(s) => match s.trim().parse::<f64>() {
5766 Ok(x) => out.push(Some(x)),
5767 Err(_) => {
5768 ok = false;
5769 break;
5770 }
5771 },
5772 }
5773 }
5774 if ok {
5775 Some(Value::FloatArray(out))
5776 } else {
5777 None
5778 }
5779 }
5780 (Value::FloatArray(items), DataType::FloatArray) => Some(Value::FloatArray(items)),
5783 #[allow(clippy::cast_precision_loss)]
5784 (Value::IntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5785 items.into_iter().map(|o| o.map(|n| f64::from(n))).collect(),
5786 )),
5787 #[allow(clippy::cast_precision_loss)]
5788 (Value::BigIntArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5789 items.into_iter().map(|o| o.map(|n| n as f64)).collect(),
5790 )),
5791 #[allow(clippy::cast_precision_loss)]
5795 (Value::NumericArray(items), DataType::FloatArray) => Some(Value::FloatArray(
5796 items
5797 .into_iter()
5798 .map(|o| {
5799 o.map(|(scaled, scale)| {
5800 crate::eval::format_numeric(scaled, scale)
5801 .parse()
5802 .unwrap_or(f64::NAN)
5803 })
5804 })
5805 .collect(),
5806 )),
5807 (Value::IntArray(items), DataType::BigIntArray) => Some(Value::BigIntArray(
5812 items.into_iter().map(|o| o.map(i64::from)).collect(),
5813 )),
5814 (Value::BigIntArray(items), DataType::IntArray) => {
5815 let mut out = alloc::vec::Vec::with_capacity(items.len());
5816 let mut ok = true;
5817 for o in items {
5818 match o {
5819 None => out.push(None),
5820 Some(n) => match i32::try_from(n) {
5821 Ok(v) => out.push(Some(v)),
5822 Err(_) => {
5823 ok = false;
5824 break;
5825 }
5826 },
5827 }
5828 }
5829 if ok { Some(Value::IntArray(out)) } else { None }
5830 }
5831 (Value::IntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5832 items
5833 .into_iter()
5834 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5835 .collect(),
5836 )),
5837 (Value::BigIntArray(items), DataType::NumericArray) => Some(Value::NumericArray(
5838 items
5839 .into_iter()
5840 .map(|o| o.map(|n| (i128::from(n), 0_u16)))
5841 .collect(),
5842 )),
5843 (Value::FloatArray(items), DataType::NumericArray) => {
5844 let mut out = alloc::vec::Vec::with_capacity(items.len());
5845 let mut ok = true;
5846 for o in items {
5847 match o {
5848 None => out.push(None),
5849 Some(x) => match parse_numeric_text(&alloc::format!("{x}")) {
5850 Some((mantissa, scale)) => out.push(Some((mantissa, scale))),
5851 None => {
5852 ok = false;
5853 break;
5854 }
5855 },
5856 }
5857 }
5858 if ok {
5859 Some(Value::NumericArray(out))
5860 } else {
5861 None
5862 }
5863 }
5864 (Value::NumericArray(items), DataType::IntArray) => {
5868 let mut out = alloc::vec::Vec::with_capacity(items.len());
5869 let mut ok = true;
5870 for o in items {
5871 match o {
5872 None => out.push(None),
5873 Some((scaled, scale)) => {
5874 match i32::try_from(numeric_round_to_integer(scaled, scale)) {
5875 Ok(v) => out.push(Some(v)),
5876 Err(_) => {
5877 ok = false;
5878 break;
5879 }
5880 }
5881 }
5882 }
5883 }
5884 if ok { Some(Value::IntArray(out)) } else { None }
5885 }
5886 (Value::NumericArray(items), DataType::BigIntArray) => {
5887 let mut out = alloc::vec::Vec::with_capacity(items.len());
5888 let mut ok = true;
5889 for o in items {
5890 match o {
5891 None => out.push(None),
5892 Some((scaled, scale)) => {
5893 match i64::try_from(numeric_round_to_integer(scaled, scale)) {
5894 Ok(v) => out.push(Some(v)),
5895 Err(_) => {
5896 ok = false;
5897 break;
5898 }
5899 }
5900 }
5901 }
5902 }
5903 if ok {
5904 Some(Value::BigIntArray(out))
5905 } else {
5906 None
5907 }
5908 }
5909 #[allow(clippy::cast_possible_truncation)]
5913 (Value::FloatArray(items), DataType::IntArray) => {
5914 let mut out = alloc::vec::Vec::with_capacity(items.len());
5915 let mut ok = true;
5916 for o in items {
5917 match o {
5918 None => out.push(None),
5919 Some(x) if x.is_finite() => {
5920 let r = crate::eval::math::f64_round_half_even(x);
5921 if r >= f64::from(i32::MIN) && r <= f64::from(i32::MAX) {
5922 out.push(Some(r as i32));
5923 } else {
5924 ok = false;
5925 break;
5926 }
5927 }
5928 Some(_) => {
5929 ok = false;
5930 break;
5931 }
5932 }
5933 }
5934 if ok { Some(Value::IntArray(out)) } else { None }
5935 }
5936 #[allow(clippy::cast_possible_truncation)]
5937 (Value::FloatArray(items), DataType::BigIntArray) => {
5938 let mut out = alloc::vec::Vec::with_capacity(items.len());
5939 let mut ok = true;
5940 for o in items {
5941 match o {
5942 None => out.push(None),
5943 Some(x) if x.is_finite() => {
5944 out.push(Some(crate::eval::math::f64_round_half_even(x) as i64));
5945 }
5946 Some(_) => {
5947 ok = false;
5948 break;
5949 }
5950 }
5951 }
5952 if ok {
5953 Some(Value::BigIntArray(out))
5954 } else {
5955 None
5956 }
5957 }
5958 (Value::TextArray(items), DataType::NumericArray) if items.is_empty() => {
5959 Some(Value::NumericArray(alloc::vec::Vec::new()))
5960 }
5961 (Value::TextArray(items), DataType::DateArray) if items.is_empty() => {
5962 Some(Value::DateArray(alloc::vec::Vec::new()))
5963 }
5964 (Value::TextArray(items), DataType::TimestampArray) if items.is_empty() => {
5965 Some(Value::TimestampArray(alloc::vec::Vec::new()))
5966 }
5967 (Value::TextArray(items), DataType::TimestamptzArray) if items.is_empty() => {
5968 Some(Value::TimestamptzArray(alloc::vec::Vec::new()))
5969 }
5970 (Value::TextArray(items), DataType::UuidArray) if items.is_empty() => {
5971 Some(Value::UuidArray(alloc::vec::Vec::new()))
5972 }
5973 (Value::TextArray(items), DataType::JsonArray) if items.is_empty() => {
5974 Some(Value::JsonArray(alloc::vec::Vec::new()))
5975 }
5976 (Value::TextArray(items), DataType::JsonbArray) if items.is_empty() => {
5977 Some(Value::JsonbArray(alloc::vec::Vec::new()))
5978 }
5979 (Value::TextArray(items), DataType::BytesArray) if items.is_empty() => {
5980 Some(Value::BytesArray(alloc::vec::Vec::new()))
5981 }
5982 (Value::TextArray(items), DataType::IntervalArray) if items.is_empty() => {
5983 Some(Value::IntervalArray(alloc::vec::Vec::new()))
5984 }
5985 (
5989 Value::TextArray(items),
5990 dt @ (DataType::BoolArray
5991 | DataType::NumericArray
5992 | DataType::DateArray
5993 | DataType::TimestampArray
5994 | DataType::TimestamptzArray
5995 | DataType::IntervalArray
5996 | DataType::UuidArray),
5997 ) => coerce_text_array_to(items, dt, col_name)?,
5998 (
6004 Value::Text(s),
6005 dt @ (DataType::TimestampArray | DataType::TimestamptzArray | DataType::IntervalArray),
6006 ) => {
6007 let items = decode_text_array_literal(&s).map_err(|_| {
6008 EngineError::Eval(EvalError::TypeMismatch {
6009 detail: malformed_array_literal(&s),
6010 })
6011 })?;
6012 coerce_text_array_to(items, dt, col_name)?
6013 }
6014 (Value::TextArray(items), DataType::MoneyArray) if items.is_empty() => {
6015 Some(Value::MoneyArray(alloc::vec::Vec::new()))
6016 }
6017 (Value::IntArray(items), DataType::SmallIntArray) => {
6022 let mut out = alloc::vec::Vec::with_capacity(items.len());
6023 let mut ok = true;
6024 for item in items {
6025 match item {
6026 None => out.push(None),
6027 Some(n) => match i16::try_from(n) {
6028 Ok(x) => out.push(Some(x)),
6029 Err(_) => {
6030 ok = false;
6031 break;
6032 }
6033 },
6034 }
6035 }
6036 if ok {
6037 Some(Value::SmallIntArray(out))
6038 } else {
6039 None
6040 }
6041 }
6042 (Value::Text(s), DataType::Vector { dim, encoding }) => {
6051 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
6052 EngineError::Eval(EvalError::TypeMismatch {
6053 detail: alloc::format!("cannot parse {s:?} as VECTOR"),
6054 })
6055 })?;
6056 if parsed.len() != dim as usize {
6057 return Err(EngineError::Eval(EvalError::TypeMismatch {
6058 detail: alloc::format!(
6059 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
6060 parsed.len()
6061 ),
6062 }));
6063 }
6064 Some(match encoding {
6065 VecEncoding::F32 => Value::vector(parsed),
6066 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
6067 VecEncoding::F16 => {
6068 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
6069 }
6070 })
6071 }
6072 (Value::Text(s), DataType::TsVector) => {
6082 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
6083 EngineError::Eval(EvalError::TypeMismatch {
6084 detail: alloc::format!("cannot parse {s:?} as TSVECTOR: {e}"),
6085 })
6086 })?;
6087 Some(Value::TsVector(lexs))
6088 }
6089 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
6090 let t = eval::parse_timestamp_literal(&s)
6091 .ok_or_else(|| datetime_parse_error("timestamp", &s))?;
6092 Some(Value::Timestamp(t))
6093 }
6094 (Value::Date(i32::MAX), DataType::Timestamp | DataType::Timestamptz) => {
6097 Some(Value::Timestamp(i64::MAX))
6098 }
6099 (Value::Date(i32::MIN), DataType::Timestamp | DataType::Timestamptz) => {
6100 Some(Value::Timestamp(i64::MIN))
6101 }
6102 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
6103 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
6104 }
6105 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
6109 (Value::Timestamp(t), DataType::Date) => {
6110 let days = t.div_euclid(86_400_000_000);
6111 i32::try_from(days).ok().map(Value::Date)
6112 }
6113 (Value::Timestamp(t), DataType::Time) => Some(Value::Time(t.rem_euclid(86_400_000_000))),
6122 (
6126 Value::NumericBig(b),
6127 DataType::Numeric {
6128 precision: 0,
6129 scale: 0,
6130 },
6131 ) => Some(Value::NumericBig(b)),
6132 (
6133 Value::Numeric {
6134 scaled,
6135 scale: src_scale,
6136 ..
6137 },
6138 DataType::Numeric { precision, scale },
6139 ) => {
6140 if precision == 0 && scale == 0 {
6146 Some(Value::Numeric {
6147 scaled,
6148 scale: src_scale,
6149 kind: spg_storage::NumericKind::Finite,
6150 })
6151 } else {
6152 Some(numeric_rescale(
6153 scaled, src_scale, precision, scale, col_name,
6154 )?)
6155 }
6156 }
6157 (Value::NumericBig(b), DataType::Numeric { precision, scale }) => {
6162 if precision == 0 && scale == 0 {
6163 Some(Value::NumericBig(b))
6164 } else {
6165 #[allow(clippy::cast_sign_loss)]
6166 let rounded = if scale < 0 {
6167 b.round_to(0)
6169 } else {
6170 b.round_to(scale as u16)
6171 };
6172 let out = crate::eval::binop::bignum_to_value(rounded);
6173 crate::numeric::check_precision_text(&out, precision, scale, col_name)?;
6176 Some(out)
6177 }
6178 }
6179 #[allow(clippy::cast_precision_loss)]
6180 (Value::Numeric { scaled, scale, .. }, DataType::Float) => {
6181 let text = crate::eval::format_numeric(scaled, scale);
6188 let x: f64 = text.parse().unwrap_or(f64::NAN);
6189 if x == 0.0 && scaled != 0 {
6193 return Err(float_out_of_range(
6194 &crate::eval::format_numeric(scaled, scale),
6195 "double precision",
6196 ));
6197 }
6198 Some(Value::Float(x))
6199 }
6200 (Value::NumericBig(b), DataType::Real) => {
6208 let text = b.to_decimal_str();
6209 let x: f32 = text.parse().map_err(|_| real_out_of_range(&text))?;
6210 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
6211 return Err(real_out_of_range(&text));
6212 }
6213 Some(Value::Real(x))
6214 }
6215 (Value::NumericBig(b), DataType::Float) => {
6216 let text = b.to_decimal_str();
6220 let x: f64 = text
6221 .parse()
6222 .map_err(|_| float_out_of_range(&text, "double precision"))?;
6223 if !x.is_finite() || (x == 0.0 && float_text_is_nonzero(&text)) {
6224 return Err(float_out_of_range(&text, "double precision"));
6225 }
6226 Some(Value::Float(x))
6227 }
6228 (Value::Float(x), DataType::Int) => {
6236 let r = crate::eval::math::f64_round_half_even(x);
6237 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
6238 return Err(EngineError::Eval(EvalError::TypeMismatch {
6239 detail: "integer out of range".into(),
6240 }));
6241 }
6242 #[allow(clippy::cast_possible_truncation)]
6243 Some(Value::Int(r as i32))
6244 }
6245 (Value::Float(x), DataType::BigInt) => {
6246 let r = crate::eval::math::f64_round_half_even(x);
6247 if !r.is_finite()
6248 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
6249 {
6250 return Err(EngineError::Eval(EvalError::TypeMismatch {
6251 detail: "bigint out of range".into(),
6252 }));
6253 }
6254 #[allow(clippy::cast_possible_truncation)]
6255 Some(Value::BigInt(r as i64))
6256 }
6257 (Value::Float(x), DataType::SmallInt) => {
6258 let r = crate::eval::math::f64_round_half_even(x);
6259 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
6260 return Err(EngineError::Eval(EvalError::TypeMismatch {
6261 detail: "smallint out of range".into(),
6262 }));
6263 }
6264 #[allow(clippy::cast_possible_truncation)]
6265 Some(Value::SmallInt(r as i16))
6266 }
6267 (Value::Real(x), DataType::Int) => {
6271 let r = crate::eval::math::f64_round_half_even(f64::from(x));
6272 if !r.is_finite() || !(-2_147_483_648.0..=2_147_483_647.0).contains(&r) {
6273 return Err(EngineError::Eval(EvalError::TypeMismatch {
6274 detail: "integer out of range".into(),
6275 }));
6276 }
6277 #[allow(clippy::cast_possible_truncation)]
6278 Some(Value::Int(r as i32))
6279 }
6280 (Value::Real(x), DataType::BigInt) => {
6281 let r = crate::eval::math::f64_round_half_even(f64::from(x));
6282 if !r.is_finite()
6283 || !(-9.223_372_036_854_776e18..=9.223_372_036_854_776e18).contains(&r)
6284 {
6285 return Err(EngineError::Eval(EvalError::TypeMismatch {
6286 detail: "bigint out of range".into(),
6287 }));
6288 }
6289 #[allow(clippy::cast_possible_truncation)]
6290 Some(Value::BigInt(r as i64))
6291 }
6292 (Value::Real(x), DataType::SmallInt) => {
6293 let r = crate::eval::math::f64_round_half_even(f64::from(x));
6294 if !r.is_finite() || !(-32768.0..=32767.0).contains(&r) {
6295 return Err(EngineError::Eval(EvalError::TypeMismatch {
6296 detail: "smallint out of range".into(),
6297 }));
6298 }
6299 #[allow(clippy::cast_possible_truncation)]
6300 Some(Value::SmallInt(r as i16))
6301 }
6302 (Value::Numeric { scaled, scale, .. }, DataType::Int) => {
6303 let rounded = numeric_round_to_integer(scaled, scale);
6304 i32::try_from(rounded).ok().map(Value::Int)
6305 }
6306 (Value::Numeric { scaled, scale, .. }, DataType::BigInt) => {
6307 let rounded = numeric_round_to_integer(scaled, scale);
6308 i64::try_from(rounded).ok().map(Value::BigInt)
6309 }
6310 (Value::Numeric { scaled, scale, .. }, DataType::SmallInt) => {
6311 let rounded = numeric_round_to_integer(scaled, scale);
6312 i16::try_from(rounded).ok().map(Value::SmallInt)
6313 }
6314 (Value::Text(s), DataType::Name) => {
6321 let mut cut = s.into_owned();
6322 if cut.len() > 63 {
6323 let mut idx = 63;
6324 while !cut.is_char_boundary(idx) {
6325 idx -= 1;
6326 }
6327 cut.truncate(idx);
6328 }
6329 Some(Value::text(cut))
6330 }
6331 (Value::Text(s), DataType::Varchar(max)) => {
6332 if max == 0 || u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
6333 Some(Value::text(s))
6334 } else {
6335 let excess_all_blanks = s.chars().skip(max as usize).all(|c| c == ' ');
6340 if excess_all_blanks {
6341 Some(Value::text(
6342 s.chars()
6343 .take(max as usize)
6344 .collect::<alloc::string::String>(),
6345 ))
6346 } else {
6347 return Err(EngineError::Unsupported(alloc::format!(
6348 "value too long for type character varying({max})"
6349 )));
6350 }
6351 }
6352 }
6353 (
6361 Value::Vector(v),
6362 DataType::Vector {
6363 dim,
6364 encoding: VecEncoding::Sq8,
6365 },
6366 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
6367 (
6372 Value::Vector(v),
6373 DataType::Vector {
6374 dim,
6375 encoding: VecEncoding::F16,
6376 },
6377 ) if v.len() == dim as usize => Some(Value::HalfVector(
6378 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
6379 )),
6380 (Value::Text(s), DataType::Char(size)) => {
6384 if size == 0 {
6388 return Ok(Value::BpChar(alloc::borrow::Cow::Owned(
6389 s.trim_end_matches(' ').to_string(),
6390 )));
6391 }
6392 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
6393 let body = if len > size {
6394 let trimmed = s.trim_end_matches(' ');
6395 let tlen = u32::try_from(trimmed.chars().count()).unwrap_or(u32::MAX);
6396 if tlen > size {
6397 return Err(EngineError::Unsupported(alloc::format!(
6398 "value too long for type character({size})"
6399 )));
6400 }
6401 trimmed.to_string()
6402 } else {
6403 s.into_owned()
6404 };
6405 let need = (size as usize) - body.chars().count();
6406 let mut padded = body;
6407 padded.reserve(need);
6408 for _ in 0..need {
6409 padded.push(' ');
6410 }
6411 Some(Value::BpChar(alloc::borrow::Cow::Owned(padded)))
6415 }
6416 _ => None,
6417 };
6418 coerced.ok_or_else(|| {
6419 EngineError::Storage(StorageError::TypeMismatch {
6420 column: col_name.into(),
6421 expected,
6422 actual,
6423 position,
6424 })
6425 })
6426}
6427
6428pub(crate) fn big_literal_to_value(s: &str) -> Value<'static> {
6431 let b = spg_storage::bignum::BigNumeric::from_decimal_str(s).expect("lexer-validated decimal");
6432 match b.to_i128() {
6433 Some(scaled) => Value::Numeric {
6434 scaled,
6435 scale: b.scale(),
6436 kind: spg_storage::NumericKind::Finite,
6437 },
6438 None => Value::NumericBig(alloc::boxed::Box::new(b)),
6439 }
6440}
6441
6442pub(crate) fn types_unify(a: DataType, b: DataType) -> bool {
6451 fn category(t: DataType) -> Option<u8> {
6452 Some(match t {
6453 DataType::SmallInt
6454 | DataType::Int
6455 | DataType::BigInt
6456 | DataType::Numeric { .. }
6457 | DataType::Real
6458 | DataType::Float => 1,
6459 DataType::Text | DataType::Varchar(_) | DataType::Char(_) => 2,
6460 DataType::Date | DataType::Timestamp | DataType::Timestamptz => 3,
6461 _ => return None,
6462 })
6463 }
6464 if a == b {
6465 return true;
6466 }
6467 match (category(a), category(b)) {
6468 (Some(x), Some(y)) => x == y,
6469 _ => false,
6472 }
6473}
6474
6475pub(crate) fn pg_type_name_for_error_opt(t: Option<DataType>) -> alloc::string::String {
6489 match t {
6490 Some(t) => pg_type_name_for_error(t),
6491 None => alloc::string::String::from("unknown"),
6492 }
6493}
6494
6495pub(crate) fn pg_type_name_for_error(t: DataType) -> alloc::string::String {
6496 use spg_storage::DataType as D;
6497 let elem = match t {
6498 D::TextArray => Some(D::Text),
6499 D::IntArray => Some(D::Int),
6500 D::BigIntArray => Some(D::BigInt),
6501 D::SmallIntArray => Some(D::SmallInt),
6502 D::FloatArray => Some(D::Float),
6503 D::NumericArray => Some(D::Numeric {
6504 precision: 0,
6505 scale: 0,
6506 }),
6507 D::BoolArray => Some(D::Bool),
6508 D::DateArray => Some(D::Date),
6509 D::TimestampArray => Some(D::Timestamp),
6510 D::TimestamptzArray => Some(D::Timestamptz),
6511 D::IntervalArray => Some(D::Interval),
6512 D::UuidArray => Some(D::Uuid),
6513 D::JsonArray | D::JsonbArray => Some(D::Jsonb),
6514 D::BytesArray => Some(D::Bytes),
6515 D::MoneyArray => Some(D::Money),
6516 _ => None,
6517 };
6518 match elem {
6519 Some(e) => alloc::format!("{}[]", crate::system_catalog::pg_data_type_text(e)),
6520 None => crate::system_catalog::pg_data_type_text(t),
6521 }
6522}