use super::*;
pub(super) fn value_cmp_for_min_max(a: &Value, b: &Value, mysql: bool) -> core::cmp::Ordering {
use core::cmp::Ordering;
if mysql {
if let (Value::Text(x), Value::Text(y)) | (Value::BpChar(x), Value::BpChar(y)) = (a, b) {
return spg_storage::mysql_compare_fold(x).cmp(&spg_storage::mysql_compare_fold(y));
}
}
if let Some(ord) = crate::orderby::numeric_bignum_cmp(a, b) {
return ord;
}
{
use spg_storage::NumericKind as NK;
let kind = |v: &Value| -> Option<NK> {
match v {
Value::Numeric { kind, .. } => Some(*kind),
Value::Int(_) | Value::BigInt(_) | Value::SmallInt(_) => Some(NK::Finite),
_ => None,
}
};
if let (Some(lk), Some(rk)) = (kind(a), kind(b)) {
if lk != NK::Finite || rk != NK::Finite {
let rank = |k: NK| match k {
NK::NegInf => -2,
NK::Finite => 0,
NK::PosInf => 1,
NK::NaN => 2,
};
return rank(lk).cmp(&rank(rk));
}
}
}
let a_int = match a {
Value::SmallInt(x) => Some(i64::from(*x)),
Value::Int(x) => Some(i64::from(*x)),
Value::BigInt(x) => Some(*x),
_ => None,
};
let b_int = match b {
Value::SmallInt(x) => Some(i64::from(*x)),
Value::Int(x) => Some(i64::from(*x)),
Value::BigInt(x) => Some(*x),
_ => None,
};
if let (Some(av), Some(bv)) = (a_int, b_int) {
return av.cmp(&bv);
}
let a_f = value_to_f64(a);
let b_f = value_to_f64(b);
if let (Some(av), Some(bv)) = (a_f, b_f) {
return av.partial_cmp(&bv).unwrap_or(Ordering::Equal);
}
match (a, b) {
(Value::Text(av), Value::Text(bv)) => av.cmp(bv),
(Value::Bytes(av), Value::Bytes(bv)) => av.cmp(bv),
(Value::Date(av), Value::Date(bv)) => av.cmp(bv),
(Value::Timestamp(av), Value::Timestamp(bv)) => av.cmp(bv),
(Value::Date(av), Value::Timestamp(bv)) => {
(i64::from(*av).saturating_mul(86_400_000_000)).cmp(bv)
}
(Value::Timestamp(av), Value::Date(bv)) => {
av.cmp(&i64::from(*bv).saturating_mul(86_400_000_000))
}
(Value::Time(av), Value::Time(bv)) => av.cmp(bv),
(Value::Bool(av), Value::Bool(bv)) => av.cmp(bv),
(
Value::Interval {
months: am,
days: ad,
micros: au,
},
Value::Interval {
months: bm,
days: bd,
micros: bu,
},
) => {
let total = |m: i32, d: i32, u: i64| -> i128 {
i128::from(m) * 30 * 86_400_000_000 + i128::from(d) * 86_400_000_000 + i128::from(u)
};
total(*am, *ad, *au).cmp(&total(*bm, *bd, *bu))
}
(Value::Tid(b1, o1), Value::Tid(b2, o2)) => b1.cmp(b2).then(o1.cmp(o2)),
(Value::Xid(a), Value::Xid(b)) => a.cmp(b),
(Value::Cid(a), Value::Cid(b)) => a.cmp(b),
_ => crate::eval::binop::compare(spg_sql::ast::BinOp::Lt, a, b)
.ok()
.and_then(|v| match v {
Value::Bool(true) => Some(Ordering::Less),
Value::Bool(false) => {
match crate::eval::binop::compare(spg_sql::ast::BinOp::Gt, a, b) {
Ok(Value::Bool(true)) => Some(Ordering::Greater),
Ok(Value::Bool(false)) => Some(Ordering::Equal),
_ => None,
}
}
_ => None,
})
.unwrap_or(Ordering::Equal),
}
}
pub(super) fn value_to_f64(v: &Value) -> Option<f64> {
match v {
Value::Float(x) => Some(*x),
Value::Real(x) => Some(f64::from(*x)),
Value::SmallInt(x) => Some(f64::from(*x)),
Value::Int(x) => Some(f64::from(*x)),
Value::BigInt(x) => Some(*x as f64),
Value::Numeric { scaled, scale, .. } => {
Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
}
_ => None,
}
}
pub(super) fn values_equal_for_nullif(a: &Value, b: &Value) -> bool {
if a == b {
return true;
}
let a_int = match a {
Value::SmallInt(x) => Some(i64::from(*x)),
Value::Int(x) => Some(i64::from(*x)),
Value::BigInt(x) => Some(*x),
_ => None,
};
let b_int = match b {
Value::SmallInt(x) => Some(i64::from(*x)),
Value::Int(x) => Some(i64::from(*x)),
Value::BigInt(x) => Some(*x),
_ => None,
};
if let (Some(a), Some(b)) = (a_int, b_int) {
return a == b;
}
let a_f = match a {
Value::Float(x) => Some(*x),
Value::SmallInt(x) => Some(f64::from(*x)),
Value::Int(x) => Some(f64::from(*x)),
Value::BigInt(x) => Some(*x as f64),
Value::Numeric { scaled, scale, .. } => {
Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
}
_ => None,
};
let b_f = match b {
Value::Float(x) => Some(*x),
Value::SmallInt(x) => Some(f64::from(*x)),
Value::Int(x) => Some(f64::from(*x)),
Value::BigInt(x) => Some(*x as f64),
Value::Numeric { scaled, scale, .. } => {
Some((*scaled as f64) / f64_powi(10.0, i32::from(*scale)))
}
_ => None,
};
if let (Some(a), Some(b)) = (a_f, b_f) {
return a == b;
}
false
}
pub fn gen_random_uuid_bytes() -> [u8; 16] {
let mut out = [0u8; 16];
let hi = prng_next_u64().to_be_bytes();
let lo = prng_next_u64().to_be_bytes();
out[..8].copy_from_slice(&hi);
out[8..].copy_from_slice(&lo);
out[6] = (out[6] & 0x0f) | 0x40;
out[8] = (out[8] & 0x3f) | 0x80;
out
}
#[must_use]
pub fn value_to_text_with_fsp(v: &Value, fsp: Option<u8>) -> String {
let Some(fsp) = fsp else {
return value_to_text(v);
};
let (whole, micros) = match v {
Value::Timestamp(us) => (
crate::eval::format_timestamp(us.div_euclid(1_000_000) * 1_000_000),
us.rem_euclid(1_000_000),
),
Value::Time(us) => (
crate::eval::format_time(us.div_euclid(1_000_000) * 1_000_000),
us.rem_euclid(1_000_000),
),
other => return value_to_text(other),
};
if fsp == 0 {
return whole;
}
let digits = usize::from(fsp.min(6));
let frac = format!("{micros:06}");
format!("{whole}.{}", &frac[..digits])
}
pub fn value_to_text(v: &Value) -> String {
value_to_text_styled(v, &crate::eval::RenderStyle::default())
}
pub fn value_to_text_styled(v: &Value, style: &crate::eval::RenderStyle) -> String {
match v {
Value::SmallInt(n) => format!("{n}"),
Value::Int(n) => format!("{n}"),
Value::BigInt(n) => format!("{n}"),
Value::Float(x) => crate::eval::format_float_styled(*x, style),
Value::Real(x) => crate::eval::format_real_styled(*x, style),
Value::BpChar(s) => s.to_string(),
Value::Text(s) | Value::Json(s) => s.to_string(),
Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
Value::NumericBig(b) => b.to_decimal_str(),
Value::Composite(fields) => {
let mut out = String::from("(");
for (i, (_, fv)) in fields.iter().enumerate() {
if i > 0 {
out.push(',');
}
if matches!(fv, Value::Null) {
continue;
}
let field = super::strings::value_to_format_text(fv);
let needs_quote = field.is_empty()
|| field
.chars()
.any(|c| matches!(c, ',' | '(' | ')' | '"' | '\\') || c.is_whitespace());
if needs_quote {
out.push('"');
for c in field.chars() {
match c {
'"' => out.push_str("\"\""),
'\\' => out.push_str("\\\\"),
other => out.push(other),
}
}
out.push('"');
} else {
out.push_str(&field);
}
}
out.push(')');
out
}
Value::Vector(v) => {
let cells: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
format!("[{}]", cells.join(","))
}
Value::Sq8Vector(q) => {
let cells: Vec<String> = spg_storage::quantize::dequantize(q)
.iter()
.map(|x| format!("{x}"))
.collect();
format!("[{}]", cells.join(","))
}
Value::HalfVector(h) => {
let cells: Vec<String> = h.to_f32_vec().iter().map(|x| format!("{x}")).collect();
format!("[{}]", cells.join(","))
}
Value::Numeric {
scaled,
scale,
kind,
} => format_numeric_kind(*kind, *scaled, *scale),
Value::Date(d) => crate::eval::format_date_styled(*d, style),
Value::Timestamp(t) => crate::eval::format_timestamp_styled(*t, style),
Value::Interval {
months,
days,
micros,
} => crate::eval::format_interval_styled(*months, *days, *micros, style),
Value::Null => "NULL".into(),
Value::Bytes(b) => {
if style.bytea_escape {
crate::eval::format::format_bytea_escape(b)
} else {
format_bytea_hex(b)
}
}
Value::TextArray(items) => format_text_array(items),
Value::IntArray(items) => format_int_array(items),
Value::BigIntArray(items) => format_bigint_array(items),
Value::TsVector(lexs) => format_tsvector(lexs),
Value::TsQuery(ast) => format_tsquery(ast),
Value::Uuid(b) => spg_storage::format_uuid(b),
Value::Time(us) => format_time(*us),
Value::TimeTz { us, offset_secs } => format_timetz(*us, *offset_secs),
Value::Year(y) => format!("{y:04}"),
Value::Money(c) => format_money(*c),
Value::Range { .. } => crate::conversions::format_range_text(v),
Value::Hstore(pairs) => crate::conversions::format_hstore_text(pairs),
Value::IntArray2D(rows) => crate::conversions::format_int_2d_text_pub(rows),
Value::BigIntArray2D(rows) => crate::conversions::format_bigint_2d_text_pub(rows),
Value::TextArray2D(rows) => crate::conversions::format_text_2d_text_pub(rows),
Value::BoolArray2D(rows) => crate::conversions::format_bool_2d_text_pub(rows),
Value::BoolArray(items) => crate::eval::format_bool_array(items),
Value::SmallIntArray(items) => crate::eval::format_smallint_array(items),
Value::FloatArray(items) => crate::eval::format_float_array_styled(items, style),
Value::NumericArray(items) => crate::eval::format_numeric_array(items),
Value::DateArray(items) => crate::eval::format_date_array_styled(items, style),
Value::TimestampArray(items) => {
crate::eval::format_timestamp_array_styled(items, false, style)
}
Value::TimestamptzArray(items) => {
crate::eval::format_timestamp_array_styled(items, true, style)
}
Value::UuidArray(items) => crate::eval::format_uuid_array(items),
Value::JsonArray(items) | Value::JsonbArray(items) => crate::eval::format_text_array(items),
Value::BytesArray(items) => crate::eval::format_bytea_array(items),
Value::IntervalArray(items) => crate::eval::format_interval_array_styled(items, style),
Value::MoneyArray(items) => crate::conversions::format_money_array(items),
Value::Point(p) => crate::conversions::format_point(*p),
Value::Lseg(a, b) => crate::conversions::format_lseg(*a, *b),
Value::Path { points, closed } => crate::conversions::format_path(points, *closed),
Value::PgBox(ur, ll) => crate::conversions::format_pg_box(*ur, *ll),
Value::Polygon(points) => crate::conversions::format_polygon(points),
Value::Line { a, b, c } => crate::conversions::format_line(*a, *b, *c),
Value::Circle { center, radius } => crate::conversions::format_circle(*center, *radius),
Value::Multirange { ranges, .. } => crate::conversions::format_multirange(ranges),
Value::Inet { family, bits, addr } => crate::conversions::format_inet(*family, *bits, addr),
Value::Cidr { family, bits, addr } => {
crate::conversions::format_inet_full(*family, *bits, addr)
}
Value::Macaddr(b) => crate::conversions::format_macaddr(b),
Value::Macaddr8(b) => crate::conversions::format_macaddr8(b),
Value::PgLsn(l) => crate::conversions::format_pg_lsn(*l),
Value::RegClass(_, name) | Value::RegProc(_, name) => name.to_string(),
Value::RegType(_, name) => name.to_string(),
Value::Tid(b, o) => alloc::format!("({b},{o})"),
Value::Xid(x) => alloc::format!("{x}"),
Value::Cid(c) => alloc::format!("{c}"),
Value::BitString { nbits, bytes } => crate::conversions::format_bit_string(*nbits, bytes),
Value::Xml(s) => s.to_string(),
Value::Char1(b) => format!("{}", *b as char),
_ => format!("{v:?}"),
}
}
pub(crate) fn array_len(v: &Value) -> Option<usize> {
match v {
Value::TextArray(items)
| Value::VarcharArray(items)
| Value::CharArray(items)
| Value::JsonArray(items)
| Value::JsonbArray(items) => Some(items.len()),
Value::IntArray(items) => Some(items.len()),
Value::BigIntArray(items) => Some(items.len()),
Value::SmallIntArray(items) => Some(items.len()),
Value::BoolArray(items) => Some(items.len()),
Value::FloatArray(items) => Some(items.len()),
Value::NumericArray(items) => Some(items.len()),
Value::DateArray(items) => Some(items.len()),
Value::TimestampArray(items) | Value::TimestamptzArray(items) => Some(items.len()),
Value::MoneyArray(items) => Some(items.len()),
Value::IntervalArray(items) => Some(items.len()),
Value::UuidArray(items) => Some(items.len()),
Value::BytesArray(items) => Some(items.len()),
_ => None,
}
}
pub(crate) fn array_elements(v: &Value) -> Option<alloc::vec::Vec<Value<'static>>> {
if let Some(n) = array_len(v) {
let mut out = alloc::vec::Vec::with_capacity(n);
for i in 0..n {
out.push(array_element_at(v, i)?);
}
return Some(out);
}
macro_rules! rows {
($m:expr, $variant:ident) => {
Some($m.iter().map(|r| Value::$variant(r.clone())).collect())
};
}
match v {
Value::IntArray2D(m) => rows!(m, IntArray),
Value::BigIntArray2D(m) => rows!(m, BigIntArray),
Value::TextArray2D(m) => rows!(m, TextArray),
Value::BoolArray2D(m) => rows!(m, BoolArray),
_ => None,
}
}
pub(super) fn array_2d_dims(v: &Value) -> Option<(usize, usize)> {
match v {
Value::IntArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
Value::BigIntArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
Value::TextArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
Value::BoolArray2D(m) => Some((m.len(), m.first().map_or(0, alloc::vec::Vec::len))),
_ => None,
}
}
pub(crate) fn array_element_at(v: &Value, pos: usize) -> Option<Value<'static>> {
use alloc::borrow::Cow;
macro_rules! nth {
($items:expr, $map:expr) => {
$items
.get(pos)
.map(|e| e.as_ref().map_or(Value::Null, $map))
};
}
match v {
Value::TextArray(items) | Value::VarcharArray(items) | Value::CharArray(items) => {
nth!(items, |s| Value::Text(Cow::Owned(s.clone())))
}
Value::JsonArray(items) | Value::JsonbArray(items) => {
nth!(items, |s| Value::Json(Cow::Owned(s.clone())))
}
Value::IntArray(items) => nth!(items, |n| Value::Int(*n)),
Value::BigIntArray(items) => nth!(items, |n| Value::BigInt(*n)),
Value::SmallIntArray(items) => nth!(items, |n| Value::SmallInt(*n)),
Value::BoolArray(items) => nth!(items, |b| Value::Bool(*b)),
Value::FloatArray(items) => nth!(items, |f| Value::Float(*f)),
Value::NumericArray(items) => {
nth!(items, |t: &(i128, u16)| Value::Numeric {
scaled: t.0,
scale: t.1,
kind: spg_storage::NumericKind::Finite
})
}
Value::DateArray(items) => nth!(items, |d| Value::Date(*d)),
Value::TimestampArray(items) | Value::TimestamptzArray(items) => {
nth!(items, |t| Value::Timestamp(*t))
}
Value::MoneyArray(items) => nth!(items, |m| Value::Money(*m)),
Value::IntervalArray(items) => nth!(items, |s| Value::Interval {
months: s.months,
days: s.days,
micros: s.micros,
}),
Value::UuidArray(items) => nth!(items, |u| Value::Uuid(*u)),
Value::BytesArray(items) => nth!(items, |b| Value::Bytes(Cow::Owned(b.clone()))),
_ => None,
}
}
pub(super) fn array_rebuild(model: &Value<'_>, elems: &[Value<'static>]) -> Option<Value<'static>> {
macro_rules! build {
($variant:ident, $conv:expr) => {{
let mut out = alloc::vec::Vec::with_capacity(elems.len());
for e in elems {
if matches!(e, Value::Null) {
out.push(None);
continue;
}
out.push(Some(($conv)(e)?));
}
Some(Value::$variant(out))
}};
}
let as_i64 = |v: &Value<'_>| -> Option<i64> {
match v {
Value::SmallInt(n) => Some(i64::from(*n)),
Value::Int(n) => Some(i64::from(*n)),
Value::BigInt(n) => Some(*n),
_ => None,
}
};
match model {
Value::TextArray(_) => build!(TextArray, |e: &Value<'_>| match e {
Value::Text(s) => Some(s.as_ref().to_string()),
_ => None,
}),
Value::VarcharArray(_) => build!(VarcharArray, |e: &Value<'_>| match e {
Value::Text(s) => Some(s.as_ref().to_string()),
_ => None,
}),
Value::JsonArray(_) => build!(JsonArray, |e: &Value<'_>| match e {
Value::Json(s) | Value::Text(s) => Some(s.as_ref().to_string()),
_ => None,
}),
Value::JsonbArray(_) => build!(JsonbArray, |e: &Value<'_>| match e {
Value::Json(s) | Value::Text(s) => Some(s.as_ref().to_string()),
_ => None,
}),
Value::IntArray(_) => build!(IntArray, |e: &Value<'_>| as_i64(e)
.and_then(|n| i32::try_from(n).ok())),
Value::BigIntArray(_) => build!(BigIntArray, as_i64),
Value::SmallIntArray(_) => build!(SmallIntArray, |e: &Value<'_>| as_i64(e)
.and_then(|n| i16::try_from(n).ok())),
Value::BoolArray(_) => build!(BoolArray, |e: &Value<'_>| match e {
Value::Bool(b) => Some(*b),
_ => None,
}),
Value::FloatArray(_) => build!(FloatArray, |e: &Value<'_>| match e {
Value::Float(f) => Some(*f),
Value::Real(f) => Some(f64::from(*f)),
other => as_i64(other).map(|n| n as f64),
}),
Value::NumericArray(_) => build!(NumericArray, |e: &Value<'_>| match e {
Value::Numeric { scaled, scale, .. } => Some((*scaled, *scale)),
other => as_i64(other).map(|n| (i128::from(n), 0u16)),
}),
Value::DateArray(_) => build!(DateArray, |e: &Value<'_>| match e {
Value::Date(d) => Some(*d),
_ => None,
}),
Value::TimestampArray(_) => build!(TimestampArray, |e: &Value<'_>| match e {
Value::Timestamp(t) => Some(*t),
_ => None,
}),
Value::TimestamptzArray(_) => build!(TimestamptzArray, |e: &Value<'_>| match e {
Value::Timestamp(t) => Some(*t),
_ => None,
}),
Value::MoneyArray(_) => build!(MoneyArray, |e: &Value<'_>| match e {
Value::Money(m) => Some(*m),
_ => None,
}),
Value::UuidArray(_) => build!(UuidArray, |e: &Value<'_>| match e {
Value::Uuid(u) => Some(*u),
_ => None,
}),
Value::BytesArray(_) => build!(BytesArray, |e: &Value<'_>| match e {
Value::Bytes(b) => Some(b.as_ref().to_vec()),
_ => None,
}),
Value::IntervalArray(_) => build!(IntervalArray, |e: &Value<'_>| match e {
Value::Interval {
months,
days,
micros,
} => Some(spg_storage::IntervalSpan {
months: *months,
days: *days,
micros: *micros,
}),
_ => None,
}),
_ => None,
}
}
pub(crate) fn build_array_from_values(vals: &[Value<'static>]) -> Value<'static> {
if let Some(v) = homogeneous_typed_array(vals) {
return v;
}
let mut has_text = false;
let mut has_float = false;
let mut has_numeric = false;
let mut has_bigint = false;
let mut has_int = false;
for v in vals {
match v {
Value::Null => {}
Value::Int(_) | Value::SmallInt(_) => has_int = true,
Value::BigInt(_) => has_bigint = true,
Value::Numeric { .. } | Value::NumericBig(_) => has_numeric = true,
Value::Float(_) | Value::Real(_) => has_float = true,
_ => has_text = true,
}
}
let as_i64 = |v: &Value<'_>| -> Option<i64> {
match v {
Value::SmallInt(n) => Some(i64::from(*n)),
Value::Int(n) => Some(i64::from(*n)),
Value::BigInt(n) => Some(*n),
_ => None,
}
};
if !has_text {
if has_float {
return Value::FloatArray(
vals.iter()
.map(|v| match v {
Value::Null => None,
Value::Float(f) => Some(*f),
Value::Real(f) => Some(f64::from(*f)),
#[allow(clippy::cast_precision_loss)]
Value::Numeric { scaled, scale, .. } => {
Some(*scaled as f64 / libm::pow(10.0, f64::from(*scale)))
}
other => as_i64(other).map(|n| n as f64),
})
.collect(),
);
}
if has_numeric {
if vals.iter().all(|v| {
matches!(
v,
Value::Null
| Value::SmallInt(_)
| Value::Int(_)
| Value::BigInt(_)
| Value::Numeric {
kind: spg_storage::NumericKind::Finite,
..
}
)
}) {
return Value::NumericArray(
vals.iter()
.map(|v| match v {
Value::Null => None,
Value::Numeric { scaled, scale, .. } => Some((*scaled, *scale)),
other => as_i64(other).map(|n| (i128::from(n), 0u16)),
})
.collect(),
);
}
} else if has_bigint {
return Value::BigIntArray(vals.iter().map(as_i64).collect());
} else if has_int {
return Value::IntArray(
vals.iter()
.map(|v| as_i64(v).and_then(|n| i32::try_from(n).ok()))
.collect(),
);
}
}
Value::TextArray(
vals.iter()
.map(|v| match v {
Value::Null => None,
Value::Text(s) | Value::Json(s) => Some(s.as_ref().to_string()),
other => Some(crate::eval::value_to_text(other)),
})
.collect(),
)
}
pub(crate) fn homogeneous_typed_array(vals: &[Value<'static>]) -> Option<Value<'static>> {
let first = vals.iter().find(|v| !matches!(v, Value::Null))?;
macro_rules! collect {
($variant:ident, $pat:pat => $val:expr) => {{
let mut out = alloc::vec::Vec::with_capacity(vals.len());
for v in vals {
match v {
Value::Null => out.push(None),
$pat => out.push(Some($val)),
_ => return None,
}
}
Some(Value::$variant(out))
}};
}
match first {
Value::Bool(_) => collect!(BoolArray, Value::Bool(b) => *b),
Value::Date(_) => collect!(DateArray, Value::Date(d) => *d),
Value::Timestamp(_) => collect!(TimestampArray, Value::Timestamp(t) => *t),
Value::Uuid(_) => collect!(UuidArray, Value::Uuid(u) => *u),
Value::Money(_) => collect!(MoneyArray, Value::Money(m) => *m),
Value::Bytes(_) => collect!(BytesArray, Value::Bytes(b) => b.as_ref().to_vec()),
Value::Interval { .. } => {
let mut out = alloc::vec::Vec::with_capacity(vals.len());
for v in vals {
match v {
Value::Null => out.push(None),
Value::Interval {
months,
days,
micros,
} => out.push(Some(spg_storage::IntervalSpan {
months: *months,
days: *days,
micros: *micros,
})),
_ => return None,
}
}
Some(Value::IntervalArray(out))
}
_ => None,
}
}
pub(crate) fn split_2d_rows(s: &str) -> Option<Vec<alloc::string::String>> {
let trimmed = s.trim();
let inner = trimmed
.strip_prefix('{')
.and_then(|x| x.strip_suffix('}'))?
.trim();
if !inner.starts_with('{') {
return None;
}
let mut rows = alloc::vec::Vec::new();
let bytes = inner.as_bytes();
let mut depth = 0i32;
let mut start = 0usize;
let mut in_quote = false;
let mut i = 0;
while i < bytes.len() {
let c = bytes[i];
if in_quote {
if c == b'\\' {
i += 2;
continue;
}
if c == b'"' {
in_quote = false;
}
} else {
match c {
b'"' => in_quote = true,
b'{' => depth += 1,
b'}' => depth -= 1,
b',' if depth == 0 => {
rows.push(inner[start..i].trim().to_string());
start = i + 1;
}
_ => {}
}
}
i += 1;
}
rows.push(inner[start..].trim().to_string());
Some(rows)
}
pub(crate) fn build_2d_from_rows(rows: &[Value<'static>]) -> Option<Value<'static>> {
if rows.is_empty() || !rows.iter().all(|v| array_len(v).is_some()) {
return None;
}
let width = array_len(&rows[0])?;
if !rows.iter().all(|v| array_len(v) == Some(width)) {
return None;
}
if rows.iter().all(|v| matches!(v, Value::BoolArray(_))) {
return Some(Value::BoolArray2D(
rows.iter()
.map(|v| match v {
Value::BoolArray(r) => r.clone(),
_ => unreachable!("checked"),
})
.collect(),
));
}
if rows.iter().all(|v| matches!(v, Value::IntArray(_))) {
return Some(Value::IntArray2D(
rows.iter()
.map(|v| match v {
Value::IntArray(r) => r.clone(),
_ => unreachable!("checked"),
})
.collect(),
));
}
if rows
.iter()
.all(|v| matches!(v, Value::IntArray(_) | Value::BigIntArray(_)))
{
return Some(Value::BigIntArray2D(
rows.iter()
.map(|v| match v {
Value::BigIntArray(r) => r.clone(),
Value::IntArray(r) => r.iter().map(|c| c.map(i64::from)).collect(),
_ => unreachable!("checked"),
})
.collect(),
));
}
Some(Value::TextArray2D(
rows.iter()
.map(|v| {
let n = array_len(v).unwrap_or(0);
(0..n)
.map(|i| match array_element_at(v, i) {
None | Some(Value::Null) => None,
Some(x) => Some(crate::eval::value_to_text(&x)),
})
.collect()
})
.collect(),
))
}
pub(crate) fn flatten_2d(v: &Value<'_>) -> Option<Value<'static>> {
Some(match v {
Value::IntArray2D(rows) => Value::IntArray(rows.iter().flatten().copied().collect()),
Value::BigIntArray2D(rows) => Value::BigIntArray(rows.iter().flatten().copied().collect()),
Value::BoolArray2D(rows) => Value::BoolArray(rows.iter().flatten().copied().collect()),
Value::TextArray2D(rows) => Value::TextArray(rows.iter().flatten().cloned().collect()),
_ => return None,
})
}