@@ -35,6 +35,7 @@ use crate::extension::datetime::Timestamp;
3535use crate :: scalar:: Scalar ;
3636use crate :: scalar_fn:: EmptyOptions ;
3737use crate :: scalar_fn:: ScalarFnId ;
38+ use crate :: scalar_fn:: ScalarFnVTable ;
3839use crate :: scalar_fn:: VecExecutionArgs ;
3940use crate :: scalar_fn:: unstable:: row:: FixedSizeListSink ;
4041use crate :: scalar_fn:: unstable:: row:: InitializedRow ;
@@ -192,6 +193,113 @@ unsafe impl InputElement for DenseRetryI64 {
192193 }
193194}
194195
196+ const REJECTED_DECODE_VALUE : i64 = i64:: MIN ;
197+
198+ struct DecodeFallibleI64 ;
199+
200+ // SAFETY: the view and unchecked access delegate to the `i64` implementation. Decoding inspects
201+ // only valid rows, so null-row payloads cannot cause its data-dependent error.
202+ unsafe impl InputElement for DecodeFallibleI64 {
203+ type Column = Buffer < i64 > ;
204+ type View < ' a > = & ' a [ i64 ] ;
205+ type Elem < ' a > = i64 ;
206+
207+ const DENSE_SAFE : bool = true ;
208+ const DECODE_INFALLIBLE : bool = false ;
209+
210+ fn validate ( dtype : & DType ) -> VortexResult < ( ) > {
211+ <i64 as InputElement >:: validate ( dtype)
212+ }
213+
214+ fn decode ( array : ArrayRef , ctx : & mut ExecutionCtx ) -> VortexResult < Self :: Column > {
215+ let valid = array. validity ( ) ?. execute_mask ( array. len ( ) , ctx) ?;
216+ let values = <i64 as InputElement >:: decode ( array, ctx) ?;
217+
218+ if valid
219+ . to_bit_buffer ( )
220+ . iter ( )
221+ . zip ( values. iter ( ) )
222+ . any ( |( is_valid, value) | is_valid && * value == REJECTED_DECODE_VALUE )
223+ {
224+ vortex_bail ! ( InvalidArgument : "test decoder rejected a valid value" ) ;
225+ }
226+
227+ Ok ( values)
228+ }
229+
230+ fn get ( column : & Self :: Column , index : usize ) -> i64 {
231+ <i64 as InputElement >:: get ( column, index)
232+ }
233+
234+ fn view ( column : & Self :: Column ) -> Self :: View < ' _ > {
235+ <i64 as InputElement >:: view ( column)
236+ }
237+
238+ fn get_from_view < ' a > ( view : & Self :: View < ' a > , index : usize ) -> i64
239+ where
240+ Self : ' a ,
241+ {
242+ <i64 as InputElement >:: get_from_view ( view, index)
243+ }
244+
245+ unsafe fn get_from_view_unchecked < ' a > ( view : & Self :: View < ' a > , index : usize ) -> i64
246+ where
247+ Self : ' a ,
248+ {
249+ // SAFETY: forwarded from this method's contract.
250+ unsafe { <i64 as InputElement >:: get_from_view_unchecked ( view, index) }
251+ }
252+ }
253+
254+ #[ derive( Clone , Default ) ]
255+ struct DecodeFallibleIdentity {
256+ prepare_count : Arc < AtomicUsize > ,
257+ apply_count : Arc < AtomicUsize > ,
258+ }
259+
260+ impl DecodeFallibleIdentity {
261+ fn prepare_count ( & self ) -> usize {
262+ self . prepare_count . load ( Ordering :: Relaxed )
263+ }
264+
265+ fn apply_count ( & self ) -> usize {
266+ self . apply_count . load ( Ordering :: Relaxed )
267+ }
268+ }
269+
270+ impl RowFn for DecodeFallibleIdentity {
271+ type Options = EmptyOptions ;
272+
273+ const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
274+ const INFALLIBLE : bool = true ;
275+ const DECODE_INFALLIBLE : bool = false ;
276+
277+ fn id ( & self ) -> ScalarFnId {
278+ static ID : CachedId = CachedId :: new ( "test.decode_fallible_identity" ) ;
279+ * ID
280+ }
281+
282+ fn dispatch < V : RowVisitor > (
283+ & self ,
284+ _options : & Self :: Options ,
285+ _args : & [ DType ] ,
286+ visitor : V ,
287+ ) -> VortexResult < V :: VisitResult > {
288+ let prepare_count = Arc :: clone ( & self . prepare_count ) ;
289+ let apply_count = Arc :: clone ( & self . apply_count ) ;
290+
291+ visitor. visit_prepared :: < ( DecodeFallibleI64 , ) , i64 , _ > (
292+ move |_| {
293+ prepare_count. fetch_add ( 1 , Ordering :: Relaxed ) ;
294+ } ,
295+ move |& ( ) , ( value, ) | {
296+ apply_count. fetch_add ( 1 , Ordering :: Relaxed ) ;
297+ value
298+ } ,
299+ )
300+ }
301+ }
302+
195303/// Produces a null row to exercise output validation at the row-function boundary.
196304#[ derive( Default ) ]
197305struct NullProducingI64 ( i64 ) ;
@@ -249,6 +357,7 @@ impl RowFn for RepeatValue {
249357
250358 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
251359 const INFALLIBLE : bool = true ;
360+ const DECODE_INFALLIBLE : bool = true ;
252361
253362 fn id ( & self ) -> ScalarFnId {
254363 static ID : CachedId = CachedId :: new ( "test.repeat_value" ) ;
@@ -278,6 +387,7 @@ impl RowFn for DeferredAdd {
278387
279388 const ARG_NAMES : & ' static [ & ' static str ] = & [ "lhs" , "rhs" ] ;
280389 const INFALLIBLE : bool = false ;
390+ const DECODE_INFALLIBLE : bool = true ;
281391
282392 fn id ( & self ) -> ScalarFnId {
283393 static ID : CachedId = CachedId :: new ( "test.deferred_add" ) ;
@@ -313,6 +423,7 @@ impl RowFn for ValidOnlyIdentity {
313423
314424 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
315425 const INFALLIBLE : bool = false ;
426+ const DECODE_INFALLIBLE : bool = true ;
316427
317428 fn id ( & self ) -> ScalarFnId {
318429 static ID : CachedId = CachedId :: new ( "test.valid_only_identity" ) ;
@@ -337,6 +448,7 @@ impl RowFn for FilterAndScatterIdentity {
337448
338449 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
339450 const INFALLIBLE : bool = false ;
451+ const DECODE_INFALLIBLE : bool = false ;
340452
341453 fn id ( & self ) -> ScalarFnId {
342454 static ID : CachedId = CachedId :: new ( "test.filter_and_scatter_identity" ) ;
@@ -358,6 +470,7 @@ impl RowFn for DenseRetryIncrement {
358470
359471 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
360472 const INFALLIBLE : bool = false ;
473+ const DECODE_INFALLIBLE : bool = true ;
361474
362475 fn id ( & self ) -> ScalarFnId {
363476 static ID : CachedId = CachedId :: new ( "test.dense_retry_increment" ) ;
@@ -388,6 +501,7 @@ impl RowFn for FilterAndScatterRepeat {
388501
389502 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
390503 const INFALLIBLE : bool = true ;
504+ const DECODE_INFALLIBLE : bool = false ;
391505
392506 fn id ( & self ) -> ScalarFnId {
393507 static ID : CachedId = CachedId :: new ( "test.filter_and_scatter_repeat" ) ;
@@ -418,6 +532,7 @@ impl RowFn for InvalidKernelOutput {
418532
419533 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
420534 const INFALLIBLE : bool = true ;
535+ const DECODE_INFALLIBLE : bool = true ;
421536
422537 fn id ( & self ) -> ScalarFnId {
423538 static ID : CachedId = CachedId :: new ( "test.invalid_kernel_output" ) ;
@@ -434,6 +549,55 @@ impl RowFn for InvalidKernelOutput {
434549 }
435550}
436551
552+ #[ test]
553+ fn test_decode_fallibility_disables_scalar_fn_infallibility_not_dense_execution ( ) -> VortexResult < ( ) >
554+ {
555+ let function = DecodeFallibleIdentity :: default ( ) ;
556+ let input = PrimitiveArray :: new (
557+ vec ! [
558+ 1_i64 , // valid
559+ REJECTED_DECODE_VALUE , // null
560+ 3 , // valid
561+ ] ,
562+ Validity :: from_iter ( [ true , false , true ] ) ,
563+ )
564+ . into_array ( ) ;
565+ let expected = input. clone ( ) ;
566+ let args = VecExecutionArgs :: new ( vec ! [ input] , 3 ) ;
567+ let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
568+
569+ let actual = execute_rows ( & function, & EmptyOptions , & args, & mut ctx) ?;
570+
571+ assert_arrays_eq ! ( actual, expected, & mut ctx) ;
572+ assert_eq ! ( function. prepare_count( ) , 1 ) ;
573+ assert_eq ! ( function. apply_count( ) , 3 ) ;
574+ assert ! ( !ScalarFnVTable :: is_infallible( & function, & EmptyOptions ) ) ;
575+ Ok ( ( ) )
576+ }
577+
578+ #[ test]
579+ fn test_decode_error_precedes_prepare_and_apply ( ) -> VortexResult < ( ) > {
580+ let function = DecodeFallibleIdentity :: default ( ) ;
581+ let input = PrimitiveArray :: from_iter ( [ REJECTED_DECODE_VALUE ] ) . into_array ( ) ;
582+ let args = VecExecutionArgs :: new ( vec ! [ input] , 1 ) ;
583+ let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
584+
585+ let error = match execute_rows ( & function, & EmptyOptions , & args, & mut ctx) {
586+ Err ( error) => error,
587+ Ok ( _) => vortex_bail ! ( "a rejected decoded value must fail execution" ) ,
588+ } ;
589+
590+ assert ! (
591+ error
592+ . to_string( )
593+ . contains( "test decoder rejected a valid value" ) ,
594+ "unexpected error: {error}" ,
595+ ) ;
596+ assert_eq ! ( function. prepare_count( ) , 0 ) ;
597+ assert_eq ! ( function. apply_count( ) , 0 ) ;
598+ Ok ( ( ) )
599+ }
600+
437601#[ rstest]
438602#[ case:: dense_width_two(
439603 vec![ 1_i64 , 2 ] ,
@@ -491,6 +655,7 @@ impl RowFn for PackedPositive {
491655
492656 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
493657 const INFALLIBLE : bool = true ;
658+ const DECODE_INFALLIBLE : bool = true ;
494659
495660 fn id ( & self ) -> ScalarFnId {
496661 static ID : CachedId = CachedId :: new ( "test.packed_positive" ) ;
@@ -512,6 +677,7 @@ impl RowFn for PackedGreaterThan {
512677
513678 const ARG_NAMES : & ' static [ & ' static str ] = & [ "lhs" , "rhs" ] ;
514679 const INFALLIBLE : bool = true ;
680+ const DECODE_INFALLIBLE : bool = true ;
515681
516682 fn id ( & self ) -> ScalarFnId {
517683 static ID : CachedId = CachedId :: new ( "test.packed_greater_than" ) ;
@@ -533,6 +699,7 @@ impl RowFn for ValidOnlyPositive {
533699
534700 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
535701 const INFALLIBLE : bool = true ;
702+ const DECODE_INFALLIBLE : bool = true ;
536703
537704 fn id ( & self ) -> ScalarFnId {
538705 static ID : CachedId = CachedId :: new ( "test.valid_only_positive" ) ;
@@ -557,6 +724,7 @@ impl RowFn for NullaryTrue {
557724
558725 const ARG_NAMES : & ' static [ & ' static str ] = & [ ] ;
559726 const INFALLIBLE : bool = true ;
727+ const DECODE_INFALLIBLE : bool = true ;
560728
561729 fn id ( & self ) -> ScalarFnId {
562730 static ID : CachedId = CachedId :: new ( "test.nullary_true" ) ;
@@ -903,6 +1071,7 @@ impl RowFn for DeclaredOutput {
9031071
9041072 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
9051073 const INFALLIBLE : bool = true ;
1074+ const DECODE_INFALLIBLE : bool = true ;
9061075
9071076 fn id ( & self ) -> ScalarFnId {
9081077 static ID : CachedId = CachedId :: new ( "test.declared_output" ) ;
@@ -933,6 +1102,7 @@ impl RowFn for DeclaredSinkOutput {
9331102
9341103 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
9351104 const INFALLIBLE : bool = false ;
1105+ const DECODE_INFALLIBLE : bool = true ;
9361106
9371107 fn id ( & self ) -> ScalarFnId {
9381108 static ID : CachedId = CachedId :: new ( "test.declared_sink_output" ) ;
@@ -965,6 +1135,7 @@ impl RowFn for ChangingOutputDType {
9651135
9661136 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
9671137 const INFALLIBLE : bool = true ;
1138+ const DECODE_INFALLIBLE : bool = true ;
9681139
9691140 fn id ( & self ) -> ScalarFnId {
9701141 static ID : CachedId = CachedId :: new ( "test.changing_output_dtype" ) ;
@@ -1125,6 +1296,7 @@ impl RowFn for MismatchedStorage {
11251296
11261297 const ARG_NAMES : & ' static [ & ' static str ] = & [ "value" ] ;
11271298 const INFALLIBLE : bool = true ;
1299+ const DECODE_INFALLIBLE : bool = true ;
11281300
11291301 fn id ( & self ) -> ScalarFnId {
11301302 static ID : CachedId = CachedId :: new ( "test.mismatched_storage" ) ;
0 commit comments