@@ -24,15 +24,16 @@ use arrow::array::{
2424 StringArrayType , StringViewArray ,
2525} ;
2626use arrow:: buffer:: { NullBuffer , ScalarBuffer } ;
27- use arrow:: datatypes:: DataType ;
27+ use arrow:: datatypes:: { DataType , Field , FieldRef } ;
2828use datafusion_common:: cast:: as_int64_array;
2929use datafusion_common:: types:: {
3030 NativeType , logical_int32, logical_int64, logical_string,
3131} ;
3232use datafusion_common:: { Result , exec_err} ;
3333use datafusion_expr:: {
34- Coercion , ColumnarValue , Documentation , ScalarFunctionArgs , ScalarUDFImpl , Signature ,
35- TypeSignature , TypeSignatureClass , Volatility ,
34+ Coercion , ColumnarValue , Documentation , EncodingPreservation , ReturnFieldArgs ,
35+ ScalarFunctionArgs , ScalarUDFImpl , Signature , TypeSignature , TypeSignatureClass ,
36+ Volatility ,
3637} ;
3738use datafusion_macros:: user_doc;
3839
@@ -73,7 +74,8 @@ impl Default for SubstrFunc {
7374
7475impl SubstrFunc {
7576 pub fn new ( ) -> Self {
76- let string = Coercion :: new_exact ( TypeSignatureClass :: Native ( logical_string ( ) ) ) ;
77+ let string = Coercion :: new_exact ( TypeSignatureClass :: Native ( logical_string ( ) ) )
78+ . with_encoding_preservation ( EncodingPreservation :: dictionary ( ) ) ;
7779 let int64 = Coercion :: new_implicit (
7880 TypeSignatureClass :: Native ( logical_int64 ( ) ) ,
7981 vec ! [ TypeSignatureClass :: Native ( logical_int32( ) ) ] ,
@@ -115,8 +117,51 @@ impl ScalarUDFImpl for SubstrFunc {
115117 Ok ( arg_types[ 0 ] . clone ( ) )
116118 }
117119
120+ fn return_field_from_args ( & self , args : ReturnFieldArgs ) -> Result < FieldRef > {
121+ // Per-row start/length materializes the dict, so the output type is the
122+ // value type; scalar tail keeps the dict wrapper.
123+ let input = & args. arg_fields [ 0 ] ;
124+ let output_type = match input. data_type ( ) {
125+ DataType :: Dictionary ( _, value_type)
126+ if !args. scalar_arguments . iter ( ) . skip ( 1 ) . all ( Option :: is_some) =>
127+ {
128+ value_type. as_ref ( ) . clone ( )
129+ }
130+ other => other. clone ( ) ,
131+ } ;
132+ Ok ( Field :: new ( self . name ( ) , output_type, input. is_nullable ( ) ) . into ( ) )
133+ }
134+
118135 fn invoke_with_args ( & self , args : ScalarFunctionArgs ) -> Result < ColumnarValue > {
119- make_scalar_function ( substr, vec ! [ ] ) ( & args. args )
136+ if let ColumnarValue :: Array ( array) = & args. args [ 0 ]
137+ && matches ! ( array. data_type( ) , DataType :: Dictionary ( _, _) )
138+ && args. args [ 1 ..]
139+ . iter ( )
140+ . all ( |v| matches ! ( v, ColumnarValue :: Scalar ( _) ) )
141+ {
142+ // Fast path: substr the N_distinct values, reuse the keys.
143+ let dict = array. as_any_dictionary ( ) ;
144+ let values = Arc :: clone ( dict. values ( ) ) ;
145+ let mut inner = Vec :: with_capacity ( args. args . len ( ) ) ;
146+ inner. push ( ColumnarValue :: Array ( values) ) ;
147+ inner. extend_from_slice ( & args. args [ 1 ..] ) ;
148+ let new_values = make_scalar_function ( substr, vec ! [ ] ) ( & inner) ?
149+ . into_array ( dict. values ( ) . len ( ) ) ?;
150+ return Ok ( ColumnarValue :: Array ( dict. with_values ( new_values) ) ) ;
151+ }
152+
153+ // Per-row start/length: materialize the dict so substr can run row-wise.
154+ let args = if let ColumnarValue :: Array ( array) = & args. args [ 0 ]
155+ && let DataType :: Dictionary ( _, value_type) = array. data_type ( )
156+ {
157+ let flat = arrow:: compute:: kernels:: cast:: cast ( array, value_type. as_ref ( ) ) ?;
158+ let mut new_args = args. args . clone ( ) ;
159+ new_args[ 0 ] = ColumnarValue :: Array ( flat) ;
160+ new_args
161+ } else {
162+ args. args
163+ } ;
164+ make_scalar_function ( substr, vec ! [ ] ) ( & args)
120165 }
121166
122167 fn aliases ( & self ) -> & [ String ] {
@@ -354,13 +399,15 @@ mod tests {
354399 use std:: sync:: Arc ;
355400
356401 use arrow:: array:: {
357- Array , ArrayRef , AsArray , Int64Array , LargeStringArray , StringArray ,
358- StringViewArray ,
402+ Array , ArrayRef , AsArray , DictionaryArray , Int64Array , LargeStringArray ,
403+ StringArray , StringViewArray ,
359404 } ;
360405 use arrow:: datatypes:: DataType :: { LargeUtf8 , Utf8 , Utf8View } ;
406+ use arrow:: datatypes:: { DataType , Field , Int32Type } ;
361407
408+ use datafusion_common:: config:: ConfigOptions ;
362409 use datafusion_common:: { Result , ScalarValue , exec_err} ;
363- use datafusion_expr:: { ColumnarValue , ScalarUDFImpl } ;
410+ use datafusion_expr:: { ColumnarValue , ScalarFunctionArgs , ScalarUDFImpl } ;
364411
365412 use crate :: unicode:: substr:: SubstrFunc ;
366413 use crate :: utils:: test:: test_function;
@@ -766,4 +813,40 @@ mod tests {
766813
767814 Ok ( ( ) )
768815 }
816+
817+ #[ test]
818+ fn test_substr_dict_fast_path_preserves_encoding ( ) -> Result < ( ) > {
819+ let dict_type = DataType :: Dictionary ( Box :: new ( DataType :: Int32 ) , Box :: new ( Utf8 ) ) ;
820+ let keys = arrow:: array:: Int32Array :: from ( vec ! [ 0 , 1 , 0 , 2 , 1 ] ) ;
821+ let values = StringArray :: from ( vec ! [ "silver" , "copper" , "golden" ] ) ;
822+ let dict: ArrayRef = Arc :: new (
823+ DictionaryArray :: < Int32Type > :: try_new ( keys, Arc :: new ( values) ) . unwrap ( ) ,
824+ ) ;
825+
826+ let result = SubstrFunc :: new ( )
827+ . invoke_with_args ( ScalarFunctionArgs {
828+ args : vec ! [
829+ ColumnarValue :: Array ( dict) ,
830+ ColumnarValue :: Scalar ( ScalarValue :: from( 1i64 ) ) ,
831+ ColumnarValue :: Scalar ( ScalarValue :: from( 3i64 ) ) ,
832+ ] ,
833+ arg_fields : vec ! [
834+ Arc :: new( Field :: new( "s" , dict_type. clone( ) , true ) ) ,
835+ Arc :: new( Field :: new( "start" , DataType :: Int64 , false ) ) ,
836+ Arc :: new( Field :: new( "len" , DataType :: Int64 , false ) ) ,
837+ ] ,
838+ number_rows : 5 ,
839+ return_field : Arc :: new ( Field :: new ( "r" , dict_type, true ) ) ,
840+ config_options : Arc :: new ( ConfigOptions :: default ( ) ) ,
841+ } ) ?
842+ . into_array ( 5 ) ?;
843+
844+ assert ! ( matches!( result. data_type( ) , DataType :: Dictionary ( _, v) if * * v == Utf8 ) ) ;
845+ let out = result. as_dictionary :: < Int32Type > ( ) ;
846+ let v = out. values ( ) . as_string :: < i32 > ( ) ;
847+ assert_eq ! ( v. len( ) , 3 ) ;
848+ assert_eq ! ( ( v. value( 0 ) , v. value( 1 ) , v. value( 2 ) ) , ( "sil" , "cop" , "gol" ) ) ;
849+
850+ Ok ( ( ) )
851+ }
769852}
0 commit comments