@@ -8,7 +8,7 @@ use crate::ArrayRef;
88use crate :: ExecutionCtx ;
99use crate :: aggregate_fn:: AggregateFnRef ;
1010use crate :: aggregate_fn:: fns:: min_max:: MinMax ;
11- use crate :: aggregate_fn:: fns:: min_max:: make_struct_dtype ;
11+ use crate :: aggregate_fn:: fns:: min_max:: make_minmax_dtype ;
1212use crate :: aggregate_fn:: fns:: min_max:: min_max;
1313use crate :: aggregate_fn:: kernels:: DynAggregateKernel ;
1414use crate :: arrays:: Dict ;
@@ -36,7 +36,7 @@ impl DynAggregateKernel for DictMinMaxKernel {
3636 return Ok ( None ) ;
3737 } ;
3838
39- let struct_dtype = make_struct_dtype ( batch. dtype ( ) ) ;
39+ let struct_dtype = make_minmax_dtype ( batch. dtype ( ) ) ;
4040
4141 let result = if dict. has_all_values_referenced ( ) {
4242 // All values are referenced, compute min/max directly on the values array.
@@ -55,3 +55,103 @@ impl DynAggregateKernel for DictMinMaxKernel {
5555 }
5656 }
5757}
58+
59+ #[ cfg( test) ]
60+ mod tests {
61+ use rstest:: rstest;
62+ use vortex_buffer:: buffer;
63+ use vortex_error:: VortexResult ;
64+
65+ use crate :: ArrayRef ;
66+ use crate :: IntoArray ;
67+ use crate :: LEGACY_SESSION ;
68+ use crate :: VortexSessionExecute ;
69+ use crate :: aggregate_fn:: fns:: min_max:: min_max;
70+ use crate :: arrays:: DictArray ;
71+ use crate :: arrays:: PrimitiveArray ;
72+ use crate :: builders:: dict:: dict_encode;
73+
74+ fn assert_min_max ( array : & ArrayRef , expected : Option < ( i32 , i32 ) > ) -> VortexResult < ( ) > {
75+ let mut ctx = LEGACY_SESSION . create_execution_ctx ( ) ;
76+ match ( min_max ( array, & mut ctx) ?, expected) {
77+ ( Some ( result) , Some ( ( expected_min, expected_max) ) ) => {
78+ assert_eq ! ( i32 :: try_from( & result. min) ?, expected_min) ;
79+ assert_eq ! ( i32 :: try_from( & result. max) ?, expected_max) ;
80+ }
81+ ( None , None ) => { }
82+ ( got, expected) => panic ! (
83+ "min_max mismatch: expected {expected:?}, got {:?}" ,
84+ got. as_ref( ) . map( |r| (
85+ i32 :: try_from( & r. min. clone( ) ) . ok( ) ,
86+ i32 :: try_from( & r. max. clone( ) ) . ok( )
87+ ) )
88+ ) ,
89+ }
90+ Ok ( ( ) )
91+ }
92+
93+ #[ rstest]
94+ #[ case:: covering(
95+ DictArray :: try_new(
96+ buffer![ 0u32 , 1 , 2 , 3 , 0 , 1 ] . into_array( ) ,
97+ buffer![ 10i32 , 20 , 30 , 40 ] . into_array( ) ,
98+ ) . unwrap( ) ,
99+ ( 10 , 40 )
100+ ) ]
101+ #[ case:: non_covering_duplicates(
102+ DictArray :: try_new(
103+ buffer![ 1u32 , 1 , 1 , 3 , 3 ] . into_array( ) ,
104+ buffer![ 1i32 , 2 , 3 , 4 , 5 ] . into_array( ) ,
105+ ) . unwrap( ) ,
106+ ( 2 , 4 )
107+ ) ]
108+ #[ case:: non_covering_gaps(
109+ DictArray :: try_new(
110+ buffer![ 0u32 , 2 , 4 ] . into_array( ) ,
111+ buffer![ 1i32 , 2 , 3 , 4 , 5 ] . into_array( ) ,
112+ ) . unwrap( ) ,
113+ ( 1 , 5 )
114+ ) ]
115+ #[ case:: single( dict_encode( & buffer![ 42i32 ] . into_array( ) ) . unwrap( ) , ( 42 , 42 ) ) ]
116+ #[ case:: nullable_codes(
117+ DictArray :: try_new(
118+ PrimitiveArray :: from_option_iter( [ Some ( 0u32 ) , None , Some ( 1 ) , Some ( 2 ) ] ) . into_array( ) ,
119+ buffer![ 10i32 , 20 , 30 ] . into_array( ) ,
120+ ) . unwrap( ) ,
121+ ( 10 , 30 )
122+ ) ]
123+ #[ case:: nullable_values(
124+ dict_encode(
125+ & PrimitiveArray :: from_option_iter( [ Some ( 1i32 ) , None , Some ( 2 ) , Some ( 1 ) , None ] ) . into_array( )
126+ ) . unwrap( ) ,
127+ ( 1 , 2 )
128+ ) ]
129+ fn test_min_max ( #[ case] dict : DictArray , #[ case] expected : ( i32 , i32 ) ) -> VortexResult < ( ) > {
130+ assert_min_max ( & dict. into_array ( ) , Some ( expected) )
131+ }
132+
133+ #[ test]
134+ fn test_sliced_dict ( ) -> VortexResult < ( ) > {
135+ let reference = PrimitiveArray :: from_iter ( [ 1 , 5 , 10 , 50 , 100 ] ) ;
136+ let dict = dict_encode ( & reference. into_array ( ) ) ?;
137+ let sliced = dict. slice ( 1 ..3 ) ?;
138+ assert_min_max ( & sliced, Some ( ( 5 , 10 ) ) )
139+ }
140+
141+ #[ rstest]
142+ #[ case:: empty(
143+ DictArray :: try_new(
144+ PrimitiveArray :: from_iter( Vec :: <u32 >:: new( ) ) . into_array( ) ,
145+ buffer![ 10i32 , 20 , 30 ] . into_array( ) ,
146+ ) . unwrap( )
147+ ) ]
148+ #[ case:: all_null_codes(
149+ DictArray :: try_new(
150+ PrimitiveArray :: from_option_iter( [ Option :: <u32 >:: None , None , None ] ) . into_array( ) ,
151+ buffer![ 10i32 , 20 , 30 ] . into_array( ) ,
152+ ) . unwrap( )
153+ ) ]
154+ fn test_min_max_none ( #[ case] dict : DictArray ) -> VortexResult < ( ) > {
155+ assert_min_max ( & dict. into_array ( ) , None )
156+ }
157+ }
0 commit comments