Skip to content

Commit 89dd04e

Browse files
committed
added tests
1 parent 0a3cad5 commit 89dd04e

2 files changed

Lines changed: 184 additions & 22 deletions

File tree

datafusion/ffi/src/physical_expr/partitioning.rs

Lines changed: 140 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,19 @@
1717

1818
use 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+
};
2124
use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
2225
use 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)]
3035
pub 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)]
3944
pub 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)]
87139
mod 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
}

datafusion/ffi/src/plan_properties.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::sync::Arc;
2020

2121
use arrow::datatypes::SchemaRef;
2222
use datafusion_common::error::{DataFusionError, Result};
23-
use datafusion_physical_expr::EquivalenceProperties;
23+
use datafusion_physical_expr::{EquivalenceProperties, Partitioning};
2424
use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr;
2525
use datafusion_physical_plan::PlanProperties;
2626
use datafusion_physical_plan::execution_plan::{Boundedness, EmissionType};
@@ -172,6 +172,7 @@ impl TryFrom<FFI_PlanProperties> for PlanProperties {
172172
.unwrap_or_default();
173173

174174
let partitioning = unsafe { (ffi_props.output_partitioning)(&ffi_props) };
175+
let partitioning = Partitioning::try_from(partitioning)?;
175176

176177
let eq_properties = if sort_exprs.is_empty() {
177178
EquivalenceProperties::new(Arc::new(schema))
@@ -187,7 +188,7 @@ impl TryFrom<FFI_PlanProperties> for PlanProperties {
187188

188189
Ok(PlanProperties::new(
189190
eq_properties,
190-
(&partitioning).into(),
191+
partitioning,
191192
emission_type,
192193
boundedness,
193194
))
@@ -282,6 +283,29 @@ mod tests {
282283
))
283284
}
284285

286+
fn create_range_test_props() -> Result<PlanProperties> {
287+
use arrow::datatypes::{DataType, Field, Schema};
288+
use datafusion_common::{ScalarValue, SplitPoint};
289+
use datafusion_physical_expr::{LexOrdering, RangePartitioning};
290+
291+
let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
292+
let col = datafusion::physical_plan::expressions::col("a", &schema)?;
293+
let ordering = LexOrdering::new([PhysicalSortExpr::new_default(col)])
294+
.expect("non-empty ordering");
295+
let split_points = vec![
296+
SplitPoint::new(vec![ScalarValue::Int64(Some(10))]),
297+
SplitPoint::new(vec![ScalarValue::Int64(Some(20))]),
298+
];
299+
let range = RangePartitioning::try_new(ordering, split_points)?;
300+
301+
Ok(PlanProperties::new(
302+
EquivalenceProperties::new(schema),
303+
Partitioning::Range(range),
304+
EmissionType::Incremental,
305+
Boundedness::Bounded,
306+
))
307+
}
308+
285309
#[test]
286310
fn test_round_trip_ffi_plan_properties() -> Result<()> {
287311
let original_props = create_test_props()?;
@@ -314,4 +338,22 @@ mod tests {
314338

315339
Ok(())
316340
}
341+
342+
#[test]
343+
fn test_round_trip_ffi_plan_properties_range_partitioning() -> Result<()> {
344+
let original_props = create_range_test_props()?;
345+
346+
let mut local_props_ptr = FFI_PlanProperties::from(&original_props);
347+
local_props_ptr.library_marker_id = crate::mock_foreign_marker_id;
348+
349+
let foreign_props: PlanProperties = local_props_ptr.try_into()?;
350+
351+
assert_eq!(
352+
format!("{:?}", foreign_props.output_partitioning()),
353+
format!("{:?}", original_props.output_partitioning())
354+
);
355+
assert_eq!(format!("{foreign_props:?}"), format!("{original_props:?}"));
356+
357+
Ok(())
358+
}
317359
}

0 commit comments

Comments
 (0)