1- //! Selectors for fields in (possibly nested) `StructDType `s
1+ //! Selectors for fields or elements in (possibly nested) `DType `s
22//!
3- //! A `Field` can either be a direct child field of the top-level struct (selected by name or index),
4- //! or a nested field (selected by a sequence of such selectors)
3+ //! A `Field` indexes a single layer of `DType`, for example: a name in a struct or the element of a
4+ //! list. A `FieldPath` indexes zero or more layers, for example: the field "child" which is within
5+ //! the struct field "parent" which is within the struct field "grandparent".
56
67use core:: fmt;
78use std:: fmt:: { Display , Formatter } ;
89use std:: sync:: Arc ;
910
1011use itertools:: Itertools ;
12+ use vortex_error:: { VortexResult , vortex_bail} ;
1113
12- /// A selector for a field in a struct
14+ use crate :: DType ;
15+
16+ /// Selects a nested type within either a struct or a list.
1317#[ derive( Clone , Debug , PartialEq , Eq , Hash ) ]
1418#[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
1519pub enum Field {
@@ -59,7 +63,19 @@ impl Field {
5963 }
6064}
6165
62- /// A path through a (possibly nested) struct, composed of a sequence of field selectors
66+ /// A sequence of field selectors representing a path through zero or more layers of `DType`.
67+ ///
68+ /// # Examples
69+ ///
70+ /// The empty path references the root:
71+ ///
72+ /// ```
73+ /// use vortex_dtype::*;
74+ ///
75+ /// let dtype_i32 = DType::Primitive(PType::I32, Nullability::NonNullable);
76+ /// assert_eq!(dtype_i32, FieldPath::root().resolve(dtype_i32.clone()).unwrap());
77+ /// ```
78+ ///
6379// TODO(ngates): we should probably reverse the path. Or better yet, store a Arc<[Field]> along
6480// with a positional index to allow cheap step_into.
6581#[ derive( Clone , Default , Debug , PartialEq , Eq , Hash ) ]
@@ -110,6 +126,62 @@ impl FieldPath {
110126 self . 0 = self . 0 . iter ( ) . skip ( 1 ) . cloned ( ) . collect ( ) ;
111127 Some ( self )
112128 }
129+
130+ /// The dtype, within the given type, to which this field path refers.
131+ ///
132+ /// Note that a nullable DType may contain a non-nullable DType. This function returns the
133+ /// literal nullability of the child.
134+ ///
135+ /// # Examples
136+ ///
137+ /// Extract the type of the "b" field from `struct{a: list(struct{b: u32})?}`:
138+ ///
139+ /// ```
140+ /// use std::sync::Arc;
141+ ///
142+ /// use vortex_dtype::*;
143+ /// use vortex_dtype::Nullability::*;
144+ ///
145+ /// let dtype = DType::Struct(
146+ /// Arc::new(StructFields::from_iter([(
147+ /// "a",
148+ /// DType::List(
149+ /// Arc::new(DType::Struct(
150+ /// Arc::new(StructFields::from_iter([(
151+ /// "b",
152+ /// DType::Primitive(PType::U32, NonNullable),
153+ /// )])),
154+ /// NonNullable,
155+ /// )),
156+ /// Nullable,
157+ /// ),
158+ /// )])),
159+ /// NonNullable,
160+ /// );
161+ ///
162+ /// let path = FieldPath::from(vec![Field::from("a"), Field::ElementType, Field::from("b")]);
163+ /// let resolved = path.resolve(dtype).unwrap();
164+ /// assert_eq!(resolved, DType::Primitive(PType::U32, NonNullable));
165+ /// ```
166+ pub fn resolve ( & self , mut dtype : DType ) -> VortexResult < DType > {
167+ for field in & self . 0 {
168+ dtype = match ( dtype, field) {
169+ ( DType :: Struct ( fields, _) , Field :: Name ( name) ) => fields. field ( name) ?,
170+ ( DType :: List ( element_dtype, _) , Field :: ElementType ) => DType :: clone ( & element_dtype) ,
171+ ( other, f) => {
172+ vortex_bail ! ( "FieldPath: invalid index {:?} for DType {:?}" , f, other)
173+ }
174+ }
175+ }
176+
177+ Ok ( dtype)
178+ }
179+
180+ /// Does the field referenced by the field path exist in the given dtype?
181+ pub fn exists ( & self , dtype : DType ) -> bool {
182+ // Indexing a struct type always allocates anyway.
183+ self . resolve ( dtype) . is_ok ( )
184+ }
113185}
114186
115187impl FromIterator < Field > for FieldPath {
@@ -139,6 +211,8 @@ impl Display for FieldPath {
139211#[ cfg( test) ]
140212mod tests {
141213 use super :: * ;
214+ use crate :: Nullability :: * ;
215+ use crate :: { DType , PType , StructFields } ;
142216
143217 #[ test]
144218 fn test_field_path ( ) {
@@ -155,4 +229,130 @@ mod tests {
155229 assert_eq ! ( vec_path. to_string( ) , "$A.$B.$C" ) ;
156230 assert_eq ! ( path, vec_path) ;
157231 }
232+
233+ #[ test]
234+ fn nested_field_single_level ( ) {
235+ let a_type = DType :: Primitive ( PType :: I32 , NonNullable ) ;
236+ let dtype = DType :: Struct (
237+ Arc :: from ( StructFields :: from_iter ( [
238+ ( "a" , a_type. clone ( ) ) ,
239+ ( "b" , DType :: Bool ( Nullable ) ) ,
240+ ] ) ) ,
241+ NonNullable ,
242+ ) ;
243+ let path = FieldPath :: from_name ( "a" ) ;
244+ assert_eq ! ( a_type, path. resolve( dtype. clone( ) ) . unwrap( ) ) ;
245+ assert ! ( path. exists( dtype) ) ;
246+ }
247+
248+ #[ test]
249+ fn nested_field_two_level ( ) {
250+ let inner = DType :: Struct (
251+ Arc :: new ( StructFields :: from_iter ( [
252+ ( "inner_a" , DType :: Primitive ( PType :: U8 , NonNullable ) ) ,
253+ ( "inner_b" , DType :: Bool ( Nullable ) ) ,
254+ ] ) ) ,
255+ NonNullable ,
256+ ) ;
257+
258+ let outer = DType :: Struct (
259+ Arc :: from ( StructFields :: from_iter ( [
260+ ( "outer_a" , DType :: Bool ( NonNullable ) ) ,
261+ ( "outer_b" , inner) ,
262+ ] ) ) ,
263+ NonNullable ,
264+ ) ;
265+
266+ let path = FieldPath :: from_name ( "outer_b" ) . push ( "inner_a" ) ;
267+ let dtype = path. resolve ( outer. clone ( ) ) . unwrap ( ) ;
268+
269+ assert_eq ! ( dtype, DType :: Primitive ( PType :: U8 , NonNullable ) ) ;
270+ assert ! ( path. exists( outer) ) ;
271+ }
272+
273+ #[ test]
274+ fn nested_field_deep_nested ( ) {
275+ let level4 = DType :: Struct (
276+ Arc :: new ( StructFields :: from_iter ( [ (
277+ "c" ,
278+ DType :: Primitive ( PType :: F64 , Nullable ) ,
279+ ) ] ) ) ,
280+ NonNullable ,
281+ ) ;
282+
283+ let level3 = DType :: List ( Arc :: from ( level4) , Nullable ) ;
284+
285+ let level2 = DType :: Struct (
286+ Arc :: new ( StructFields :: from_iter ( [ ( "b" , level3) ] ) ) ,
287+ NonNullable ,
288+ ) ;
289+
290+ let level1 = DType :: Struct (
291+ Arc :: from ( StructFields :: from_iter ( [ ( "a" , level2) ] ) ) ,
292+ NonNullable ,
293+ ) ;
294+
295+ let path = FieldPath :: from_name ( "a" )
296+ . push ( "b" )
297+ . push ( Field :: ElementType )
298+ . push ( "c" ) ;
299+ let dtype = path. resolve ( level1. clone ( ) ) . unwrap ( ) ;
300+
301+ assert_eq ! ( dtype, DType :: Primitive ( PType :: F64 , Nullable ) ) ;
302+ assert ! ( path. exists( level1. clone( ) ) ) ;
303+
304+ let path = FieldPath :: from_name ( "a" )
305+ . push ( "b" )
306+ . push ( "c" )
307+ . push ( Field :: ElementType ) ;
308+ assert ! ( path. resolve( level1. clone( ) ) . is_err( ) ) ;
309+ assert ! ( !path. exists( level1. clone( ) ) ) ;
310+
311+ let path = FieldPath :: from_name ( "a" )
312+ . push ( Field :: ElementType )
313+ . push ( "b" )
314+ . push ( "c" ) ;
315+ assert ! ( path. resolve( level1. clone( ) ) . is_err( ) ) ;
316+ assert ! ( !path. exists( level1. clone( ) ) ) ;
317+
318+ let path = FieldPath :: from_name ( Field :: ElementType )
319+ . push ( "a" )
320+ . push ( "b" )
321+ . push ( "c" ) ;
322+ assert ! ( path. resolve( level1. clone( ) ) . is_err( ) ) ;
323+ assert ! ( !path. exists( level1) ) ;
324+ }
325+
326+ #[ test]
327+ fn nested_field_not_found ( ) {
328+ let dtype = DType :: Struct (
329+ Arc :: from ( StructFields :: from_iter ( [ ( "a" , DType :: Bool ( NonNullable ) ) ] ) ) ,
330+ NonNullable ,
331+ ) ;
332+ let path = FieldPath :: from_name ( "b" ) ;
333+ assert ! ( path. resolve( dtype. clone( ) ) . is_err( ) ) ;
334+ assert ! ( !path. exists( dtype. clone( ) ) ) ;
335+
336+ let path = FieldPath :: from ( Field :: ElementType ) ;
337+ assert ! ( path. resolve( dtype. clone( ) ) . is_err( ) ) ;
338+ assert ! ( !path. exists( dtype) ) ;
339+ }
340+
341+ #[ test]
342+ fn nested_field_non_struct_intermediate ( ) {
343+ let dtype = DType :: Struct (
344+ Arc :: from ( StructFields :: from_iter ( [ (
345+ "a" ,
346+ DType :: Primitive ( PType :: I32 , NonNullable ) ,
347+ ) ] ) ) ,
348+ NonNullable ,
349+ ) ;
350+ let path = FieldPath :: from_name ( "a" ) . push ( "b" ) ;
351+ assert ! ( path. resolve( dtype. clone( ) ) . is_err( ) ) ;
352+ assert ! ( !path. exists( dtype. clone( ) ) ) ;
353+
354+ let path = FieldPath :: from_name ( "a" ) . push ( Field :: ElementType ) ;
355+ assert ! ( path. resolve( dtype. clone( ) ) . is_err( ) ) ;
356+ assert ! ( !path. exists( dtype) ) ;
357+ }
158358}
0 commit comments