Skip to content

Commit 4af5d55

Browse files
authored
Struct reader partitioning respects struct validity (#10449)
Current implementation returns lit(true) for pack()'s validity. This in turn makes many things incorrect. For example, "a.x > 5" where a is nullable may read x's value under NULL rows. Fix this by adding a discriminant virtual field which is struct's validity. This is internal and not addressable by user. This is a temporary solution, since you need to construct FieldName (~ a string) from a validity discriminant option as well. This validity field is replaced to is_not_null($) while evaluating the expression, and struct's bound expression is now mask(pack(...), is_not_null($)) in case of a nullable struct.
1 parent 1269e82 commit 4af5d55

4 files changed

Lines changed: 686 additions & 122 deletions

File tree

‎vortex-array/src/dtype/arbitrary/mod.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::dtype::FieldNames;
1616
use crate::dtype::NativeDecimalType;
1717
use crate::dtype::Nullability;
1818
use crate::dtype::PType;
19+
use crate::dtype::RESERVED_VALIDITY_FIELD_NAME;
1920
use crate::dtype::StructFields;
2021
use crate::dtype::i256;
2122

@@ -127,8 +128,13 @@ fn random_struct_dtype(u: &mut Unstructured<'_>, depth: u8) -> Result<StructFiel
127128
let names: Vec<FieldName> = (0..field_count)
128129
.map(|_| FieldName::arbitrary(u))
129130
.collect::<Result<Vec<_>>>()?;
130-
// Deduplicate field names, keeping only the first occurrence of each.
131-
let names: FieldNames = names.into_iter().unique().collect();
131+
// Deduplicate field names, keeping only the first occurrence of each, and drop the name
132+
// reserved for struct validity.
133+
let names: FieldNames = names
134+
.into_iter()
135+
.filter(|name| name.as_ref() != RESERVED_VALIDITY_FIELD_NAME)
136+
.unique()
137+
.collect();
132138
let dtypes = (0..names.len())
133139
.map(|_| random_dtype(u, depth))
134140
.collect::<Result<Vec<_>>>()?;

‎vortex-array/src/dtype/field_names.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ use std::sync::Arc;
99
use itertools::Itertools;
1010
use vortex_utils::aliases::StringEscape;
1111

12+
/// Field name reserved for a nullable struct's own validity when expressions over the struct
13+
/// are partitioned by field. Structs must not contain a field with this name.
14+
pub const RESERVED_VALIDITY_FIELD_NAME: &str = "$__vortex_validity";
15+
1216
/// A name for a field in a struct.
1317
#[derive(Clone, Debug, Eq, PartialOrd, Ord, Hash)]
1418
#[allow(clippy::derived_hash_with_manual_eq)] // manual PartialEq adds Arc::ptr_eq fast path only

‎vortex-array/src/expr/transform/bound_partition.rs‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,6 @@ use crate::expr::traversal::TraversalOrder;
3232
/// Partition an expression into sub-expressions that are uniquely associated with an annotation.
3333
/// A root expression is also returned that can be used to recombine the results of the partitions
3434
/// into the result of the original expression.
35-
///
36-
/// ## Note
37-
///
38-
/// This function currently respects the validity of each field in the scope, but the not validity
39-
/// of the scope itself. The fix would be for the returned `BoundPartitionedExpr` to include a
40-
/// partition expression for computing the validity, or to include that expression as part of the
41-
/// root.
42-
///
43-
/// See <https://github.com/vortex-data/vortex/issues/1907>.
4435
pub fn partition_bound<A: AnnotationFn<BoundExpression>>(
4536
expr: BoundExpression,
4637
annotate_fn: A,

0 commit comments

Comments
 (0)