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.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +274 -0
  3. data/Cargo.lock +1465 -867
  4. data/Cargo.toml +3 -0
  5. data/LICENSE.txt +2 -2
  6. data/README.md +87 -37
  7. data/ext/polars/Cargo.toml +47 -16
  8. data/ext/polars/src/c_api/allocator.rs +7 -0
  9. data/ext/polars/src/c_api/mod.rs +1 -0
  10. data/ext/polars/src/catalog/mod.rs +1 -0
  11. data/ext/polars/src/catalog/unity.rs +470 -0
  12. data/ext/polars/src/conversion/any_value.rs +99 -84
  13. data/ext/polars/src/conversion/categorical.rs +30 -0
  14. data/ext/polars/src/conversion/chunked_array.rs +71 -62
  15. data/ext/polars/src/conversion/datetime.rs +63 -0
  16. data/ext/polars/src/conversion/mod.rs +796 -312
  17. data/ext/polars/src/dataframe/construction.rs +6 -18
  18. data/ext/polars/src/dataframe/export.rs +30 -39
  19. data/ext/polars/src/dataframe/general.rs +294 -362
  20. data/ext/polars/src/dataframe/io.rs +33 -150
  21. data/ext/polars/src/dataframe/map.rs +175 -0
  22. data/ext/polars/src/dataframe/mod.rs +37 -5
  23. data/ext/polars/src/dataframe/serde.rs +23 -8
  24. data/ext/polars/src/error.rs +44 -7
  25. data/ext/polars/src/exceptions.rs +21 -8
  26. data/ext/polars/src/expr/array.rs +86 -22
  27. data/ext/polars/src/expr/binary.rs +50 -1
  28. data/ext/polars/src/expr/bitwise.rs +39 -0
  29. data/ext/polars/src/expr/categorical.rs +20 -0
  30. data/ext/polars/src/expr/datatype.rs +51 -0
  31. data/ext/polars/src/expr/datetime.rs +99 -41
  32. data/ext/polars/src/expr/extension.rs +12 -0
  33. data/ext/polars/src/expr/general.rs +252 -128
  34. data/ext/polars/src/expr/list.rs +56 -60
  35. data/ext/polars/src/expr/meta.rs +30 -35
  36. data/ext/polars/src/expr/mod.rs +28 -6
  37. data/ext/polars/src/expr/name.rs +29 -14
  38. data/ext/polars/src/expr/rolling.rs +111 -3
  39. data/ext/polars/src/expr/selector.rs +219 -0
  40. data/ext/polars/src/expr/serde.rs +28 -0
  41. data/ext/polars/src/expr/string.rs +118 -20
  42. data/ext/polars/src/expr/struct.rs +14 -1
  43. data/ext/polars/src/file.rs +194 -86
  44. data/ext/polars/src/functions/aggregation.rs +13 -12
  45. data/ext/polars/src/functions/business.rs +2 -3
  46. data/ext/polars/src/functions/eager.rs +3 -2
  47. data/ext/polars/src/functions/io.rs +90 -18
  48. data/ext/polars/src/functions/lazy.rs +267 -118
  49. data/ext/polars/src/functions/meta.rs +8 -7
  50. data/ext/polars/src/functions/misc.rs +1 -1
  51. data/ext/polars/src/functions/mod.rs +2 -1
  52. data/ext/polars/src/functions/range.rs +88 -31
  53. data/ext/polars/src/functions/strings.rs +6 -0
  54. data/ext/polars/src/functions/utils.rs +8 -0
  55. data/ext/polars/src/interop/arrow/mod.rs +52 -1
  56. data/ext/polars/src/interop/arrow/{to_ruby.rs → to_rb.rs} +37 -7
  57. data/ext/polars/src/interop/arrow/to_rust.rs +43 -0
  58. data/ext/polars/src/interop/numo/to_numo_df.rs +1 -1
  59. data/ext/polars/src/interop/numo/to_numo_series.rs +72 -50
  60. data/ext/polars/src/io/cloud_options.rs +107 -0
  61. data/ext/polars/src/io/mod.rs +4 -0
  62. data/ext/polars/src/io/scan_options.rs +113 -0
  63. data/ext/polars/src/io/sink_options.rs +46 -0
  64. data/ext/polars/src/io/sink_output.rs +21 -0
  65. data/ext/polars/src/lazyframe/exitable.rs +39 -0
  66. data/ext/polars/src/lazyframe/general.rs +846 -368
  67. data/ext/polars/src/lazyframe/mod.rs +58 -5
  68. data/ext/polars/src/lazyframe/optflags.rs +59 -0
  69. data/ext/polars/src/lazyframe/serde.rs +36 -4
  70. data/ext/polars/src/lazyframe/sink.rs +46 -0
  71. data/ext/polars/src/lazygroupby.rs +38 -9
  72. data/ext/polars/src/lib.rs +574 -165
  73. data/ext/polars/src/map/lazy.rs +44 -74
  74. data/ext/polars/src/map/mod.rs +18 -254
  75. data/ext/polars/src/map/series.rs +241 -1087
  76. data/ext/polars/src/on_startup.rs +192 -9
  77. data/ext/polars/src/prelude.rs +1 -0
  78. data/ext/polars/src/rb_modules.rs +10 -57
  79. data/ext/polars/src/ruby/exceptions.rs +26 -0
  80. data/ext/polars/src/ruby/gvl.rs +104 -0
  81. data/ext/polars/src/ruby/lazy.rs +46 -0
  82. data/ext/polars/src/ruby/mod.rs +11 -0
  83. data/ext/polars/src/ruby/numo.rs +52 -0
  84. data/ext/polars/src/ruby/plan_callback.rs +198 -0
  85. data/ext/polars/src/ruby/rb_modules.rs +16 -0
  86. data/ext/polars/src/ruby/ruby_convert_registry.rs +51 -0
  87. data/ext/polars/src/ruby/ruby_function.rs +11 -0
  88. data/ext/polars/src/ruby/ruby_udf.rs +164 -0
  89. data/ext/polars/src/ruby/thread.rs +65 -0
  90. data/ext/polars/src/ruby/utils.rs +39 -0
  91. data/ext/polars/src/series/aggregation.rs +116 -91
  92. data/ext/polars/src/series/arithmetic.rs +16 -22
  93. data/ext/polars/src/series/comparison.rs +101 -222
  94. data/ext/polars/src/series/construction.rs +80 -70
  95. data/ext/polars/src/series/export.rs +98 -56
  96. data/ext/polars/src/series/general.rs +323 -440
  97. data/ext/polars/src/series/import.rs +22 -5
  98. data/ext/polars/src/series/map.rs +103 -0
  99. data/ext/polars/src/series/mod.rs +57 -15
  100. data/ext/polars/src/series/scatter.rs +139 -82
  101. data/ext/polars/src/sql.rs +16 -9
  102. data/ext/polars/src/testing/frame.rs +31 -0
  103. data/ext/polars/src/testing/mod.rs +5 -0
  104. data/ext/polars/src/testing/series.rs +31 -0
  105. data/ext/polars/src/timeout.rs +105 -0
  106. data/ext/polars/src/utils.rs +105 -4
  107. data/lib/polars/array_expr.rb +500 -22
  108. data/lib/polars/array_name_space.rb +384 -10
  109. data/lib/polars/batched_csv_reader.rb +48 -66
  110. data/lib/polars/binary_expr.rb +217 -0
  111. data/lib/polars/binary_name_space.rb +155 -1
  112. data/lib/polars/cat_expr.rb +224 -0
  113. data/lib/polars/cat_name_space.rb +132 -32
  114. data/lib/polars/catalog/unity/catalog_info.rb +20 -0
  115. data/lib/polars/catalog/unity/column_info.rb +31 -0
  116. data/lib/polars/catalog/unity/namespace_info.rb +21 -0
  117. data/lib/polars/catalog/unity/table_info.rb +50 -0
  118. data/lib/polars/catalog.rb +448 -0
  119. data/lib/polars/collect_batches.rb +22 -0
  120. data/lib/polars/config.rb +3 -3
  121. data/lib/polars/convert.rb +201 -36
  122. data/lib/polars/data_frame.rb +2851 -1017
  123. data/lib/polars/data_frame_plot.rb +173 -0
  124. data/lib/polars/data_type_expr.rb +52 -0
  125. data/lib/polars/data_type_group.rb +6 -0
  126. data/lib/polars/data_types.rb +118 -18
  127. data/lib/polars/date_time_expr.rb +426 -84
  128. data/lib/polars/date_time_name_space.rb +384 -111
  129. data/lib/polars/dynamic_group_by.rb +102 -10
  130. data/lib/polars/exceptions.rb +50 -5
  131. data/lib/polars/expr.rb +2159 -915
  132. data/lib/polars/extension_expr.rb +39 -0
  133. data/lib/polars/extension_name_space.rb +39 -0
  134. data/lib/polars/functions/aggregation/horizontal.rb +11 -6
  135. data/lib/polars/functions/aggregation/vertical.rb +2 -3
  136. data/lib/polars/functions/as_datatype.rb +290 -8
  137. data/lib/polars/functions/business.rb +95 -0
  138. data/lib/polars/functions/col.rb +6 -5
  139. data/lib/polars/functions/datatype.rb +62 -0
  140. data/lib/polars/functions/eager.rb +426 -24
  141. data/lib/polars/functions/escape_regex.rb +21 -0
  142. data/lib/polars/functions/lazy.rb +813 -195
  143. data/lib/polars/functions/lit.rb +21 -10
  144. data/lib/polars/functions/range/int_range.rb +74 -2
  145. data/lib/polars/functions/range/linear_space.rb +195 -0
  146. data/lib/polars/functions/range/time_range.rb +1 -1
  147. data/lib/polars/functions/repeat.rb +7 -12
  148. data/lib/polars/functions/whenthen.rb +2 -2
  149. data/lib/polars/group_by.rb +188 -58
  150. data/lib/polars/iceberg_dataset.rb +108 -0
  151. data/lib/polars/in_process_query.rb +37 -0
  152. data/lib/polars/io/cloud.rb +18 -0
  153. data/lib/polars/io/csv.rb +336 -128
  154. data/lib/polars/io/database.rb +19 -4
  155. data/lib/polars/io/delta.rb +134 -0
  156. data/lib/polars/io/iceberg.rb +34 -0
  157. data/lib/polars/io/ipc.rb +63 -63
  158. data/lib/polars/io/json.rb +16 -0
  159. data/lib/polars/io/lines.rb +172 -0
  160. data/lib/polars/io/ndjson.rb +176 -20
  161. data/lib/polars/io/parquet.rb +173 -95
  162. data/lib/polars/io/scan_options.rb +55 -0
  163. data/lib/polars/io/sink_options.rb +27 -0
  164. data/lib/polars/io/utils.rb +17 -0
  165. data/lib/polars/lazy_frame.rb +3017 -622
  166. data/lib/polars/lazy_group_by.rb +436 -2
  167. data/lib/polars/list_expr.rb +551 -59
  168. data/lib/polars/list_name_space.rb +465 -51
  169. data/lib/polars/meta_expr.rb +146 -24
  170. data/lib/polars/name_expr.rb +87 -2
  171. data/lib/polars/query_opt_flags.rb +264 -0
  172. data/lib/polars/rolling_group_by.rb +90 -5
  173. data/lib/polars/scan_cast_options.rb +86 -0
  174. data/lib/polars/schema.rb +128 -0
  175. data/lib/polars/selector.rb +245 -0
  176. data/lib/polars/selectors.rb +1048 -201
  177. data/lib/polars/series.rb +2522 -774
  178. data/lib/polars/series_plot.rb +72 -0
  179. data/lib/polars/slice.rb +1 -1
  180. data/lib/polars/sql_context.rb +13 -6
  181. data/lib/polars/string_cache.rb +19 -72
  182. data/lib/polars/string_expr.rb +561 -107
  183. data/lib/polars/string_name_space.rb +781 -109
  184. data/lib/polars/struct_expr.rb +139 -18
  185. data/lib/polars/struct_name_space.rb +19 -1
  186. data/lib/polars/testing.rb +24 -273
  187. data/lib/polars/utils/constants.rb +2 -0
  188. data/lib/polars/utils/construction/data_frame.rb +410 -0
  189. data/lib/polars/utils/construction/series.rb +350 -0
  190. data/lib/polars/utils/construction/utils.rb +9 -0
  191. data/lib/polars/utils/convert.rb +18 -8
  192. data/lib/polars/utils/deprecation.rb +11 -0
  193. data/lib/polars/utils/parse.rb +62 -9
  194. data/lib/polars/utils/reduce_balanced.rb +43 -0
  195. data/lib/polars/utils/serde.rb +22 -0
  196. data/lib/polars/utils/unstable.rb +19 -0
  197. data/lib/polars/utils/various.rb +86 -1
  198. data/lib/polars/utils.rb +63 -48
  199. data/lib/polars/version.rb +1 -1
  200. data/lib/polars.rb +85 -2
  201. metadata +80 -28
  202. data/ext/polars/src/allocator.rs +0 -13
  203. data/ext/polars/src/batched_csv.rs +0 -138
  204. data/ext/polars/src/functions/string_cache.rs +0 -25
  205. data/ext/polars/src/map/dataframe.rs +0 -338
  206. 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,11 @@
1
+ use magnus::{Value, value::Opaque};
2
+
3
+ pub struct RubyObject(pub Opaque<Value>);
4
+
5
+ pub type RubyFunction = RubyObject;
6
+
7
+ impl From<Value> for RubyObject {
8
+ fn from(value: Value) -> Self {
9
+ Self(value.into())
10
+ }
11
+ }
@@ -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
+ }