polars-df 0.15.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +274 -0
- data/Cargo.lock +1465 -867
- data/Cargo.toml +3 -0
- data/LICENSE.txt +2 -2
- data/README.md +87 -37
- data/ext/polars/Cargo.toml +47 -16
- data/ext/polars/src/c_api/allocator.rs +7 -0
- data/ext/polars/src/c_api/mod.rs +1 -0
- data/ext/polars/src/catalog/mod.rs +1 -0
- data/ext/polars/src/catalog/unity.rs +470 -0
- data/ext/polars/src/conversion/any_value.rs +99 -84
- data/ext/polars/src/conversion/categorical.rs +30 -0
- data/ext/polars/src/conversion/chunked_array.rs +71 -62
- data/ext/polars/src/conversion/datetime.rs +63 -0
- data/ext/polars/src/conversion/mod.rs +796 -312
- data/ext/polars/src/dataframe/construction.rs +6 -18
- data/ext/polars/src/dataframe/export.rs +30 -39
- data/ext/polars/src/dataframe/general.rs +294 -362
- data/ext/polars/src/dataframe/io.rs +33 -150
- data/ext/polars/src/dataframe/map.rs +175 -0
- data/ext/polars/src/dataframe/mod.rs +37 -5
- data/ext/polars/src/dataframe/serde.rs +23 -8
- data/ext/polars/src/error.rs +44 -7
- data/ext/polars/src/exceptions.rs +21 -8
- data/ext/polars/src/expr/array.rs +86 -22
- data/ext/polars/src/expr/binary.rs +50 -1
- data/ext/polars/src/expr/bitwise.rs +39 -0
- data/ext/polars/src/expr/categorical.rs +20 -0
- data/ext/polars/src/expr/datatype.rs +51 -0
- data/ext/polars/src/expr/datetime.rs +99 -41
- data/ext/polars/src/expr/extension.rs +12 -0
- data/ext/polars/src/expr/general.rs +252 -128
- data/ext/polars/src/expr/list.rs +56 -60
- data/ext/polars/src/expr/meta.rs +30 -35
- data/ext/polars/src/expr/mod.rs +28 -6
- data/ext/polars/src/expr/name.rs +29 -14
- data/ext/polars/src/expr/rolling.rs +111 -3
- data/ext/polars/src/expr/selector.rs +219 -0
- data/ext/polars/src/expr/serde.rs +28 -0
- data/ext/polars/src/expr/string.rs +118 -20
- data/ext/polars/src/expr/struct.rs +14 -1
- data/ext/polars/src/file.rs +194 -86
- data/ext/polars/src/functions/aggregation.rs +13 -12
- data/ext/polars/src/functions/business.rs +2 -3
- data/ext/polars/src/functions/eager.rs +3 -2
- data/ext/polars/src/functions/io.rs +90 -18
- data/ext/polars/src/functions/lazy.rs +267 -118
- data/ext/polars/src/functions/meta.rs +8 -7
- data/ext/polars/src/functions/misc.rs +1 -1
- data/ext/polars/src/functions/mod.rs +2 -1
- data/ext/polars/src/functions/range.rs +88 -31
- data/ext/polars/src/functions/strings.rs +6 -0
- data/ext/polars/src/functions/utils.rs +8 -0
- data/ext/polars/src/interop/arrow/mod.rs +52 -1
- data/ext/polars/src/interop/arrow/{to_ruby.rs → to_rb.rs} +37 -7
- data/ext/polars/src/interop/arrow/to_rust.rs +43 -0
- data/ext/polars/src/interop/numo/to_numo_df.rs +1 -1
- data/ext/polars/src/interop/numo/to_numo_series.rs +72 -50
- data/ext/polars/src/io/cloud_options.rs +107 -0
- data/ext/polars/src/io/mod.rs +4 -0
- data/ext/polars/src/io/scan_options.rs +113 -0
- data/ext/polars/src/io/sink_options.rs +46 -0
- data/ext/polars/src/io/sink_output.rs +21 -0
- data/ext/polars/src/lazyframe/exitable.rs +39 -0
- data/ext/polars/src/lazyframe/general.rs +846 -368
- data/ext/polars/src/lazyframe/mod.rs +58 -5
- data/ext/polars/src/lazyframe/optflags.rs +59 -0
- data/ext/polars/src/lazyframe/serde.rs +36 -4
- data/ext/polars/src/lazyframe/sink.rs +46 -0
- data/ext/polars/src/lazygroupby.rs +38 -9
- data/ext/polars/src/lib.rs +574 -165
- data/ext/polars/src/map/lazy.rs +44 -74
- data/ext/polars/src/map/mod.rs +18 -254
- data/ext/polars/src/map/series.rs +241 -1087
- data/ext/polars/src/on_startup.rs +192 -9
- data/ext/polars/src/prelude.rs +1 -0
- data/ext/polars/src/rb_modules.rs +10 -57
- data/ext/polars/src/ruby/exceptions.rs +26 -0
- data/ext/polars/src/ruby/gvl.rs +104 -0
- data/ext/polars/src/ruby/lazy.rs +46 -0
- data/ext/polars/src/ruby/mod.rs +11 -0
- data/ext/polars/src/ruby/numo.rs +52 -0
- data/ext/polars/src/ruby/plan_callback.rs +198 -0
- data/ext/polars/src/ruby/rb_modules.rs +16 -0
- data/ext/polars/src/ruby/ruby_convert_registry.rs +51 -0
- data/ext/polars/src/ruby/ruby_function.rs +11 -0
- data/ext/polars/src/ruby/ruby_udf.rs +164 -0
- data/ext/polars/src/ruby/thread.rs +65 -0
- data/ext/polars/src/ruby/utils.rs +39 -0
- data/ext/polars/src/series/aggregation.rs +116 -91
- data/ext/polars/src/series/arithmetic.rs +16 -22
- data/ext/polars/src/series/comparison.rs +101 -222
- data/ext/polars/src/series/construction.rs +80 -70
- data/ext/polars/src/series/export.rs +98 -56
- data/ext/polars/src/series/general.rs +323 -440
- data/ext/polars/src/series/import.rs +22 -5
- data/ext/polars/src/series/map.rs +103 -0
- data/ext/polars/src/series/mod.rs +57 -15
- data/ext/polars/src/series/scatter.rs +139 -82
- data/ext/polars/src/sql.rs +16 -9
- data/ext/polars/src/testing/frame.rs +31 -0
- data/ext/polars/src/testing/mod.rs +5 -0
- data/ext/polars/src/testing/series.rs +31 -0
- data/ext/polars/src/timeout.rs +105 -0
- data/ext/polars/src/utils.rs +105 -4
- data/lib/polars/array_expr.rb +500 -22
- data/lib/polars/array_name_space.rb +384 -10
- data/lib/polars/batched_csv_reader.rb +48 -66
- data/lib/polars/binary_expr.rb +217 -0
- data/lib/polars/binary_name_space.rb +155 -1
- data/lib/polars/cat_expr.rb +224 -0
- data/lib/polars/cat_name_space.rb +132 -32
- data/lib/polars/catalog/unity/catalog_info.rb +20 -0
- data/lib/polars/catalog/unity/column_info.rb +31 -0
- data/lib/polars/catalog/unity/namespace_info.rb +21 -0
- data/lib/polars/catalog/unity/table_info.rb +50 -0
- data/lib/polars/catalog.rb +448 -0
- data/lib/polars/collect_batches.rb +22 -0
- data/lib/polars/config.rb +3 -3
- data/lib/polars/convert.rb +201 -36
- data/lib/polars/data_frame.rb +2851 -1017
- data/lib/polars/data_frame_plot.rb +173 -0
- data/lib/polars/data_type_expr.rb +52 -0
- data/lib/polars/data_type_group.rb +6 -0
- data/lib/polars/data_types.rb +118 -18
- data/lib/polars/date_time_expr.rb +426 -84
- data/lib/polars/date_time_name_space.rb +384 -111
- data/lib/polars/dynamic_group_by.rb +102 -10
- data/lib/polars/exceptions.rb +50 -5
- data/lib/polars/expr.rb +2159 -915
- data/lib/polars/extension_expr.rb +39 -0
- data/lib/polars/extension_name_space.rb +39 -0
- data/lib/polars/functions/aggregation/horizontal.rb +11 -6
- data/lib/polars/functions/aggregation/vertical.rb +2 -3
- data/lib/polars/functions/as_datatype.rb +290 -8
- data/lib/polars/functions/business.rb +95 -0
- data/lib/polars/functions/col.rb +6 -5
- data/lib/polars/functions/datatype.rb +62 -0
- data/lib/polars/functions/eager.rb +426 -24
- data/lib/polars/functions/escape_regex.rb +21 -0
- data/lib/polars/functions/lazy.rb +813 -195
- data/lib/polars/functions/lit.rb +21 -10
- data/lib/polars/functions/range/int_range.rb +74 -2
- data/lib/polars/functions/range/linear_space.rb +195 -0
- data/lib/polars/functions/range/time_range.rb +1 -1
- data/lib/polars/functions/repeat.rb +7 -12
- data/lib/polars/functions/whenthen.rb +2 -2
- data/lib/polars/group_by.rb +188 -58
- data/lib/polars/iceberg_dataset.rb +108 -0
- data/lib/polars/in_process_query.rb +37 -0
- data/lib/polars/io/cloud.rb +18 -0
- data/lib/polars/io/csv.rb +336 -128
- data/lib/polars/io/database.rb +19 -4
- data/lib/polars/io/delta.rb +134 -0
- data/lib/polars/io/iceberg.rb +34 -0
- data/lib/polars/io/ipc.rb +63 -63
- data/lib/polars/io/json.rb +16 -0
- data/lib/polars/io/lines.rb +172 -0
- data/lib/polars/io/ndjson.rb +176 -20
- data/lib/polars/io/parquet.rb +173 -95
- data/lib/polars/io/scan_options.rb +55 -0
- data/lib/polars/io/sink_options.rb +27 -0
- data/lib/polars/io/utils.rb +17 -0
- data/lib/polars/lazy_frame.rb +3017 -622
- data/lib/polars/lazy_group_by.rb +436 -2
- data/lib/polars/list_expr.rb +551 -59
- data/lib/polars/list_name_space.rb +465 -51
- data/lib/polars/meta_expr.rb +146 -24
- data/lib/polars/name_expr.rb +87 -2
- data/lib/polars/query_opt_flags.rb +264 -0
- data/lib/polars/rolling_group_by.rb +90 -5
- data/lib/polars/scan_cast_options.rb +86 -0
- data/lib/polars/schema.rb +128 -0
- data/lib/polars/selector.rb +245 -0
- data/lib/polars/selectors.rb +1048 -201
- data/lib/polars/series.rb +2522 -774
- data/lib/polars/series_plot.rb +72 -0
- data/lib/polars/slice.rb +1 -1
- data/lib/polars/sql_context.rb +13 -6
- data/lib/polars/string_cache.rb +19 -72
- data/lib/polars/string_expr.rb +561 -107
- data/lib/polars/string_name_space.rb +781 -109
- data/lib/polars/struct_expr.rb +139 -18
- data/lib/polars/struct_name_space.rb +19 -1
- data/lib/polars/testing.rb +24 -273
- data/lib/polars/utils/constants.rb +2 -0
- data/lib/polars/utils/construction/data_frame.rb +410 -0
- data/lib/polars/utils/construction/series.rb +350 -0
- data/lib/polars/utils/construction/utils.rb +9 -0
- data/lib/polars/utils/convert.rb +18 -8
- data/lib/polars/utils/deprecation.rb +11 -0
- data/lib/polars/utils/parse.rb +62 -9
- data/lib/polars/utils/reduce_balanced.rb +43 -0
- data/lib/polars/utils/serde.rb +22 -0
- data/lib/polars/utils/unstable.rb +19 -0
- data/lib/polars/utils/various.rb +86 -1
- data/lib/polars/utils.rb +63 -48
- data/lib/polars/version.rb +1 -1
- data/lib/polars.rb +85 -2
- metadata +80 -28
- data/ext/polars/src/allocator.rs +0 -13
- data/ext/polars/src/batched_csv.rs +0 -138
- data/ext/polars/src/functions/string_cache.rs +0 -25
- data/ext/polars/src/map/dataframe.rs +0 -338
- data/lib/polars/plot.rb +0 -109
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
use magnus::{Ruby, Value, value::Opaque, value::ReprValue};
|
|
2
|
+
use polars_plan::prelude::PlanCallback;
|
|
3
|
+
|
|
4
|
+
use crate::RbResult;
|
|
5
|
+
use crate::ruby::gvl::GvlExt;
|
|
6
|
+
use crate::ruby::ruby_function::RubyFunction;
|
|
7
|
+
use crate::ruby::thread::{is_non_ruby_thread, run_in_ruby_thread, start_background_ruby_thread};
|
|
8
|
+
use crate::ruby::utils::{ArcValue, to_pl_err};
|
|
9
|
+
|
|
10
|
+
pub trait PlanCallbackArgs {
|
|
11
|
+
fn into_rbany(self, rb: &Ruby) -> RbResult<Value>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub trait PlanCallbackOut: Sized {
|
|
15
|
+
fn from_rbany(rbany: Value, rb: &Ruby) -> RbResult<Self>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
mod _ruby {
|
|
19
|
+
use std::sync::Arc;
|
|
20
|
+
|
|
21
|
+
use crate::RbResult;
|
|
22
|
+
use magnus::{IntoValue, RArray, Ruby, TryConvert, Value, value::ReprValue};
|
|
23
|
+
use polars_utils::pl_str::PlSmallStr;
|
|
24
|
+
|
|
25
|
+
macro_rules! impl_rbcb_type {
|
|
26
|
+
($($type:ty),+) => {
|
|
27
|
+
$(
|
|
28
|
+
impl super::PlanCallbackArgs for $type {
|
|
29
|
+
fn into_rbany(self, rb: &Ruby) -> RbResult<Value> {
|
|
30
|
+
Ok(self.into_value_with(rb))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl super::PlanCallbackOut for $type {
|
|
35
|
+
fn from_rbany(rbany: Value, _rb: &Ruby) -> RbResult<Self> {
|
|
36
|
+
Self::try_convert(rbany)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
)+
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
macro_rules! impl_rbcb_type_to_from {
|
|
44
|
+
($($type:ty => $transformed:ty),+) => {
|
|
45
|
+
$(
|
|
46
|
+
impl super::PlanCallbackArgs for $type {
|
|
47
|
+
fn into_rbany(self, rb: &Ruby) -> RbResult<Value> {
|
|
48
|
+
Ok(<$transformed>::from(self).into_value_with(rb))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl super::PlanCallbackOut for $type {
|
|
53
|
+
fn from_rbany(rbany: Value, _rb: &Ruby) -> RbResult<Self> {
|
|
54
|
+
<$transformed>::try_convert(rbany).map(Into::into)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
)+
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
macro_rules! impl_registrycb_type {
|
|
62
|
+
($(($type:path, $from:ident, $to:ident)),+) => {
|
|
63
|
+
$(
|
|
64
|
+
impl super::PlanCallbackArgs for $type {
|
|
65
|
+
fn into_rbany(self, _rb: &Ruby) -> RbResult<Value> {
|
|
66
|
+
let registry = crate::ruby::ruby_convert_registry::get_ruby_convert_registry();
|
|
67
|
+
(registry.to_rb.$to)(&self)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
impl super::PlanCallbackOut for $type {
|
|
72
|
+
fn from_rbany(rbany: Value, _rb: &Ruby) -> RbResult<Self> {
|
|
73
|
+
let registry = crate::ruby::ruby_convert_registry::get_ruby_convert_registry();
|
|
74
|
+
// TODO remove into
|
|
75
|
+
let obj = (registry.from_rb.$from)(rbany.into())?;
|
|
76
|
+
let obj = obj.downcast().unwrap();
|
|
77
|
+
Ok(*obj)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
)+
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
impl<T: super::PlanCallbackArgs> super::PlanCallbackArgs for Option<T> {
|
|
85
|
+
fn into_rbany(self, rb: &Ruby) -> RbResult<Value> {
|
|
86
|
+
match self {
|
|
87
|
+
None => Ok(rb.qnil().as_value()),
|
|
88
|
+
Some(v) => v.into_rbany(rb),
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
impl<T: super::PlanCallbackOut> super::PlanCallbackOut for Option<T> {
|
|
94
|
+
fn from_rbany(rbany: Value, rb: &Ruby) -> RbResult<Self> {
|
|
95
|
+
if rbany.is_nil() {
|
|
96
|
+
Ok(None)
|
|
97
|
+
} else {
|
|
98
|
+
T::from_rbany(rbany, rb).map(Some)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl<T, U> super::PlanCallbackArgs for (T, U)
|
|
104
|
+
where
|
|
105
|
+
T: super::PlanCallbackArgs,
|
|
106
|
+
U: super::PlanCallbackArgs,
|
|
107
|
+
{
|
|
108
|
+
fn into_rbany(self, rb: &Ruby) -> RbResult<Value> {
|
|
109
|
+
Ok(rb
|
|
110
|
+
.ary_new_from_values(&[self.0.into_rbany(rb)?, self.1.into_rbany(rb)?])
|
|
111
|
+
.as_value())
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
impl<T, U> super::PlanCallbackOut for (T, U)
|
|
116
|
+
where
|
|
117
|
+
T: super::PlanCallbackOut,
|
|
118
|
+
U: super::PlanCallbackOut,
|
|
119
|
+
{
|
|
120
|
+
fn from_rbany(rbany: Value, rb: &Ruby) -> RbResult<Self> {
|
|
121
|
+
let tuple = RArray::try_convert(rbany)?;
|
|
122
|
+
Ok((
|
|
123
|
+
T::from_rbany(tuple.entry(0)?, rb)?,
|
|
124
|
+
U::from_rbany(tuple.entry(1)?, rb)?,
|
|
125
|
+
))
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
impl_rbcb_type! {
|
|
130
|
+
bool,
|
|
131
|
+
usize,
|
|
132
|
+
String
|
|
133
|
+
}
|
|
134
|
+
impl_rbcb_type_to_from! {
|
|
135
|
+
PlSmallStr => String
|
|
136
|
+
}
|
|
137
|
+
impl_registrycb_type! {
|
|
138
|
+
(polars_core::series::Series, series, series),
|
|
139
|
+
(polars_core::frame::DataFrame, df, df),
|
|
140
|
+
(polars_plan::dsl::DslPlan, dsl_plan, dsl_plan),
|
|
141
|
+
(polars_core::schema::Schema, schema, schema)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
impl<T: super::PlanCallbackArgs + Clone> super::PlanCallbackArgs for Arc<T> {
|
|
145
|
+
fn into_rbany(self, rb: &Ruby) -> RbResult<Value> {
|
|
146
|
+
Arc::unwrap_or_clone(self).into_rbany(rb)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
impl<T: super::PlanCallbackArgs + Clone> super::PlanCallbackArgs for Vec<T> {
|
|
151
|
+
fn into_rbany(self, rb: &Ruby) -> RbResult<Value> {
|
|
152
|
+
rb.ary_try_from_iter(self.into_iter().map(|v| v.into_rbany(rb)))
|
|
153
|
+
.map(|v| v.as_value())
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
impl<T: super::PlanCallbackOut> super::PlanCallbackOut for Arc<T> {
|
|
158
|
+
fn from_rbany(rbany: Value, rb: &Ruby) -> RbResult<Self> {
|
|
159
|
+
T::from_rbany(rbany, rb).map(Arc::from)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pub(crate) trait PlanCallbackExt<Args, Out> {
|
|
165
|
+
fn new_ruby(rbfn: RubyFunction) -> Self;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
impl<Args: PlanCallbackArgs + Send + 'static, Out: PlanCallbackOut + Send + 'static>
|
|
169
|
+
PlanCallbackExt<Args, Out> for PlanCallback<Args, Out>
|
|
170
|
+
{
|
|
171
|
+
fn new_ruby(rbfn: RubyFunction) -> Self {
|
|
172
|
+
let f = move |args: Args, lambda: Opaque<Value>| {
|
|
173
|
+
Ruby::attach(|rb| {
|
|
174
|
+
let out = Out::from_rbany(
|
|
175
|
+
rb.get_inner(lambda)
|
|
176
|
+
.funcall("call", (args.into_rbany(rb).map_err(to_pl_err)?,))
|
|
177
|
+
.map_err(to_pl_err)?,
|
|
178
|
+
rb,
|
|
179
|
+
)
|
|
180
|
+
.map_err(to_pl_err)?;
|
|
181
|
+
Ok(out)
|
|
182
|
+
})
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// handle non-Ruby threads
|
|
186
|
+
start_background_ruby_thread(&Ruby::get().unwrap());
|
|
187
|
+
let udf = ArcValue::new(rbfn.0);
|
|
188
|
+
let f = move |args: Args| {
|
|
189
|
+
if is_non_ruby_thread() {
|
|
190
|
+
let udf = udf.clone();
|
|
191
|
+
return run_in_ruby_thread(move || f(args, *udf.0));
|
|
192
|
+
}
|
|
193
|
+
f(args, *udf.0)
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
Self::new(f)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
use magnus::{Module, RClass, Ruby, value::Lazy};
|
|
2
|
+
|
|
3
|
+
static DATE: Lazy<RClass> = Lazy::new(|rb| rb.class_object().const_get("Date").unwrap());
|
|
4
|
+
static DATETIME: Lazy<RClass> = Lazy::new(|rb| rb.class_object().const_get("DateTime").unwrap());
|
|
5
|
+
|
|
6
|
+
pub(crate) fn bigdecimal(rb: &Ruby) -> Option<RClass> {
|
|
7
|
+
rb.class_object().const_get("BigDecimal").ok()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
pub(crate) fn date(rb: &Ruby) -> RClass {
|
|
11
|
+
rb.get_inner(&DATE)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub(crate) fn datetime(rb: &Ruby) -> RClass {
|
|
15
|
+
rb.get_inner(&DATETIME)
|
|
16
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
use std::any::Any;
|
|
2
|
+
use std::ops::Deref;
|
|
3
|
+
use std::sync::{Arc, LazyLock, RwLock};
|
|
4
|
+
|
|
5
|
+
use magnus::{Value, value::Opaque};
|
|
6
|
+
|
|
7
|
+
use crate::RbResult;
|
|
8
|
+
|
|
9
|
+
pub type FromRuby = Arc<dyn Fn(Opaque<Value>) -> RbResult<Box<dyn Any>> + Send + Sync>;
|
|
10
|
+
pub type ToRuby = Arc<dyn for<'a> Fn(&'a dyn Any) -> RbResult<Value> + Send + Sync>;
|
|
11
|
+
|
|
12
|
+
#[derive(Clone)]
|
|
13
|
+
pub struct FromRubyConvertRegistry {
|
|
14
|
+
#[allow(unused)]
|
|
15
|
+
pub file_provider_result: FromRuby,
|
|
16
|
+
pub series: FromRuby,
|
|
17
|
+
pub df: FromRuby,
|
|
18
|
+
pub dsl_plan: FromRuby,
|
|
19
|
+
pub schema: FromRuby,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[derive(Clone)]
|
|
23
|
+
pub struct ToRubyConvertRegistry {
|
|
24
|
+
pub df: ToRuby,
|
|
25
|
+
pub series: ToRuby,
|
|
26
|
+
pub dsl_plan: ToRuby,
|
|
27
|
+
pub schema: ToRuby,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[derive(Clone)]
|
|
31
|
+
pub struct RubyConvertRegistry {
|
|
32
|
+
pub from_rb: FromRubyConvertRegistry,
|
|
33
|
+
pub to_rb: ToRubyConvertRegistry,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static RUBY_CONVERT_REGISTRY: LazyLock<RwLock<Option<RubyConvertRegistry>>> =
|
|
37
|
+
LazyLock::new(Default::default);
|
|
38
|
+
|
|
39
|
+
pub fn get_ruby_convert_registry() -> RubyConvertRegistry {
|
|
40
|
+
RUBY_CONVERT_REGISTRY
|
|
41
|
+
.deref()
|
|
42
|
+
.read()
|
|
43
|
+
.unwrap()
|
|
44
|
+
.as_ref()
|
|
45
|
+
.unwrap()
|
|
46
|
+
.clone()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn register_converters(registry: RubyConvertRegistry) {
|
|
50
|
+
*RUBY_CONVERT_REGISTRY.deref().write().unwrap() = Some(registry);
|
|
51
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
use std::sync::{Arc, OnceLock};
|
|
2
|
+
|
|
3
|
+
use magnus::{Ruby, Value, value::Opaque, value::ReprValue};
|
|
4
|
+
use polars::prelude::*;
|
|
5
|
+
use polars_core::datatypes::{DataType, Field};
|
|
6
|
+
use polars_core::frame::column::Column;
|
|
7
|
+
use polars_core::schema::Schema;
|
|
8
|
+
use polars_plan::dsl::udf::try_infer_udf_output_dtype;
|
|
9
|
+
use polars_plan::prelude::*;
|
|
10
|
+
use polars_utils::pl_str::PlSmallStr;
|
|
11
|
+
|
|
12
|
+
use crate::ruby::gvl::GvlExt;
|
|
13
|
+
use crate::ruby::utils::ArcValue;
|
|
14
|
+
|
|
15
|
+
#[allow(clippy::type_complexity)]
|
|
16
|
+
pub static mut CALL_COLUMNS_UDF_RUBY: Option<
|
|
17
|
+
fn(s: &[Column], output_dtype: Option<DataType>, lambda: Opaque<Value>) -> PolarsResult<Column>,
|
|
18
|
+
> = None;
|
|
19
|
+
|
|
20
|
+
#[allow(clippy::type_complexity)]
|
|
21
|
+
pub static mut CALL_DF_UDF_RUBY: Option<
|
|
22
|
+
fn(s: DataFrame, lambda: Opaque<Value>) -> PolarsResult<DataFrame>,
|
|
23
|
+
> = None;
|
|
24
|
+
|
|
25
|
+
pub struct RubyUdfExpression {
|
|
26
|
+
ruby_function: ArcValue,
|
|
27
|
+
output_type: Option<DataTypeExpr>,
|
|
28
|
+
materialized_field: OnceLock<Field>,
|
|
29
|
+
is_elementwise: bool,
|
|
30
|
+
returns_scalar: bool,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl RubyUdfExpression {
|
|
34
|
+
pub fn new(
|
|
35
|
+
lambda: Value,
|
|
36
|
+
output_type: Option<impl Into<DataTypeExpr>>,
|
|
37
|
+
is_elementwise: bool,
|
|
38
|
+
returns_scalar: bool,
|
|
39
|
+
) -> Self {
|
|
40
|
+
let output_type = output_type.map(Into::into);
|
|
41
|
+
Self {
|
|
42
|
+
ruby_function: ArcValue::new(Opaque::from(lambda)),
|
|
43
|
+
output_type,
|
|
44
|
+
materialized_field: OnceLock::new(),
|
|
45
|
+
is_elementwise,
|
|
46
|
+
returns_scalar,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl ColumnsUdf for RubyUdfExpression {
|
|
52
|
+
fn call_udf(&self, s: &mut [Column]) -> PolarsResult<Column> {
|
|
53
|
+
let func = unsafe { CALL_COLUMNS_UDF_RUBY.unwrap() };
|
|
54
|
+
let field = self
|
|
55
|
+
.materialized_field
|
|
56
|
+
.get()
|
|
57
|
+
.expect("should have been materialized at this point");
|
|
58
|
+
let mut out = func(
|
|
59
|
+
s,
|
|
60
|
+
self.materialized_field.get().map(|f| f.dtype.clone()),
|
|
61
|
+
*self.ruby_function.0,
|
|
62
|
+
)?;
|
|
63
|
+
|
|
64
|
+
let must_cast = out.dtype().matches_schema_type(field.dtype()).map_err(|_| {
|
|
65
|
+
polars_err!(
|
|
66
|
+
SchemaMismatch: "expected output type '{:?}', got '{:?}'; set `return_dtype` to the proper datatype",
|
|
67
|
+
field.dtype(), out.dtype(),
|
|
68
|
+
)
|
|
69
|
+
})?;
|
|
70
|
+
if must_cast {
|
|
71
|
+
out = out.cast(field.dtype())?;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Ok(out)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
impl AnonymousColumnsUdf for RubyUdfExpression {
|
|
79
|
+
fn as_column_udf(self: Arc<Self>) -> Arc<dyn ColumnsUdf> {
|
|
80
|
+
self as _
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn deep_clone(self: Arc<Self>) -> Arc<dyn AnonymousColumnsUdf> {
|
|
84
|
+
Arc::new(Self {
|
|
85
|
+
ruby_function: ArcValue::new(Ruby::attach(|rb| {
|
|
86
|
+
// TODO fix
|
|
87
|
+
rb.get_inner(*self.ruby_function.0)
|
|
88
|
+
.funcall::<_, _, Value>("dup", ())
|
|
89
|
+
.map(Opaque::from)
|
|
90
|
+
.unwrap()
|
|
91
|
+
})),
|
|
92
|
+
output_type: self.output_type.clone(),
|
|
93
|
+
materialized_field: OnceLock::new(),
|
|
94
|
+
is_elementwise: self.is_elementwise,
|
|
95
|
+
returns_scalar: self.returns_scalar,
|
|
96
|
+
}) as _
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn try_serialize(&self, _buf: &mut Vec<u8>) -> PolarsResult<()> {
|
|
100
|
+
todo!();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn get_field(&self, input_schema: &Schema, fields: &[Field]) -> PolarsResult<Field> {
|
|
104
|
+
let field = match self.materialized_field.get() {
|
|
105
|
+
Some(f) => f.clone(),
|
|
106
|
+
None => {
|
|
107
|
+
let dtype = match self.output_type.as_ref() {
|
|
108
|
+
None => {
|
|
109
|
+
let func = unsafe { CALL_COLUMNS_UDF_RUBY.unwrap() };
|
|
110
|
+
let f = |s: &[Column]| func(s, None, *self.ruby_function.0);
|
|
111
|
+
try_infer_udf_output_dtype(&f as _, fields)?
|
|
112
|
+
}
|
|
113
|
+
Some(output_type) => output_type
|
|
114
|
+
.clone()
|
|
115
|
+
.into_datatype_with_self(input_schema, fields[0].dtype())?,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Take the name of first field, just like `map_field`.
|
|
119
|
+
let name = fields[0].name();
|
|
120
|
+
let f = Field::new(name.clone(), dtype);
|
|
121
|
+
self.materialized_field.get_or_init(|| f.clone());
|
|
122
|
+
f
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
Ok(field)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pub trait RubyUdfExt {
|
|
130
|
+
#[allow(unused)]
|
|
131
|
+
fn map_ruby(self, func: RubyUdfExpression) -> Expr;
|
|
132
|
+
|
|
133
|
+
fn map_many_ruby(exprs: Vec<Expr>, func: RubyUdfExpression) -> Expr;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
impl RubyUdfExt for Expr {
|
|
137
|
+
fn map_ruby(self, func: RubyUdfExpression) -> Expr {
|
|
138
|
+
Self::map_many_ruby(vec![self], func)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fn map_many_ruby(exprs: Vec<Expr>, func: RubyUdfExpression) -> Expr {
|
|
142
|
+
const NAME: &str = "ruby_udf";
|
|
143
|
+
|
|
144
|
+
let returns_scalar = func.returns_scalar;
|
|
145
|
+
|
|
146
|
+
let mut flags = FunctionFlags::default() | FunctionFlags::OPTIONAL_RE_ENTRANT;
|
|
147
|
+
if func.is_elementwise {
|
|
148
|
+
flags.set_elementwise();
|
|
149
|
+
}
|
|
150
|
+
if returns_scalar {
|
|
151
|
+
flags |= FunctionFlags::RETURNS_SCALAR;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Expr::AnonymousFunction {
|
|
155
|
+
input: exprs,
|
|
156
|
+
function: new_column_udf(func),
|
|
157
|
+
options: FunctionOptions {
|
|
158
|
+
flags,
|
|
159
|
+
..Default::default()
|
|
160
|
+
},
|
|
161
|
+
fmt_str: Box::new(PlSmallStr::from(NAME)),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
use std::any::Any;
|
|
2
|
+
use std::sync::OnceLock;
|
|
3
|
+
use std::sync::mpsc::{RecvTimeoutError, SyncSender, sync_channel};
|
|
4
|
+
|
|
5
|
+
use magnus::Ruby;
|
|
6
|
+
use magnus::error::RubyUnavailableError;
|
|
7
|
+
|
|
8
|
+
use crate::ruby::gvl::GvlExt;
|
|
9
|
+
|
|
10
|
+
type BackgroundMessage = (
|
|
11
|
+
Box<dyn FnOnce() -> Box<dyn Any + Send> + Send>,
|
|
12
|
+
SyncSender<Box<dyn Any + Send>>,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
static BACKGROUND_THREAD_MAILBOX: OnceLock<SyncSender<BackgroundMessage>> = OnceLock::new();
|
|
16
|
+
|
|
17
|
+
// TODO figure out better approach
|
|
18
|
+
pub(crate) fn start_background_ruby_thread(rb: &Ruby) {
|
|
19
|
+
BACKGROUND_THREAD_MAILBOX.get_or_init(|| {
|
|
20
|
+
let (sender, receiver) = sync_channel::<BackgroundMessage>(0);
|
|
21
|
+
|
|
22
|
+
// TODO save reference to thread?
|
|
23
|
+
rb.thread_create_from_fn(move |rb2| {
|
|
24
|
+
rb2.detach(move || {
|
|
25
|
+
loop {
|
|
26
|
+
match receiver.recv_timeout(std::time::Duration::from_millis(10)) {
|
|
27
|
+
Ok((f, sender2)) => {
|
|
28
|
+
sender2.send(f()).unwrap();
|
|
29
|
+
}
|
|
30
|
+
Err(RecvTimeoutError::Timeout) => {
|
|
31
|
+
Ruby::attach(|rb3| rb3.thread_schedule());
|
|
32
|
+
}
|
|
33
|
+
Err(RecvTimeoutError::Disconnected) => {
|
|
34
|
+
todo!();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[allow(unreachable_code)]
|
|
40
|
+
()
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
sender
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub(crate) fn run_in_ruby_thread<T, F>(func: F) -> T
|
|
49
|
+
where
|
|
50
|
+
T: Send + 'static,
|
|
51
|
+
F: FnOnce() -> T + Send + 'static,
|
|
52
|
+
{
|
|
53
|
+
let func2 = move || -> Box<dyn Any + Send> { Box::new(func()) };
|
|
54
|
+
let (sender, receiver) = sync_channel(0);
|
|
55
|
+
BACKGROUND_THREAD_MAILBOX
|
|
56
|
+
.get()
|
|
57
|
+
.unwrap()
|
|
58
|
+
.send((Box::new(func2), sender))
|
|
59
|
+
.unwrap();
|
|
60
|
+
*receiver.recv().unwrap().downcast().unwrap()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub(crate) fn is_non_ruby_thread() -> bool {
|
|
64
|
+
matches!(Ruby::get(), Err(RubyUnavailableError::NonRubyThread))
|
|
65
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
use std::sync::Arc;
|
|
2
|
+
|
|
3
|
+
use magnus::{Error, IntoValue, Ruby, Value, gc, value::Opaque};
|
|
4
|
+
use polars::error::PolarsError;
|
|
5
|
+
|
|
6
|
+
use crate::RbResult;
|
|
7
|
+
use crate::ruby::gvl::GvlExt;
|
|
8
|
+
|
|
9
|
+
pub(crate) fn to_pl_err(e: Error) -> PolarsError {
|
|
10
|
+
PolarsError::ComputeError(e.to_string().into())
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#[derive(Clone)]
|
|
14
|
+
pub struct ArcValue(pub Arc<Opaque<Value>>);
|
|
15
|
+
|
|
16
|
+
impl ArcValue {
|
|
17
|
+
pub fn new(value: Opaque<Value>) -> Self {
|
|
18
|
+
let ob = Arc::new(value);
|
|
19
|
+
gc::register_address(&*ob);
|
|
20
|
+
Self(ob)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl Drop for ArcValue {
|
|
25
|
+
fn drop(&mut self) {
|
|
26
|
+
// TODO use rb.gc_register_address
|
|
27
|
+
Ruby::attach(|_| gc::unregister_address(&*self.0));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub trait TryIntoValue {
|
|
32
|
+
fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl<T: IntoValue> TryIntoValue for T {
|
|
36
|
+
fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
|
|
37
|
+
Ok(self.into_value_with(ruby))
|
|
38
|
+
}
|
|
39
|
+
}
|