11// SPDX-License-Identifier: Apache-2.0
22// SPDX-FileCopyrightText: Copyright the Vortex contributors
33
4+ use std:: any:: Any ;
45use std:: mem:: MaybeUninit ;
6+ use std:: ops:: Range ;
57use std:: sync:: Arc ;
68use std:: sync:: LazyLock ;
79use std:: sync:: atomic:: AtomicUsize ;
810use std:: sync:: atomic:: Ordering ;
911
12+ use futures:: future:: BoxFuture ;
1013use rstest:: rstest;
14+ use vortex_buffer:: Alignment ;
1115use vortex_buffer:: Buffer ;
1216use vortex_buffer:: BufferAllocatorRef ;
1317use vortex_buffer:: BufferMut ;
18+ use vortex_buffer:: ByteBuffer ;
1419use vortex_error:: VortexResult ;
1520use vortex_error:: vortex_bail;
1621use vortex_error:: vortex_ensure;
@@ -28,11 +33,13 @@ use crate::arrays::Constant;
2833use crate :: arrays:: ConstantArray ;
2934use crate :: arrays:: ExtensionArray ;
3035use crate :: arrays:: FixedSizeListArray ;
36+ use crate :: arrays:: MaskedArray ;
3137use crate :: arrays:: PrimitiveArray ;
3238use crate :: arrays:: VarBinViewArray ;
3339use crate :: arrays:: varbinview:: BinaryView ;
3440use crate :: assert_arrays_eq;
3541use crate :: buffer:: BufferHandle ;
42+ use crate :: buffer:: DeviceBuffer ;
3643use crate :: dtype:: DType ;
3744use crate :: dtype:: NativePType ;
3845use crate :: dtype:: Nullability ;
@@ -44,6 +51,8 @@ use crate::scalar::Scalar;
4451use crate :: scalar_fn:: EmptyOptions ;
4552use crate :: scalar_fn:: ScalarFnId ;
4653use crate :: scalar_fn:: VecExecutionArgs ;
54+ use crate :: scalar_fn:: fns:: binary:: Binary ;
55+ use crate :: scalar_fn:: fns:: operators:: Operator ;
4756use crate :: scalar_fn:: unstable:: row:: FixedSizeListSink ;
4857use crate :: scalar_fn:: unstable:: row:: InitializedRow ;
4958use crate :: scalar_fn:: unstable:: row:: InputElement ;
@@ -61,6 +70,70 @@ use crate::validity::Validity;
6170
6271static SESSION : LazyLock < VortexSession > = LazyLock :: new ( VortexSession :: empty) ;
6372
73+ #[ derive( Clone ) ]
74+ struct FilteredBoolean ;
75+
76+ impl RowFn for FilteredBoolean {
77+ type Options = EmptyOptions ;
78+
79+ const ARG_NAMES : & ' static [ & ' static str ] = & [ "lhs" , "rhs" ] ;
80+ const INFALLIBLE : bool = true ;
81+
82+ fn id ( & self ) -> ScalarFnId {
83+ static ID : CachedId = CachedId :: new ( "test.filtered_boolean" ) ;
84+ * ID
85+ }
86+
87+ fn dispatch < V : RowVisitor > (
88+ & self ,
89+ _options : & Self :: Options ,
90+ _args : & [ DType ] ,
91+ visitor : V ,
92+ ) -> VortexResult < V :: VisitResult > {
93+ visitor. visit_bool :: < ( FilterOnlyI64 , i64 ) , false > ( |( lhs, rhs) | lhs > rhs)
94+ }
95+ }
96+
97+ #[ derive( Debug , PartialEq , Eq , Hash ) ]
98+ struct UnreadableDeviceBuffer {
99+ len : usize ,
100+ alignment : Alignment ,
101+ }
102+
103+ impl DeviceBuffer for UnreadableDeviceBuffer {
104+ fn as_any ( & self ) -> & dyn Any {
105+ self
106+ }
107+
108+ fn len ( & self ) -> usize {
109+ self . len
110+ }
111+
112+ fn alignment ( & self ) -> Alignment {
113+ self . alignment
114+ }
115+
116+ fn copy_to_host_sync ( & self , _alignment : Alignment ) -> VortexResult < ByteBuffer > {
117+ vortex_bail ! ( "execution must not read the device buffer" )
118+ }
119+
120+ fn copy_to_host (
121+ & self ,
122+ _alignment : Alignment ,
123+ ) -> VortexResult < BoxFuture < ' static , VortexResult < ByteBuffer > > > {
124+ vortex_bail ! ( "execution must not read the device buffer" )
125+ }
126+
127+ fn slice ( & self , _range : Range < usize > ) -> Arc < dyn DeviceBuffer > {
128+ panic ! ( "execution must not slice the device buffer" )
129+ }
130+
131+ fn aligned ( self : Arc < Self > , alignment : Alignment ) -> VortexResult < Arc < dyn DeviceBuffer > > {
132+ assert_eq ! ( alignment, self . alignment( ) ) ;
133+ Ok ( self )
134+ }
135+ }
136+
64137#[ derive( Clone , Default ) ]
65138struct DeferredAdd {
66139 /// The number of preparations across the dense attempt and any valid-row retry.
@@ -807,6 +880,64 @@ fn test_bool_output_handles_partial_constants() -> VortexResult<()> {
807880 assert_bool_output_handles_partial_constants :: < true > ( )
808881}
809882
883+ #[ test]
884+ fn test_bool_output_handles_null_literal_constant ( ) -> VortexResult < ( ) > {
885+ let varying = PrimitiveArray :: from_iter ( 0_i64 ..65 ) . into_array ( ) ;
886+ let null = Scalar :: null ( DType :: Primitive ( i64:: PTYPE , Nullability :: Nullable ) ) ;
887+ let constant = ConstantArray :: new ( null, 65 ) . into_array ( ) ;
888+ let args = VecExecutionArgs :: new ( vec ! [ varying, constant] , 65 ) ;
889+ let mut ctx = SESSION . create_execution_ctx ( ) ;
890+
891+ let actual = execute_rows ( & PackedGreaterThan :: < true > , & EmptyOptions , & args, & mut ctx) ?;
892+ let expected = BoolArray :: from_iter ( std:: iter:: repeat_n ( None :: < bool > , 65 ) ) . into_array ( ) ;
893+
894+ assert_arrays_eq ! ( & actual, & expected, & mut ctx) ;
895+ Ok ( ( ) )
896+ }
897+
898+ #[ test]
899+ fn test_bool_output_handles_masked_constant ( ) -> VortexResult < ( ) > {
900+ let mut ctx = SESSION . create_execution_ctx ( ) ;
901+ let varying = PrimitiveArray :: from_iter ( 0_i64 ..65 ) . into_array ( ) ;
902+ let valid: Vec < bool > = ( 0 ..65 ) . map ( |index| index % 3 != 0 ) . collect ( ) ;
903+ let constant = MaskedArray :: try_new (
904+ ConstantArray :: new ( 32_i64 , 65 ) . into_array ( ) ,
905+ Validity :: from_iter ( valid. iter ( ) . copied ( ) ) ,
906+ & mut ctx,
907+ ) ?
908+ . into_array ( ) ;
909+ let args = VecExecutionArgs :: new ( vec ! [ varying, constant] , 65 ) ;
910+
911+ let actual = execute_rows ( & PackedGreaterThan :: < true > , & EmptyOptions , & args, & mut ctx) ?;
912+ let expected = BoolArray :: from_iter (
913+ ( 0_i64 ..65 )
914+ . zip ( valid)
915+ . map ( |( value, valid) | valid. then_some ( value > 32 ) ) ,
916+ )
917+ . into_array ( ) ;
918+
919+ assert_arrays_eq ! ( & actual, & expected, & mut ctx) ;
920+ Ok ( ( ) )
921+ }
922+
923+ #[ test]
924+ fn test_bool_output_rejects_wrong_row_count ( ) {
925+ let short = PrimitiveArray :: from_iter ( 0_i64 ..64 ) . into_array ( ) ;
926+ let long = PrimitiveArray :: from_iter ( 0_i64 ..65 ) . into_array ( ) ;
927+ let args = VecExecutionArgs :: new ( vec ! [ long, short] , 65 ) ;
928+ let mut ctx = SESSION . create_execution_ctx ( ) ;
929+
930+ let error = execute_rows ( & PackedGreaterThan :: < true > , & EmptyOptions , & args, & mut ctx)
931+ . expect_err ( "a short input must fail" ) ;
932+
933+ assert ! (
934+ error
935+ . to_string( )
936+ . contains( "the test.packed_greater_than input 1 has the wrong row count" ) ,
937+ "{error}" ,
938+ ) ;
939+ }
940+
810941#[ test]
811942fn test_bool_output_handles_all_constant_input ( ) -> VortexResult < ( ) > {
812943 let input = ConstantArray :: new ( 7_i64 , 65 ) . into_array ( ) ;
@@ -1539,3 +1670,99 @@ fn constant_output_retains_execution_allocator_payload() -> VortexResult<()> {
15391670 tracker. assert_owns ( value. inner ( ) . as_slice ( ) ) ;
15401671 Ok ( ( ) )
15411672}
1673+
1674+ #[ test]
1675+ fn valid_only_bool_skips_invalid_decode_payloads ( ) -> VortexResult < ( ) > {
1676+ let lhs = PrimitiveArray :: new ( vec ! [ i64 :: MIN , 7 ] , Validity :: from_iter ( [ false , true ] ) ) ;
1677+ let rhs = ConstantArray :: new ( 0_i64 , 2 ) ;
1678+ let args = VecExecutionArgs :: new ( vec ! [ lhs. into_array( ) , rhs. into_array( ) ] , 2 ) ;
1679+ let mut ctx = SESSION . create_execution_ctx ( ) ;
1680+
1681+ let actual = execute_rows ( & FilteredBoolean , & EmptyOptions , & args, & mut ctx) ?;
1682+ assert_arrays_eq ! ( actual, BoolArray :: from_iter( [ None , Some ( true ) ] ) , & mut ctx) ;
1683+ Ok ( ( ) )
1684+ }
1685+
1686+ #[ test]
1687+ fn null_literal_skips_earlier_decode ( ) -> VortexResult < ( ) > {
1688+ let lhs = PrimitiveArray :: from_iter ( [ i64:: MIN , 7 ] ) ;
1689+ let rhs = ConstantArray :: new (
1690+ Scalar :: null ( DType :: Primitive ( i64:: PTYPE , Nullability :: Nullable ) ) ,
1691+ 2 ,
1692+ ) ;
1693+ let args = VecExecutionArgs :: new ( vec ! [ lhs. into_array( ) , rhs. into_array( ) ] , 2 ) ;
1694+ let mut ctx = SESSION . create_execution_ctx ( ) ;
1695+
1696+ let actual = execute_rows ( & FilteredBoolean , & EmptyOptions , & args, & mut ctx) ?;
1697+ assert_arrays_eq ! ( actual, BoolArray :: from_iter( [ None :: <bool >, None ] ) , & mut ctx) ;
1698+ Ok ( ( ) )
1699+ }
1700+
1701+ #[ test]
1702+ fn null_literal_skips_lazy_input_execution ( ) -> VortexResult < ( ) > {
1703+ let lhs = Binary :: try_new (
1704+ PrimitiveArray :: from_iter ( [ 1_i64 , 2 ] ) . into_array ( ) ,
1705+ ConstantArray :: new ( 0_i64 , 2 ) . into_array ( ) ,
1706+ Operator :: Div ,
1707+ ) ?
1708+ . into_array ( ) ;
1709+ let rhs = ConstantArray :: new (
1710+ Scalar :: null ( DType :: Primitive ( i64:: PTYPE , Nullability :: Nullable ) ) ,
1711+ 2 ,
1712+ ) ;
1713+ let args = VecExecutionArgs :: new ( vec ! [ lhs, rhs. into_array( ) ] , 2 ) ;
1714+ let mut ctx = SESSION . create_execution_ctx ( ) ;
1715+
1716+ let actual = execute_rows ( & PackedGreaterThan :: < false > , & EmptyOptions , & args, & mut ctx) ?;
1717+ assert_arrays_eq ! ( actual, BoolArray :: from_iter( [ None :: <bool >, None ] ) , & mut ctx) ;
1718+ Ok ( ( ) )
1719+ }
1720+
1721+ #[ test]
1722+ fn device_validity_stays_lazy ( ) -> VortexResult < ( ) > {
1723+ let validity = BoolArray :: try_new_from_handle (
1724+ BufferHandle :: new_device ( Arc :: new ( UnreadableDeviceBuffer {
1725+ len : 1 ,
1726+ alignment : Alignment :: of :: < u8 > ( ) ,
1727+ } ) ) ,
1728+ 0 ,
1729+ 2 ,
1730+ Validity :: NonNullable ,
1731+ ) ?
1732+ . into_array ( ) ;
1733+ let lhs = PrimitiveArray :: new ( vec ! [ 1_i64 , 2 ] , Validity :: Array ( validity. clone ( ) ) ) ;
1734+ let rhs = PrimitiveArray :: new ( vec ! [ 3_i64 , 4 ] , Validity :: Array ( validity) ) ;
1735+ let args = VecExecutionArgs :: new ( vec ! [ lhs. into_array( ) , rhs. into_array( ) ] , 2 ) ;
1736+ let mut ctx = SESSION . create_execution_ctx ( ) ;
1737+
1738+ let actual = execute_rows ( & DeferredAdd :: default ( ) , & EmptyOptions , & args, & mut ctx) ?;
1739+ assert_eq ! ( actual. len( ) , 2 ) ;
1740+ assert_eq ! (
1741+ actual. dtype( ) ,
1742+ & DType :: Primitive ( i64 :: PTYPE , Nullability :: Nullable )
1743+ ) ;
1744+ assert ! ( matches!( actual. validity( ) ?, Validity :: Array ( _) ) ) ;
1745+ Ok ( ( ) )
1746+ }
1747+
1748+ #[ test]
1749+ fn null_literal_skips_device_input_transfer ( ) -> VortexResult < ( ) > {
1750+ let input = PrimitiveArray :: from_buffer_handle (
1751+ BufferHandle :: new_device ( Arc :: new ( UnreadableDeviceBuffer {
1752+ len : 2 * size_of :: < i64 > ( ) ,
1753+ alignment : Alignment :: of :: < i64 > ( ) ,
1754+ } ) ) ,
1755+ i64:: PTYPE ,
1756+ Validity :: NonNullable ,
1757+ ) ;
1758+ let null = ConstantArray :: new (
1759+ Scalar :: null ( DType :: Primitive ( i64:: PTYPE , Nullability :: Nullable ) ) ,
1760+ 2 ,
1761+ ) ;
1762+ let args = VecExecutionArgs :: new ( vec ! [ input. into_array( ) , null. into_array( ) ] , 2 ) ;
1763+ let mut ctx = SESSION . create_execution_ctx ( ) ;
1764+
1765+ let actual = execute_rows ( & PackedGreaterThan :: < false > , & EmptyOptions , & args, & mut ctx) ?;
1766+ assert_arrays_eq ! ( actual, BoolArray :: from_iter( [ None :: <bool >, None ] ) , & mut ctx) ;
1767+ Ok ( ( ) )
1768+ }
0 commit comments