1use super::*;
11
12pub(super) fn value_cmp_for_min_max(a: &Value, b: &Value, mysql: bool) -> core::cmp::Ordering {
16 use core::cmp::Ordering;
17 if mysql {
21 if let (Value::Text(x), Value::Text(y)) | (Value::BpChar(x), Value::BpChar(y)) = (a, b) {
22 return spg_storage::mysql_compare_fold(x).cmp(&spg_storage::mysql_compare_fold(y));
23 }
24 }
25 if let Some(ord) = crate::orderby::numeric_bignum_cmp(a, b) {
27 return ord;
28 }
29 {
33 use spg_storage::NumericKind as NK;
34 let kind = |v: &Value| -> Option<NK> {
35 match v {
36 Value::Numeric { kind, .. } => Some(*kind),
37 Value::Int(_) | Value::BigInt(_) | Value::SmallInt(_) => Some(NK::Finite),
38 _ => None,
39 }
40 };
41 if let (Some(lk), Some(rk)) = (kind(a), kind(b)) {
42 if lk != NK::Finite || rk != NK::Finite {
43 let rank = |k: NK| match k {
44 NK::NegInf => -2,
45 NK::Finite => 0,
46 NK::PosInf => 1,
47 NK::NaN => 2,
48 };
49 return rank(lk).cmp(&rank(rk));
50 }
51 }
52 }
53 let a_int = match a {
55 Value::SmallInt(x) => Some(i64::from(*x)),
56 Value::Int(x) => Some(i64::from(*x)),
57 Value::BigInt(x) => Some(*x),
58 _ => None,
59 };
60 let b_int = match b {
61 Value::SmallInt(x) => Some(i64::from(*x)),
62 Value::Int(x) => Some(i64::from(*x)),
63 Value::BigInt(x) => Some(*x),
64 _ => None,
65 };
66 if let (Some(av), Some(bv)) = (a_int, b_int) {
67 return av.cmp(&bv);
68 }
69 let a_f = value_to_f64(a);
71 let b_f = value_to_f64(b);
72 if let (Some(av), Some(bv)) = (a_f, b_f) {
73 return av.partial_cmp(&bv).unwrap_or(Ordering::Equal);
74 }
75 match (a, b) {
79 (Value::Text(av), Value::Text(bv)) => av.cmp(bv),
80 (Value::Bytes(av), Value::Bytes(bv)) => av.cmp(bv),
81 (Value::Date(av), Value::Date(bv)) => av.cmp(bv),
82 (Value::Timestamp(av), Value::Timestamp(bv)) => av.cmp(bv),
83 (Value::Date(av), Value::Timestamp(bv)) => {
85 (i64::from(*av).saturating_mul(86_400_000_000)).cmp(bv)
86 }
87 (Value::Timestamp(av), Value::Date(bv)) => {
88 av.cmp(&i64::from(*bv).saturating_mul(86_400_000_000))
89 }
90 (Value::Time(av), Value::Time(bv)) => av.cmp(bv),
91 (Value::Bool(av), Value::Bool(bv)) => av.cmp(bv),
92 (
95 Value::Interval {
96 months: am,
97 days: ad,
98 micros: au,
99 },
100 Value::Interval {
101 months: bm,
102 days: bd,
103 micros: bu,
104 },
105 ) => {
106 let total = |m: i32, d: i32, u: i64| -> i128 {
107 i128::from(m) * 30 * 86_400_000_000 + i128::from(d) * 86_400_000_000 + i128::from(u)
108 };
109 total(*am, *ad, *au).cmp(&total(*bm, *bd, *bu))
110 }
111 (Value::Tid(b1, o1), Value::Tid(b2, o2)) => b1.cmp(b2).then(o1.cmp(o2)),
115 (Value::Xid(a), Value::Xid(b)) => a.cmp(b),
116 (Value::Cid(a), Value::Cid(b)) => a.cmp(b),
117 _ => crate::eval::binop::compare(spg_sql::ast::BinOp::Lt, a, b)
127 .ok()
128 .and_then(|v| match v {
129 Value::Bool(true) => Some(Ordering::Less),
130 Value::Bool(false) => {
131 match crate::eval::binop::compare(spg_sql::ast::BinOp::Gt, a, b) {
132 Ok(Value::Bool(true)) => Some(Ordering::Greater),
133 Ok(Value::Bool(false)) => Some(Ordering::Equal),
134 _ => None,
135 }
136 }
137 _ => None,
138 })
139 .unwrap_or(Ordering::Equal),
140 }
141}
142
143pub(super) fn value_to_f64(v: &Value) -> Option<f64> {
144 match v {
145 Value::Float(x) => Some(*x),
146 Value::Real(x) => Some(f64::from(*x)),
147 Value::SmallInt(x) => Some(f64::from(*x)),
148 Value::Int(x) => Some(f64::from(*x)),
149 Value::BigInt(x) => Some(*x as f64),
150 Value::Numeric { scaled, scale, .. } => {
151 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
152 }
153 _ => None,
154 }
155}
156
157pub(super) fn values_equal_for_nullif(a: &Value, b: &Value) -> bool {
162 if a == b {
164 return true;
165 }
166 let a_int = match a {
168 Value::SmallInt(x) => Some(i64::from(*x)),
169 Value::Int(x) => Some(i64::from(*x)),
170 Value::BigInt(x) => Some(*x),
171 _ => None,
172 };
173 let b_int = match b {
174 Value::SmallInt(x) => Some(i64::from(*x)),
175 Value::Int(x) => Some(i64::from(*x)),
176 Value::BigInt(x) => Some(*x),
177 _ => None,
178 };
179 if let (Some(a), Some(b)) = (a_int, b_int) {
180 return a == b;
181 }
182 let a_f = match a {
184 Value::Float(x) => Some(*x),
185 Value::SmallInt(x) => Some(f64::from(*x)),
186 Value::Int(x) => Some(f64::from(*x)),
187 Value::BigInt(x) => Some(*x as f64),
188 Value::Numeric { scaled, scale, .. } => {
189 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
190 }
191 _ => None,
192 };
193 let b_f = match b {
194 Value::Float(x) => Some(*x),
195 Value::SmallInt(x) => Some(f64::from(*x)),
196 Value::Int(x) => Some(f64::from(*x)),
197 Value::BigInt(x) => Some(*x as f64),
198 Value::Numeric { scaled, scale, .. } => {
199 Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
200 }
201 _ => None,
202 };
203 if let (Some(a), Some(b)) = (a_f, b_f) {
204 return a == b;
205 }
206 false
207}
208
209pub fn gen_random_uuid_bytes() -> [u8; 16] {
216 let mut out = [0u8; 16];
217 let hi = prng_next_u64().to_be_bytes();
218 let lo = prng_next_u64().to_be_bytes();
219 out[..8].copy_from_slice(&hi);
220 out[8..].copy_from_slice(&lo);
221 out[6] = (out[6] & 0x0f) | 0x40;
223 out[8] = (out[8] & 0x3f) | 0x80;
225 out
226}
227
228#[must_use]
239pub fn value_to_text_with_fsp(v: &Value, fsp: Option<u8>) -> String {
240 let Some(fsp) = fsp else {
241 return value_to_text(v);
242 };
243 let (whole, micros) = match v {
244 Value::Timestamp(us) => (
245 crate::eval::format_timestamp(us.div_euclid(1_000_000) * 1_000_000),
246 us.rem_euclid(1_000_000),
247 ),
248 Value::Time(us) => (
249 crate::eval::format_time(us.div_euclid(1_000_000) * 1_000_000),
250 us.rem_euclid(1_000_000),
251 ),
252 other => return value_to_text(other),
253 };
254 if fsp == 0 {
255 return whole;
256 }
257 let digits = usize::from(fsp.min(6));
258 let frac = format!("{micros:06}");
261 format!("{whole}.{}", &frac[..digits])
262}
263
264pub fn value_to_text(v: &Value) -> String {
265 value_to_text_styled(v, &crate::eval::RenderStyle::default())
266}
267
268pub fn value_to_text_styled(v: &Value, style: &crate::eval::RenderStyle) -> String {
272 match v {
273 Value::SmallInt(n) => format!("{n}"),
277 Value::Int(n) => format!("{n}"),
278 Value::BigInt(n) => format!("{n}"),
279 Value::Float(x) => crate::eval::format_float_styled(*x, style),
283 Value::Real(x) => crate::eval::format_real_styled(*x, style),
285 Value::BpChar(s) => s.to_string(),
288 Value::Text(s) | Value::Json(s) => s.to_string(),
290 Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
291 Value::NumericBig(b) => b.to_decimal_str(),
294 Value::Composite(fields) => {
297 let mut out = String::from("(");
298 for (i, (_, fv)) in fields.iter().enumerate() {
299 if i > 0 {
300 out.push(',');
301 }
302 if matches!(fv, Value::Null) {
303 continue;
304 }
305 let field = super::strings::value_to_format_text(fv);
306 let needs_quote = field.is_empty()
307 || field
308 .chars()
309 .any(|c| matches!(c, ',' | '(' | ')' | '"' | '\\') || c.is_whitespace());
310 if needs_quote {
311 out.push('"');
312 for c in field.chars() {
313 match c {
314 '"' => out.push_str("\"\""),
315 '\\' => out.push_str("\\\\"),
316 other => out.push(other),
317 }
318 }
319 out.push('"');
320 } else {
321 out.push_str(&field);
322 }
323 }
324 out.push(')');
325 out
326 }
327 Value::Vector(v) => {
328 let cells: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
329 format!("[{}]", cells.join(","))
330 }
331 Value::Sq8Vector(q) => {
336 let cells: Vec<String> = spg_storage::quantize::dequantize(q)
337 .iter()
338 .map(|x| format!("{x}"))
339 .collect();
340 format!("[{}]", cells.join(","))
341 }
342 Value::HalfVector(h) => {
345 let cells: Vec<String> = h.to_f32_vec().iter().map(|x| format!("{x}")).collect();
346 format!("[{}]", cells.join(","))
347 }
348 Value::Numeric {
349 scaled,
350 scale,
351 kind,
352 } => format_numeric_kind(*kind, *scaled, *scale),
353 Value::Date(d) => crate::eval::format_date_styled(*d, style),
354 Value::Timestamp(t) => crate::eval::format_timestamp_styled(*t, style),
355 Value::Interval {
356 months,
357 days,
358 micros,
359 } => crate::eval::format_interval_styled(*months, *days, *micros, style),
360 Value::Null => "NULL".into(),
361 Value::Bytes(b) => {
364 if style.bytea_escape {
365 crate::eval::format::format_bytea_escape(b)
366 } else {
367 format_bytea_hex(b)
368 }
369 }
370 Value::TextArray(items) => format_text_array(items),
372 Value::IntArray(items) => format_int_array(items),
373 Value::BigIntArray(items) => format_bigint_array(items),
374 Value::TsVector(lexs) => format_tsvector(lexs),
376 Value::TsQuery(ast) => format_tsquery(ast),
377 Value::Uuid(b) => spg_storage::format_uuid(b),
380 Value::Time(us) => format_time(*us),
382 Value::TimeTz { us, offset_secs } => format_timetz(*us, *offset_secs),
384 Value::Year(y) => format!("{y:04}"),
386 Value::Money(c) => format_money(*c),
388 Value::Range { .. } => crate::conversions::format_range_text(v),
392 Value::Hstore(pairs) => crate::conversions::format_hstore_text(pairs),
394 Value::IntArray2D(rows) => crate::conversions::format_int_2d_text_pub(rows),
396 Value::BigIntArray2D(rows) => crate::conversions::format_bigint_2d_text_pub(rows),
397 Value::TextArray2D(rows) => crate::conversions::format_text_2d_text_pub(rows),
398 Value::BoolArray2D(rows) => crate::conversions::format_bool_2d_text_pub(rows),
399 Value::BoolArray(items) => crate::eval::format_bool_array(items),
402 Value::SmallIntArray(items) => crate::eval::format_smallint_array(items),
403 Value::FloatArray(items) => crate::eval::format_float_array_styled(items, style),
404 Value::NumericArray(items) => crate::eval::format_numeric_array(items),
405 Value::DateArray(items) => crate::eval::format_date_array_styled(items, style),
406 Value::TimestampArray(items) => {
407 crate::eval::format_timestamp_array_styled(items, false, style)
408 }
409 Value::TimestamptzArray(items) => {
410 crate::eval::format_timestamp_array_styled(items, true, style)
411 }
412 Value::UuidArray(items) => crate::eval::format_uuid_array(items),
413 Value::JsonArray(items) | Value::JsonbArray(items) => crate::eval::format_text_array(items),
414 Value::BytesArray(items) => crate::eval::format_bytea_array(items),
415 Value::IntervalArray(items) => crate::eval::format_interval_array_styled(items, style),
416 Value::MoneyArray(items) => crate::conversions::format_money_array(items),
417 Value::Point(p) => crate::conversions::format_point(*p),
419 Value::Lseg(a, b) => crate::conversions::format_lseg(*a, *b),
420 Value::Path { points, closed } => crate::conversions::format_path(points, *closed),
421 Value::PgBox(ur, ll) => crate::conversions::format_pg_box(*ur, *ll),
422 Value::Polygon(points) => crate::conversions::format_polygon(points),
423 Value::Line { a, b, c } => crate::conversions::format_line(*a, *b, *c),
424 Value::Circle { center, radius } => crate::conversions::format_circle(*center, *radius),
425 Value::Multirange { ranges, .. } => crate::conversions::format_multirange(ranges),
427 Value::Inet { family, bits, addr } => crate::conversions::format_inet(*family, *bits, addr),
429 Value::Cidr { family, bits, addr } => {
435 crate::conversions::format_inet_full(*family, *bits, addr)
436 }
437 Value::Macaddr(b) => crate::conversions::format_macaddr(b),
438 Value::Macaddr8(b) => crate::conversions::format_macaddr8(b),
439 Value::PgLsn(l) => crate::conversions::format_pg_lsn(*l),
440 Value::RegClass(_, name) | Value::RegProc(_, name) => name.to_string(),
441 Value::RegType(_, name) => name.to_string(),
442 Value::Tid(b, o) => alloc::format!("({b},{o})"),
444 Value::Xid(x) => alloc::format!("{x}"),
446 Value::Cid(c) => alloc::format!("{c}"),
447 Value::BitString { nbits, bytes } => crate::conversions::format_bit_string(*nbits, bytes),
448 Value::Xml(s) => s.to_string(),
449 Value::Char1(b) => format!("{}", *b as char),
450 _ => format!("{v:?}"),
452 }
453}
454
455pub(crate) fn array_len(v: &Value) -> Option<usize> {
460 match v {
461 Value::TextArray(items)
462 | Value::VarcharArray(items)
463 | Value::CharArray(items)
464 | Value::JsonArray(items)
465 | Value::JsonbArray(items) => Some(items.len()),
466 Value::IntArray(items) => Some(items.len()),
467 Value::BigIntArray(items) => Some(items.len()),
468 Value::SmallIntArray(items) => Some(items.len()),
469 Value::BoolArray(items) => Some(items.len()),
470 Value::FloatArray(items) => Some(items.len()),
471 Value::NumericArray(items) => Some(items.len()),
472 Value::DateArray(items) => Some(items.len()),
473 Value::TimestampArray(items) | Value::TimestamptzArray(items) => Some(items.len()),
474 Value::MoneyArray(items) => Some(items.len()),
475 Value::IntervalArray(items) => Some(items.len()),
476 Value::UuidArray(items) => Some(items.len()),
477 Value::BytesArray(items) => Some(items.len()),
478 _ => None,
479 }
480}
481
482pub(crate) fn array_elements(v: &Value) -> Option<alloc::vec::Vec<Value<'static>>> {
493 if let Some(n) = array_len(v) {
494 let mut out = alloc::vec::Vec::with_capacity(n);
495 for i in 0..n {
496 out.push(array_element_at(v, i)?);
497 }
498 return Some(out);
499 }
500 macro_rules! rows {
502 ($m:expr, $variant:ident) => {
503 Some($m.iter().map(|r| Value::$variant(r.clone())).collect())
504 };
505 }
506 match v {
507 Value::IntArray2D(m) => rows!(m, IntArray),
508 Value::BigIntArray2D(m) => rows!(m, BigIntArray),
509 Value::TextArray2D(m) => rows!(m, TextArray),
510 Value::BoolArray2D(m) => rows!(m, BoolArray),
511 _ => None,
512 }
513}
514
515pub(super) fn array_2d_dims(v: &Value) -> Option<(usize, usize)> {
518 match v {
519 Value::IntArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
520 Value::BigIntArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
521 Value::TextArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
522 Value::BoolArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
523 _ => None,
524 }
525}
526
527pub(crate) fn array_element_at(v: &Value, pos: usize) -> Option<Value<'static>> {
534 use alloc::borrow::Cow;
535 macro_rules! nth {
536 ($items:expr, $map:expr) => {
537 $items
538 .get(pos)
539 .map(|e| e.as_ref().map_or(Value::Null, $map))
540 };
541 }
542 match v {
543 Value::TextArray(items) | Value::VarcharArray(items) | Value::CharArray(items) => {
544 nth!(items, |s| Value::Text(Cow::Owned(s.clone())))
545 }
546 Value::JsonArray(items) | Value::JsonbArray(items) => {
547 nth!(items, |s| Value::Json(Cow::Owned(s.clone())))
548 }
549 Value::IntArray(items) => nth!(items, |n| Value::Int(*n)),
550 Value::BigIntArray(items) => nth!(items, |n| Value::BigInt(*n)),
551 Value::SmallIntArray(items) => nth!(items, |n| Value::SmallInt(*n)),
552 Value::BoolArray(items) => nth!(items, |b| Value::Bool(*b)),
553 Value::FloatArray(items) => nth!(items, |f| Value::Float(*f)),
554 Value::NumericArray(items) => {
555 nth!(items, |t: &(i128, u16)| Value::Numeric {
556 scaled: t.0,
557 scale: t.1,
558 kind: spg_storage::NumericKind::Finite
559 })
560 }
561 Value::DateArray(items) => nth!(items, |d| Value::Date(*d)),
562 Value::TimestampArray(items) | Value::TimestamptzArray(items) => {
563 nth!(items, |t| Value::Timestamp(*t))
564 }
565 Value::MoneyArray(items) => nth!(items, |m| Value::Money(*m)),
566 Value::IntervalArray(items) => nth!(items, |s| Value::Interval {
567 months: s.months,
568 days: s.days,
569 micros: s.micros,
570 }),
571 Value::UuidArray(items) => nth!(items, |u| Value::Uuid(*u)),
572 Value::BytesArray(items) => nth!(items, |b| Value::Bytes(Cow::Owned(b.clone()))),
573 _ => None,
574 }
575}
576
577pub(super) fn array_rebuild(model: &Value<'_>, elems: &[Value<'static>]) -> Option<Value<'static>> {
586 macro_rules! build {
587 ($variant:ident, $conv:expr) => {{
588 let mut out = alloc::vec::Vec::with_capacity(elems.len());
589 for e in elems {
590 if matches!(e, Value::Null) {
591 out.push(None);
592 continue;
593 }
594 out.push(Some(($conv)(e)?));
595 }
596 Some(Value::$variant(out))
597 }};
598 }
599 let as_i64 = |v: &Value<'_>| -> Option<i64> {
600 match v {
601 Value::SmallInt(n) => Some(i64::from(*n)),
602 Value::Int(n) => Some(i64::from(*n)),
603 Value::BigInt(n) => Some(*n),
604 _ => None,
605 }
606 };
607 match model {
608 Value::TextArray(_) => build!(TextArray, |e: &Value<'_>| match e {
609 Value::Text(s) => Some(s.as_ref().to_string()),
610 _ => None,
611 }),
612 Value::VarcharArray(_) => build!(VarcharArray, |e: &Value<'_>| match e {
613 Value::Text(s) => Some(s.as_ref().to_string()),
614 _ => None,
615 }),
616 Value::JsonArray(_) => build!(JsonArray, |e: &Value<'_>| match e {
617 Value::Json(s) | Value::Text(s) => Some(s.as_ref().to_string()),
618 _ => None,
619 }),
620 Value::JsonbArray(_) => build!(JsonbArray, |e: &Value<'_>| match e {
621 Value::Json(s) | Value::Text(s) => Some(s.as_ref().to_string()),
622 _ => None,
623 }),
624 Value::IntArray(_) => build!(IntArray, |e: &Value<'_>| as_i64(e)
625 .and_then(|n| i32::try_from(n).ok())),
626 Value::BigIntArray(_) => build!(BigIntArray, as_i64),
627 Value::SmallIntArray(_) => build!(SmallIntArray, |e: &Value<'_>| as_i64(e)
628 .and_then(|n| i16::try_from(n).ok())),
629 Value::BoolArray(_) => build!(BoolArray, |e: &Value<'_>| match e {
630 Value::Bool(b) => Some(*b),
631 _ => None,
632 }),
633 Value::FloatArray(_) => build!(FloatArray, |e: &Value<'_>| match e {
634 Value::Float(f) => Some(*f),
635 Value::Real(f) => Some(f64::from(*f)),
636 other => as_i64(other).map(|n| n as f64),
637 }),
638 Value::NumericArray(_) => build!(NumericArray, |e: &Value<'_>| match e {
639 Value::Numeric { scaled, scale, .. } => Some((*scaled, *scale)),
640 other => as_i64(other).map(|n| (i128::from(n), 0u16)),
641 }),
642 Value::DateArray(_) => build!(DateArray, |e: &Value<'_>| match e {
643 Value::Date(d) => Some(*d),
644 _ => None,
645 }),
646 Value::TimestampArray(_) => build!(TimestampArray, |e: &Value<'_>| match e {
647 Value::Timestamp(t) => Some(*t),
648 _ => None,
649 }),
650 Value::TimestamptzArray(_) => build!(TimestamptzArray, |e: &Value<'_>| match e {
651 Value::Timestamp(t) => Some(*t),
652 _ => None,
653 }),
654 Value::MoneyArray(_) => build!(MoneyArray, |e: &Value<'_>| match e {
655 Value::Money(m) => Some(*m),
656 _ => None,
657 }),
658 Value::UuidArray(_) => build!(UuidArray, |e: &Value<'_>| match e {
659 Value::Uuid(u) => Some(*u),
660 _ => None,
661 }),
662 Value::BytesArray(_) => build!(BytesArray, |e: &Value<'_>| match e {
663 Value::Bytes(b) => Some(b.as_ref().to_vec()),
664 _ => None,
665 }),
666 Value::IntervalArray(_) => build!(IntervalArray, |e: &Value<'_>| match e {
667 Value::Interval {
668 months,
669 days,
670 micros,
671 } => Some(spg_storage::IntervalSpan {
672 months: *months,
673 days: *days,
674 micros: *micros,
675 }),
676 _ => None,
677 }),
678 _ => None,
679 }
680}
681
682pub(crate) fn build_array_from_values(vals: &[Value<'static>]) -> Value<'static> {
696 if let Some(v) = homogeneous_typed_array(vals) {
697 return v;
698 }
699 let mut has_text = false;
700 let mut has_float = false;
701 let mut has_numeric = false;
702 let mut has_bigint = false;
703 let mut has_int = false;
704 for v in vals {
705 match v {
706 Value::Null => {}
707 Value::Int(_) | Value::SmallInt(_) => has_int = true,
708 Value::BigInt(_) => has_bigint = true,
709 Value::Numeric { .. } | Value::NumericBig(_) => has_numeric = true,
710 Value::Float(_) | Value::Real(_) => has_float = true,
711 _ => has_text = true,
712 }
713 }
714 let as_i64 = |v: &Value<'_>| -> Option<i64> {
715 match v {
716 Value::SmallInt(n) => Some(i64::from(*n)),
717 Value::Int(n) => Some(i64::from(*n)),
718 Value::BigInt(n) => Some(*n),
719 _ => None,
720 }
721 };
722 if !has_text {
723 if has_float {
724 return Value::FloatArray(
725 vals.iter()
726 .map(|v| match v {
727 Value::Null => None,
728 Value::Float(f) => Some(*f),
729 Value::Real(f) => Some(f64::from(*f)),
730 #[allow(clippy::cast_precision_loss)]
731 Value::Numeric { scaled, scale, .. } => {
732 Some(*scaled as f64 / libm::pow(10.0, f64::from(*scale)))
733 }
734 other => as_i64(other).map(|n| n as f64),
735 })
736 .collect(),
737 );
738 }
739 if has_numeric {
740 if vals.iter().all(|v| {
743 matches!(
744 v,
745 Value::Null
746 | Value::SmallInt(_)
747 | Value::Int(_)
748 | Value::BigInt(_)
749 | Value::Numeric {
750 kind: spg_storage::NumericKind::Finite,
751 ..
752 }
753 )
754 }) {
755 return Value::NumericArray(
756 vals.iter()
757 .map(|v| match v {
758 Value::Null => None,
759 Value::Numeric { scaled, scale, .. } => Some((*scaled, *scale)),
760 other => as_i64(other).map(|n| (i128::from(n), 0u16)),
761 })
762 .collect(),
763 );
764 }
765 } else if has_bigint {
766 return Value::BigIntArray(vals.iter().map(as_i64).collect());
767 } else if has_int {
768 return Value::IntArray(
769 vals.iter()
770 .map(|v| as_i64(v).and_then(|n| i32::try_from(n).ok()))
771 .collect(),
772 );
773 }
774 }
775 Value::TextArray(
776 vals.iter()
777 .map(|v| match v {
778 Value::Null => None,
779 Value::Text(s) | Value::Json(s) => Some(s.as_ref().to_string()),
780 other => Some(crate::eval::value_to_text(other)),
781 })
782 .collect(),
783 )
784}
785
786pub(crate) fn homogeneous_typed_array(vals: &[Value<'static>]) -> Option<Value<'static>> {
789 let first = vals.iter().find(|v| !matches!(v, Value::Null))?;
790 macro_rules! collect {
791 ($variant:ident, $pat:pat => $val:expr) => {{
792 let mut out = alloc::vec::Vec::with_capacity(vals.len());
793 for v in vals {
794 match v {
795 Value::Null => out.push(None),
796 $pat => out.push(Some($val)),
797 _ => return None,
798 }
799 }
800 Some(Value::$variant(out))
801 }};
802 }
803 match first {
804 Value::Bool(_) => collect!(BoolArray, Value::Bool(b) => *b),
805 Value::Date(_) => collect!(DateArray, Value::Date(d) => *d),
806 Value::Timestamp(_) => collect!(TimestampArray, Value::Timestamp(t) => *t),
807 Value::Uuid(_) => collect!(UuidArray, Value::Uuid(u) => *u),
808 Value::Money(_) => collect!(MoneyArray, Value::Money(m) => *m),
809 Value::Bytes(_) => collect!(BytesArray, Value::Bytes(b) => b.as_ref().to_vec()),
810 Value::Interval { .. } => {
811 let mut out = alloc::vec::Vec::with_capacity(vals.len());
812 for v in vals {
813 match v {
814 Value::Null => out.push(None),
815 Value::Interval {
816 months,
817 days,
818 micros,
819 } => out.push(Some(spg_storage::IntervalSpan {
820 months: *months,
821 days: *days,
822 micros: *micros,
823 })),
824 _ => return None,
825 }
826 }
827 Some(Value::IntervalArray(out))
828 }
829 _ => None,
830 }
831}
832
833pub(crate) fn split_2d_rows(s: &str) -> Option<Vec<alloc::string::String>> {
847 let trimmed = s.trim();
848 let inner = trimmed
849 .strip_prefix('{')
850 .and_then(|x| x.strip_suffix('}'))?
851 .trim();
852 if !inner.starts_with('{') {
853 return None;
854 }
855 let mut rows = alloc::vec::Vec::new();
856 let bytes = inner.as_bytes();
857 let mut depth = 0i32;
858 let mut start = 0usize;
859 let mut in_quote = false;
860 let mut i = 0;
861 while i < bytes.len() {
862 let c = bytes[i];
863 if in_quote {
864 if c == b'\\' {
865 i += 2;
866 continue;
867 }
868 if c == b'"' {
869 in_quote = false;
870 }
871 } else {
872 match c {
873 b'"' => in_quote = true,
874 b'{' => depth += 1,
875 b'}' => depth -= 1,
876 b',' if depth == 0 => {
877 rows.push(inner[start..i].trim().to_string());
878 start = i + 1;
879 }
880 _ => {}
881 }
882 }
883 i += 1;
884 }
885 rows.push(inner[start..].trim().to_string());
886 Some(rows)
887}
888
889pub(crate) fn build_2d_from_rows(rows: &[Value<'static>]) -> Option<Value<'static>> {
890 if rows.is_empty() || !rows.iter().all(|v| array_len(v).is_some()) {
891 return None;
892 }
893 let width = array_len(&rows[0])?;
894 if !rows.iter().all(|v| array_len(v) == Some(width)) {
895 return None;
896 }
897 if rows.iter().all(|v| matches!(v, Value::BoolArray(_))) {
898 return Some(Value::BoolArray2D(
899 rows.iter()
900 .map(|v| match v {
901 Value::BoolArray(r) => r.clone(),
902 _ => unreachable!("checked"),
903 })
904 .collect(),
905 ));
906 }
907 if rows.iter().all(|v| matches!(v, Value::IntArray(_))) {
908 return Some(Value::IntArray2D(
909 rows.iter()
910 .map(|v| match v {
911 Value::IntArray(r) => r.clone(),
912 _ => unreachable!("checked"),
913 })
914 .collect(),
915 ));
916 }
917 if rows
918 .iter()
919 .all(|v| matches!(v, Value::IntArray(_) | Value::BigIntArray(_)))
920 {
921 return Some(Value::BigIntArray2D(
922 rows.iter()
923 .map(|v| match v {
924 Value::BigIntArray(r) => r.clone(),
925 Value::IntArray(r) => r.iter().map(|c| c.map(i64::from)).collect(),
926 _ => unreachable!("checked"),
927 })
928 .collect(),
929 ));
930 }
931 Some(Value::TextArray2D(
935 rows.iter()
936 .map(|v| {
937 let n = array_len(v).unwrap_or(0);
938 (0..n)
939 .map(|i| match array_element_at(v, i) {
940 None | Some(Value::Null) => None,
941 Some(x) => Some(crate::eval::value_to_text(&x)),
942 })
943 .collect()
944 })
945 .collect(),
946 ))
947}
948
949pub(crate) fn flatten_2d(v: &Value<'_>) -> Option<Value<'static>> {
956 Some(match v {
957 Value::IntArray2D(rows) => Value::IntArray(rows.iter().flatten().copied().collect()),
958 Value::BigIntArray2D(rows) => Value::BigIntArray(rows.iter().flatten().copied().collect()),
959 Value::BoolArray2D(rows) => Value::BoolArray(rows.iter().flatten().copied().collect()),
960 Value::TextArray2D(rows) => Value::TextArray(rows.iter().flatten().cloned().collect()),
961 _ => return None,
962 })
963}