@@ -7,6 +7,7 @@ use vortex_dtype::FieldName;
77use vortex_dtype:: FieldNames ;
88use vortex_dtype:: Nullability ;
99use vortex_dtype:: PType ;
10+ use vortex_scalar:: Scalar ;
1011
1112use crate :: Array ;
1213use crate :: IntoArray ;
@@ -150,3 +151,179 @@ fn test_uncompressed_size_in_bytes() {
150151 assert_eq ! ( canonical_size, 2 ) ;
151152 assert_eq ! ( uncompressed_size, Some ( 4000 ) ) ;
152153}
154+
155+ #[ test]
156+ fn test_push_validity_into_children_preserve_struct ( ) {
157+ // Create struct with top-level nulls
158+ // structArray : [a, b]
159+ // fields: [1, 2, 3] (a), [10, 20, 30] (b)
160+ // validity: [true, false, true]
161+ // row 1 is null at struct level
162+ let struct_array = StructArray :: try_new (
163+ [ "a" , "b" ] . into ( ) ,
164+ vec ! [
165+ buffer![ 1i32 , 2i32 , 3i32 ] . into_array( ) ,
166+ buffer![ 10i32 , 20i32 , 30i32 ] . into_array( ) ,
167+ ] ,
168+ 3 ,
169+ Validity :: from_iter ( [ true , false , true ] ) , // row 1 is null at struct level
170+ )
171+ . unwrap ( ) ;
172+
173+ // Push validity into children, preserving struct validity
174+ let pushed = struct_array. push_validity_into_children ( true ) . unwrap ( ) ;
175+
176+ // Check that struct validity is preserved
177+ assert_eq ! ( pushed. validity_mask( ) , struct_array. validity_mask( ) ) ;
178+
179+ // Check that children now have nulls where struct was null
180+ let field_a = pushed. fields ( ) [ 0 ] . as_ref ( ) ;
181+ let field_b = pushed. fields ( ) [ 1 ] . as_ref ( ) ;
182+
183+
184+ assert ! ( field_a. is_valid( 0 ) ) ;
185+ assert ! ( !field_a. is_valid( 1 ) ) ; // Should be null due to struct null
186+ assert ! ( field_a. is_valid( 2 ) ) ;
187+
188+ assert ! ( field_b. is_valid( 0 ) ) ;
189+ assert ! ( !field_b. is_valid( 1 ) ) ; // Should be null due to struct null
190+ assert ! ( field_b. is_valid( 2 ) ) ;
191+
192+
193+ // Original values should be preserved where valid
194+ assert_eq ! ( field_a. scalar_at( 0 ) , 1i32 . into( ) ) ;
195+ assert_eq ! ( field_a. scalar_at( 2 ) , 3i32 . into( ) ) ;
196+ assert_eq ! ( field_b. scalar_at( 0 ) , 10i32 . into( ) ) ;
197+ assert_eq ! ( field_b. scalar_at( 2 ) , 30i32 . into( ) ) ;
198+
199+
200+ // Verify pushed struct array values (preserve_struct_validity = true)
201+ assert ! ( pushed. is_valid( 0 ) ) ; // Row 0 should be valid
202+ assert ! ( !pushed. is_valid( 1 ) ) ; // Row 1 should be null (preserved)
203+ assert ! ( pushed. is_valid( 2 ) ) ; // Row 2 should be valid
204+
205+ // Row 0: {a: 1, b: 10} - should be valid struct with valid fields
206+ let row0 = pushed. scalar_at ( 0 ) ;
207+ assert ! ( row0. is_valid( ) ) ;
208+
209+ // Row 1: null - should be null struct (preserved from original)
210+ let row1 = pushed. scalar_at ( 1 ) ;
211+ assert ! ( !row1. is_valid( ) ) ;
212+
213+ // Row 2: {a: 3, b: 30} - should be valid struct with valid fields
214+ let row2 = pushed. scalar_at ( 2 ) ;
215+ assert ! ( row2. is_valid( ) ) ;
216+
217+ }
218+
219+ #[ test]
220+ fn test_push_validity_into_children_remove_struct ( ) {
221+
222+ // Create struct with top-level nulls
223+ let struct_array = StructArray :: try_new (
224+ [ "a" , "b" ] . into ( ) ,
225+ vec ! [
226+ buffer![ 1i32 , 2i32 , 3i32 ] . into_array( ) ,
227+ buffer![ 10i32 , 20i32 , 30i32 ] . into_array( ) ,
228+ ] ,
229+ 3 ,
230+ Validity :: from_iter ( [ true , false , true ] ) , // row 1 is null at struct level
231+ )
232+ . unwrap ( ) ;
233+
234+
235+ // Push validity into children, removing struct validity when default behavior is used (preserve_struct_validity = false)
236+ let pushed = struct_array. push_validity_into_children_default ( ) . unwrap ( ) ;
237+
238+
239+ // Check that struct validity is now AllValid
240+ assert ! ( pushed. validity_mask( ) . all_true( ) ) ;
241+
242+ // Check that children still have nulls where struct was null
243+ let field_a = pushed. fields ( ) [ 0 ] . as_ref ( ) ;
244+ let field_b = pushed. fields ( ) [ 1 ] . as_ref ( ) ;
245+
246+
247+ assert ! ( field_a. is_valid( 0 ) ) ;
248+ assert ! ( !field_a. is_valid( 1 ) ) ; // Should be null due to struct null
249+ assert ! ( field_a. is_valid( 2 ) ) ;
250+
251+ assert ! ( field_b. is_valid( 0 ) ) ;
252+ assert ! ( !field_b. is_valid( 1 ) ) ; // Should be null due to struct null
253+ assert ! ( field_b. is_valid( 2 ) ) ;
254+
255+ // Original values should be preserved where valid
256+ assert_eq ! ( field_a. scalar_at( 0 ) , 1i32 . into( ) ) ;
257+ assert_eq ! ( field_a. scalar_at( 2 ) , 3i32 . into( ) ) ;
258+ assert_eq ! ( field_b. scalar_at( 0 ) , 10i32 . into( ) ) ;
259+ assert_eq ! ( field_b. scalar_at( 2 ) , 30i32 . into( ) ) ;
260+
261+ // Verify null values using proper null scalar comparison
262+ use vortex_dtype:: { DType , Nullability , PType } ;
263+ let null_i32_scalar = Scalar :: null ( DType :: Primitive ( PType :: I32 , Nullability :: Nullable ) ) ;
264+ assert_eq ! ( field_a. scalar_at( 1 ) , null_i32_scalar) ;
265+ assert_eq ! ( field_b. scalar_at( 1 ) , null_i32_scalar) ;
266+
267+ // Alternative: check if the scalar is null
268+ assert ! ( !field_a. scalar_at( 1 ) . is_valid( ) ) ;
269+ assert ! ( !field_b. scalar_at( 1 ) . is_valid( ) ) ;
270+
271+ // Verify pushed struct array values (preserve_struct_validity = false)
272+ assert ! ( pushed. is_valid( 0 ) ) ; // Row 0 should be valid
273+ assert ! ( pushed. is_valid( 1 ) ) ; // Row 1 should be valid (validity removed)
274+ assert ! ( pushed. is_valid( 2 ) ) ; // Row 2 should be valid
275+
276+ // Row 0: {a: 1, b: 10} - should be valid struct with valid fields
277+ let row0 = pushed. scalar_at ( 0 ) ;
278+ assert ! ( row0. is_valid( ) ) ;
279+
280+ // Row 1: {a: null, b: null} - should be valid struct but with null fields
281+ let row1 = pushed. scalar_at ( 1 ) ;
282+ assert ! ( row1. is_valid( ) ) ; // Struct is valid, but fields are null
283+
284+ // Row 2: {a: 3, b: 30} - should be valid struct with valid fields
285+ let row2 = pushed. scalar_at ( 2 ) ;
286+ assert ! ( row2. is_valid( ) ) ;
287+
288+ }
289+
290+ #[ test]
291+ fn test_push_validity_into_children_no_nulls ( ) {
292+ // Create struct without any nulls
293+ let struct_array = StructArray :: try_new (
294+ [ "a" , "b" ] . into ( ) ,
295+ vec ! [
296+ buffer![ 1i32 , 2i32 , 3i32 ] . into_array( ) ,
297+ buffer![ 10i32 , 20i32 , 30i32 ] . into_array( ) ,
298+ ] ,
299+ 3 ,
300+ Validity :: AllValid ,
301+ )
302+ . unwrap ( ) ;
303+
304+
305+ // Push validity into children (should be no-op when preserve=true)
306+ let pushed_preserve = struct_array. push_validity_into_children ( true ) . unwrap ( ) ;
307+ assert_eq ! ( pushed_preserve. validity_mask( ) , struct_array. validity_mask( ) ) ;
308+
309+ // Push validity into children (should change validity to AllValid when preserve=false)
310+ let pushed_remove = struct_array. push_validity_into_children ( false ) . unwrap ( ) ;
311+ assert ! ( pushed_remove. validity_mask( ) . all_true( ) ) ;
312+
313+ // Fields should remain unchanged
314+ for i in 0 ..struct_array. fields ( ) . len ( ) {
315+ assert_eq ! (
316+ pushed_preserve. fields( ) [ i] . scalar_at( 0 ) ,
317+ struct_array. fields( ) [ i] . scalar_at( 0 )
318+ ) ;
319+ assert_eq ! (
320+ pushed_preserve. fields( ) [ i] . scalar_at( 1 ) ,
321+ struct_array. fields( ) [ i] . scalar_at( 1 )
322+ ) ;
323+ assert_eq ! (
324+ pushed_preserve. fields( ) [ i] . scalar_at( 2 ) ,
325+ struct_array. fields( ) [ i] . scalar_at( 2 )
326+ ) ;
327+ }
328+
329+ }
0 commit comments