Skip to content

Commit 0a11b85

Browse files
committed
Added serialize and deserialize methods to Expr
1 parent f9c0f34 commit 0a11b85

File tree

7 files changed

+91
-2
lines changed

7 files changed

+91
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 0.21.2 (unreleased)
22

33
- Added `serialize` and `deserialize` methods to `LazyFrame`
4+
- Added `serialize` and `deserialize` methods to `Expr`
45
- Added `storage_options` and `retries` options to `sink_ipc` method
56
- Added experimental support for Iceberg
67
- Added experimental `cast_options` option to `scan_parquet` method

ext/polars/src/expr/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod meta;
1010
mod name;
1111
mod rolling;
1212
pub mod selector;
13+
mod serde;
1314
mod string;
1415
mod r#struct;
1516

ext/polars/src/expr/serde.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use std::io::{BufReader, BufWriter};
2+
3+
use magnus::Value;
4+
use polars::lazy::prelude::Expr;
5+
use polars_utils::pl_serialize;
6+
7+
use crate::exceptions::ComputeError;
8+
use crate::file::get_file_like;
9+
use crate::{RbExpr, RbResult};
10+
11+
impl RbExpr {
12+
pub fn serialize_binary(&self, rb_f: Value) -> RbResult<()> {
13+
let file = get_file_like(rb_f, true)?;
14+
let writer = BufWriter::new(file);
15+
pl_serialize::SerializeOptions::default()
16+
.serialize_into_writer::<_, _, true>(writer, &self.inner)
17+
.map_err(|err| ComputeError::new_err(err.to_string()))
18+
}
19+
20+
pub fn deserialize_binary(rb_f: Value) -> RbResult<RbExpr> {
21+
let file = get_file_like(rb_f, false)?;
22+
let reader = BufReader::new(file);
23+
let expr: Expr = pl_serialize::SerializeOptions::default()
24+
.deserialize_from_reader::<_, _, true>(reader)
25+
.map_err(|err| ComputeError::new_err(err.to_string()))?;
26+
Ok(expr.into())
27+
}
28+
}

ext/polars/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,11 @@ fn init(ruby: &Ruby) -> RbResult<()> {
578578
class.define_method("hist", method!(RbExpr::hist, 4))?;
579579
class.define_method("into_selector", method!(RbExpr::into_selector, 0))?;
580580
class.define_singleton_method("new_selector", function!(RbExpr::new_selector, 1))?;
581+
class.define_method("serialize_binary", method!(RbExpr::serialize_binary, 1))?;
582+
class.define_singleton_method(
583+
"deserialize_binary",
584+
function!(RbExpr::deserialize_binary, 1),
585+
)?;
581586

582587
// bitwise
583588
class.define_method("bitwise_count_ones", method!(RbExpr::bitwise_count_ones, 0))?;

lib/polars/expr.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,38 @@ def -@
146146
wrap_expr(_rbexpr.neg)
147147
end
148148

149+
# Read a serialized expression from a file.
150+
#
151+
# @param source [Object]
152+
# Path to a file or a file-like object (by file-like object, we refer to
153+
# objects that have a `read` method, such as a file handler or `StringIO`).
154+
#
155+
# @return [Expr]
156+
#
157+
# @note
158+
# This function uses marshaling if the logical plan contains Ruby UDFs,
159+
# and as such inherits the security implications. Deserializing can execute
160+
# arbitrary code, so it should only be attempted on trusted data.
161+
#
162+
# @note
163+
# Serialization is not stable across Polars versions: a LazyFrame serialized
164+
# in one Polars version may not be deserializable in another Polars version.
165+
#
166+
# @example
167+
# expr = Polars.col("foo").sum.over("bar")
168+
# bytes = expr.meta.serialize
169+
# Polars::Expr.deserialize(StringIO.new(bytes))
170+
# # => col("foo").sum().over([col("bar")])
171+
def self.deserialize(source)
172+
if Utils.pathlike?(source)
173+
source = Utils.normalize_filepath(source)
174+
end
175+
176+
deserializer = RbExpr.method(:deserialize_binary)
177+
178+
_from_rbexpr(deserializer.(source))
179+
end
180+
149181
# Cast to physical representation of the logical dtype.
150182
#
151183
# - `:date` -> `:i32`

lib/polars/lazy_frame.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ def self.read_json(file)
4545
#
4646
# @param source [Object]
4747
# Path to a file or a file-like object (by file-like object, we refer to
48-
# objects that have a `read` method, such as a file handler (e.g.
49-
# via builtin `open` function) or `StringIO`).
48+
# objects that have a `read` method, such as a file handler or `StringIO`).
5049
#
5150
# @return [LazyFrame]
5251
#

lib/polars/meta_expr.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,29 @@ def as_selector
248248
Selector._from_rbselector(_rbexpr.into_selector)
249249
end
250250

251+
# Serialize this expression to a file or string.
252+
#
253+
# @param file [Object]
254+
# File path to which the result should be written. If set to `nil`
255+
# (default), the output is returned as a string instead.
256+
#
257+
# @return [Object]
258+
#
259+
# @note
260+
# Serialization is not stable across Polars versions: a LazyFrame serialized
261+
# in one Polars version may not be deserializable in another Polars version.
262+
#
263+
# @example Serialize the expression into a binary representation.
264+
# expr = Polars.col("foo").sum.over("bar")
265+
# bytes = expr.meta.serialize
266+
# Polars::Expr.deserialize(StringIO.new(bytes))
267+
# # => col("foo").sum().over([col("bar")])
268+
def serialize(file = nil)
269+
serializer = _rbexpr.method(:serialize_binary)
270+
271+
Utils.serialize_polars_object(serializer, file)
272+
end
273+
251274
# Format the expression as a tree.
252275
#
253276
# @param return_as_string [Boolean]

0 commit comments

Comments
 (0)