use super::arrow_convert::ArrowConvertError;
use super::arrow_convert_util::{
bigint_to_i128, checked_binary_offsets, checked_string_offsets, unwrap_frozen_type,
unwrap_frozen_value, Cells,
};
use super::arrow_decimal::rescale_decimal;
use super::arrow_schema::{DECIMAL_FIXED_SCALE, DECIMAL_MAX_PRECISION};
use crate::query::ColumnInfo;
use crate::schema::CqlType;
use crate::types::Value;
use crate::util::value_fmt::ValueFormatter;
use arrow::array::{
ArrayRef, BinaryArray, BooleanArray, Date32Array, Float32Array, Float64Array, Int16Array,
Int32Array, Int64Array, Int8Array, StringArray, Time64NanosecondArray,
TimestampMillisecondArray,
};
use std::borrow::Cow;
use std::sync::Arc;
pub(super) fn build_boolean_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<bool>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::Boolean(b)) => Ok(Some(*b)),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Boolean value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<bool>>, ArrowConvertError>>()?;
Ok(Arc::new(BooleanArray::from(values)))
}
pub(super) fn build_int8_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<i8>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::TinyInt(i)) => Ok(Some(*i)),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected TinyInt value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<i8>>, ArrowConvertError>>()?;
Ok(Arc::new(Int8Array::from(values)))
}
pub(super) fn build_int16_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<i16>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::SmallInt(i)) => Ok(Some(*i)),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected SmallInt value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<i16>>, ArrowConvertError>>()?;
Ok(Arc::new(Int16Array::from(values)))
}
pub(super) fn build_int32_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let allow_compat = col.cql_type.is_none();
let values: Vec<Option<i32>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::Integer(i)) => Ok(Some(*i)),
Some(Value::Date(d)) if allow_compat => Ok(Some(*d)), Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Int value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<i32>>, ArrowConvertError>>()?;
Ok(Arc::new(Int32Array::from(values)))
}
pub(super) fn build_int64_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let effective = col.cql_type.as_ref().map(unwrap_frozen_type);
let allow_counter = matches!(effective, None | Some(CqlType::Counter));
let allow_compat = effective.is_none();
let values: Vec<Option<i64>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::BigInt(i)) => Ok(Some(*i)),
Some(Value::Counter(c)) if allow_counter => Ok(Some(*c)),
Some(Value::Time(t)) if allow_compat => Ok(Some(*t)), Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected BigInt value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<i64>>, ArrowConvertError>>()?;
Ok(Arc::new(Int64Array::from(values)))
}
pub(super) fn build_float32_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<f32>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::Float32(f)) => Ok(Some(*f)),
Some(Value::Float(f)) => Ok(Some(*f as f32)),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Float value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<f32>>, ArrowConvertError>>()?;
Ok(Arc::new(Float32Array::from(values)))
}
pub(super) fn build_float64_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<f64>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::Float(f)) => Ok(Some(*f)),
Some(Value::Float32(f)) => Ok(Some(*f as f64)),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Double value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<f64>>, ArrowConvertError>>()?;
Ok(Arc::new(Float64Array::from(values)))
}
pub(super) fn build_string_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let strict_text = matches!(
col.cql_type.as_ref().map(unwrap_frozen_type),
Some(CqlType::Text | CqlType::Ascii | CqlType::Varchar)
);
if strict_text {
let refs: Vec<Option<&str>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None | Some(Value::Null) => Ok(None),
Some(Value::Text(s)) => std::str::from_utf8(s).map(Some).map_err(|e| {
ArrowConvertError::InvalidValue(format!("invalid UTF-8 in text: {e}"))
}),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Text value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<&str>>, ArrowConvertError>>()?;
checked_string_offsets(&refs)?;
return Ok(Arc::new(StringArray::from(refs)));
}
let values: Vec<Option<Cow<str>>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::Null) => Ok(None),
Some(Value::Text(s)) => std::str::from_utf8(s)
.map(|st| Some(Cow::Borrowed(st)))
.map_err(|e| {
ArrowConvertError::InvalidValue(format!("invalid UTF-8 in text: {e}"))
}),
Some(Value::Json(j)) => Ok(Some(Cow::Owned(j.to_string()))),
Some(other) => Ok(Some(Cow::Owned(ValueFormatter::format_value(other)))),
})
.collect::<Result<Vec<Option<Cow<str>>>, ArrowConvertError>>()?;
checked_string_offsets(&values)?;
Ok(Arc::new(StringArray::from_iter(
values.iter().map(|v| v.as_deref()),
)))
}
pub(super) fn build_binary_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<&[u8]>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::Blob(b)) => Ok(Some(b.as_ref())),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Blob value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<&[u8]>>, ArrowConvertError>>()?;
checked_binary_offsets(&values)?;
Ok(Arc::new(BinaryArray::from(values)))
}
pub(super) fn build_timestamp_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<i64>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::Timestamp(ts)) => Ok(Some(*ts)),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Timestamp value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<i64>>, ArrowConvertError>>()?;
Ok(Arc::new(
TimestampMillisecondArray::from(values).with_timezone("UTC"),
))
}
pub(super) fn build_uuid_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let mut builder = arrow::array::FixedSizeBinaryBuilder::with_capacity(cells.len(), 16);
for cell in cells {
match unwrap_frozen_value(*cell) {
None | Some(Value::Null) => builder.append_null(),
Some(Value::Uuid(uuid)) => builder.append_value(uuid)?,
Some(other) => {
return Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Uuid value, got {:?}",
col.name, other
)));
}
}
}
Ok(Arc::new(builder.finish()))
}
pub(super) fn build_date32_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<i32>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::Date(days)) => Ok(Some(*days)),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Date value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<i32>>, ArrowConvertError>>()?;
Ok(Arc::new(Date32Array::from(values)))
}
pub(super) fn build_time64_ns_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<i64>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(Value::Time(nanos)) => Ok(Some(*nanos)),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Time value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<i64>>, ArrowConvertError>>()?;
Ok(Arc::new(Time64NanosecondArray::from(values)))
}
pub(super) fn build_decimal128_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let mut builder = arrow::array::Decimal128Builder::with_capacity(cells.len())
.with_precision_and_scale(DECIMAL_MAX_PRECISION, DECIMAL_FIXED_SCALE as i8)?;
for cell in cells {
match unwrap_frozen_value(*cell) {
Some(Value::Decimal { scale, unscaled }) => {
let rescaled = rescale_decimal(*scale, unscaled).map_err(|e| {
ArrowConvertError::InvalidValue(format!("Column '{}': {e}", col.name))
})?;
builder.append_value(rescaled);
}
Some(Value::Null) | None => {
builder.append_null();
}
Some(other) => {
return Err(ArrowConvertError::InvalidValue(format!(
"Column '{}': expected Decimal value, got {:?}",
col.name, other
)));
}
}
}
Ok(Arc::new(builder.finish()))
}
pub(super) fn build_varint_as_decimal128_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
use num_bigint::BigInt;
let mut builder = arrow::array::Decimal128Builder::with_capacity(cells.len())
.with_precision_and_scale(DECIMAL_MAX_PRECISION, 0)?;
for cell in cells {
match unwrap_frozen_value(*cell) {
Some(Value::Varint(bytes)) => {
if bytes.is_empty() {
builder.append_value(0);
} else {
let bigint = BigInt::from_signed_bytes_be(bytes);
let max_abs = BigInt::from(10i64).pow(38u32) - BigInt::from(1i64);
let abs_val = if bigint.sign() == num_bigint::Sign::Minus {
-bigint.clone()
} else {
bigint.clone()
};
if abs_val > max_abs {
return Err(ArrowConvertError::InvalidValue(format!(
"Column '{}': varint value exceeds Decimal128(38, 0) range",
col.name
)));
}
let i128_val = bigint_to_i128(&bigint).map_err(|e| {
ArrowConvertError::InvalidValue(format!("Column '{}': {e}", col.name))
})?;
builder.append_value(i128_val);
}
}
Some(Value::Null) | None => {
builder.append_null();
}
Some(other) => {
return Err(ArrowConvertError::InvalidValue(format!(
"Column '{}': expected Varint value, got {:?}",
col.name, other
)));
}
}
}
Ok(Arc::new(builder.finish()))
}
pub(super) fn build_duration_utf8_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<String>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(v @ Value::Duration { .. }) => Ok(Some(ValueFormatter::format_value(v))),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Duration value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<String>>, ArrowConvertError>>()?;
checked_string_offsets(&values)?;
Ok(Arc::new(StringArray::from(values)))
}
pub(super) fn build_uuid_fixed_binary_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let mut builder = arrow::array::FixedSizeBinaryBuilder::with_capacity(cells.len(), 16);
for cell in cells {
match unwrap_frozen_value(*cell) {
Some(Value::Uuid(bytes)) => builder.append_value(bytes)?,
Some(Value::Null) | None => builder.append_null(),
Some(other) => {
return Err(ArrowConvertError::InvalidValue(format!(
"Column '{}': expected Uuid value, got {:?}",
col.name, other
)));
}
}
}
Ok(Arc::new(builder.finish()))
}
pub(super) fn build_inet_utf8_array(
col: &ColumnInfo,
cells: Cells,
) -> Result<ArrayRef, ArrowConvertError> {
let values: Vec<Option<String>> = cells
.iter()
.map(|cell| match unwrap_frozen_value(*cell) {
None => Ok(None),
Some(inet @ Value::Inet(_)) => Ok(Some(ValueFormatter::format_value(inet))),
Some(Value::Null) => Ok(None),
Some(other) => Err(ArrowConvertError::InvalidValue(format!(
"column '{}': expected Inet value, got {:?}",
col.name, other
))),
})
.collect::<Result<Vec<Option<String>>, ArrowConvertError>>()?;
checked_string_offsets(&values)?;
Ok(Arc::new(StringArray::from(values)))
}