@@ -8,14 +8,12 @@ use arrow_schema::Fields;
88use arrow_schema:: extension:: ExtensionType ;
99use arrow_schema:: { DataType , Field } ;
1010use datafusion:: common:: exec_datafusion_err;
11- use datafusion:: error:: Result ;
11+ use datafusion:: error:: { DataFusionError , Result } ;
1212use datafusion:: logical_expr:: { ColumnarValue , ScalarFunctionArgs } ;
1313use datafusion:: { common:: exec_err, scalar:: ScalarValue } ;
14- use parquet_variant:: Variant ;
14+ use parquet_variant:: { Variant , VariantPath , VariantPathElement } ;
1515use parquet_variant_compute:: { VariantArray , VariantType } ;
1616
17- use crate :: variant_get:: PathMode ;
18-
1917#[ cfg( test) ]
2018use parquet_variant_compute:: VariantArrayBuilder ;
2119
@@ -129,16 +127,14 @@ pub fn try_parse_string_columnar(array: &Arc<dyn Array>) -> Result<Vec<Option<&s
129127pub fn variant_get_single_value < T > (
130128 variant_array : & VariantArray ,
131129 index : usize ,
132- path : & str ,
133- path_mode : PathMode ,
130+ path : & VariantPath < ' _ > ,
134131 extract : for <' m , ' v > fn ( Variant < ' m , ' v > ) -> Result < Option < T > > ,
135132) -> Result < Option < T > > {
136133 let Some ( variant) = variant_array. iter ( ) . nth ( index) . flatten ( ) else {
137134 return Ok ( None ) ;
138135 } ;
139136
140- let variant_path = path_mode. try_build_path ( path) ?;
141- let Some ( value) = variant. get_path ( & variant_path) else {
137+ let Some ( value) = variant. get_path ( path) else {
142138 return Ok ( None ) ;
143139 } ;
144140
@@ -147,20 +143,17 @@ pub fn variant_get_single_value<T>(
147143
148144pub fn variant_get_array_values < T > (
149145 variant_array : & VariantArray ,
150- path : & str ,
151- path_mode : PathMode ,
146+ path : & VariantPath < ' _ > ,
152147 extract : for <' m , ' v > fn ( Variant < ' m , ' v > ) -> Result < Option < T > > ,
153148) -> Result < Vec < Option < T > > > {
154- let variant_path = path_mode. try_build_path ( path) ?;
155-
156149 variant_array
157150 . iter ( )
158151 . map ( |maybe_variant| {
159152 let Some ( variant) = maybe_variant else {
160153 return Ok ( None ) ;
161154 } ;
162155
163- let Some ( value) = variant. get_path ( & variant_path ) else {
156+ let Some ( value) = variant. get_path ( path ) else {
164157 return Ok ( None ) ;
165158 } ;
166159
@@ -169,12 +162,66 @@ pub fn variant_get_array_values<T>(
169162 . collect ( )
170163}
171164
165+ /// Build a [`VariantPath`] from a scalar value.
166+ ///
167+ /// - **String** scalars use dot-notation parsing (e.g. `'a.b.c'` → path `[a, b, c]`)
168+ /// - **List** scalars treat each element as a single field name
169+ /// (e.g. `['a.b', 'c']` → path `[a.b, c]`), which is critical for keys that
170+ /// contain dots such as OTEL attribute keys like `http.response.status_code`.
171+ fn path_from_scalar ( scalar : & ScalarValue ) -> Result < VariantPath < ' static > > {
172+ match scalar {
173+ ScalarValue :: Utf8 ( Some ( s) )
174+ | ScalarValue :: Utf8View ( Some ( s) )
175+ | ScalarValue :: LargeUtf8 ( Some ( s) ) => {
176+ let parsed =
177+ VariantPath :: try_from ( s. as_str ( ) ) . map_err ( Into :: < DataFusionError > :: into) ?;
178+ Ok ( to_owned_path ( & parsed) )
179+ }
180+ ScalarValue :: Utf8 ( None ) | ScalarValue :: Utf8View ( None ) | ScalarValue :: LargeUtf8 ( None ) => {
181+ Ok ( VariantPath :: default ( ) )
182+ }
183+ ScalarValue :: List ( list_arr) => {
184+ if list_arr. is_null ( 0 ) {
185+ return Ok ( VariantPath :: default ( ) ) ;
186+ }
187+
188+ path_from_list_values ( list_arr. value ( 0 ) )
189+ }
190+ other => exec_err ! (
191+ "path must be a string or list of strings, got {}" ,
192+ other. data_type( )
193+ ) ,
194+ }
195+ }
196+
197+ fn path_from_list_values ( values : ArrayRef ) -> Result < VariantPath < ' static > > {
198+ let strings = try_parse_string_columnar ( & values) ?;
199+ let elements = strings
200+ . iter ( )
201+ . map ( |s| VariantPathElement :: field ( s. unwrap_or_default ( ) . to_string ( ) ) )
202+ . collect ( ) ;
203+
204+ Ok ( VariantPath :: new ( elements) )
205+ }
206+
207+ fn to_owned_path ( path : & VariantPath < ' _ > ) -> VariantPath < ' static > {
208+ let elements = path
209+ . path ( )
210+ . iter ( )
211+ . map ( |elem| match elem {
212+ VariantPathElement :: Field { name } => VariantPathElement :: field ( name. to_string ( ) ) ,
213+ VariantPathElement :: Index { index } => VariantPathElement :: index ( * index) ,
214+ } )
215+ . collect ( ) ;
216+
217+ VariantPath :: new ( elements)
218+ }
219+
172220pub fn invoke_variant_get_typed < T > (
173221 args : ScalarFunctionArgs ,
174222 scalar_from_option : fn ( Option < T > ) -> ScalarValue ,
175223 array_from_values : fn ( Vec < Option < T > > ) -> ArrayRef ,
176224 extract : for <' m , ' v > fn ( Variant < ' m , ' v > ) -> Result < Option < T > > ,
177- path_mode : PathMode ,
178225) -> Result < ColumnarValue > {
179226 let ( variant_arg, path_arg) = match args. args . as_slice ( ) {
180227 [ variant_arg, path_arg] => ( variant_arg, path_arg) ,
@@ -190,25 +237,19 @@ pub fn invoke_variant_get_typed<T>(
190237
191238 let out = match ( variant_arg, path_arg) {
192239 ( ColumnarValue :: Array ( variant_array) , ColumnarValue :: Scalar ( path_scalar) ) => {
193- let path = try_parse_string_scalar ( path_scalar) ?
194- . map ( |s| s. as_str ( ) )
195- . unwrap_or_default ( ) ;
196-
240+ let path = path_from_scalar ( path_scalar) ?;
197241 let variant_array = VariantArray :: try_new ( variant_array. as_ref ( ) ) ?;
198- let values = variant_get_array_values ( & variant_array, path, path_mode , extract) ?;
242+ let values = variant_get_array_values ( & variant_array, & path, extract) ?;
199243 ColumnarValue :: Array ( array_from_values ( values) )
200244 }
201245 ( ColumnarValue :: Scalar ( scalar_variant) , ColumnarValue :: Scalar ( path_scalar) ) => {
202246 let ScalarValue :: Struct ( variant_array) = scalar_variant else {
203247 return exec_err ! ( "expected struct array" ) ;
204248 } ;
205249
206- let path = try_parse_string_scalar ( path_scalar) ?
207- . map ( |s| s. as_str ( ) )
208- . unwrap_or_default ( ) ;
209-
250+ let path = path_from_scalar ( path_scalar) ?;
210251 let variant_array = VariantArray :: try_new ( variant_array. as_ref ( ) ) ?;
211- let value = variant_get_single_value ( & variant_array, 0 , path, path_mode , extract) ?;
252+ let value = variant_get_single_value ( & variant_array, 0 , & path, extract) ?;
212253
213254 ColumnarValue :: Scalar ( scalar_from_option ( value) )
214255 }
@@ -220,10 +261,13 @@ pub fn invoke_variant_get_typed<T>(
220261 let paths = try_parse_string_columnar ( paths) ?;
221262 let variant_array = VariantArray :: try_new ( variant_array. as_ref ( ) ) ?;
222263
223- let values: Vec < Option < T > > = ( 0 ..variant_array. len ( ) )
264+ let values = ( 0 ..variant_array. len ( ) )
224265 . map ( |i| {
225- let path = paths[ i] . unwrap_or_default ( ) ;
226- variant_get_single_value ( & variant_array, i, path, path_mode, extract)
266+ let path_str = paths[ i] . unwrap_or_default ( ) ;
267+ let path =
268+ VariantPath :: try_from ( path_str) . map_err ( Into :: < DataFusionError > :: into) ?;
269+
270+ variant_get_single_value ( & variant_array, i, & path, extract)
227271 } )
228272 . collect :: < Result < _ > > ( ) ?;
229273
@@ -237,11 +281,13 @@ pub fn invoke_variant_get_typed<T>(
237281 let variant_array = VariantArray :: try_new ( variant_array. as_ref ( ) ) ?;
238282 let paths = try_parse_string_columnar ( paths) ?;
239283
240- let values: Vec < Option < T > > = paths
284+ let values = paths
241285 . iter ( )
242- . map ( |path| {
243- let path = path. unwrap_or_default ( ) ;
244- variant_get_single_value ( & variant_array, 0 , path, path_mode, extract)
286+ . map ( |path_str| {
287+ let path_str = path_str. unwrap_or_default ( ) ;
288+ let path =
289+ VariantPath :: try_from ( path_str) . map_err ( Into :: < DataFusionError > :: into) ?;
290+ variant_get_single_value ( & variant_array, 0 , & path, extract)
245291 } )
246292 . collect :: < Result < _ > > ( ) ?;
247293
0 commit comments