33
44use std:: fmt:: Display ;
55use std:: fmt:: Formatter ;
6+ use std:: num:: NonZeroUsize ;
67
78use vortex_buffer:: BufferString ;
89use vortex_buffer:: ByteBuffer ;
@@ -30,12 +31,12 @@ use crate::scalar::upper_bound;
3031#[ derive( Clone , Debug , PartialEq , Eq , Hash ) ]
3132pub struct BoundedMaxOptions {
3233 /// Maximum byte length for UTF8/Binary bounds.
33- pub max_bytes : usize ,
34+ pub max_bytes : NonZeroUsize ,
3435}
3536
3637impl Display for BoundedMaxOptions {
3738 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
38- write ! ( f, "{}" , self . max_bytes)
39+ write ! ( f, "{}" , self . max_bytes. get ( ) )
3940 }
4041}
4142
@@ -53,7 +54,7 @@ enum BoundedMaxState {
5354pub struct BoundedMaxPartial {
5455 state : BoundedMaxState ,
5556 element_dtype : DType ,
56- max_bytes : usize ,
57+ max_bytes : NonZeroUsize ,
5758}
5859
5960impl BoundedMaxPartial {
@@ -86,7 +87,7 @@ impl AggregateFnVTable for BoundedMax {
8687 }
8788
8889 fn serialize ( & self , options : & Self :: Options ) -> VortexResult < Option < Vec < u8 > > > {
89- let max_bytes = u64:: try_from ( options. max_bytes ) ?;
90+ let max_bytes = u64:: try_from ( options. max_bytes . get ( ) ) ?;
9091 Ok ( Some ( max_bytes. to_le_bytes ( ) . to_vec ( ) ) )
9192 }
9293
@@ -105,7 +106,9 @@ impl AggregateFnVTable for BoundedMax {
105106 bytes. copy_from_slice ( metadata) ;
106107 let max_bytes = usize:: try_from ( u64:: from_le_bytes ( bytes) ) ?;
107108 vortex_ensure ! ( max_bytes > 0 , "BoundedMax requires max_bytes > 0" ) ;
108- Ok ( BoundedMaxOptions { max_bytes } )
109+ Ok ( BoundedMaxOptions {
110+ max_bytes : NonZeroUsize :: new ( max_bytes) . vortex_expect ( "checked non-zero max_bytes" ) ,
111+ } )
109112 }
110113
111114 fn return_dtype ( & self , options : & Self :: Options , input_dtype : & DType ) -> Option < DType > {
@@ -162,7 +165,7 @@ impl AggregateFnVTable for BoundedMax {
162165 let Some ( result) = min_max ( & array, ctx) ? else {
163166 return Ok ( ( ) ) ;
164167 } ;
165- match truncate_max ( result. max , partial. max_bytes ) ? {
168+ match truncate_max ( result. max , partial. max_bytes . get ( ) ) ? {
166169 Some ( bound) => partial. merge ( bound) ,
167170 None => partial. unknown ( ) ,
168171 }
@@ -178,11 +181,7 @@ impl AggregateFnVTable for BoundedMax {
178181 }
179182}
180183
181- fn supported_dtype < ' a > ( options : & BoundedMaxOptions , input_dtype : & ' a DType ) -> Option < & ' a DType > {
182- if options. max_bytes == 0 {
183- return None ;
184- }
185-
184+ fn supported_dtype < ' a > ( _options : & BoundedMaxOptions , input_dtype : & ' a DType ) -> Option < & ' a DType > {
186185 MinMax
187186 . return_dtype ( & EmptyOptions , input_dtype)
188187 . map ( |_| input_dtype)
@@ -209,7 +208,10 @@ fn truncate_max(value: Scalar, max_bytes: usize) -> VortexResult<Option<Scalar>>
209208
210209#[ cfg( test) ]
211210mod tests {
211+ use std:: num:: NonZeroUsize ;
212+
212213 use vortex_buffer:: buffer;
214+ use vortex_error:: VortexExpect ;
213215 use vortex_error:: VortexResult ;
214216 use vortex_session:: VortexSession ;
215217
@@ -227,13 +229,19 @@ mod tests {
227229 use crate :: scalar:: Scalar ;
228230 use crate :: validity:: Validity ;
229231
232+ fn max_bytes ( value : usize ) -> NonZeroUsize {
233+ NonZeroUsize :: new ( value) . vortex_expect ( "non-zero max_bytes" )
234+ }
235+
230236 #[ test]
231237 fn bounded_max_truncates_utf8_to_upper_bound ( ) -> VortexResult < ( ) > {
232238 let mut ctx = LEGACY_SESSION . create_execution_ctx ( ) ;
233239 let array = VarBinViewArray :: from_iter_str ( [ "aardvark" , "char🪩" ] ) . into_array ( ) ;
234240 let mut acc = Accumulator :: try_new (
235241 BoundedMax ,
236- BoundedMaxOptions { max_bytes : 5 } ,
242+ BoundedMaxOptions {
243+ max_bytes : max_bytes ( 5 ) ,
244+ } ,
237245 array. dtype ( ) . clone ( ) ,
238246 ) ?;
239247
@@ -249,7 +257,9 @@ mod tests {
249257 let array = VarBinViewArray :: from_iter_bin ( [ & [ 255u8 , 255 , 255 ] [ ..] ] ) . into_array ( ) ;
250258 let mut acc = Accumulator :: try_new (
251259 BoundedMax ,
252- BoundedMaxOptions { max_bytes : 2 } ,
260+ BoundedMaxOptions {
261+ max_bytes : max_bytes ( 2 ) ,
262+ } ,
253263 array. dtype ( ) . clone ( ) ,
254264 ) ?;
255265
@@ -259,13 +269,58 @@ mod tests {
259269 Ok ( ( ) )
260270 }
261271
272+ #[ test]
273+ fn bounded_max_empty_does_not_poison_later_values ( ) -> VortexResult < ( ) > {
274+ let mut ctx = LEGACY_SESSION . create_execution_ctx ( ) ;
275+ let empty = VarBinViewArray :: from_iter_bin ( Vec :: < & [ u8 ] > :: new ( ) ) . into_array ( ) ;
276+ let values = VarBinViewArray :: from_iter_bin ( [ & [ 1u8 ] [ ..] ] ) . into_array ( ) ;
277+ let mut acc = Accumulator :: try_new (
278+ BoundedMax ,
279+ BoundedMaxOptions {
280+ max_bytes : max_bytes ( 2 ) ,
281+ } ,
282+ empty. dtype ( ) . clone ( ) ,
283+ ) ?;
284+
285+ acc. accumulate ( & empty, & mut ctx) ?;
286+ acc. accumulate ( & values, & mut ctx) ?;
287+
288+ assert_eq ! (
289+ acc. finish( ) ?,
290+ Scalar :: binary( buffer![ 1u8 ] , Nullability :: Nullable )
291+ ) ;
292+ Ok ( ( ) )
293+ }
294+
295+ #[ test]
296+ fn bounded_max_unknown_poisons_later_values ( ) -> VortexResult < ( ) > {
297+ let mut ctx = LEGACY_SESSION . create_execution_ctx ( ) ;
298+ let unknown = VarBinViewArray :: from_iter_bin ( [ & [ 255u8 , 255 , 255 ] [ ..] ] ) . into_array ( ) ;
299+ let values = VarBinViewArray :: from_iter_bin ( [ & [ 1u8 ] [ ..] ] ) . into_array ( ) ;
300+ let mut acc = Accumulator :: try_new (
301+ BoundedMax ,
302+ BoundedMaxOptions {
303+ max_bytes : max_bytes ( 2 ) ,
304+ } ,
305+ unknown. dtype ( ) . clone ( ) ,
306+ ) ?;
307+
308+ acc. accumulate ( & unknown, & mut ctx) ?;
309+ acc. accumulate ( & values, & mut ctx) ?;
310+
311+ assert_eq ! ( acc. finish( ) ?, Scalar :: null( unknown. dtype( ) . as_nullable( ) ) ) ;
312+ Ok ( ( ) )
313+ }
314+
262315 #[ test]
263316 fn bounded_max_keeps_fixed_width_values_exact ( ) -> VortexResult < ( ) > {
264317 let mut ctx = LEGACY_SESSION . create_execution_ctx ( ) ;
265318 let array = PrimitiveArray :: new ( buffer ! [ 10i32 , 20 , 5 ] , Validity :: NonNullable ) . into_array ( ) ;
266319 let mut acc = Accumulator :: try_new (
267320 BoundedMax ,
268- BoundedMaxOptions { max_bytes : 9 } ,
321+ BoundedMaxOptions {
322+ max_bytes : max_bytes ( 9 ) ,
323+ } ,
269324 array. dtype ( ) . clone ( ) ,
270325 ) ?;
271326
@@ -280,7 +335,9 @@ mod tests {
280335
281336 #[ test]
282337 fn bounded_max_options_round_trip ( ) -> VortexResult < ( ) > {
283- let options = BoundedMaxOptions { max_bytes : 64 } ;
338+ let options = BoundedMaxOptions {
339+ max_bytes : max_bytes ( 64 ) ,
340+ } ;
284341 let metadata = BoundedMax
285342 . serialize ( & options) ?
286343 . expect ( "serializable options" ) ;
0 commit comments