1use std::ptr::NonNull;
19use std::sync::Arc;
20
21use datafusion::arrow::array::ArrayRef;
22use datafusion::arrow::datatypes::{DataType, Field, FieldRef};
23use datafusion::arrow::pyarrow::{PyArrowType, ToPyArrow};
24use datafusion::common::ScalarValue;
25use datafusion::error::{DataFusionError, Result};
26use datafusion::logical_expr::function::{AccumulatorArgs, StateFieldsArgs};
27use datafusion::logical_expr::{
28 Accumulator, AccumulatorFactoryFunction, AggregateUDF, AggregateUDFImpl, Signature, Volatility,
29};
30use datafusion_ffi::udaf::FFI_AggregateUDF;
31use datafusion_python_util::parse_volatility;
32use pyo3::prelude::*;
33use pyo3::types::{PyCapsule, PyTuple};
34
35use crate::common::data_type::PyScalarValue;
36use crate::errors::{PyDataFusionResult, py_datafusion_err, to_datafusion_err};
37use crate::expr::PyExpr;
38
39#[derive(Debug)]
40struct RustAccumulator {
41 accum: Py<PyAny>,
42}
43
44impl RustAccumulator {
45 fn new(accum: Py<PyAny>) -> Self {
46 Self { accum }
47 }
48}
49
50impl Accumulator for RustAccumulator {
51 fn state(&mut self) -> Result<Vec<ScalarValue>> {
52 Python::attach(|py| -> PyResult<Vec<ScalarValue>> {
53 let values = self.accum.bind(py).call_method0("state")?;
54 let mut scalars = Vec::new();
55 for item in values.try_iter()? {
56 let item: Bound<'_, PyAny> = item?;
57 let scalar = item.extract::<PyScalarValue>()?.0;
58 scalars.push(scalar);
59 }
60 Ok(scalars)
61 })
62 .map_err(|e| DataFusionError::Execution(format!("{e}")))
63 }
64
65 fn evaluate(&mut self) -> Result<ScalarValue> {
66 Python::attach(|py| -> PyResult<ScalarValue> {
67 let value = self.accum.bind(py).call_method0("evaluate")?;
68 value.extract::<PyScalarValue>().map(|v| v.0)
69 })
70 .map_err(|e| DataFusionError::Execution(format!("{e}")))
71 }
72
73 fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
74 Python::attach(|py| {
75 let py_args = values
77 .iter()
78 .map(|arg| arg.to_data().to_pyarrow(py).unwrap())
79 .collect::<Vec<_>>();
80 let py_args = PyTuple::new(py, py_args).map_err(to_datafusion_err)?;
81
82 self.accum
84 .bind(py)
85 .call_method1("update", py_args)
86 .map_err(|e| DataFusionError::Execution(format!("{e}")))?;
87
88 Ok(())
89 })
90 }
91
92 fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
93 Python::attach(|py| {
94 let py_states: Result<Vec<Bound<'_, PyAny>>> = states
96 .iter()
97 .map(|state| {
98 state
99 .to_data()
100 .to_pyarrow(py)
101 .map_err(|e| DataFusionError::Execution(format!("{e}")))
102 })
103 .collect();
104
105 self.accum
107 .bind(py)
108 .call_method1("merge", (py_states?,))
109 .map_err(|e| DataFusionError::Execution(format!("{e}")))?;
110
111 Ok(())
112 })
113 }
114
115 fn size(&self) -> usize {
116 std::mem::size_of_val(self)
117 }
118
119 fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
120 Python::attach(|py| {
121 let py_args = values
123 .iter()
124 .map(|arg| arg.to_data().to_pyarrow(py).unwrap())
125 .collect::<Vec<_>>();
126 let py_args = PyTuple::new(py, py_args).map_err(to_datafusion_err)?;
127
128 self.accum
130 .bind(py)
131 .call_method1("retract_batch", py_args)
132 .map_err(|e| DataFusionError::Execution(format!("{e}")))?;
133
134 Ok(())
135 })
136 }
137
138 fn supports_retract_batch(&self) -> bool {
139 Python::attach(
140 |py| match self.accum.bind(py).call_method0("supports_retract_batch") {
141 Ok(x) => x.extract().unwrap_or(false),
142 Err(_) => false,
143 },
144 )
145 }
146}
147
148fn instantiate_accumulator(accum: &Py<PyAny>) -> Result<Box<dyn Accumulator>> {
149 let instance = Python::attach(|py| {
150 accum
151 .call0(py)
152 .map_err(|e| DataFusionError::Execution(format!("{e}")))
153 })?;
154 Ok(Box::new(RustAccumulator::new(instance)))
155}
156
157pub fn to_rust_accumulator(accum: Py<PyAny>) -> AccumulatorFactoryFunction {
165 Arc::new(move |_args| instantiate_accumulator(&accum))
166}
167
168#[derive(Debug)]
172pub(crate) struct PythonFunctionAggregateUDF {
173 name: String,
174 accumulator: Py<PyAny>,
175 signature: Signature,
176 return_type: DataType,
177 state_fields: Vec<FieldRef>,
178}
179
180impl PythonFunctionAggregateUDF {
181 fn new(
182 name: String,
183 accumulator: Py<PyAny>,
184 input_types: Vec<DataType>,
185 return_type: DataType,
186 state_types: Vec<DataType>,
187 volatility: Volatility,
188 ) -> Self {
189 let signature = Signature::exact(input_types, volatility);
190 let state_fields = state_types
191 .into_iter()
192 .enumerate()
193 .map(|(i, t)| Arc::new(Field::new(format!("state_{i}"), t, true)))
194 .collect();
195 Self {
196 name,
197 accumulator,
198 signature,
199 return_type,
200 state_fields,
201 }
202 }
203
204 pub(crate) fn accumulator(&self) -> &Py<PyAny> {
208 &self.accumulator
209 }
210
211 pub(crate) fn return_type(&self) -> &DataType {
212 &self.return_type
213 }
214
215 pub(crate) fn state_fields_ref(&self) -> &[FieldRef] {
216 &self.state_fields
217 }
218
219 pub(crate) fn from_parts(
227 name: String,
228 accumulator: Py<PyAny>,
229 input_types: Vec<DataType>,
230 return_type: DataType,
231 state_fields: Vec<FieldRef>,
232 volatility: Volatility,
233 ) -> Self {
234 Self {
235 name,
236 accumulator,
237 signature: Signature::exact(input_types, volatility),
238 return_type,
239 state_fields,
240 }
241 }
242}
243
244impl Eq for PythonFunctionAggregateUDF {}
245impl PartialEq for PythonFunctionAggregateUDF {
246 fn eq(&self, other: &Self) -> bool {
247 self.name == other.name
248 && self.signature == other.signature
249 && self.return_type == other.return_type
250 && self.state_fields == other.state_fields
251 && (self.accumulator.as_ptr() == other.accumulator.as_ptr()
255 || Python::attach(|py| {
256 self.accumulator
262 .bind(py)
263 .eq(other.accumulator.bind(py))
264 .unwrap_or_else(|e| {
265 log::debug!(
266 target: "datafusion_python::udaf",
267 "PythonFunctionAggregateUDF {:?} __eq__ raised; treating as unequal: {e}",
268 self.name,
269 );
270 false
271 })
272 }))
273 }
274}
275
276impl std::hash::Hash for PythonFunctionAggregateUDF {
277 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
278 self.name.hash(state);
282 self.signature.hash(state);
283 self.return_type.hash(state);
284 for f in &self.state_fields {
285 f.hash(state);
286 }
287 }
288}
289
290impl AggregateUDFImpl for PythonFunctionAggregateUDF {
291 fn name(&self) -> &str {
292 &self.name
293 }
294
295 fn signature(&self) -> &Signature {
296 &self.signature
297 }
298
299 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
300 Ok(self.return_type.clone())
301 }
302
303 fn accumulator(&self, _acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
304 instantiate_accumulator(&self.accumulator)
305 }
306
307 fn state_fields(&self, _args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
308 Ok(self.state_fields.clone())
309 }
310}
311
312fn aggregate_udf_from_capsule(capsule: &Bound<'_, PyCapsule>) -> PyDataFusionResult<AggregateUDF> {
313 let data: NonNull<FFI_AggregateUDF> = capsule
314 .pointer_checked(Some(c"datafusion_aggregate_udf"))?
315 .cast();
316 let udaf = unsafe { data.as_ref() };
317 let udaf: Arc<dyn AggregateUDFImpl> = udaf.into();
318
319 Ok(AggregateUDF::new_from_shared_impl(udaf))
320}
321
322#[pyclass(
324 from_py_object,
325 frozen,
326 name = "AggregateUDF",
327 module = "datafusion",
328 subclass
329)]
330#[derive(Debug, Clone)]
331pub struct PyAggregateUDF {
332 pub(crate) function: AggregateUDF,
333}
334
335#[pymethods]
336impl PyAggregateUDF {
337 #[new]
338 #[pyo3(signature=(name, accumulator, input_type, return_type, state_type, volatility))]
339 fn new(
340 name: &str,
341 accumulator: Py<PyAny>,
342 input_type: PyArrowType<Vec<DataType>>,
343 return_type: PyArrowType<DataType>,
344 state_type: PyArrowType<Vec<DataType>>,
345 volatility: &str,
346 ) -> PyResult<Self> {
347 let py_udf = PythonFunctionAggregateUDF::new(
348 name.to_string(),
349 accumulator,
350 input_type.0,
351 return_type.0,
352 state_type.0,
353 parse_volatility(volatility)?,
354 );
355 let function = AggregateUDF::new_from_impl(py_udf);
356 Ok(Self { function })
357 }
358
359 #[staticmethod]
360 pub fn from_pycapsule(func: Bound<'_, PyAny>) -> PyDataFusionResult<Self> {
361 if func.is_instance_of::<PyCapsule>() {
362 let capsule = func.cast::<PyCapsule>().map_err(py_datafusion_err)?;
363 let function = aggregate_udf_from_capsule(capsule)?;
364 return Ok(Self { function });
365 }
366
367 if func.hasattr("__datafusion_aggregate_udf__")? {
368 let capsule = func.getattr("__datafusion_aggregate_udf__")?.call0()?;
369 let capsule = capsule.cast::<PyCapsule>().map_err(py_datafusion_err)?;
370 let function = aggregate_udf_from_capsule(capsule)?;
371 return Ok(Self { function });
372 }
373
374 Err(crate::errors::PyDataFusionError::Common(
375 "__datafusion_aggregate_udf__ does not exist on AggregateUDF object.".to_string(),
376 ))
377 }
378
379 #[pyo3(signature = (*args))]
381 fn __call__(&self, args: Vec<PyExpr>) -> PyResult<PyExpr> {
382 let args = args.iter().map(|e| e.expr.clone()).collect();
383 Ok(self.function.call(args).into())
384 }
385
386 fn __repr__(&self) -> PyResult<String> {
387 Ok(format!("AggregateUDF({})", self.function.name()))
388 }
389
390 #[getter]
391 fn name(&self) -> &str {
392 self.function.name()
393 }
394}