Skip to content

Commit 775f7c0

Browse files
authored
feat: Support serializing Expr::RenameAlias (#22988)
1 parent 2995cdd commit 775f7c0

File tree

7 files changed

+82
-53
lines changed

7 files changed

+82
-53
lines changed

crates/polars-plan/dsl-schema.sha256

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4dbeeec6bffb6c93698545b09282c70a2c3849a3c5e36aabd19b484684269ef2
1+
d1baedde1bef7b2e9be40f5aae0a12f31ccbde329d03d0fab9b967f51da715d9

crates/polars-plan/src/dsl/expr/expr_dyn_fn.rs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -67,29 +67,6 @@ impl Default for SpecialEq<Arc<dyn BinaryUdfOutputField>> {
6767
}
6868
}
6969

70-
pub trait RenameAliasFn: Send + Sync {
71-
fn call(&self, name: &PlSmallStr) -> PolarsResult<PlSmallStr>;
72-
73-
fn try_serialize(&self, _buf: &mut Vec<u8>) -> PolarsResult<()> {
74-
polars_bail!(ComputeError: "serialization not supported for this renaming function")
75-
}
76-
}
77-
78-
impl<F> RenameAliasFn for F
79-
where
80-
F: Fn(&PlSmallStr) -> PolarsResult<PlSmallStr> + Send + Sync,
81-
{
82-
fn call(&self, name: &PlSmallStr) -> PolarsResult<PlSmallStr> {
83-
self(name)
84-
}
85-
}
86-
87-
impl Debug for dyn RenameAliasFn {
88-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
89-
write!(f, "RenameAliasFn")
90-
}
91-
}
92-
9370
#[derive(Clone)]
9471
/// Wrapper type that has special equality properties
9572
/// depending on the inner type specialization

crates/polars-plan/src/dsl/expr/mod.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use polars_compute::rolling::QuantileMethod;
88
use polars_core::chunked_array::cast::CastOptions;
99
use polars_core::error::feature_gated;
1010
use polars_core::prelude::*;
11+
use polars_utils::format_pl_smallstr;
1112
#[cfg(feature = "serde")]
1213
use serde::{Deserialize, Serialize};
1314
#[cfg(feature = "serde")]
@@ -182,9 +183,8 @@ pub enum Expr {
182183
/// `Expr::Wildcard`
183184
/// `Expr::Exclude`
184185
Selector(super::selector::Selector),
185-
#[cfg_attr(any(feature = "serde", feature = "dsl-schema"), serde(skip))]
186186
RenameAlias {
187-
function: SpecialEq<Arc<dyn RenameAliasFn>>,
187+
function: RenameAliasFn,
188188
expr: Arc<Expr>,
189189
},
190190
}
@@ -595,3 +595,51 @@ impl Operator {
595595
!(self.is_comparison_or_bitwise())
596596
}
597597
}
598+
599+
#[derive(Clone, PartialEq)]
600+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
601+
#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
602+
pub enum RenameAliasFn {
603+
Prefix(PlSmallStr),
604+
Suffix(PlSmallStr),
605+
ToLowercase,
606+
ToUppercase,
607+
#[cfg(feature = "python")]
608+
Python(Arc<polars_utils::python_function::PythonObject>),
609+
#[cfg_attr(any(feature = "serde", feature = "dsl-schema"), serde(skip))]
610+
Rust(SpecialEq<Arc<RenameAliasRustFn>>),
611+
}
612+
613+
impl RenameAliasFn {
614+
pub fn call(&self, name: &PlSmallStr) -> PolarsResult<PlSmallStr> {
615+
let out = match self {
616+
Self::Prefix(prefix) => format_pl_smallstr!("{prefix}{name}"),
617+
Self::Suffix(suffix) => format_pl_smallstr!("{name}{suffix}"),
618+
Self::ToLowercase => PlSmallStr::from_string(name.to_lowercase()),
619+
Self::ToUppercase => PlSmallStr::from_string(name.to_uppercase()),
620+
#[cfg(feature = "python")]
621+
Self::Python(lambda) => {
622+
let name = name.as_str();
623+
let out = pyo3::marker::Python::with_gil(|py| {
624+
let out: PlSmallStr = lambda
625+
.call1(py, (name,))?
626+
.extract::<std::borrow::Cow<str>>(py)?
627+
.as_ref()
628+
.into();
629+
pyo3::PyResult::<_>::Ok(out)
630+
});
631+
match out {
632+
Ok(out) => format_pl_smallstr!("{}", out),
633+
Err(e) => {
634+
polars_bail!(ComputeError: "Python function in 'name.map' produced an error: {e}.")
635+
},
636+
}
637+
},
638+
Self::Rust(f) => f(name)?,
639+
};
640+
Ok(out)
641+
}
642+
}
643+
644+
pub type RenameAliasRustFn =
645+
dyn Fn(&PlSmallStr) -> PolarsResult<PlSmallStr> + 'static + Send + Sync;

crates/polars-plan/src/dsl/name.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use polars_utils::format_pl_smallstr;
21
#[cfg(feature = "dtype-struct")]
32
use polars_utils::pl_str::PlSmallStr;
43

