use std::sync::Arc;
use arrow::array as aa;
use arrow::datatypes as ad;
use lutra_bin::Encode;
use lutra_bin::bytes::{self, BufMut};
use lutra_bin::ir;
use crate::context::{Context, Error};
pub fn arrow_to_lutra(
batches: Vec<aa::RecordBatch>,
ty: &ir::Ty,
ty_defs: &[ir::TyDef],
) -> Result<bytes::BytesMut, Error> {
let ctx = Context::new(ty_defs);
let ty = ctx.get_ty_mat(ty)?;
if ty.is_unit() {
return Ok(bytes::BytesMut::new());
}
if let ir::TyKind::Array(item_ty) = &ty.kind {
return encode_array(&ctx, batches, item_ty);
}
encode_single_value(&ctx, batches, ty)
}
fn encode_array(
ctx: &Context,
batches: Vec<aa::RecordBatch>,
item_ty: &ir::Ty,
) -> Result<bytes::BytesMut, Error> {
let num_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
if num_rows == 0 {
return Ok(empty_array_bytes());
}
let item_ty = ctx.get_ty_mat(item_ty)?;
let expected_cols = expected_column_count(item_ty);
validate_column_count(&batches, expected_cols)?;
let mut encoder = Encoder::new(ctx);
encoder.encode_array(&batches, item_ty, num_rows)
}
fn encode_single_value(
ctx: &Context,
batches: Vec<aa::RecordBatch>,
ty: &ir::Ty,
) -> Result<bytes::BytesMut, Error> {
let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
if total_rows != 1 {
return Err(Error::ExpectedSingleRow { got: total_rows });
}
let ty = ctx.get_ty_mat(ty)?;
let expected_cols = expected_column_count(ty);
validate_column_count(&batches, expected_cols)?;
let batch = batches.iter().find(|b| b.num_rows() > 0).unwrap();
let mut encoder = Encoder::new(ctx);
encoder.encode_single_row(batch, 0, ty)
}
fn expected_column_count(ty: &ir::Ty) -> usize {
match &ty.kind {
ir::TyKind::Tuple(fields) => fields.len(),
_ => 1, }
}
fn validate_column_count(batches: &[aa::RecordBatch], expected: usize) -> Result<(), Error> {
for batch in batches {
if batch.num_columns() != expected {
return Err(Error::ColumnCountMismatch {
expected,
got: batch.num_columns(),
});
}
}
Ok(())
}
fn empty_array_bytes() -> bytes::BytesMut {
let mut buf = bytes::BytesMut::new();
buf.put_u32_le(8); buf.put_u32_le(0); buf
}
fn validate_type_match(
arrow_ty: &ad::DataType,
lutra_ty: &ir::Ty,
ctx: &Context,
) -> Result<(), Error> {
let lutra_ty = ctx.get_ty_mat(lutra_ty)?;
if let ir::TyKind::Enum(variants) = &lutra_ty.kind
&& ctx.is_option(variants)?
{
return Ok(());
}
let ok = match (arrow_ty, &lutra_ty.kind) {
(ad::DataType::Boolean, ir::TyKind::Primitive(ir::TyPrimitive::bool)) => true,
(ad::DataType::Int8, ir::TyKind::Primitive(ir::TyPrimitive::int8)) => true,
(ad::DataType::Int16, ir::TyKind::Primitive(ir::TyPrimitive::int16)) => true,
(ad::DataType::Int32, ir::TyKind::Primitive(ir::TyPrimitive::int32)) => true,
(ad::DataType::Date32, ir::TyKind::Primitive(ir::TyPrimitive::int32)) => true,
(ad::DataType::Int64, ir::TyKind::Primitive(ir::TyPrimitive::int64)) => true,
(ad::DataType::Timestamp(_, _), ir::TyKind::Primitive(ir::TyPrimitive::int64)) => true,
(ad::DataType::UInt8, ir::TyKind::Primitive(ir::TyPrimitive::uint8)) => true,
(ad::DataType::UInt16, ir::TyKind::Primitive(ir::TyPrimitive::uint16)) => true,
(ad::DataType::UInt32, ir::TyKind::Primitive(ir::TyPrimitive::uint32)) => true,
(ad::DataType::UInt64, ir::TyKind::Primitive(ir::TyPrimitive::uint64)) => true,
(ad::DataType::Float32, ir::TyKind::Primitive(ir::TyPrimitive::float32)) => true,
(ad::DataType::Float64, ir::TyKind::Primitive(ir::TyPrimitive::float64)) => true,
(
ad::DataType::Utf8 | ad::DataType::LargeUtf8,
ir::TyKind::Primitive(ir::TyPrimitive::text),
) => true,
(ad::DataType::Struct(arrow_fields), ir::TyKind::Tuple(lutra_fields)) => {
arrow_fields.len() == lutra_fields.len()
}
(ad::DataType::List(_) | ad::DataType::LargeList(_), ir::TyKind::Array(_)) => true,
(ad::DataType::Union(arrow_fields, _), ir::TyKind::Enum(lutra_variants)) => {
arrow_fields.len() == lutra_variants.len()
}
_ => false,
};
if ok {
Ok(())
} else {
Err(Error::TypeMismatch {
arrow_ty: format!("{:?}", arrow_ty),
lutra_ty: format!("{:?}", lutra_ty.kind),
})
}
}
#[derive(Debug)]
enum HeadResidual {
None, Ptr(lutra_bin::ReversePointer), Struct(Vec<HeadResidual>), OptionSome(lutra_bin::ReversePointer), Enum {
tag: usize,
variant_residual: Box<HeadResidual>,
has_ptr: bool,
ptr: Option<lutra_bin::ReversePointer>,
},
}
struct Encoder<'a> {
arr_buf: bytes::BytesMut,
ctx: &'a Context<'a>,
}
impl<'a> Encoder<'a> {
fn new(ctx: &'a Context<'a>) -> Self {
Self {
arr_buf: bytes::BytesMut::new(),
ctx,
}
}
fn encode_array(
&mut self,
batches: &[aa::RecordBatch],
item_ty: &ir::Ty,
num_rows: usize,
) -> Result<bytes::BytesMut, Error> {
let arr_body_ptr = lutra_bin::ReversePointer::new(&mut self.arr_buf);
self.arr_buf.put_u32_le(num_rows as u32);
arr_body_ptr.write_cur_len(&mut self.arr_buf);
let mut residuals = Vec::with_capacity(num_rows);
for batch in batches {
for row_idx in 0..batch.num_rows() {
residuals.push(self.write_row_head(batch, row_idx, item_ty)?);
}
}
let mut residuals = residuals.into_iter();
for batch in batches {
for row_idx in 0..batch.num_rows() {
self.write_row_body(residuals.next().unwrap(), batch, row_idx, item_ty)?;
}
}
Ok(std::mem::take(&mut self.arr_buf))
}
fn encode_single_row(
&mut self,
batch: &aa::RecordBatch,
row_idx: usize,
ty: &ir::Ty,
) -> Result<bytes::BytesMut, Error> {
let residual = self.write_row_head(batch, row_idx, ty)?;
self.write_row_body(residual, batch, row_idx, ty)?;
Ok(std::mem::take(&mut self.arr_buf))
}
fn write_row_head(
&mut self,
batch: &aa::RecordBatch,
row_idx: usize,
ty: &ir::Ty,
) -> Result<HeadResidual, Error> {
let ty = self.ctx.get_ty_mat(ty)?;
match &ty.kind {
ir::TyKind::Tuple(fields) => {
let mut residuals = Vec::with_capacity(fields.len());
for (col_idx, field) in fields.iter().enumerate() {
let array = batch.column(col_idx).as_ref();
residuals.push(self.write_head(array, row_idx, &field.ty)?);
}
Ok(HeadResidual::Struct(residuals))
}
_ => {
let array = batch.column(0).as_ref();
self.write_head(array, row_idx, ty)
}
}
}
fn write_row_body(
&mut self,
residual: HeadResidual,
batch: &aa::RecordBatch,
row_idx: usize,
ty: &ir::Ty,
) -> Result<(), Error> {
let ty = self.ctx.get_ty_mat(ty)?;
match &ty.kind {
ir::TyKind::Tuple(fields) => {
let HeadResidual::Struct(residuals) = residual else {
unreachable!()
};
for (col_idx, (field, res)) in fields.iter().zip(residuals).enumerate() {
let array = batch.column(col_idx).as_ref();
self.write_body(res, array, row_idx, &field.ty)?;
}
Ok(())
}
_ => {
let array = batch.column(0).as_ref();
self.write_body(residual, array, row_idx, ty)
}
}
}
fn write_head(
&mut self,
array: &dyn aa::Array,
idx: usize,
ty: &ir::Ty,
) -> Result<HeadResidual, Error> {
let ty = self.ctx.get_ty_mat(ty)?;
if let ir::TyKind::Enum(variants) = &ty.kind
&& self.ctx.is_option(variants)?
{
let inner_ty = &variants[1].ty;
let head_format = lutra_bin::layout::enum_head_format(variants, &ty.variants_recursive);
if array.is_null(idx) {
return Ok(self.encode_option_none(&head_format));
} else {
self.arr_buf.put_u8(1);
if head_format.has_ptr {
let ptr = lutra_bin::ReversePointer::new(&mut self.arr_buf);
return Ok(HeadResidual::OptionSome(ptr));
} else {
return self.write_head(array, idx, inner_ty);
}
}
}
if array.is_null(idx) {
return Err(Error::UnexpectedNull);
}
validate_type_match(array.data_type(), ty, self.ctx)?;
fn downcast<T: 'static>(array: &dyn std::any::Any) -> &T {
array.downcast_ref::<T>().unwrap()
}
let residual = match array.data_type() {
ad::DataType::Boolean => {
downcast::<aa::BooleanArray>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::Int8 => {
downcast::<aa::Int8Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::Int16 => {
downcast::<aa::Int16Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::Int32 => {
downcast::<aa::Int32Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::Date32 => {
downcast::<aa::Date32Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::Int64 => {
downcast::<aa::Int64Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::Timestamp(ad::TimeUnit::Microsecond, _) => {
downcast::<aa::TimestampMicrosecondArray>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::UInt8 => {
downcast::<aa::UInt8Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::UInt16 => {
downcast::<aa::UInt16Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::UInt32 => {
downcast::<aa::UInt32Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::UInt64 => {
downcast::<aa::UInt64Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::Float32 => {
downcast::<aa::Float32Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::Float64 => {
downcast::<aa::Float64Array>(array.as_any())
.value(idx)
.encode_head(&mut self.arr_buf);
HeadResidual::None
}
ad::DataType::Utf8 => {
let string = downcast::<aa::StringArray>(array.as_any()).value(idx);
let ptr = lutra_bin::ReversePointer::new(&mut self.arr_buf);
self.arr_buf.put_u32_le(string.len() as u32);
HeadResidual::Ptr(ptr)
}
ad::DataType::LargeUtf8 => {
let string = downcast::<aa::LargeStringArray>(array.as_any()).value(idx);
let ptr = lutra_bin::ReversePointer::new(&mut self.arr_buf);
self.arr_buf.put_u32_le(string.len() as u32);
HeadResidual::Ptr(ptr)
}
ad::DataType::List(_) => {
let list = downcast::<aa::ListArray>(array.as_any()).value(idx);
let len = list.len();
let ptr = lutra_bin::ReversePointer::new(&mut self.arr_buf);
self.arr_buf.put_u32_le(len as u32);
HeadResidual::Ptr(ptr)
}
ad::DataType::LargeList(_) => {
let list = downcast::<aa::LargeListArray>(array.as_any()).value(idx);
let len = list.len();
let ptr = lutra_bin::ReversePointer::new(&mut self.arr_buf);
self.arr_buf.put_u32_le(len as u32);
HeadResidual::Ptr(ptr)
}
ad::DataType::Struct(_) => {
let ir::TyKind::Tuple(fields) = &ty.kind else {
unreachable!("validated above")
};
let struct_arr = downcast::<aa::StructArray>(array.as_any());
let mut residuals = Vec::new();
for (col_idx, field) in fields.iter().enumerate() {
let field_arr = struct_arr.column(col_idx);
residuals.push(self.write_head(field_arr.as_ref(), idx, &field.ty)?);
}
HeadResidual::Struct(residuals)
}
ad::DataType::Union(_fields, _mode) => {
let ir::TyKind::Enum(variants) = &ty.kind else {
unreachable!("validated above")
};
let union_arr = downcast::<aa::UnionArray>(array.as_any());
let tag = union_arr.type_id(idx) as usize;
let variant = &variants[tag];
let head_format =
lutra_bin::layout::enum_head_format(variants, &ty.variants_recursive);
self.arr_buf.put_u8(tag as u8);
if head_format.has_ptr {
if variant.ty.is_unit() {
self.arr_buf.put_bytes(0, head_format.inner_bytes as usize);
HeadResidual::Enum {
tag,
variant_residual: Box::new(HeadResidual::None),
has_ptr: true,
ptr: None,
}
} else {
let ptr = lutra_bin::ReversePointer::new(&mut self.arr_buf);
HeadResidual::Enum {
tag,
variant_residual: Box::new(HeadResidual::None),
has_ptr: true,
ptr: Some(ptr),
}
}
} else {
let variant_arr = union_arr.child(tag as i8);
let variant_idx = idx;
let variant_residual = if variant.ty.is_unit() {
HeadResidual::None
} else {
self.write_head(variant_arr.as_ref(), variant_idx, &variant.ty)?
};
let variant_format =
lutra_bin::layout::enum_variant_format(&head_format, &variant.ty);
if variant_format.padding_bytes > 0 {
self.arr_buf
.put_bytes(0, variant_format.padding_bytes as usize);
}
HeadResidual::Enum {
tag,
variant_residual: Box::new(variant_residual),
has_ptr: false,
ptr: None,
}
}
}
_ => panic!("Unsupported Arrow data type: {:?}", array.data_type()),
};
Ok(residual)
}
fn write_body(
&mut self,
residual: HeadResidual,
array: &dyn aa::Array,
idx: usize,
ty: &ir::Ty,
) -> Result<(), Error> {
let ty = self.ctx.get_ty_mat(ty)?;
if let ir::TyKind::Enum(variants) = &ty.kind
&& self.ctx.is_option(variants)?
{
if array.is_null(idx) {
return Ok(());
}
match residual {
HeadResidual::OptionSome(ptr) => {
ptr.write_cur_len(&mut self.arr_buf);
let inner_ty = &variants[1].ty;
let inner_residual = self.write_head(array, idx, inner_ty)?;
return self.write_body(inner_residual, array, idx, inner_ty);
}
_ => {
return self.write_body(residual, array, idx, &variants[1].ty);
}
}
}
fn downcast<T: 'static>(array: &dyn std::any::Any) -> &T {
array.downcast_ref::<T>().unwrap()
}
match residual {
HeadResidual::None => {
}
HeadResidual::Ptr(ptr) => {
ptr.write_cur_len(&mut self.arr_buf);
match array.data_type() {
ad::DataType::Utf8 => {
let string = downcast::<aa::StringArray>(array.as_any()).value(idx);
self.arr_buf.put_slice(string.as_bytes());
}
ad::DataType::LargeUtf8 => {
let string = downcast::<aa::LargeStringArray>(array.as_any()).value(idx);
self.arr_buf.put_slice(string.as_bytes());
}
ad::DataType::List(_) => {
let ir::TyKind::Array(item_ty) = &ty.kind else {
unreachable!()
};
let values = downcast::<aa::ListArray>(array.as_any()).value(idx);
self.write_list_body(values, item_ty)?;
}
ad::DataType::LargeList(_) => {
let ir::TyKind::Array(item_ty) = &ty.kind else {
unreachable!()
};
let values = downcast::<aa::LargeListArray>(array.as_any()).value(idx);
self.write_list_body(values, item_ty)?;
}
_ => unreachable!("Ptr residual for non-text/list type"),
}
}
HeadResidual::Struct(residuals) => {
let ir::TyKind::Tuple(fields) = &ty.kind else {
unreachable!()
};
let struct_arr = downcast::<aa::StructArray>(array.as_any());
for (col_idx, (field, res)) in fields.iter().zip(residuals).enumerate() {
let field_arr = struct_arr.column(col_idx);
self.write_body(res, field_arr.as_ref(), idx, &field.ty)?;
}
}
HeadResidual::OptionSome(_) => {
unreachable!("OptionSome residual should be handled in option enum case")
}
HeadResidual::Enum {
tag,
variant_residual,
has_ptr,
ptr,
} => {
let ir::TyKind::Enum(variants) = &ty.kind else {
unreachable!()
};
let variant = &variants[tag];
let union_arr = downcast::<aa::UnionArray>(array.as_any());
if has_ptr {
if let Some(ptr) = ptr {
ptr.write_cur_len(&mut self.arr_buf);
let variant_arr = union_arr.child(tag as i8);
let inner_residual =
self.write_head(variant_arr.as_ref(), idx, &variant.ty)?;
self.write_body(inner_residual, variant_arr.as_ref(), idx, &variant.ty)?;
}
} else {
let variant_arr = union_arr.child(tag as i8);
self.write_body(*variant_residual, variant_arr.as_ref(), idx, &variant.ty)?;
}
}
}
Ok(())
}
fn write_list_body(
&mut self,
values: Arc<dyn aa::Array>,
item_ty: &ir::Ty,
) -> Result<(), Error> {
let len = values.len();
let mut item_residuals = Vec::with_capacity(len);
for i in 0..len {
item_residuals.push(self.write_head(values.as_ref(), i, item_ty)?);
}
for (i, residual) in item_residuals.into_iter().enumerate() {
self.write_body(residual, values.as_ref(), i, item_ty)?;
}
Ok(())
}
fn encode_option_none(
&mut self,
head_format: &lutra_bin::layout::EnumHeadFormat,
) -> HeadResidual {
self.arr_buf.put_u8(0);
self.arr_buf.put_bytes(0, head_format.inner_bytes as usize);
HeadResidual::None
}
}