1use std::ops::Range;
19use std::ptr::NonNull;
20use std::sync::Arc;
21
22use arrow::array::{Array, ArrayData, ArrayRef, make_array};
23use datafusion::arrow::datatypes::DataType;
24use datafusion::arrow::pyarrow::{FromPyArrow, PyArrowType, ToPyArrow};
25use datafusion::error::{DataFusionError, Result};
26use datafusion::logical_expr::function::{PartitionEvaluatorArgs, WindowUDFFieldArgs};
27use datafusion::logical_expr::window_state::WindowAggState;
28use datafusion::logical_expr::{
29 PartitionEvaluator, PartitionEvaluatorFactory, Signature, Volatility, WindowUDF, WindowUDFImpl,
30};
31use datafusion::scalar::ScalarValue;
32use datafusion_ffi::udwf::FFI_WindowUDF;
33use datafusion_python_util::parse_volatility;
34use pyo3::exceptions::PyValueError;
35use pyo3::prelude::*;
36use pyo3::types::{PyCapsule, PyList, PyTuple};
37
38use crate::common::data_type::PyScalarValue;
39use crate::errors::{PyDataFusionResult, to_datafusion_err};
40use crate::expr::PyExpr;
41
42#[derive(Debug)]
43struct RustPartitionEvaluator {
44 evaluator: Py<PyAny>,
45}
46
47impl RustPartitionEvaluator {
48 fn new(evaluator: Py<PyAny>) -> Self {
49 Self { evaluator }
50 }
51}
52
53impl PartitionEvaluator for RustPartitionEvaluator {
54 fn memoize(&mut self, _state: &mut WindowAggState) -> Result<()> {
55 Python::attach(|py| self.evaluator.bind(py).call_method0("memoize").map(|_| ()))
56 .map_err(|e| DataFusionError::Execution(format!("{e}")))
57 }
58
59 fn get_range(&self, idx: usize, n_rows: usize) -> Result<Range<usize>> {
60 Python::attach(|py| {
61 let py_args = vec![idx.into_pyobject(py)?, n_rows.into_pyobject(py)?];
62 let py_args = PyTuple::new(py, py_args)?;
63
64 self.evaluator
65 .bind(py)
66 .call_method1("get_range", py_args)
67 .and_then(|v| {
68 let tuple: Bound<'_, PyTuple> = v.extract()?;
69 if tuple.len() != 2 {
70 return Err(PyValueError::new_err(format!(
71 "Expected get_range to return tuple of length 2. Received length {}",
72 tuple.len()
73 )));
74 }
75
76 let start: usize = tuple.get_item(0).unwrap().extract()?;
77 let end: usize = tuple.get_item(1).unwrap().extract()?;
78
79 Ok(Range { start, end })
80 })
81 })
82 .map_err(|e| DataFusionError::Execution(format!("{e}")))
83 }
84
85 fn is_causal(&self) -> bool {
86 Python::attach(|py| {
87 self.evaluator
88 .bind(py)
89 .call_method0("is_causal")
90 .and_then(|v| v.extract())
91 .unwrap_or(false)
92 })
93 }
94
95 fn evaluate_all(&mut self, values: &[ArrayRef], num_rows: usize) -> Result<ArrayRef> {
96 Python::attach(|py| {
97 let py_values = PyList::new(
98 py,
99 values
100 .iter()
101 .map(|arg| arg.into_data().to_pyarrow(py).unwrap()),
102 )?;
103 let py_num_rows = num_rows.into_pyobject(py)?;
104 let py_args = PyTuple::new(py, vec![py_values.as_any(), &py_num_rows])?;
105
106 self.evaluator
107 .bind(py)
108 .call_method1("evaluate_all", py_args)
109 .map(|v| {
110 let array_data = ArrayData::from_pyarrow_bound(&v).unwrap();
111 make_array(array_data)
112 })
113 })
114 .map_err(to_datafusion_err)
115 }
116
117 fn evaluate(&mut self, values: &[ArrayRef], range: &Range<usize>) -> Result<ScalarValue> {
118 Python::attach(|py| {
119 let py_values = PyList::new(
120 py,
121 values
122 .iter()
123 .map(|arg| arg.into_data().to_pyarrow(py).unwrap()),
124 )?;
125 let range_tuple = PyTuple::new(py, vec![range.start, range.end])?;
126 let py_args = PyTuple::new(py, vec![py_values.as_any(), range_tuple.as_any()])?;
127
128 self.evaluator
129 .bind(py)
130 .call_method1("evaluate", py_args)
131 .and_then(|v| v.extract::<PyScalarValue>())
132 .map(|v| v.0)
133 })
134 .map_err(to_datafusion_err)
135 }
136
137 fn evaluate_all_with_rank(
138 &self,
139 num_rows: usize,
140 ranks_in_partition: &[Range<usize>],
141 ) -> Result<ArrayRef> {
142 Python::attach(|py| {
143 let ranks = ranks_in_partition
144 .iter()
145 .map(|r| PyTuple::new(py, vec![r.start, r.end]))
146 .collect::<PyResult<Vec<_>>>()?;
147
148 let py_args = vec![
150 num_rows.into_pyobject(py)?.into_any(),
151 PyList::new(py, ranks)?.into_any(),
152 ];
153
154 let py_args = PyTuple::new(py, py_args)?;
155
156 self.evaluator
158 .bind(py)
159 .call_method1("evaluate_all_with_rank", py_args)
160 .map(|v| {
161 let array_data = ArrayData::from_pyarrow_bound(&v).unwrap();
162 make_array(array_data)
163 })
164 })
165 .map_err(to_datafusion_err)
166 }
167
168 fn supports_bounded_execution(&self) -> bool {
169 Python::attach(|py| {
170 self.evaluator
171 .bind(py)
172 .call_method0("supports_bounded_execution")
173 .and_then(|v| v.extract())
174 .unwrap_or(false)
175 })
176 }
177
178 fn uses_window_frame(&self) -> bool {
179 Python::attach(|py| {
180 self.evaluator
181 .bind(py)
182 .call_method0("uses_window_frame")
183 .and_then(|v| v.extract())
184 .unwrap_or(false)
185 })
186 }
187
188 fn include_rank(&self) -> bool {
189 Python::attach(|py| {
190 self.evaluator
191 .bind(py)
192 .call_method0("include_rank")
193 .and_then(|v| v.extract())
194 .unwrap_or(false)
195 })
196 }
197}
198
199fn instantiate_partition_evaluator(evaluator: &Py<PyAny>) -> Result<Box<dyn PartitionEvaluator>> {
200 let instance = Python::attach(|py| {
201 evaluator
202 .call0(py)
203 .map_err(|e| DataFusionError::Execution(e.to_string()))
204 })?;
205 Ok(Box::new(RustPartitionEvaluator::new(instance)))
206}
207
208pub fn to_rust_partition_evaluator(evaluator: Py<PyAny>) -> PartitionEvaluatorFactory {
216 Arc::new(move || instantiate_partition_evaluator(&evaluator))
217}
218
219#[pyclass(
221 from_py_object,
222 frozen,
223 name = "WindowUDF",
224 module = "datafusion",
225 subclass
226)]
227#[derive(Debug, Clone)]
228pub struct PyWindowUDF {
229 pub(crate) function: WindowUDF,
230}
231
232#[pymethods]
233impl PyWindowUDF {
234 #[new]
235 #[pyo3(signature=(name, evaluator, input_types, return_type, volatility))]
236 fn new(
237 name: &str,
238 evaluator: Py<PyAny>,
239 input_types: Vec<PyArrowType<DataType>>,
240 return_type: PyArrowType<DataType>,
241 volatility: &str,
242 ) -> PyResult<Self> {
243 let return_type = return_type.0;
244 let input_types: Vec<DataType> = input_types.into_iter().map(|t| t.0).collect();
245
246 let function = WindowUDF::from(PythonFunctionWindowUDF::new(
247 name,
248 evaluator,
249 input_types,
250 return_type,
251 parse_volatility(volatility)?,
252 ));
253 Ok(Self { function })
254 }
255
256 #[pyo3(signature = (*args))]
258 fn __call__(&self, args: Vec<PyExpr>) -> PyResult<PyExpr> {
259 let args = args.iter().map(|e| e.expr.clone()).collect();
260 Ok(self.function.call(args).into())
261 }
262
263 #[staticmethod]
264 pub fn from_pycapsule(func: Bound<'_, PyAny>) -> PyDataFusionResult<Self> {
265 let capsule = if func.hasattr("__datafusion_window_udf__")? {
266 func.getattr("__datafusion_window_udf__")?.call0()?
267 } else {
268 func
269 };
270
271 let capsule = capsule.cast::<PyCapsule>().map_err(to_datafusion_err)?;
272 let data: NonNull<FFI_WindowUDF> = capsule
273 .pointer_checked(Some(c"datafusion_window_udf"))?
274 .cast();
275 let udwf = unsafe { data.as_ref() };
276 let udwf: Arc<dyn WindowUDFImpl> = udwf.into();
277
278 Ok(Self {
279 function: WindowUDF::new_from_shared_impl(udwf),
280 })
281 }
282
283 fn __repr__(&self) -> PyResult<String> {
284 Ok(format!("WindowUDF({})", self.function.name()))
285 }
286
287 #[getter]
288 fn name(&self) -> &str {
289 self.function.name()
290 }
291}
292
293#[derive(Debug)]
300pub struct PythonFunctionWindowUDF {
301 name: String,
302 evaluator: Py<PyAny>,
303 signature: Signature,
304 return_type: DataType,
305}
306
307pub type MultiColumnWindowUDF = PythonFunctionWindowUDF;
310
311impl PythonFunctionWindowUDF {
312 pub fn new(
313 name: impl Into<String>,
314 evaluator: Py<PyAny>,
315 input_types: Vec<DataType>,
316 return_type: DataType,
317 volatility: Volatility,
318 ) -> Self {
319 let name = name.into();
320 let signature = Signature::exact(input_types, volatility);
321 Self {
322 name,
323 evaluator,
324 signature,
325 return_type,
326 }
327 }
328
329 pub(crate) fn evaluator(&self) -> &Py<PyAny> {
333 &self.evaluator
334 }
335
336 pub(crate) fn return_type(&self) -> &DataType {
337 &self.return_type
338 }
339}
340
341impl Eq for PythonFunctionWindowUDF {}
342impl PartialEq for PythonFunctionWindowUDF {
343 fn eq(&self, other: &Self) -> bool {
344 self.name == other.name
345 && self.signature == other.signature
346 && self.return_type == other.return_type
347 && (self.evaluator.as_ptr() == other.evaluator.as_ptr()
351 || Python::attach(|py| {
352 self.evaluator
358 .bind(py)
359 .eq(other.evaluator.bind(py))
360 .unwrap_or_else(|e| {
361 log::debug!(
362 target: "datafusion_python::udwf",
363 "PythonFunctionWindowUDF {:?} __eq__ raised; treating as unequal: {e}",
364 self.name,
365 );
366 false
367 })
368 }))
369 }
370}
371
372impl std::hash::Hash for PythonFunctionWindowUDF {
373 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
374 self.name.hash(state);
378 self.signature.hash(state);
379 self.return_type.hash(state);
380 }
381}
382
383impl WindowUDFImpl for PythonFunctionWindowUDF {
384 fn name(&self) -> &str {
385 &self.name
386 }
387
388 fn signature(&self) -> &Signature {
389 &self.signature
390 }
391
392 fn field(&self, field_args: WindowUDFFieldArgs) -> Result<arrow::datatypes::FieldRef> {
393 Ok(arrow::datatypes::Field::new(field_args.name(), self.return_type.clone(), true).into())
395 }
396
397 fn partition_evaluator(
399 &self,
400 _partition_evaluator_args: PartitionEvaluatorArgs,
401 ) -> Result<Box<dyn PartitionEvaluator>> {
402 instantiate_partition_evaluator(&self.evaluator)
403 }
404}