Skip to content

Commit 75f7f7b

Browse files
timsaucerclaude
andcommitted
feat: wrap higher-order, lambda, and lambda-variable Expr variants
DataFusion 54 introduces Expr::HigherOrderFunction, Expr::Lambda, and Expr::LambdaVariable. PyExpr::to_variant previously errored on each with py_unsupported_variant_err. Add PyHigherOrderFunction, PyLambda, and PyLambdaVariable wrappers, register them in the expr pymodule and re-export from python/datafusion/expr.py, and dispatch to_variant to the new wrappers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 331425c commit 75f7f7b

5 files changed

Lines changed: 249 additions & 9 deletions

File tree

crates/core/src/expr.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,12 @@ pub mod explain;
8585
pub mod extension;
8686
pub mod filter;
8787
pub mod grouping_set;
88+
pub mod higher_order_function;
8889
pub mod in_list;
8990
pub mod in_subquery;
9091
pub mod join;
92+
pub mod lambda;
93+
pub mod lambda_variable;
9194
pub mod like;
9295
pub mod limit;
9396
pub mod literal;
@@ -220,15 +223,14 @@ impl PyExpr {
220223
Expr::SetComparison(value) => {
221224
Ok(set_comparison::PySetComparison::from(value.clone()).into_bound_py_any(py)?)
222225
}
223-
Expr::HigherOrderFunction(value) => Err(py_unsupported_variant_err(format!(
224-
"Converting Expr::HigherOrderFunction to a Python object is not implemented: {value:?}"
225-
))),
226-
Expr::Lambda(value) => Err(py_unsupported_variant_err(format!(
227-
"Converting Expr::Lambda to a Python object is not implemented: {value:?}"
228-
))),
229-
Expr::LambdaVariable(value) => Err(py_unsupported_variant_err(format!(
230-
"Converting Expr::LambdaVariable to a Python object is not implemented: {value:?}"
231-
))),
226+
Expr::HigherOrderFunction(value) => Ok(
227+
higher_order_function::PyHigherOrderFunction::from(value.clone())
228+
.into_bound_py_any(py)?,
229+
),
230+
Expr::Lambda(value) => Ok(lambda::PyLambda::from(value.clone()).into_bound_py_any(py)?),
231+
Expr::LambdaVariable(value) => {
232+
Ok(lambda_variable::PyLambdaVariable::from(value.clone()).into_bound_py_any(py)?)
233+
}
232234
})
233235
}
234236

@@ -860,6 +862,9 @@ pub(crate) fn init_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
860862
m.add_class::<union::PyUnion>()?;
861863
m.add_class::<unnest::PyUnnest>()?;
862864
m.add_class::<unnest_expr::PyUnnestExpr>()?;
865+
m.add_class::<higher_order_function::PyHigherOrderFunction>()?;
866+
m.add_class::<lambda::PyLambda>()?;
867+
m.add_class::<lambda_variable::PyLambdaVariable>()?;
863868
m.add_class::<extension::PyExtension>()?;
864869
m.add_class::<filter::PyFilter>()?;
865870
m.add_class::<projection::PyProjection>()?;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use std::fmt::{self, Display, Formatter};
19+
20+
use datafusion::logical_expr::expr::HigherOrderFunction;
21+
use pyo3::prelude::*;
22+
23+
use super::PyExpr;
24+
25+
#[pyclass(
26+
from_py_object,
27+
frozen,
28+
name = "HigherOrderFunction",
29+
module = "datafusion.expr",
30+
subclass
31+
)]
32+
#[derive(Clone)]
33+
pub struct PyHigherOrderFunction {
34+
higher_order: HigherOrderFunction,
35+
}
36+
37+
impl From<HigherOrderFunction> for PyHigherOrderFunction {
38+
fn from(higher_order: HigherOrderFunction) -> PyHigherOrderFunction {
39+
PyHigherOrderFunction { higher_order }
40+
}
41+
}
42+
43+
impl From<PyHigherOrderFunction> for HigherOrderFunction {
44+
fn from(higher_order: PyHigherOrderFunction) -> Self {
45+
higher_order.higher_order
46+
}
47+
}
48+
49+
impl Display for PyHigherOrderFunction {
50+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
51+
write!(
52+
f,
53+
"HigherOrderFunction(name={}, args={:?})",
54+
self.higher_order.name(),
55+
&self.higher_order.args,
56+
)
57+
}
58+
}
59+
60+
#[pymethods]
61+
impl PyHigherOrderFunction {
62+
/// Name of the higher-order function being invoked.
63+
fn name(&self) -> String {
64+
self.higher_order.name().to_string()
65+
}
66+
67+
/// Arguments passed to the higher-order function. Some entries may be
68+
/// `Lambda` expressions; others are ordinary value expressions.
69+
fn args(&self) -> Vec<PyExpr> {
70+
self.higher_order
71+
.args
72+
.iter()
73+
.map(|e| PyExpr::from(e.clone()))
74+
.collect()
75+
}
76+
77+
fn __repr__(&self) -> PyResult<String> {
78+
Ok(format!("HigherOrderFunction({self})"))
79+
}
80+
81+
fn __name__(&self) -> PyResult<String> {
82+
Ok("HigherOrderFunction".to_string())
83+
}
84+
}