@@ -30,35 +29,54 @@ impl ExprNameNameSpace {
3029
where
3130
F: Fn(&PlSmallStr) -> PolarsResult<PlSmallStr> + 'static + Send + Sync,
3231
{
33-
let function = SpecialEq::new(Arc::new(function) as Arc<dyn RenameAliasFn>);
32+
let function = SpecialEq::new(Arc::new(function) as Arc<RenameAliasRustFn>);
3433
Expr::RenameAlias {
3534
expr: Arc::new(self.0),
36-
function,
35+
function: RenameAliasFn::Rust(function),
36+
}
37+
}
38+
39+
/// Define an alias by mapping a python lambda over the original root column name.
40+
#[cfg(feature = "python")]
41+
pub fn map_udf(self, function: polars_utils::python_function::PythonObject) -> Expr {
42+
Expr::RenameAlias {
43+
expr: Arc::new(self.0),
44+
function: RenameAliasFn::Python(Arc::new(function)),
3745
}
3846
}
3947

4048
/// Add a prefix to the root column name.
4149
pub fn prefix(self, prefix: &str) -> Expr {
42-
let prefix = prefix.to_string();
43-
self.map(move |name| Ok(format_pl_smallstr!("{prefix}{name}")))
50+
Expr::RenameAlias {
51+
expr: Arc::new(self.0),
52+
function: RenameAliasFn::Prefix(prefix.into()),
53+
}
4454
}
4555

4656
/// Add a suffix to the root column name.
4757
pub fn suffix(self, suffix: &str) -> Expr {
48-
let suffix = suffix.to_string();
49-
self.map(move |name| Ok(format_pl_smallstr!("{name}{suffix}")))
58+
Expr::RenameAlias {
59+
expr: Arc::new(self.0),
60+
function: RenameAliasFn::Suffix(suffix.into()),
61+
}
5062
}
5163

5264
/// Update the root column name to use lowercase characters.
5365
#[allow(clippy::wrong_self_convention)]
5466
pub fn to_lowercase(self) -> Expr {
55-
self.map(move |name| Ok(PlSmallStr::from_string(name.to_lowercase())))
67+
Expr::RenameAlias {
68+
expr: Arc::new(self.0),
69+
function: RenameAliasFn::ToLowercase,
70+
}
5671
}
5772

5873
/// Update the root column name to use uppercase characters.
5974
#[allow(clippy::wrong_self_convention)]
6075
pub fn to_uppercase(self) -> Expr {
61-
self.map(move |name| Ok(PlSmallStr::from_string(name.to_uppercase())))
76+
Expr::RenameAlias {
77+
expr: Arc::new(self.0),
78+
function: RenameAliasFn::ToUppercase,
79+
}
6280
}
6381

6482
#[cfg(feature = "dtype-struct")]

crates/polars-plan/src/dsl/plan.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use super::*;
4848
// - changing a name, type, or meaning of a field or an enum variant
4949
// - changing a default value of a field or a default enum variant
5050
// - restricting the range of allowed values a field can have
51-
pub static DSL_VERSION: (u16, u16) = (8, 0);
51+
pub static DSL_VERSION: (u16, u16) = (8, 1);
5252
static DSL_MAGIC_BYTES: &[u8] = b"DSL_VERSION";
5353

5454
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

crates/polars-python/src/expr/name.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use std::borrow::Cow;
22

33
use polars::prelude::*;
4-
use polars_utils::format_pl_smallstr;
5-
use polars_utils::pl_str::PlSmallStr;
4+
use polars_utils::python_function::PythonObject;
65
use pyo3::prelude::*;
76

87
use crate::PyExpr;
@@ -17,15 +16,7 @@ impl PyExpr {
1716
self.inner
1817
.clone()
1918
.name()
20-
.map(move |name| {
21-
let out = Python::with_gil(|py| lambda.call1(py, (name.as_str(),)));
22-
match out {
23-
Ok(out) => Ok(format_pl_smallstr!("{}", out)),
24-
Err(e) => Err(PolarsError::ComputeError(
25-
format!("Python function in 'name.map' produced an error: {e}.").into(),
26-
)),
27-
}
28-
})
19+
.map_udf(PythonObject(lambda))
2920
.into()
3021
}
3122

py-polars/tests/unit/cloud/test_prepare_cloud_plan.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,11 @@ def test_prepare_cloud_plan_fail_on_local_data_source(lf: pl.LazyFrame) -> None:
9393
@pytest.mark.parametrize(
9494
"lf",
9595
[
96-
pl.LazyFrame({"a": [{"x": 1, "y": 2}]})
97-
.select(pl.col("a").name.map(lambda x: x.upper()))
98-
.sink_parquet(DST, lazy=True),
9996
pl.LazyFrame({"a": [{"x": 1, "y": 2}]})
10097
.select(pl.col("a").name.map_fields(lambda x: x.upper()))
10198
.sink_parquet(DST, lazy=True),
10299
],
103100
)
104101
def test_prepare_cloud_plan_fail_on_serialization(lf: pl.LazyFrame) -> None:
105-
with pytest.raises(
106-
ComputeError, match="cannot be serialized|serialization not supported"
107-
):
102+
with pytest.raises(ComputeError, match="serialization not supported"):
108103
prepare_cloud_plan(lf)

0 commit comments

Comments
 (0)