use std::sync::Arc;
use arrow_array::builder::StringBuilder;
use arrow_array::cast::AsArray;
use arrow_array::types::{Decimal128Type, Int32Type};
use arrow_array::{
Array, ArrayRef, BooleanArray, Decimal128Array, DictionaryArray, StringArray, UInt32Array,
};
use arrow_buffer::{NullBuffer, ScalarBuffer};
use arrow_schema::DataType;
use crate::series::ColumnKind;
#[derive(Debug, Clone)]
pub struct Float64Column {
values: ScalarBuffer<f64>,
all_finite: bool,
}
impl Float64Column {
pub fn from_values(values: Vec<f64>) -> Self {
Self::from_buffer(values.into())
}
pub(crate) fn from_buffer(values: ScalarBuffer<f64>) -> Self {
let all_finite = values.iter().all(|v| v.is_finite());
Self { values, all_finite }
}
pub(crate) fn buffer(&self) -> &ScalarBuffer<f64> {
&self.values
}
pub fn from_options(values: impl IntoIterator<Item = Option<f64>>) -> Self {
Self::from_values(values.into_iter().map(|v| v.unwrap_or(f64::NAN)).collect())
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn get(&self, i: usize) -> Option<f64> {
let v = self.values[i];
v.is_finite().then_some(v)
}
pub fn values(&self) -> &[f64] {
&self.values
}
pub fn all_finite(&self) -> bool {
self.all_finite
}
pub fn missing_count(&self) -> usize {
if self.all_finite {
0
} else {
self.values.iter().filter(|v| !v.is_finite()).count()
}
}
pub fn iter(&self) -> impl Iterator<Item = Option<f64>> + '_ {
self.values.iter().map(|&v| v.is_finite().then_some(v))
}
}
impl PartialEq for Float64Column {
fn eq(&self, other: &Self) -> bool {
let bits = |c: &Self| c.iter().map(|v| v.map(f64::to_bits)).collect::<Vec<_>>();
self.len() == other.len() && bits(self) == bits(other)
}
}
#[derive(Debug, Clone)]
pub struct StringColumn {
repr: StringRepr,
}
#[derive(Debug, Clone)]
enum StringRepr {
Plain(StringArray),
Dictionary(DictionaryArray<Int32Type>),
}
impl StringColumn {
pub fn from_options<S: AsRef<str>>(values: impl IntoIterator<Item = Option<S>>) -> Self {
Self::plain(values.into_iter().collect::<StringArray>())
}
pub(crate) fn plain(array: StringArray) -> Self {
Self {
repr: StringRepr::Plain(array),
}
}
pub(crate) fn dictionary(array: DictionaryArray<Int32Type>) -> Option<Self> {
let values = array.values();
(values.data_type() == &DataType::Utf8 && values.null_count() == 0).then_some(Self {
repr: StringRepr::Dictionary(array),
})
}
pub fn len(&self) -> usize {
match &self.repr {
StringRepr::Plain(a) => a.len(),
StringRepr::Dictionary(a) => a.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, i: usize) -> Option<&str> {
assert!(i < self.len(), "row {i} out of range ({} rows)", self.len());
match &self.repr {
StringRepr::Plain(a) => a.is_valid(i).then(|| a.value(i)),
StringRepr::Dictionary(a) => a.is_valid(i).then(|| {
let key = a.keys().value(i) as usize;
a.values().as_string::<i32>().value(key)
}),
}
}
pub fn iter(&self) -> impl Iterator<Item = Option<&str>> + '_ {
(0..self.len()).map(|i| self.get(i))
}
pub fn missing_count(&self) -> usize {
self.arrow().logical_null_count()
}
pub fn is_dictionary(&self) -> bool {
matches!(self.repr, StringRepr::Dictionary(_))
}
pub(crate) fn arrow(&self) -> ArrayRef {
match &self.repr {
StringRepr::Plain(a) => Arc::new(a.clone()),
StringRepr::Dictionary(a) => Arc::new(a.clone()),
}
}
pub(crate) fn take(&self, rows: &[Option<usize>]) -> Self {
self.take_indices(&indices_of(rows))
}
}
impl StringColumn {
pub(crate) fn take_indices(&self, indices: &UInt32Array) -> Self {
let array: &dyn Array = match &self.repr {
StringRepr::Plain(a) => a,
StringRepr::Dictionary(a) => a,
};
let out = arrow_select::take::take(array, indices, None).expect("row numbers are in range");
match &self.repr {
StringRepr::Plain(_) => Self::plain(out.as_string::<i32>().clone()),
StringRepr::Dictionary(_) => Self {
repr: StringRepr::Dictionary(out.as_dictionary::<Int32Type>().clone()),
},
}
}
}
pub(crate) fn indices_of(rows: &[Option<usize>]) -> UInt32Array {
rows.iter()
.map(|r| r.map(|i| u32::try_from(i).expect("a text column has under 2^32 rows")))
.collect()
}
impl PartialEq for StringColumn {
fn eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().eq(other.iter())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BooleanColumn {
values: BooleanArray,
}
impl BooleanColumn {
pub fn from_options(values: impl IntoIterator<Item = Option<bool>>) -> Self {
Self::from_arrow(values.into_iter().collect())
}
pub(crate) fn from_arrow(values: BooleanArray) -> Self {
Self { values }
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn get(&self, i: usize) -> Option<bool> {
assert!(i < self.len(), "row {i} out of range ({} rows)", self.len());
self.values.is_valid(i).then(|| self.values.value(i))
}
pub fn iter(&self) -> impl Iterator<Item = Option<bool>> + '_ {
self.values.iter()
}
pub fn missing_count(&self) -> usize {
self.values.null_count()
}
pub(crate) fn arrow(&self) -> ArrayRef {
Arc::new(self.values.clone())
}
pub(crate) fn take(&self, rows: &[Option<usize>]) -> Self {
Self::from_options(rows.iter().map(|r| r.and_then(|i| self.get(i))))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Number(f64),
Decimal(Decimal),
String(String),
Boolean(bool),
Array(Vec<Scalar>),
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::Number(v)
}
}
impl From<&str> for Value {
fn from(v: &str) -> Self {
Value::String(v.to_string())
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Value::String(v)
}
}
impl From<bool> for Value {
fn from(v: bool) -> Self {
Value::Boolean(v)
}
}
impl From<Vec<Scalar>> for Value {
fn from(v: Vec<Scalar>) -> Self {
Value::Array(v)
}
}
impl From<Decimal> for Value {
fn from(v: Decimal) -> Self {
Value::Decimal(v)
}
}
impl Value {
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::Number(v) => Some(*v),
_ => None,
}
}
pub fn as_decimal(&self) -> Option<Decimal> {
match self {
Value::Decimal(v) => Some(*v),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(v) => Some(v),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Boolean(v) => Some(*v),
_ => None,
}
}
pub fn as_array(&self) -> Option<&[Scalar]> {
match self {
Value::Array(v) => Some(v),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Decimal {
pub value: i128,
pub scale: i8,
}
impl Decimal {
pub fn to_f64(self) -> f64 {
const EXACT: u128 = 1 << 53;
if (-22..=22).contains(&self.scale) && self.value.unsigned_abs() <= EXACT {
let power = 10f64.powi(i32::from(self.scale.abs()));
return if self.scale >= 0 {
self.value as f64 / power
} else {
self.value as f64 * power
};
}
self.to_string().parse().expect("a decimal parses as f64")
}
}
impl std::fmt::Display for Decimal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let digits = self.value.unsigned_abs().to_string();
let sign = if self.value < 0 { "-" } else { "" };
let scale = i32::from(self.scale);
if scale <= 0 {
let zeros = if self.value == 0 {
0
} else {
(-scale) as usize
};
return write!(f, "{sign}{digits}{}", "0".repeat(zeros));
}
let scale = scale as usize;
let padded = format!("{digits:0>width$}", width = scale + 1);
let (whole, frac) = padded.split_at(padded.len() - scale);
write!(f, "{sign}{whole}.{frac}")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DecimalColumn {
values: Decimal128Array,
}
impl DecimalColumn {
pub fn from_options(
values: impl IntoIterator<Item = Option<i128>>,
scale: i8,
) -> crate::error::Result<Self> {
let array: Decimal128Array = values.into_iter().collect();
Self::with_type(array, 38, scale)
}
pub fn from_values(values: Vec<i128>, scale: i8) -> crate::error::Result<Self> {
Self::from_options(values.into_iter().map(Some), scale)
}
pub(crate) fn with_type(
array: Decimal128Array,
precision: u8,
scale: i8,
) -> crate::error::Result<Self> {
let invalid = |e: arrow_schema::ArrowError| crate::error::PondError::InvalidData {
call: "DecimalColumn",
message: e.to_string(),
};
let array = array
.with_precision_and_scale(precision, scale)
.map_err(invalid)?;
let max = 10u128.pow(u32::from(precision)) - 1;
if let Some(i) =
(0..array.len()).find(|&i| array.is_valid(i) && array.value(i).unsigned_abs() > max)
{
return Err(crate::error::PondError::InvalidData {
call: "DecimalColumn",
message: format!(
"row {i}: {} has more than {precision} digits (the precision)",
Decimal {
value: array.value(i),
scale
}
),
});
}
Ok(Self { values: array })
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn scale(&self) -> i8 {
self.values.scale()
}
pub fn precision(&self) -> u8 {
self.values.precision()
}
pub fn get(&self, i: usize) -> Option<Decimal> {
assert!(i < self.len(), "row {i} out of range ({} rows)", self.len());
self.values.is_valid(i).then(|| Decimal {
value: self.values.value(i),
scale: self.scale(),
})
}
pub fn iter(&self) -> impl Iterator<Item = Option<Decimal>> + '_ {
(0..self.len()).map(|i| self.get(i))
}
pub(crate) fn raw(&self) -> &[i128] {
self.values.values()
}
pub fn missing_count(&self) -> usize {
self.values.null_count()
}
pub(crate) fn arrow(&self) -> ArrayRef {
Arc::new(self.values.clone())
}
pub(crate) fn nulls(&self) -> Option<&NullBuffer> {
self.values.nulls()
}
pub(crate) fn to_f64s(&self) -> Vec<f64> {
let scale = self.scale();
let divisor = (0..=22)
.contains(&scale)
.then(|| 10f64.powi(i32::from(scale)));
const EXACT: i128 = 1 << 53;
let raw = self.raw();
(0..self.len())
.map(|i| {
let value = raw[i];
match divisor {
_ if self.values.is_null(i) => f64::NAN,
Some(d) if (-EXACT..=EXACT).contains(&value) => value as i64 as f64 / d,
_ => Decimal { value, scale }.to_f64(),
}
})
.collect()
}
pub(crate) fn rebuild(
&self,
values: impl IntoIterator<Item = Option<i128>>,
precision: u8,
) -> Self {
let array: Decimal128Array = values.into_iter().collect();
Self {
values: array
.with_precision_and_scale(precision, self.scale())
.expect("this column's own scale"),
}
}
pub(crate) fn take(&self, rows: &[Option<usize>]) -> Self {
self.rebuild(
rows.iter()
.map(|r| r.and_then(|i| self.values.is_valid(i).then(|| self.values.value(i)))),
self.precision(),
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Scalar {
Number(f64),
String(String),
Boolean(bool),
}
impl Scalar {
pub fn kind(&self) -> ColumnKind {
match self {
Scalar::Number(_) => ColumnKind::Number,
Scalar::String(_) => ColumnKind::String,
Scalar::Boolean(_) => ColumnKind::Boolean,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Scalar::Number(v) => Some(*v),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Scalar::String(v) => Some(v),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Scalar::Boolean(v) => Some(*v),
_ => None,
}
}
pub fn to_value(&self) -> Value {
match self {
Scalar::Number(v) => Value::Number(*v),
Scalar::String(v) => Value::String(v.clone()),
Scalar::Boolean(v) => Value::Boolean(*v),
}
}
pub(crate) fn same(&self, other: &Scalar) -> bool {
match (self, other) {
(Scalar::Number(a), Scalar::Number(b)) => a.to_bits() == b.to_bits(),
_ => self == other,
}
}
}
impl From<f64> for Scalar {
fn from(v: f64) -> Self {
Scalar::Number(v)
}
}
impl From<i32> for Scalar {
fn from(v: i32) -> Self {
Scalar::Number(f64::from(v))
}
}
impl From<&str> for Scalar {
fn from(v: &str) -> Self {
Scalar::String(v.to_string())
}
}
impl From<String> for Scalar {
fn from(v: String) -> Self {
Scalar::String(v)
}
}
impl From<bool> for Scalar {
fn from(v: bool) -> Self {
Scalar::Boolean(v)
}
}
pub(crate) type Cell = Arc<[Scalar]>;
#[derive(Debug, Clone)]
pub struct ArrayColumn {
cells: Arc<[Option<Cell>]>,
offset: usize,
len: usize,
}
impl ArrayColumn {
pub fn from_options(values: impl IntoIterator<Item = Option<Vec<Scalar>>>) -> Self {
Self::from_cells(values.into_iter().map(|v| v.map(Cell::from)).collect())
}
pub(crate) fn from_cells(cells: Vec<Option<Cell>>) -> Self {
let len = cells.len();
Self {
cells: cells.into(),
offset: 0,
len,
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
fn cells(&self) -> &[Option<Cell>] {
&self.cells[self.offset..self.offset + self.len]
}
pub fn get(&self, i: usize) -> Option<&[Scalar]> {
self.cells()[i].as_deref()
}
pub(crate) fn cell(&self, i: usize) -> Option<&Cell> {
self.cells()[i].as_ref()
}
pub fn iter(&self) -> impl Iterator<Item = Option<&[Scalar]>> + '_ {
self.cells().iter().map(|c| c.as_deref())
}
pub fn missing_count(&self) -> usize {
self.cells().iter().filter(|c| c.is_none()).count()
}
pub(crate) fn first_bad_element(&self) -> Option<(usize, usize)> {
self.iter().enumerate().find_map(|(r, cell)| {
cell?
.iter()
.position(|e| matches!(e, Scalar::Number(v) if !v.is_finite()))
.map(|e| (r, e))
})
}
pub(crate) fn take(&self, rows: &[Option<usize>]) -> Self {
Self::from_cells(
rows.iter()
.map(|r| r.and_then(|i| self.cell(i).cloned()))
.collect(),
)
}
}
impl PartialEq for ArrayColumn {
fn eq(&self, other: &Self) -> bool {
self.len == other.len
&& self.iter().zip(other.iter()).all(|(a, b)| match (a, b) {
(None, None) => true,
(Some(a), Some(b)) => a.len() == b.len() && a.iter().zip(b).all(|(x, y)| x.same(y)),
_ => false,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ColumnData {
Number(Float64Column),
Decimal(DecimalColumn),
String(StringColumn),
Boolean(BooleanColumn),
Array(ArrayColumn),
}
impl ColumnData {
pub fn len(&self) -> usize {
match self {
ColumnData::Number(c) => c.len(),
ColumnData::Decimal(c) => c.len(),
ColumnData::String(c) => c.len(),
ColumnData::Boolean(c) => c.len(),
ColumnData::Array(c) => c.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn missing_count(&self) -> usize {
match self {
ColumnData::Number(c) => c.missing_count(),
ColumnData::Decimal(c) => c.missing_count(),
ColumnData::String(c) => c.missing_count(),
ColumnData::Boolean(c) => c.missing_count(),
ColumnData::Array(c) => c.missing_count(),
}
}
pub(crate) fn arrow_nulls(&self) -> Option<NullBuffer> {
match self {
ColumnData::Number(_) => None,
ColumnData::Decimal(c) => c.nulls().cloned(),
ColumnData::String(c) => c.arrow().logical_nulls(),
ColumnData::Boolean(c) => c.values.nulls().cloned(),
ColumnData::Array(c) => (c.missing_count() > 0)
.then(|| NullBuffer::from(c.iter().map(|v| v.is_some()).collect::<Vec<bool>>())),
}
}
pub(crate) fn slice(&self, range: std::ops::Range<usize>) -> ColumnData {
let (offset, len) = (range.start, range.len());
match self {
ColumnData::Number(c) => ColumnData::Number(Float64Column {
values: c.values.slice(offset, len),
all_finite: c.all_finite || c.values[range].iter().all(|v| v.is_finite()),
}),
ColumnData::Decimal(c) => ColumnData::Decimal(DecimalColumn {
values: c.values.slice(offset, len),
}),
ColumnData::String(c) => ColumnData::String(StringColumn {
repr: match &c.repr {
StringRepr::Plain(a) => StringRepr::Plain(a.slice(offset, len)),
StringRepr::Dictionary(a) => StringRepr::Dictionary(a.slice(offset, len)),
},
}),
ColumnData::Boolean(c) => ColumnData::Boolean(BooleanColumn {
values: c.values.slice(offset, len),
}),
ColumnData::Array(c) => ColumnData::Array(ArrayColumn {
cells: c.cells.clone(),
offset: c.offset + offset,
len,
}),
}
}
pub(crate) fn gather(sources: &[&ColumnData], picks: &[(u32, u32)]) -> ColumnData {
macro_rules! each {
($variant:ident) => {
sources
.iter()
.map(|s| match s {
ColumnData::$variant(c) => c,
_ => unreachable!("sources checked to share a kind"),
})
.collect::<Vec<_>>()
};
}
match sources[0] {
ColumnData::Number(_) => {
let cols = each!(Number);
let values: Vec<&[f64]> = cols.iter().map(|c| c.values()).collect();
ColumnData::from(
picks
.iter()
.map(|&(s, r)| values[s as usize][r as usize])
.collect::<Vec<f64>>(),
)
}
ColumnData::Decimal(first) => {
let cols = each!(Decimal);
let precision = cols.iter().map(|c| c.precision()).max().unwrap_or(38);
first
.rebuild(
picks
.iter()
.map(|&(s, r)| cols[s as usize].get(r as usize).map(|d| d.value)),
precision,
)
.into()
}
ColumnData::String(_) => {
let cols = each!(String);
let picked: Vec<Option<&str>> = picks
.iter()
.map(|&(s, r)| cols[s as usize].get(r as usize))
.collect();
let bytes = picked.iter().flatten().map(|s| s.len()).sum();
let mut out = StringBuilder::with_capacity(picked.len(), bytes);
for v in picked {
out.append_option(v);
}
StringColumn::plain(out.finish()).into()
}
ColumnData::Boolean(_) => {
let cols = each!(Boolean);
BooleanColumn::from_options(
picks.iter().map(|&(s, r)| cols[s as usize].get(r as usize)),
)
.into()
}
ColumnData::Array(_) => {
let cols = each!(Array);
ArrayColumn::from_cells(
picks
.iter()
.map(|&(s, r)| cols[s as usize].cell(r as usize).cloned())
.collect(),
)
.into()
}
}
}
pub(crate) fn take(&self, rows: &[Option<usize>]) -> ColumnData {
match self {
ColumnData::Number(c) => {
let v = c.values();
ColumnData::from(
rows.iter()
.map(|r| r.map_or(f64::NAN, |i| v[i]))
.collect::<Vec<f64>>(),
)
}
ColumnData::Decimal(c) => c.take(rows).into(),
ColumnData::String(c) => c.take(rows).into(),
ColumnData::Boolean(c) => c.take(rows).into(),
ColumnData::Array(c) => c.take(rows).into(),
}
}
pub(crate) fn take_indices(&self, indices: &UInt32Array) -> ColumnData {
let gather = |a: &dyn Array| {
arrow_select::take::take(a, indices, None).expect("row numbers are in range")
};
match self {
ColumnData::Number(c) => {
let v = c.values();
ColumnData::from(
indices
.iter()
.map(|r| r.map_or(f64::NAN, |i| v[i as usize]))
.collect::<Vec<f64>>(),
)
}
ColumnData::Decimal(c) => DecimalColumn {
values: gather(&c.values).as_primitive::<Decimal128Type>().clone(),
}
.into(),
ColumnData::String(c) => c.take_indices(indices).into(),
ColumnData::Boolean(c) => {
BooleanColumn::from_arrow(gather(&c.values).as_boolean().clone()).into()
}
ColumnData::Array(c) => ArrayColumn::from_cells(
indices
.iter()
.map(|r| r.and_then(|i| c.cell(i as usize).cloned()))
.collect(),
)
.into(),
}
}
pub fn is_present(&self, i: usize) -> bool {
match self {
ColumnData::Number(c) => c.get(i).is_some(),
ColumnData::Decimal(c) => c.get(i).is_some(),
ColumnData::String(c) => c.get(i).is_some(),
ColumnData::Boolean(c) => c.get(i).is_some(),
ColumnData::Array(c) => c.get(i).is_some(),
}
}
pub fn kind(&self) -> ColumnKind {
match self {
ColumnData::Number(_) => ColumnKind::Number,
ColumnData::Decimal(_) => ColumnKind::Decimal,
ColumnData::String(_) => ColumnKind::String,
ColumnData::Boolean(_) => ColumnKind::Boolean,
ColumnData::Array(_) => ColumnKind::Array,
}
}
}
impl From<Float64Column> for ColumnData {
fn from(c: Float64Column) -> Self {
ColumnData::Number(c)
}
}
impl From<DecimalColumn> for ColumnData {
fn from(c: DecimalColumn) -> Self {
ColumnData::Decimal(c)
}
}
impl From<StringColumn> for ColumnData {
fn from(c: StringColumn) -> Self {
ColumnData::String(c)
}
}
impl From<BooleanColumn> for ColumnData {
fn from(c: BooleanColumn) -> Self {
ColumnData::Boolean(c)
}
}
impl From<ArrayColumn> for ColumnData {
fn from(c: ArrayColumn) -> Self {
ColumnData::Array(c)
}
}
macro_rules! column_data_from {
($($item:ty => |$v:ident| $build:expr;)*) => {$(
impl From<Vec<$item>> for ColumnData {
fn from($v: Vec<$item>) -> Self {
$build
}
}
impl<const N: usize> From<[$item; N]> for ColumnData {
fn from(values: [$item; N]) -> Self {
Vec::from(values).into()
}
}
)*};
}
column_data_from! {
f64 => |v| ColumnData::Number(Float64Column::from_values(v));
Option<f64> => |v| ColumnData::Number(Float64Column::from_options(v));
&str => |v| ColumnData::String(StringColumn::from_options(v.into_iter().map(Some)));
Option<&str> => |v| ColumnData::String(StringColumn::from_options(v));
String => |v| ColumnData::String(StringColumn::from_options(v.into_iter().map(Some)));
Option<String> => |v| ColumnData::String(StringColumn::from_options(v));
bool => |v| ColumnData::Boolean(BooleanColumn::from_options(v.into_iter().map(Some)));
Option<bool> => |v| ColumnData::Boolean(BooleanColumn::from_options(v));
Vec<Scalar> => |v| ColumnData::Array(ArrayColumn::from_options(v.into_iter().map(Some)));
Option<Vec<Scalar>> => |v| ColumnData::Array(ArrayColumn::from_options(v));
}