Skip to content

Commit 2a65426

Browse files
committed
more things
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
1 parent 44e23c0 commit 2a65426

5 files changed

Lines changed: 49 additions & 2 deletions

File tree

encodings/parquet-variant/src/array.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,29 @@ use vortex_mask::Mask;
2828
/// `ParquetVariantArray` preserves semi-structured data stored as Parquet Variant values in a
2929
/// lossless form and supports both unshredded and shredded layouts. Its storage matches the
3030
/// canonical extension type contract:
31-
/// - `metadata` is always present and non-nullable
31+
/// - `metadata` is always present and non-nullable.
3232
/// - `value` stores unshredded variant bytes when present
3333
/// - `typed_value` stores shredded data when present
3434
///
3535
/// At least one of `value` or `typed_value` must be present. `typed_value` may be a primitive,
3636
/// list, or struct, with nested shredded children following the same recursive rules as the
3737
/// Arrow canonical extension type docs.
38+
///
39+
/// # Nullability
40+
///
41+
/// There are three independent levels of nullability in this encoding:
42+
///
43+
/// 1. **Outer validity** (`validity`): controls which *rows* of the variant column are null.
44+
/// This drives the `DType::Variant(Nullable | NonNullable)` of the enclosing `VariantArray`.
45+
///
46+
/// 2. **Value child nullability**: the `value` child is `Binary` and may be nullable or
47+
/// non-nullable. In partially-shredded layouts some rows have their data in `typed_value`
48+
/// instead, so the corresponding `value` slot is null — making the child nullable. This
49+
/// nullability is persisted in [`ParquetVariantMetadata::value_nullable`] so that
50+
/// serialization round-trips preserve the original Arrow field nullability.
51+
///
52+
/// 3. **Typed-value child nullability**: the `typed_value` child carries its own `DType`
53+
/// (which includes nullability).
3854
#[derive(Clone, Debug)]
3955
pub struct ParquetVariantArray {
4056
pub(crate) dtype: DType,

encodings/parquet-variant/src/vtable.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ impl ParquetVariant {
4949
pub struct ParquetVariantMetadata {
5050
/// Whether the un-shredded `value` child is present.
5151
pub has_value: bool,
52+
/// Whether the `value` child is nullable.
53+
///
54+
/// In partially-shredded layouts, rows whose data lives entirely in `typed_value` have a
55+
/// null `value` slot, so the Arrow field is marked nullable. This flag preserves that
56+
/// distinction across serialization round-trips.
57+
pub value_nullable: bool,
5258
/// DType of the shredded `typed_value`, if present.
5359
///
5460
/// This is required to deserialize non-variant shredded children.
@@ -63,6 +69,9 @@ struct ParquetVariantMetadataProto {
6369
/// DType of the shredded `typed_value`, if present.
6470
#[prost(message, optional, tag = "2")]
6571
pub typed_value_dtype: Option<pb::DType>,
72+
/// Whether the `value` child is nullable.
73+
#[prost(bool, tag = "3")]
74+
pub value_nullable: bool,
6675
}
6776

6877
vtable!(ParquetVariant);
@@ -194,6 +203,10 @@ impl VTable for ParquetVariant {
194203
fn metadata(array: &ParquetVariantArray) -> VortexResult<Self::Metadata> {
195204
Ok(ParquetVariantMetadata {
196205
has_value: array.value.is_some(),
206+
value_nullable: array
207+
.value
208+
.as_ref()
209+
.is_some_and(|v| v.dtype().is_nullable()),
197210
typed_value_dtype: array.typed_value.as_ref().map(|tv| tv.dtype().clone()),
198211
})
199212
}
@@ -208,6 +221,7 @@ impl VTable for ParquetVariant {
208221
ParquetVariantMetadataProto {
209222
has_value: metadata.has_value,
210223
typed_value_dtype,
224+
value_nullable: metadata.value_nullable,
211225
}
212226
.encode_to_vec(),
213227
))
@@ -227,6 +241,7 @@ impl VTable for ParquetVariant {
227241
};
228242
Ok(ParquetVariantMetadata {
229243
has_value: proto.has_value,
244+
value_nullable: proto.value_nullable,
230245
typed_value_dtype,
231246
})
232247
}
@@ -264,7 +279,11 @@ impl VTable for ParquetVariant {
264279
child_idx += 1;
265280

266281
let value = if metadata.has_value {
267-
let v = children.get(child_idx, &DType::Binary(Nullability::NonNullable), len)?;
282+
let v = children.get(
283+
child_idx,
284+
&DType::Binary(metadata.value_nullable.into()),
285+
len,
286+
)?;
268287
child_idx += 1;
269288
Some(v)
270289
} else {

vortex-array/public-api.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22198,6 +22198,8 @@ pub vortex_array::CanonicalView::Struct(&'a vortex_array::arrays::StructArray)
2219822198

2219922199
pub vortex_array::CanonicalView::VarBinView(&'a vortex_array::arrays::VarBinViewArray)
2220022200

22201+
pub vortex_array::CanonicalView::Variant(&'a vortex_array::arrays::variant::VariantArray)
22202+
2220122203
impl core::convert::AsRef<dyn vortex_array::DynArray> for vortex_array::CanonicalView<'_>
2220222204

2220322205
pub fn vortex_array::CanonicalView<'_>::as_ref(&self) -> &dyn vortex_array::DynArray

vortex-array/src/canonical.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use crate::arrays::Struct;
3535
use crate::arrays::StructArray;
3636
use crate::arrays::VarBinView;
3737
use crate::arrays::VarBinViewArray;
38+
use crate::arrays::Variant;
3839
use crate::arrays::VariantArray;
3940
use crate::arrays::bool::BoolArrayParts;
4041
use crate::arrays::decimal::DecimalArrayParts;
@@ -950,6 +951,7 @@ pub enum CanonicalView<'a> {
950951
FixedSizeList(&'a FixedSizeListArray),
951952
Struct(&'a StructArray),
952953
Extension(&'a ExtensionArray),
954+
Variant(&'a VariantArray),
953955
}
954956

955957
impl From<CanonicalView<'_>> for Canonical {
@@ -964,6 +966,7 @@ impl From<CanonicalView<'_>> for Canonical {
964966
CanonicalView::FixedSizeList(a) => Canonical::FixedSizeList(a.clone()),
965967
CanonicalView::Struct(a) => Canonical::Struct(a.clone()),
966968
CanonicalView::Extension(a) => Canonical::Extension(a.clone()),
969+
CanonicalView::Variant(a) => Canonical::Variant(a.clone()),
967970
}
968971
}
969972
}
@@ -980,6 +983,7 @@ impl AsRef<dyn DynArray> for CanonicalView<'_> {
980983
CanonicalView::FixedSizeList(a) => a.as_ref(),
981984
CanonicalView::Struct(a) => a.as_ref(),
982985
CanonicalView::Extension(a) => a.as_ref(),
986+
CanonicalView::Variant(a) => a.as_ref(),
983987
}
984988
}
985989
}
@@ -999,6 +1003,7 @@ impl Matcher for AnyCanonical {
9991003
|| array.is::<FixedSizeList>()
10001004
|| array.is::<VarBinView>()
10011005
|| array.is::<Extension>()
1006+
|| array.is::<Variant>()
10021007
}
10031008

10041009
fn try_match<'a>(array: &'a dyn DynArray) -> Option<Self::Match<'a>> {
@@ -1018,6 +1023,8 @@ impl Matcher for AnyCanonical {
10181023
Some(CanonicalView::FixedSizeList(a))
10191024
} else if let Some(a) = array.as_opt::<VarBinView>() {
10201025
Some(CanonicalView::VarBinView(a))
1026+
} else if let Some(a) = array.as_opt::<Variant>() {
1027+
Some(CanonicalView::Variant(a))
10211028
} else {
10221029
array.as_opt::<Extension>().map(CanonicalView::Extension)
10231030
}

vortex-array/src/scalar_fn/fns/cast/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ fn cast_canonical(
214214
CanonicalView::FixedSizeList(a) => <FixedSizeList as CastReduce>::cast(a, dtype),
215215
CanonicalView::Struct(a) => <Struct as CastKernel>::cast(a, dtype, ctx),
216216
CanonicalView::Extension(a) => <Extension as CastReduce>::cast(a, dtype),
217+
CanonicalView::Variant(_) => {
218+
vortex_bail!("Variant arrays don't support casting")
219+
}
217220
}
218221
}
219222

0 commit comments

Comments
 (0)