crates/core/src/expr/lambda.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use std::fmt::{self, Display, Formatter};
19+
20+
use datafusion::logical_expr::expr::Lambda;
21+
use pyo3::prelude::*;
22+
23+
use super::PyExpr;
24+
25+
#[pyclass(
26+
from_py_object,
27+
frozen,
28+
name = "Lambda",
29+
module = "datafusion.expr",
30+
subclass
31+
)]
32+
#[derive(Clone)]
33+
pub struct PyLambda {
34+
lambda: Lambda,
35+
}
36+
37+
impl From<Lambda> for PyLambda {
38+
fn from(lambda: Lambda) -> PyLambda {
39+
PyLambda { lambda }
40+
}
41+
}
42+
43+
impl From<PyLambda> for Lambda {
44+
fn from(lambda: PyLambda) -> Self {
45+
lambda.lambda
46+
}
47+
}
48+
49+
impl Display for PyLambda {
50+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
51+
write!(
52+
f,
53+
"Lambda(params={:?}, body={:?})",
54+
&self.lambda.params, &self.lambda.body,
55+
)
56+
}
57+
}
58+
59+
#[pymethods]
60+
impl PyLambda {
61+
/// Parameter names of the lambda.
62+
fn params(&self) -> Vec<String> {
63+
self.lambda.params.clone()
64+
}
65+
66+
/// Body expression of the lambda.
67+
fn body(&self) -> PyExpr {
68+
(*self.lambda.body).clone().into()
69+
}
70+
71+
fn __repr__(&self) -> PyResult<String> {
72+
Ok(format!("Lambda({self})"))
73+
}
74+
75+
fn __name__(&self) -> PyResult<String> {
76+
Ok("Lambda".to_string())
77+
}
78+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use std::fmt::{self, Display, Formatter};
19+
20+
use datafusion::logical_expr::expr::LambdaVariable;
21+
use pyo3::prelude::*;
22+
23+
#[pyclass(
24+
from_py_object,
25+
frozen,
26+
name = "LambdaVariable",
27+
module = "datafusion.expr",
28+
subclass
29+
)]
30+
#[derive(Clone)]
31+
pub struct PyLambdaVariable {
32+
variable: LambdaVariable,
33+
}
34+
35+
impl From<LambdaVariable> for PyLambdaVariable {
36+
fn from(variable: LambdaVariable) -> PyLambdaVariable {
37+
PyLambdaVariable { variable }
38+
}
39+
}
40+
41+
impl From<PyLambdaVariable> for LambdaVariable {
42+
fn from(variable: PyLambdaVariable) -> Self {
43+
variable.variable
44+
}
45+
}
46+
47+
impl Display for PyLambdaVariable {
48+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
49+
write!(f, "LambdaVariable({})", &self.variable.name)
50+
}
51+
}
52+
53+
#[pymethods]
54+
impl PyLambdaVariable {
55+
/// Reference name of the lambda parameter.
56+
fn name(&self) -> String {
57+
self.variable.name.clone()
58+
}
59+
60+
fn __repr__(&self) -> PyResult<String> {
61+
Ok(format!("LambdaVariable({self})"))
62+
}
63+
64+
fn __name__(&self) -> PyResult<String> {
65+
Ok("LambdaVariable".to_string())
66+
}
67+
}

python/datafusion/expr.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@
149149
TransactionStart = expr_internal.TransactionStart
150150
TryCast = expr_internal.TryCast
151151
Union = expr_internal.Union
152+
HigherOrderFunction = expr_internal.HigherOrderFunction
153+
Lambda = expr_internal.Lambda
154+
LambdaVariable = expr_internal.LambdaVariable
152155
Unnest = expr_internal.Unnest
153156
UnnestExpr = expr_internal.UnnestExpr
154157
Values = expr_internal.Values
@@ -192,6 +195,7 @@
192195
"FileType",
193196
"Filter",
194197
"GroupingSet",
198+
"HigherOrderFunction",
195199
"ILike",
196200
"InList",
197201
"InSubquery",
@@ -206,6 +210,8 @@
206210
"Join",
207211
"JoinConstraint",
208212
"JoinType",
213+
"Lambda",
214+
"LambdaVariable",
209215
"Like",
210216
"Limit",
211217
"Literal",

0 commit comments

Comments
 (0)