1717
1818use std:: sync:: Arc ;
1919
20- use datafusion_physical_expr:: Partitioning ;
20+ use datafusion_common:: { DataFusionError , ScalarValue , SplitPoint } ;
21+ use datafusion_physical_expr:: {
22+ LexOrdering , Partitioning , PhysicalSortExpr , RangePartitioning ,
23+ } ;
2124use datafusion_physical_expr_common:: physical_expr:: PhysicalExpr ;
2225use stabby:: vec:: Vec as SVec ;
2326
24- use crate :: { arrow_wrappers:: WrappedArray , physical_expr:: { FFI_PhysicalExpr , sort:: FFI_PhysicalSortExpr } } ;
27+ use crate :: arrow_wrappers:: WrappedArray ;
28+ use crate :: physical_expr:: FFI_PhysicalExpr ;
29+ use crate :: physical_expr:: sort:: FFI_PhysicalSortExpr ;
2530
2631/// A stable struct for sharing [`RangePartitioning`] across FFI boundaries.
27- /// See [' RangePartitioning' ] for the descriptions of each field.
32+ /// See [` RangePartitioning` ] for the descriptions of each field.
2833#[ repr( C ) ]
2934#[ derive( Debug ) ]
3035pub struct FFI_RangePartitioning {
@@ -33,7 +38,7 @@ pub struct FFI_RangePartitioning {
3338}
3439
3540/// A stable struct for sharing [`Partitioning`] across FFI boundaries.
36- /// See [' Partitioning' ] for the meaning of each variant.
41+ /// See [` Partitioning` ] for the meaning of each variant.
3742#[ repr( C ) ]
3843#[ derive( Debug ) ]
3944pub enum FFI_Partitioning {
@@ -56,49 +61,119 @@ impl From<&Partitioning> for FFI_Partitioning {
5661 Self :: Hash ( exprs, * size)
5762 }
5863 Partitioning :: Range ( range) => {
59- let split_points = range. split_points ( ) . iter ( ) . map ( |s| s. values ( ) . iter ( ) . map ( |v| v. try_into ( ) . expect ( "Fail" ) ) . collect ( ) ) . collect ( ) ;
60- let ordering = range. ordering ( ) . iter ( ) . map ( |e| FFI_PhysicalSortExpr :: from ( e) ) . collect ( ) ;
61- Self :: Range ( FFI_RangePartitioning { split_points, ordering} )
64+ // Producer-side conversion should be infallible at ABI boundary
65+ let split_points = range
66+ . split_points ( )
67+ . iter ( )
68+ . map ( |split_point| {
69+ split_point
70+ . values ( )
71+ . iter ( )
72+ . map ( |value| {
73+ WrappedArray :: try_from ( value) . expect (
74+ "ScalarValue in RangePartitioning should convert to WrappedArray" ,
75+ )
76+ } )
77+ . collect ( )
78+ } )
79+ . collect ( ) ;
80+ let ordering = range
81+ . ordering ( )
82+ . iter ( )
83+ . map ( FFI_PhysicalSortExpr :: from)
84+ . collect ( ) ;
85+ Self :: Range ( FFI_RangePartitioning {
86+ split_points,
87+ ordering,
88+ } )
6289 }
6390 Partitioning :: UnknownPartitioning ( size) => Self :: UnknownPartitioning ( * size) ,
6491 }
6592 }
6693}
6794
68- impl From < & FFI_Partitioning > for Partitioning {
69- fn from( value : & FFI_Partitioning ) -> Self {
70- match value {
95+ impl TryFrom < FFI_Partitioning > for Partitioning {
96+ type Error = DataFusionError ;
97+
98+ fn try_from ( value : FFI_Partitioning ) -> Result < Self , Self :: Error > {
99+ Ok ( match value {
71100 FFI_Partitioning :: RoundRobinBatch ( size) => {
72- Partitioning :: RoundRobinBatch ( * size)
101+ Partitioning :: RoundRobinBatch ( size)
73102 }
74103 FFI_Partitioning :: Hash ( exprs, size) => {
75104 let exprs = exprs. iter ( ) . map ( <Arc < dyn PhysicalExpr > >:: from) . collect ( ) ;
76- Self :: Hash ( exprs, * size)
105+ Self :: Hash ( exprs, size)
106+ }
107+ FFI_Partitioning :: Range ( range) => {
108+ let split_points = range
109+ . split_points
110+ . into_iter ( )
111+ . map ( |split_point| {
112+ split_point
113+ . into_iter ( )
114+ . map ( ScalarValue :: try_from)
115+ . collect :: < Result < Vec < _ > , _ > > ( )
116+ . map ( SplitPoint :: new)
117+ } )
118+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
119+
120+ let ordering =
121+ LexOrdering :: new ( range. ordering . iter ( ) . map ( PhysicalSortExpr :: from) )
122+ . ok_or_else ( || {
123+ DataFusionError :: Internal (
124+ "FFI Range partitioning ordering must be non-empty"
125+ . to_string ( ) ,
126+ )
127+ } ) ?;
128+
129+ Self :: Range ( RangePartitioning :: try_new ( ordering, split_points) ?)
77130 }
78- FFI_Partitioning :: Range ( )
79131 FFI_Partitioning :: UnknownPartitioning ( size) => {
80- Self :: UnknownPartitioning ( * size)
132+ Self :: UnknownPartitioning ( size)
81133 }
82- }
134+ } )
83135 }
84136}
85137
86138#[ cfg( test) ]
87139mod tests {
88- use datafusion_physical_expr:: Partitioning ;
89- use datafusion_physical_expr: : expressions:: lit;
140+ use std:: sync:: Arc ;
141+
142+ use arrow_schema:: SortOptions ;
143+ use datafusion_common:: { Result , ScalarValue , SplitPoint } ;
144+ use datafusion_physical_expr:: expressions:: { Column , lit} ;
145+ use datafusion_physical_expr:: {
146+ LexOrdering , Partitioning , PhysicalSortExpr , RangePartitioning ,
147+ } ;
148+ use datafusion_physical_expr_common:: physical_expr:: PhysicalExpr ;
149+ use stabby:: vec:: Vec as SVec ;
90150
91- use crate :: physical_expr:: partitioning:: FFI_Partitioning ;
151+ use crate :: physical_expr:: partitioning:: { FFI_Partitioning , FFI_RangePartitioning } ;
152+
153+ fn range_partitioning ( ) -> Result < Partitioning > {
154+ let col_expr = Arc :: new ( Column :: new ( "a" , 0 ) ) as Arc < dyn PhysicalExpr > ;
155+ let sort_expr = PhysicalSortExpr :: new ( col_expr, SortOptions :: default ( ) ) ;
156+ let ordering = LexOrdering :: new ( [ sort_expr] ) . expect ( "non-empty ordering" ) ;
157+ let split_points = vec ! [
158+ SplitPoint :: new( vec![ ScalarValue :: Int64 ( Some ( 10 ) ) ] ) ,
159+ SplitPoint :: new( vec![ ScalarValue :: Int64 ( Some ( 20 ) ) ] ) ,
160+ ] ;
161+ Ok ( Partitioning :: Range ( RangePartitioning :: try_new (
162+ ordering,
163+ split_points,
164+ ) ?) )
165+ }
92166
93167 #[ test]
94- fn round_trip_ffi_partitioning( ) {
168+ fn round_trip_ffi_partitioning ( ) -> Result < ( ) > {
95169 for partitioning in [
96170 Partitioning :: RoundRobinBatch ( 10 ) ,
97171 Partitioning :: Hash ( vec ! [ lit( 1 ) ] , 10 ) ,
98172 Partitioning :: UnknownPartitioning ( 10 ) ,
173+ range_partitioning ( ) ?,
99174 ] {
100175 let ffi_partitioning: FFI_Partitioning = ( & partitioning) . into ( ) ;
101- let returned: Partitioning = ( & ffi_partitioning) . into ( ) ;
176+ let returned: Partitioning = ffi_partitioning. try_into ( ) ? ;
102177
103178 if let Partitioning :: UnknownPartitioning ( return_size) = returned {
104179 let Partitioning :: UnknownPartitioning ( original_size) = partitioning
@@ -110,5 +185,50 @@ mod tests {
110185 assert_eq ! ( partitioning, returned) ;
111186 }
112187 }
188+
189+ Ok ( ( ) )
190+ }
191+
192+ #[ test]
193+ fn round_trip_ffi_range_partitioning_compound_key ( ) -> Result < ( ) > {
194+ let a = Arc :: new ( Column :: new ( "a" , 0 ) ) as Arc < dyn PhysicalExpr > ;
195+ let b = Arc :: new ( Column :: new ( "b" , 1 ) ) as Arc < dyn PhysicalExpr > ;
196+ let ordering = LexOrdering :: new ( [
197+ PhysicalSortExpr :: new ( a, SortOptions :: default ( ) ) ,
198+ PhysicalSortExpr :: new ( b, SortOptions :: new ( true , false ) ) ,
199+ ] )
200+ . expect ( "non-empty ordering" ) ;
201+ let split_points = vec ! [
202+ SplitPoint :: new( vec![
203+ ScalarValue :: Int64 ( Some ( 10 ) ) ,
204+ ScalarValue :: Utf8 ( Some ( "a" . to_string( ) ) ) ,
205+ ] ) ,
206+ SplitPoint :: new( vec![
207+ ScalarValue :: Int64 ( Some ( 20 ) ) ,
208+ ScalarValue :: Utf8 ( Some ( "b" . to_string( ) ) ) ,
209+ ] ) ,
210+ ] ;
211+ let partitioning =
212+ Partitioning :: Range ( RangePartitioning :: try_new ( ordering, split_points) ?) ;
213+
214+ let ffi_partitioning: FFI_Partitioning = ( & partitioning) . into ( ) ;
215+ let returned: Partitioning = ffi_partitioning. try_into ( ) ?;
216+ assert_eq ! ( partitioning, returned) ;
217+
218+ Ok ( ( ) )
219+ }
220+
221+ #[ test]
222+ fn ffi_range_partitioning_rejects_empty_ordering ( ) {
223+ let ffi_partitioning = FFI_Partitioning :: Range ( FFI_RangePartitioning {
224+ split_points : SVec :: new ( ) ,
225+ ordering : SVec :: new ( ) ,
226+ } ) ;
227+
228+ let err = Partitioning :: try_from ( ffi_partitioning) . unwrap_err ( ) ;
229+ assert ! (
230+ err. to_string( ) . contains( "ordering must be non-empty" ) ,
231+ "{err}"
232+ ) ;
113233 }
114234}
0 commit comments