mod reader;
pub mod writer;
use std::sync::Arc;
use reader::StructReader;
use vortex_array::EmptyMetadata;
use vortex_array::dtype::DType;
use vortex_array::dtype::Field;
use vortex_array::dtype::FieldMask;
use vortex_array::dtype::Nullability;
use vortex_array::dtype::StructFields;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_ensure;
use vortex_error::vortex_err;
use vortex_session::SessionExt;
use vortex_session::VortexSession;
use vortex_session::registry::CachedId;
pub use writer::StructStrategy;
use crate::Layout;
use crate::LayoutChildType;
use crate::LayoutDeserializeArgs;
use crate::LayoutId;
use crate::LayoutParts;
use crate::LayoutReaderContext;
use crate::LayoutReaderRef;
use crate::LayoutRef;
use crate::VTable;
use crate::children::OwnedLayoutChildren;
use crate::segments::SegmentSource;
#[derive(Clone, Debug)]
pub struct Struct;
pub use Struct as StructLayoutEncoding;
pub type StructLayout = Layout<Struct>;
impl VTable for Struct {
type LayoutData = ();
type Metadata = EmptyMetadata;
fn id(&self) -> LayoutId {
static ID: CachedId = CachedId::new("vortex.struct");
*ID
}
fn metadata(_layout: &Layout<Self>) -> Self::Metadata {
EmptyMetadata
}
fn deserialize(
&self,
args: &LayoutDeserializeArgs<'_>,
_metadata: &EmptyMetadata,
) -> VortexResult<Self::LayoutData> {
Layout::<Struct>::validate_children(args.dtype, args.children.nchildren())?;
for idx in 0..args.children.nchildren() {
let child_row_count = args.children.child_row_count(idx);
vortex_ensure!(
child_row_count == args.row_count,
"Struct child {idx} row count does not match parent"
);
}
Ok(())
}
fn nslots(layout: &Layout<Self>) -> usize {
layout.struct_fields().nfields() + 1
}
fn slot_to_child(layout: &Layout<Self>, slot: usize) -> Option<usize> {
let nullable = layout.dtype().is_nullable();
match slot {
0 => nullable.then_some(0),
_ => Some(slot - 1 + usize::from(nullable)),
}
}
fn child_dtype(layout: &Layout<Self>, slot: usize) -> VortexResult<DType> {
StructLayout::slot_dtype(layout.dtype(), slot)
}
fn child_type(layout: &Layout<Self>, slot: usize) -> LayoutChildType {
if slot == 0 {
LayoutChildType::Auxiliary("validity".into())
} else {
LayoutChildType::Field(
layout
.struct_fields()
.field_name(slot - 1)
.vortex_expect("Field index out of bounds")
.clone(),
)
}
}
fn new_reader(
layout: &Layout<Self>,
name: Arc<str>,
segment_source: Arc<dyn SegmentSource>,
session: &VortexSession,
ctx: &LayoutReaderContext,
) -> VortexResult<LayoutReaderRef> {
Ok(Arc::new(StructReader::try_new(
layout.clone(),
name,
segment_source,
session.session(),
ctx.clone(),
)?))
}
}
impl Layout<Struct> {
pub fn new(row_count: u64, dtype: DType, children: Vec<LayoutRef>) -> Self {
Self::validate_children(&dtype, children.len()).vortex_expect("invalid struct children");
LayoutParts::new(
Struct,
dtype,
row_count,
Vec::new(),
OwnedLayoutChildren::layout_children(children),
(),
)
.into_typed()
}
pub fn struct_fields(&self) -> &StructFields {
self.dtype()
.as_struct_fields_opt()
.vortex_expect("Struct layout dtype must be a struct")
}
pub fn matching_fields<F>(&self, field_mask: &[FieldMask], mut per_child: F) -> VortexResult<()>
where
F: FnMut(FieldMask, usize) -> VortexResult<()>,
{
if field_mask.iter().any(|mask| mask.matches_all()) {
for idx in 0..self.struct_fields().nfields() {
per_child(FieldMask::All, idx)?;
}
return Ok(());
}
for path in field_mask {
let Some(field) = path.starting_field()? else {
continue;
};
let Field::Name(field_name) = field else {
vortex_bail!("Expected field name, got {field:?}");
};
let idx = self
.struct_fields()
.find(field_name)
.ok_or_else(|| vortex_err!("Field not found: {field_name}"))?;
per_child(path.clone().step_into()?, idx)?;
}
Ok(())
}
fn validate_children(dtype: &DType, nchildren: usize) -> VortexResult<()> {
let fields = dtype
.as_struct_fields_opt()
.ok_or_else(|| vortex_err!("Expected struct dtype"))?;
let expected = fields.nfields() + usize::from(dtype.is_nullable());
vortex_ensure!(
nchildren == expected,
"Struct layout has {nchildren} children, expected {expected}"
);
Ok(())
}
fn slot_dtype(dtype: &DType, slot: usize) -> VortexResult<DType> {
if slot == 0 {
Ok(DType::Bool(Nullability::NonNullable))
} else {
dtype
.as_struct_fields_opt()
.and_then(|fields| fields.field_by_index(slot - 1))
.ok_or_else(|| vortex_err!("Missing field {}", slot - 1))
}
}
}
#[cfg(test)]
mod tests {
use vortex_array::dtype::FieldName;
use vortex_array::dtype::PType;
use vortex_session::registry::ReadContext;
use super::*;
use crate::layouts::flat::FlatLayout;
use crate::segments::SegmentId;
fn flat_child(dtype: DType, segment: u32) -> LayoutRef {
FlatLayout::new(3, dtype, SegmentId::from(segment), ReadContext::new([])).into_layout()
}
fn two_field_struct(nullability: Nullability) -> DType {
let i32 = DType::Primitive(PType::I32, Nullability::NonNullable);
DType::Struct(
StructFields::from_iter([("a", i32.clone()), ("b", i32)]),
nullability,
)
}
#[test]
fn field_slots_are_stable_across_nullability() -> VortexResult<()> {
let i32 = DType::Primitive(PType::I32, Nullability::NonNullable);
let bool_ = DType::Bool(Nullability::NonNullable);
let non_null = StructLayout::new(
3,
two_field_struct(Nullability::NonNullable),
vec![flat_child(i32.clone(), 0), flat_child(i32.clone(), 1)],
);
assert_eq!(non_null.nslots(), 3);
assert_eq!(non_null.nchildren(), 2);
assert_eq!(non_null.slot_to_child(0), None);
assert_eq!(non_null.slot_to_child(1), Some(0));
assert_eq!(non_null.slot_to_child(2), Some(1));
assert!(non_null.slot(0)?.is_none());
assert_eq!(non_null.slot_type(0), None);
let nullable = StructLayout::new(
3,
two_field_struct(Nullability::Nullable),
vec![
flat_child(bool_, 0),
flat_child(i32.clone(), 1),
flat_child(i32, 2),
],
);
assert_eq!(nullable.nslots(), 3);
assert_eq!(nullable.nchildren(), 3);
assert_eq!(nullable.slot_to_child(0), Some(0));
assert_eq!(nullable.slot_to_child(1), Some(1));
assert_eq!(nullable.slot_to_child(2), Some(2));
assert!(nullable.slot(0)?.is_some());
assert_eq!(
nullable.slot_type(0),
Some(LayoutChildType::Auxiliary("validity".into()))
);
for layout in [&non_null, &nullable] {
assert_eq!(
layout.slot_type(1),
Some(LayoutChildType::Field(FieldName::from("a")))
);
}
Ok(())
}
}