@@ -25,6 +25,8 @@ use crate::dtype::Nullability;
2525/// Per Arrow's spec, the per-row type tag is an `int8`. By default, tag `i` selects the child at
2626/// offset `i` (`type_ids = [0, 1, ..., N-1]`).
2727///
28+ /// Type IDs are restricted to the Arrow-compatible range `0..=127`; negative tags are rejected.
29+ ///
2830/// Schemas may also use non-consecutive tags (e.g. `[0, 5, 7]`), in which case the value of
2931/// `type_ids[i]` is the tag used in the data to select the child at offset `i`. Supporting
3032/// non-consecutive tags lets the schema remove children without renumbering the remaining tags.
@@ -179,6 +181,11 @@ impl UnionVariants {
179181 "type_ids must be distinct, got {:?}" ,
180182 type_ids
181183 ) ;
184+ vortex_ensure ! (
185+ type_ids. iter( ) . all( |type_id| * type_id >= 0 ) ,
186+ "type_ids must be non-negative, got {:?}" ,
187+ type_ids
188+ ) ;
182189 vortex_ensure ! (
183190 names. iter( ) . all_unique( ) ,
184191 "union variant names must be distinct, got {:?}" ,
@@ -193,7 +200,7 @@ impl UnionVariants {
193200 /// # Errors
194201 ///
195202 /// Returns an error if names, dtypes, or type IDs do not all have the same length, or if there
196- /// are any duplicate names or type ids.
203+ /// are any duplicate names or type ids, or if a type ID is negative .
197204 pub fn try_new ( names : FieldNames , dtypes : Vec < DType > , type_ids : Vec < i8 > ) -> VortexResult < Self > {
198205 Self :: validate_shape ( & names, dtypes. len ( ) , & type_ids) ?;
199206
@@ -237,7 +244,7 @@ impl UnionVariants {
237244 /// # Errors
238245 ///
239246 /// Returns an error if names, dtypes, or type IDs do not all have the same length, or if there
240- /// are any duplicate names or type ids.
247+ /// are any duplicate names or type ids, or if a type ID is negative .
241248 pub ( crate ) fn try_from_fields (
242249 names : FieldNames ,
243250 dtypes : Vec < FieldDType > ,
@@ -429,6 +436,17 @@ mod tests {
429436 assert ! ( result. unwrap_err( ) . to_string( ) . contains( "distinct" ) ) ;
430437 }
431438
439+ #[ test]
440+ fn test_negative_type_ids_rejected ( ) {
441+ let result = UnionVariants :: try_new (
442+ [ "negative" ] . into ( ) ,
443+ vec ! [ DType :: Primitive ( PType :: I32 , Nullability :: NonNullable ) ] ,
444+ vec ! [ -1 ] ,
445+ ) ;
446+ assert ! ( result. is_err( ) ) ;
447+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "non-negative" ) ) ;
448+ }
449+
432450 #[ test]
433451 fn test_length_mismatch_rejected ( ) {
434452 let result = UnionVariants :: try_new (
0 commit comments