@@ -8,6 +8,7 @@ use vortex_buffer::Buffer;
88use vortex_buffer:: buffer;
99use vortex_error:: VortexExpect ;
1010use vortex_error:: VortexResult ;
11+ use vortex_error:: vortex_err;
1112
1213use crate :: Canonical ;
1314use crate :: ExecutionCtx ;
@@ -23,13 +24,16 @@ use crate::arrays::ListViewArray;
2324use crate :: arrays:: NullArray ;
2425use crate :: arrays:: PrimitiveArray ;
2526use crate :: arrays:: StructArray ;
27+ use crate :: arrays:: UnionArray ;
2628use crate :: arrays:: VarBinViewArray ;
2729use crate :: arrays:: VariantArray ;
2830use crate :: arrays:: varbinview:: BinaryView ;
2931use crate :: builders:: builder_with_capacity;
3032use crate :: dtype:: DType ;
3133use crate :: dtype:: DecimalType ;
3234use crate :: dtype:: Nullability ;
35+ use crate :: dtype:: PType ;
36+ use crate :: dtype:: UnionVariants ;
3337use crate :: match_each_decimal_value;
3438use crate :: match_each_decimal_value_type;
3539use crate :: match_each_native_ptype;
@@ -146,16 +150,22 @@ pub(crate) fn constant_canonicalize(
146150 . collect ( ) ,
147151 None => {
148152 assert ! ( matches!( validity, Validity :: AllInvalid ) ) ;
149- // The struct is entirely null, so fields just need placeholder values with the
150- // correct dtype. We use `default_value` which returns a zero for non-nullable
151- // dtypes and null for nullable dtypes, preserving each field's nullability.
152153 struct_dtype
153154 . fields ( )
154155 . map ( |dt| {
155- let scalar = Scalar :: default_value ( & dt) ;
156- ConstantArray :: new ( scalar, array. len ( ) ) . into_array ( )
156+ if array. is_empty ( ) {
157+ return Ok ( Canonical :: empty ( & dt) . into_array ( ) ) ;
158+ }
159+
160+ let scalar = try_placeholder_scalar ( & dt) . ok_or_else ( || {
161+ vortex_err ! (
162+ "cannot canonicalize null constant struct: field dtype {dt} \
163+ has no placeholder value"
164+ )
165+ } ) ?;
166+ Ok ( ConstantArray :: new ( scalar, array. len ( ) ) . into_array ( ) )
157167 } )
158- . collect ( )
168+ . collect :: < VortexResult < Vec < _ > > > ( ) ?
159169 }
160170 } ;
161171 // SAFETY: Fields are constructed from the same struct scalar, all have same
@@ -164,7 +174,12 @@ pub(crate) fn constant_canonicalize(
164174 StructArray :: new_unchecked ( fields, struct_dtype. clone ( ) , array. len ( ) , validity)
165175 } )
166176 }
167- DType :: Union ( ..) => todo ! ( "TODO(connor)[Union]: unimplemented" ) ,
177+ DType :: Union ( variants, nullability) => Canonical :: Union ( constant_canonical_union (
178+ scalar,
179+ variants,
180+ * nullability,
181+ array. len ( ) ,
182+ ) ?) ,
168183 DType :: Variant ( _) => Canonical :: Variant ( VariantArray :: try_new (
169184 array. array ( ) . clone ( ) . into_array ( ) ,
170185 None ,
@@ -188,6 +203,97 @@ pub(crate) fn constant_canonicalize(
188203 } )
189204}
190205
206+ fn constant_canonical_union (
207+ scalar : & Scalar ,
208+ variants : & UnionVariants ,
209+ nullability : Nullability ,
210+ len : usize ,
211+ ) -> VortexResult < UnionArray > {
212+ if len == 0 {
213+ return Ok ( UnionArray :: empty ( variants. clone ( ) , nullability) ) ;
214+ }
215+
216+ let union = scalar. as_union ( ) ;
217+ let selected = union. child_index ( ) . zip ( union. child ( ) ) ;
218+
219+ let children = variants
220+ . variants ( )
221+ . enumerate ( )
222+ . map ( |( index, dtype) | {
223+ let value = match & selected {
224+ Some ( ( selected_child, selected_value) ) if index == * selected_child => {
225+ selected_value. clone ( )
226+ }
227+ _ => try_placeholder_scalar ( & dtype) . ok_or_else ( || {
228+ vortex_err ! (
229+ "cannot canonicalize constant union: unselected variant {} ({dtype}) has \
230+ no placeholder value",
231+ variants. names( ) [ index]
232+ )
233+ } ) ?,
234+ } ;
235+ Ok ( ConstantArray :: new ( value, len) . into_array ( ) )
236+ } )
237+ . collect :: < VortexResult < Vec < _ > > > ( ) ?;
238+
239+ let type_id = match union. type_id ( ) {
240+ Some ( type_id) => Scalar :: primitive ( type_id, nullability) ,
241+ None => Scalar :: null ( DType :: Primitive ( PType :: U8 , Nullability :: Nullable ) ) ,
242+ } ;
243+
244+ UnionArray :: try_new (
245+ ConstantArray :: new ( type_id, len) . into_array ( ) ,
246+ variants. clone ( ) ,
247+ children,
248+ )
249+ }
250+
251+ /// Builds an arbitrary valid scalar for array positions ignored by a parent validity or type ID.
252+ /// This does not give the dtype a semantic default value.
253+ fn try_placeholder_scalar ( dtype : & DType ) -> Option < Scalar > {
254+ if let Some ( default) = Scalar :: try_default_value ( dtype) {
255+ return Some ( default) ;
256+ }
257+
258+ match dtype {
259+ DType :: FixedSizeList ( element_dtype, size, nullability) => {
260+ let element = try_placeholder_scalar ( element_dtype) ?;
261+ Some ( Scalar :: fixed_size_list (
262+ Arc :: clone ( element_dtype) ,
263+ vec ! [ element; * size as usize ] ,
264+ * nullability,
265+ ) )
266+ }
267+ DType :: Struct ( fields, _) => {
268+ let children = fields
269+ . fields ( )
270+ . map ( |field| try_placeholder_scalar ( & field) )
271+ . collect :: < Option < Vec < _ > > > ( ) ?;
272+ Some ( Scalar :: struct_ ( dtype. clone ( ) , children) )
273+ }
274+ DType :: Union ( variants, nullability) => {
275+ variants
276+ . variants ( )
277+ . enumerate ( )
278+ . find_map ( |( index, child_dtype) | {
279+ let child = try_placeholder_scalar ( & child_dtype) ?;
280+ Scalar :: union (
281+ variants. clone ( ) ,
282+ variants. child_index_to_tag ( index) ,
283+ child,
284+ * nullability,
285+ )
286+ . ok ( )
287+ } )
288+ }
289+ DType :: Extension ( ext_dtype) => {
290+ let storage = try_placeholder_scalar ( ext_dtype. storage_dtype ( ) ) ?;
291+ Some ( Scalar :: extension_ref ( ext_dtype. clone ( ) , storage) )
292+ }
293+ _ => None ,
294+ }
295+ }
296+
191297fn constant_canonical_byte_view (
192298 scalar_bytes : Option < & [ u8 ] > ,
193299 dtype : & DType ,
0 commit comments