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_typed(v: &Value, dt: &spg_storage::DataType) -> String {
279 value_to_text_typed_styled(v, dt, &crate::eval::RenderStyle::default())
280}
281
282pub fn value_to_text_typed_styled(
284 v: &Value,
285 dt: &spg_storage::DataType,
286 style: &crate::eval::RenderStyle,
287) -> String {
288 match (dt, v) {
289 (spg_storage::DataType::Timestamptz, Value::Timestamp(us)) => {
290 crate::eval::format_timestamptz_styled(*us, style)
291 }
292 _ => value_to_text_styled(v, style),
293 }
294}
295
296pub fn value_to_text_styled(v: &Value, style: &crate::eval::RenderStyle) -> String {
300 match v {
301 Value::SmallInt(n) => format!("{n}"),
305 Value::Int(n) => format!("{n}"),
306 Value::BigInt(n) => format!("{n}"),
307 Value::Float(x) => crate::eval::format_float_styled(*x, style),
311 Value::Real(x) => crate::eval::format_real_styled(*x, style),
313 Value::BpChar(s) => s.to_string(),
316 Value::Text(s) | Value::Json(s) => s.to_string(),
318 Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
319 Value::NumericBig(b) => b.to_decimal_str(),
322 Value::Composite(fields) => {
325 let mut out = String::from("(");
326 for (i, (_, fv)) in fields.iter().enumerate() {
327 if i > 0 {
328 out.push(',');
329 }
330 if matches!(fv, Value::Null) {
331 continue;
332 }
333 let field = super::strings::value_to_format_text(fv);
334 let needs_quote = field.is_empty()
335 || field
336 .chars()
337 .any(|c| matches!(c, ',' | '(' | ')' | '"' | '\\') || c.is_whitespace());
338 if needs_quote {
339 out.push('"');
340 for c in field.chars() {
341 match c {
342 '"' => out.push_str("\"\""),
343 '\\' => out.push_str("\\\\"),
344 other => out.push(other),
345 }
346 }
347 out.push('"');
348 } else {
349 out.push_str(&field);
350 }
351 }
352 out.push(')');
353 out
354 }
355 Value::Vector(v) => {
356 let cells: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
357 format!("[{}]", cells.join(","))
358 }
359 Value::Sq8Vector(q) => {
364 let cells: Vec<String> = spg_storage::quantize::dequantize(q)
365 .iter()
366 .map(|x| format!("{x}"))
367 .collect();
368 format!("[{}]", cells.join(","))
369 }
370 Value::HalfVector(h) => {
373 let cells: Vec<String> = h.to_f32_vec().iter().map(|x| format!("{x}")).collect();
374 format!("[{}]", cells.join(","))
375 }
376 Value::Numeric {
377 scaled,
378 scale,
379 kind,
380 } => format_numeric_kind(*kind, *scaled, *scale),
381 Value::Date(d) => crate::eval::format_date_styled(*d, style),
382 Value::Timestamp(t) => crate::eval::format_timestamp_styled(*t, style),
383 Value::Interval {
384 months,
385 days,
386 micros,
387 } => crate::eval::format_interval_styled(*months, *days, *micros, style),
388 Value::Null => "NULL".into(),
389 Value::Bytes(b) => {
392 if style.bytea_escape {
393 crate::eval::format::format_bytea_escape(b)
394 } else {
395 format_bytea_hex(b)
396 }
397 }
398 Value::TextArray(items) => format_text_array(items),
400 Value::IntArray(items) => format_int_array(items),
401 Value::BigIntArray(items) => format_bigint_array(items),
402 Value::TsVector(lexs) => format_tsvector(lexs),
404 Value::TsQuery(ast) => format_tsquery(ast),
405 Value::Uuid(b) => spg_storage::format_uuid(b),
408 Value::Time(us) => format_time(*us),
410 Value::TimeTz { us, offset_secs } => format_timetz(*us, *offset_secs),
412 Value::Year(y) => format!("{y:04}"),
414 Value::Money(c) => format_money(*c),
416 Value::Range { .. } => crate::conversions::format_range_text(v),
420 Value::Hstore(pairs) => crate::conversions::format_hstore_text(pairs),
422 Value::IntArray2D(rows) => crate::conversions::format_int_2d_text_pub(rows),
424 Value::BigIntArray2D(rows) => crate::conversions::format_bigint_2d_text_pub(rows),
425 Value::TextArray2D(rows) => crate::conversions::format_text_2d_text_pub(rows),
426 Value::BoolArray2D(rows) => crate::conversions::format_bool_2d_text_pub(rows),
427 Value::BoolArray(items) => crate::eval::format_bool_array(items),
430 Value::SmallIntArray(items) => crate::eval::format_smallint_array(items),
431 Value::FloatArray(items) => crate::eval::format_float_array_styled(items, style),
432 Value::NumericArray(items) => crate::eval::format_numeric_array(items),
433 Value::DateArray(items) => crate::eval::format_date_array_styled(items, style),
434 Value::TimestampArray(items) => {
435 crate::eval::format_timestamp_array_styled(items, false, style)
436 }
437 Value::TimestamptzArray(items) => {
438 crate::eval::format_timestamp_array_styled(items, true, style)
439 }
440 Value::UuidArray(items) => crate::eval::format_uuid_array(items),
441 Value::JsonArray(items) | Value::JsonbArray(items) => crate::eval::format_text_array(items),
442 Value::BytesArray(items) => crate::eval::format_bytea_array(items),
443 Value::IntervalArray(items) => crate::eval::format_interval_array_styled(items, style),
444 Value::MoneyArray(items) => crate::conversions::format_money_array(items),
445 Value::Point(p) => crate::conversions::format_point(*p),
447 Value::Lseg(a, b) => crate::conversions::format_lseg(*a, *b),
448 Value::Path { points, closed } => crate::conversions::format_path(points, *closed),
449 Value::PgBox(ur, ll) => crate::conversions::format_pg_box(*ur, *ll),
450 Value::Polygon(points) => crate::conversions::format_polygon(points),
451 Value::Line { a, b, c } => crate::conversions::format_line(*a, *b, *c),
452 Value::Circle { center, radius } => crate::conversions::format_circle(*center, *radius),
453 Value::Multirange { ranges, .. } => crate::conversions::format_multirange(ranges),
455 Value::Inet { family, bits, addr } => crate::conversions::format_inet(*family, *bits, addr),
457 Value::Cidr { family, bits, addr } => {
463 crate::conversions::format_inet_full(*family, *bits, addr)
464 }
465 Value::Macaddr(b) => crate::conversions::format_macaddr(b),
466 Value::Macaddr8(b) => crate::conversions::format_macaddr8(b),
467 Value::PgLsn(l) => crate::conversions::format_pg_lsn(*l),
468 Value::RegClass(_, name) | Value::RegProc(_, name) => name.to_string(),
469 Value::RegType(_, name) => name.to_string(),
470 Value::Tid(b, o) => alloc::format!("({b},{o})"),
472 Value::Xid(x) => alloc::format!("{x}"),
474 Value::Cid(c) => alloc::format!("{c}"),
475 Value::BitString { nbits, bytes } => crate::conversions::format_bit_string(*nbits, bytes),
476 Value::Xml(s) => s.to_string(),
477 Value::Char1(b) => format!("{}", *b as char),
478 _ => format!("{v:?}"),
480 }
481}
482
483pub(crate) fn array_len(v: &Value) -> Option<usize> {
488 match v {
489 Value::TextArray(items)
490 | Value::VarcharArray(items)
491 | Value::CharArray(items)
492 | Value::JsonArray(items)
493 | Value::JsonbArray(items) => Some(items.len()),
494 Value::IntArray(items) => Some(items.len()),
495 Value::BigIntArray(items) => Some(items.len()),
496 Value::SmallIntArray(items) => Some(items.len()),
497 Value::BoolArray(items) => Some(items.len()),
498 Value::FloatArray(items) => Some(items.len()),
499 Value::NumericArray(items) => Some(items.len()),
500 Value::DateArray(items) => Some(items.len()),
501 Value::TimestampArray(items) | Value::TimestamptzArray(items) => Some(items.len()),
502 Value::MoneyArray(items) => Some(items.len()),
503 Value::IntervalArray(items) => Some(items.len()),
504 Value::UuidArray(items) => Some(items.len()),
505 Value::BytesArray(items) => Some(items.len()),
506 _ => None,
507 }
508}
509
510pub(crate) fn array_elements(v: &Value) -> Option<alloc::vec::Vec<Value<'static>>> {
521 if let Some(n) = array_len(v) {
522 let mut out = alloc::vec::Vec::with_capacity(n);
523 for i in 0..n {
524 out.push(array_element_at(v, i)?);
525 }
526 return Some(out);
527 }
528 macro_rules! rows {
530 ($m:expr, $variant:ident) => {
531 Some($m.iter().map(|r| Value::$variant(r.clone())).collect())
532 };
533 }
534 match v {
535 Value::IntArray2D(m) => rows!(m, IntArray),
536 Value::BigIntArray2D(m) => rows!(m, BigIntArray),
537 Value::TextArray2D(m) => rows!(m, TextArray),
538 Value::BoolArray2D(m) => rows!(m, BoolArray),
539 _ => None,
540 }
541}
542
543pub(super) fn array_2d_dims(v: &Value) -> Option<(usize, usize)> {
546 match v {
547 Value::IntArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
548 Value::BigIntArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
549 Value::TextArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
550 Value::BoolArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
551 _ => None,
552 }
553}
554
555pub(crate) fn array_element_at(v: &Value, pos: usize) -> Option<Value<'static>> {
562 use alloc::borrow::Cow;
563 macro_rules! nth {
564 ($items:expr, $map:expr) => {
565 $items
566 .get(pos)
567 .map(|e| e.as_ref().map_or(Value::Null, $map))
568 };
569 }
570 match v {
571 Value::TextArray(items) | Value::VarcharArray(items) | Value::CharArray(items) => {
572 nth!(items, |s| Value::Text(Cow::Owned(s.clone())))
573 }
574 Value::JsonArray(items) | Value::JsonbArray(items) => {
575 nth!(items, |s| Value::Json(Cow::Owned(s.clone())))
576 }
577 Value::IntArray(items) => nth!(items, |n| Value::Int(*n)),
578 Value::BigIntArray(items) => nth!(items, |n| Value::BigInt(*n)),
579 Value::SmallIntArray(items) => nth!(items, |n| Value::SmallInt(*n)),
580 Value::BoolArray(items) => nth!(items, |b| Value::Bool(*b)),
581 Value::FloatArray(items) => nth!(items, |f| Value::Float(*f)),
582 Value::NumericArray(items) => {
583 nth!(items, |t: &(i128, u16)| Value::Numeric {
584 scaled: t.0,
585 scale: t.1,
586 kind: spg_storage::NumericKind::Finite
587 })
588 }
589 Value::DateArray(items) => nth!(items, |d| Value::Date(*d)),
590 Value::TimestampArray(items) | Value::TimestamptzArray(items) => {
591 nth!(items, |t| Value::Timestamp(*t))
592 }
593 Value::MoneyArray(items) => nth!(items, |m| Value::Money(*m)),
594 Value::IntervalArray(items) => nth!(items, |s| Value::Interval {
595 months: s.months,
596 days: s.days,
597 micros: s.micros,
598 }),
599 Value::UuidArray(items) => nth!(items, |u| Value::Uuid(*u)),
600 Value::BytesArray(items) => nth!(items, |b| Value::Bytes(Cow::Owned(b.clone()))),
601 _ => None,
602 }
603}
604
605pub(super) fn array_rebuild(model: &Value<'_>, elems: &[Value<'static>]) -> Option<Value<'static>> {
614 macro_rules! build {
615 ($variant:ident, $conv:expr) => {{
616 let mut out = alloc::vec::Vec::with_capacity(elems.len());
617 for e in elems {
618 if matches!(e, Value::Null) {
619 out.push(None);
620 continue;
621 }
622 out.push(Some(($conv)(e)?));
623 }
624 Some(Value::$variant(out))
625 }};
626 }
627 let as_i64 = |v: &Value<'_>| -> Option<i64> {
628 match v {
629 Value::SmallInt(n) => Some(i64::from(*n)),
630 Value::Int(n) => Some(i64::from(*n)),
631 Value::BigInt(n) => Some(*n),
632 _ => None,
633 }
634 };
635 match model {
636 Value::TextArray(_) => build!(TextArray, |e: &Value<'_>| match e {
637 Value::Text(s) => Some(s.as_ref().to_string()),
638 _ => None,
639 }),
640 Value::VarcharArray(_) => build!(VarcharArray, |e: &Value<'_>| match e {
641 Value::Text(s) => Some(s.as_ref().to_string()),
642 _ => None,
643 }),
644 Value::JsonArray(_) => build!(JsonArray, |e: &Value<'_>| match e {
645 Value::Json(s) | Value::Text(s) => Some(s.as_ref().to_string()),
646 _ => None,
647 }),
648 Value::JsonbArray(_) => build!(JsonbArray, |e: &Value<'_>| match e {
649 Value::Json(s) | Value::Text(s) => Some(s.as_ref().to_string()),
650 _ => None,
651 }),
652 Value::IntArray(_) => build!(IntArray, |e: &Value<'_>| as_i64(e)
653 .and_then(|n| i32::try_from(n).ok())),
654 Value::BigIntArray(_) => build!(BigIntArray, as_i64),
655 Value::SmallIntArray(_) => build!(SmallIntArray, |e: &Value<'_>| as_i64(e)
656 .and_then(|n| i16::try_from(n).ok())),
657 Value::BoolArray(_) => build!(BoolArray, |e: &Value<'_>| match e {
658 Value::Bool(b) => Some(*b),
659 _ => None,
660 }),
661 Value::FloatArray(_) => build!(FloatArray, |e: &Value<'_>| match e {
662 Value::Float(f) => Some(*f),
663 Value::Real(f) => Some(f64::from(*f)),
664 other => as_i64(other).map(|n| n as f64),
665 }),
666 Value::NumericArray(_) => build!(NumericArray, |e: &Value<'_>| match e {
667 Value::Numeric { scaled, scale, .. } => Some((*scaled, *scale)),
668 other => as_i64(other).map(|n| (i128::from(n), 0u16)),
669 }),
670 Value::DateArray(_) => build!(DateArray, |e: &Value<'_>| match e {
671 Value::Date(d) => Some(*d),
672 _ => None,
673 }),
674 Value::TimestampArray(_) => build!(TimestampArray, |e: &Value<'_>| match e {
675 Value::Timestamp(t) => Some(*t),
676 _ => None,
677 }),
678 Value::TimestamptzArray(_) => build!(TimestamptzArray, |e: &Value<'_>| match e {
679 Value::Timestamp(t) => Some(*t),
680 _ => None,
681 }),
682 Value::MoneyArray(_) => build!(MoneyArray, |e: &Value<'_>| match e {
683 Value::Money(m) => Some(*m),
684 _ => None,
685 }),
686 Value::UuidArray(_) => build!(UuidArray, |e: &Value<'_>| match e {
687 Value::Uuid(u) => Some(*u),
688 _ => None,
689 }),
690 Value::BytesArray(_) => build!(BytesArray, |e: &Value<'_>| match e {
691 Value::Bytes(b) => Some(b.as_ref().to_vec()),
692 _ => None,
693 }),
694 Value::IntervalArray(_) => build!(IntervalArray, |e: &Value<'_>| match e {
695 Value::Interval {
696 months,
697 days,
698 micros,
699 } => Some(spg_storage::IntervalSpan {
700 months: *months,
701 days: *days,
702 micros: *micros,
703 }),
704 _ => None,
705 }),
706 _ => None,
707 }
708}
709
710pub(crate) fn build_array_from_values(vals: &[Value<'static>]) -> Value<'static> {
724 if let Some(v) = homogeneous_typed_array(vals) {
725 return v;
726 }
727 let mut has_text = false;
728 let mut has_float = false;
729 let mut has_numeric = false;
730 let mut has_bigint = false;
731 let mut has_int = false;
732 for v in vals {
733 match v {
734 Value::Null => {}
735 Value::Int(_) | Value::SmallInt(_) => has_int = true,
736 Value::BigInt(_) => has_bigint = true,
737 Value::Numeric { .. } | Value::NumericBig(_) => has_numeric = true,
738 Value::Float(_) | Value::Real(_) => has_float = true,
739 _ => has_text = true,
740 }
741 }
742 let as_i64 = |v: &Value<'_>| -> Option<i64> {
743 match v {
744 Value::SmallInt(n) => Some(i64::from(*n)),
745 Value::Int(n) => Some(i64::from(*n)),
746 Value::BigInt(n) => Some(*n),
747 _ => None,
748 }
749 };
750 if !has_text {
751 if has_float {
752 return Value::FloatArray(
753 vals.iter()
754 .map(|v| match v {
755 Value::Null => None,
756 Value::Float(f) => Some(*f),
757 Value::Real(f) => Some(f64::from(*f)),
758 #[allow(clippy::cast_precision_loss)]
759 Value::Numeric { scaled, scale, .. } => {
760 Some(*scaled as f64 / libm::pow(10.0, f64::from(*scale)))
761 }
762 other => as_i64(other).map(|n| n as f64),
763 })
764 .collect(),
765 );
766 }
767 if has_numeric {
768 if vals.iter().all(|v| {
771 matches!(
772 v,
773 Value::Null
774 | Value::SmallInt(_)
775 | Value::Int(_)
776 | Value::BigInt(_)
777 | Value::Numeric {
778 kind: spg_storage::NumericKind::Finite,
779 ..
780 }
781 )
782 }) {
783 return Value::NumericArray(
784 vals.iter()
785 .map(|v| match v {
786 Value::Null => None,
787 Value::Numeric { scaled, scale, .. } => Some((*scaled, *scale)),
788 other => as_i64(other).map(|n| (i128::from(n), 0u16)),
789 })
790 .collect(),
791 );
792 }
793 } else if has_bigint {
794 return Value::BigIntArray(vals.iter().map(as_i64).collect());
795 } else if has_int {
796 return Value::IntArray(
797 vals.iter()
798 .map(|v| as_i64(v).and_then(|n| i32::try_from(n).ok()))
799 .collect(),
800 );
801 }
802 }
803 Value::TextArray(
804 vals.iter()
805 .map(|v| match v {
806 Value::Null => None,
807 Value::Text(s) | Value::Json(s) => Some(s.as_ref().to_string()),
808 other => Some(crate::eval::value_to_text(other)),
809 })
810 .collect(),
811 )
812}
813
814pub(crate) fn homogeneous_typed_array(vals: &[Value<'static>]) -> Option<Value<'static>> {
817 let first = vals.iter().find(|v| !matches!(v, Value::Null))?;
818 macro_rules! collect {
819 ($variant:ident, $pat:pat => $val:expr) => {{
820 let mut out = alloc::vec::Vec::with_capacity(vals.len());
821 for v in vals {
822 match v {
823 Value::Null => out.push(None),
824 $pat => out.push(Some($val)),
825 _ => return None,
826 }
827 }
828 Some(Value::$variant(out))
829 }};
830 }
831 match first {
832 Value::Bool(_) => collect!(BoolArray, Value::Bool(b) => *b),
833 Value::Date(_) => collect!(DateArray, Value::Date(d) => *d),
834 Value::Timestamp(_) => collect!(TimestampArray, Value::Timestamp(t) => *t),
835 Value::Uuid(_) => collect!(UuidArray, Value::Uuid(u) => *u),
836 Value::Money(_) => collect!(MoneyArray, Value::Money(m) => *m),
837 Value::Bytes(_) => collect!(BytesArray, Value::Bytes(b) => b.as_ref().to_vec()),
838 Value::Interval { .. } => {
839 let mut out = alloc::vec::Vec::with_capacity(vals.len());
840 for v in vals {
841 match v {
842 Value::Null => out.push(None),
843 Value::Interval {
844 months,
845 days,
846 micros,
847 } => out.push(Some(spg_storage::IntervalSpan {
848 months: *months,
849 days: *days,
850 micros: *micros,
851 })),
852 _ => return None,
853 }
854 }
855 Some(Value::IntervalArray(out))
856 }
857 _ => None,
858 }
859}
860
861pub(crate) fn split_2d_rows(s: &str) -> Option<Vec<alloc::string::String>> {
875 let trimmed = s.trim();
876 let inner = trimmed
877 .strip_prefix('{')
878 .and_then(|x| x.strip_suffix('}'))?
879 .trim();
880 if !inner.starts_with('{') {
881 return None;
882 }
883 let mut rows = alloc::vec::Vec::new();
884 let bytes = inner.as_bytes();
885 let mut depth = 0i32;
886 let mut start = 0usize;
887 let mut in_quote = false;
888 let mut i = 0;
889 while i < bytes.len() {
890 let c = bytes[i];
891 if in_quote {
892 if c == b'\\' {
893 i += 2;
894 continue;
895 }
896 if c == b'"' {
897 in_quote = false;
898 }
899 } else {
900 match c {
901 b'"' => in_quote = true,
902 b'{' => depth += 1,
903 b'}' => depth -= 1,
904 b',' if depth == 0 => {
905 rows.push(inner[start..i].trim().to_string());
906 start = i + 1;
907 }
908 _ => {}
909 }
910 }
911 i += 1;
912 }
913 rows.push(inner[start..].trim().to_string());
914 Some(rows)
915}
916
917pub(crate) fn build_2d_from_rows(rows: &[Value<'static>]) -> Option<Value<'static>> {
918 if rows.is_empty() || !rows.iter().all(|v| array_len(v).is_some()) {
919 return None;
920 }
921 let width = array_len(&rows[0])?;
922 if !rows.iter().all(|v| array_len(v) == Some(width)) {
923 return None;
924 }
925 if rows.iter().all(|v| matches!(v, Value::BoolArray(_))) {
926 return Some(Value::BoolArray2D(
927 rows.iter()
928 .map(|v| match v {
929 Value::BoolArray(r) => r.clone(),
930 _ => unreachable!("checked"),
931 })
932 .collect(),
933 ));
934 }
935 if rows.iter().all(|v| matches!(v, Value::IntArray(_))) {
936 return Some(Value::IntArray2D(
937 rows.iter()
938 .map(|v| match v {
939 Value::IntArray(r) => r.clone(),
940 _ => unreachable!("checked"),
941 })
942 .collect(),
943 ));
944 }
945 if rows
946 .iter()
947 .all(|v| matches!(v, Value::IntArray(_) | Value::BigIntArray(_)))
948 {
949 return Some(Value::BigIntArray2D(
950 rows.iter()
951 .map(|v| match v {
952 Value::BigIntArray(r) => r.clone(),
953 Value::IntArray(r) => r.iter().map(|c| c.map(i64::from)).collect(),
954 _ => unreachable!("checked"),
955 })
956 .collect(),
957 ));
958 }
959 Some(Value::TextArray2D(
963 rows.iter()
964 .map(|v| {
965 let n = array_len(v).unwrap_or(0);
966 (0..n)
967 .map(|i| match array_element_at(v, i) {
968 None | Some(Value::Null) => None,
969 Some(x) => Some(crate::eval::value_to_text(&x)),
970 })
971 .collect()
972 })
973 .collect(),
974 ))
975}
976
977pub(crate) fn flatten_2d(v: &Value<'_>) -> Option<Value<'static>> {
984 Some(match v {
985 Value::IntArray2D(rows) => Value::IntArray(rows.iter().flatten().copied().collect()),
986 Value::BigIntArray2D(rows) => Value::BigIntArray(rows.iter().flatten().copied().collect()),
987 Value::BoolArray2D(rows) => Value::BoolArray(rows.iter().flatten().copied().collect()),
988 Value::TextArray2D(rows) => Value::TextArray(rows.iter().flatten().cloned().collect()),
989 _ => return None,
990 })
991}