Skip to content

Commit a9cc0b1

Browse files
committed
more things
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
1 parent 140be1c commit a9cc0b1

3 files changed

Lines changed: 38 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/src/canonical.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,7 @@ impl Matcher for AnyCanonical {
10021002
|| array.is::<VarBinView>()
10031003
|| array.is::<Variant>()
10041004
|| array.is::<Extension>()
1005+
|| array.is::<Variant>()
10051006
}
10061007

10071008
fn try_match<'a>(array: &'a dyn DynArray) -> Option<Self::Match<'a>> {

0 commit comments

Comments
 (0)