polars-df 0.9.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/Cargo.lock +144 -57
  4. data/README.md +7 -6
  5. data/ext/polars/Cargo.toml +10 -6
  6. data/ext/polars/src/batched_csv.rs +53 -50
  7. data/ext/polars/src/conversion/anyvalue.rs +3 -2
  8. data/ext/polars/src/conversion/mod.rs +31 -67
  9. data/ext/polars/src/dataframe/construction.rs +186 -0
  10. data/ext/polars/src/dataframe/export.rs +48 -0
  11. data/ext/polars/src/dataframe/general.rs +607 -0
  12. data/ext/polars/src/dataframe/io.rs +463 -0
  13. data/ext/polars/src/dataframe/mod.rs +26 -0
  14. data/ext/polars/src/expr/array.rs +6 -2
  15. data/ext/polars/src/expr/datetime.rs +13 -4
  16. data/ext/polars/src/expr/general.rs +50 -9
  17. data/ext/polars/src/expr/list.rs +6 -2
  18. data/ext/polars/src/expr/rolling.rs +185 -69
  19. data/ext/polars/src/expr/string.rs +12 -33
  20. data/ext/polars/src/file.rs +158 -11
  21. data/ext/polars/src/functions/lazy.rs +20 -3
  22. data/ext/polars/src/functions/range.rs +74 -0
  23. data/ext/polars/src/functions/whenthen.rs +47 -17
  24. data/ext/polars/src/interop/mod.rs +1 -0
  25. data/ext/polars/src/interop/numo/mod.rs +2 -0
  26. data/ext/polars/src/interop/numo/to_numo_df.rs +23 -0
  27. data/ext/polars/src/interop/numo/to_numo_series.rs +60 -0
  28. data/ext/polars/src/lazyframe/mod.rs +111 -56
  29. data/ext/polars/src/lib.rs +68 -34
  30. data/ext/polars/src/map/dataframe.rs +17 -9
  31. data/ext/polars/src/map/lazy.rs +5 -25
  32. data/ext/polars/src/map/series.rs +7 -1
  33. data/ext/polars/src/series/aggregation.rs +47 -30
  34. data/ext/polars/src/series/export.rs +131 -49
  35. data/ext/polars/src/series/mod.rs +13 -133
  36. data/lib/polars/array_expr.rb +6 -2
  37. data/lib/polars/batched_csv_reader.rb +11 -3
  38. data/lib/polars/convert.rb +6 -1
  39. data/lib/polars/data_frame.rb +225 -370
  40. data/lib/polars/date_time_expr.rb +11 -4
  41. data/lib/polars/date_time_name_space.rb +14 -4
  42. data/lib/polars/dynamic_group_by.rb +2 -2
  43. data/lib/polars/exceptions.rb +4 -0
  44. data/lib/polars/expr.rb +1171 -54
  45. data/lib/polars/functions/lazy.rb +3 -3
  46. data/lib/polars/functions/range/date_range.rb +92 -0
  47. data/lib/polars/functions/range/datetime_range.rb +149 -0
  48. data/lib/polars/functions/range/time_range.rb +141 -0
  49. data/lib/polars/functions/whenthen.rb +74 -5
  50. data/lib/polars/group_by.rb +88 -23
  51. data/lib/polars/io/avro.rb +24 -0
  52. data/lib/polars/{io.rb → io/csv.rb} +307 -489
  53. data/lib/polars/io/database.rb +73 -0
  54. data/lib/polars/io/ipc.rb +247 -0
  55. data/lib/polars/io/json.rb +18 -0
  56. data/lib/polars/io/ndjson.rb +69 -0
  57. data/lib/polars/io/parquet.rb +226 -0
  58. data/lib/polars/lazy_frame.rb +55 -195
  59. data/lib/polars/lazy_group_by.rb +100 -3
  60. data/lib/polars/list_expr.rb +6 -2
  61. data/lib/polars/rolling_group_by.rb +2 -2
  62. data/lib/polars/series.rb +14 -12
  63. data/lib/polars/string_expr.rb +38 -36
  64. data/lib/polars/utils.rb +89 -1
  65. data/lib/polars/version.rb +1 -1
  66. data/lib/polars/whenthen.rb +83 -0
  67. data/lib/polars.rb +10 -3
  68. metadata +23 -8
  69. data/ext/polars/src/dataframe.rs +0 -1182
  70. data/lib/polars/when.rb +0 -16
  71. data/lib/polars/when_then.rb +0 -19
@@ -7,187 +7,281 @@ use crate::RbExpr;
7
7
  impl RbExpr {
8
8
  pub fn rolling_sum(
9
9
  &self,
10
- window_size: String,
10
+ window_size: usize,
11
11
  weights: Option<Vec<f64>>,
12
12
  min_periods: usize,
13
13
  center: bool,
14
- by: Option<String>,
15
- closed: Option<Wrap<ClosedWindow>>,
16
14
  ) -> Self {
17
- let options = RollingOptions {
18
- window_size: Duration::parse(&window_size),
15
+ let options = RollingOptionsFixedWindow {
16
+ window_size,
19
17
  weights,
20
18
  min_periods,
21
19
  center,
22
- by,
23
- closed_window: closed.map(|c| c.0),
24
20
  ..Default::default()
25
21
  };
26
22
  self.inner.clone().rolling_sum(options).into()
27
23
  }
28
24
 
29
- pub fn rolling_min(
25
+ pub fn rolling_sum_by(
30
26
  &self,
27
+ by: &RbExpr,
31
28
  window_size: String,
29
+ min_periods: usize,
30
+ closed: Wrap<ClosedWindow>,
31
+ ) -> Self {
32
+ let options = RollingOptionsDynamicWindow {
33
+ window_size: Duration::parse(&window_size),
34
+ min_periods,
35
+ closed_window: closed.0,
36
+ fn_params: None,
37
+ };
38
+ self.inner
39
+ .clone()
40
+ .rolling_sum_by(by.inner.clone(), options)
41
+ .into()
42
+ }
43
+
44
+ pub fn rolling_min(
45
+ &self,
46
+ window_size: usize,
32
47
  weights: Option<Vec<f64>>,
33
48
  min_periods: usize,
34
49
  center: bool,
35
- by: Option<String>,
36
- closed: Option<Wrap<ClosedWindow>>,
37
50
  ) -> Self {
38
- let options = RollingOptions {
39
- window_size: Duration::parse(&window_size),
51
+ let options = RollingOptionsFixedWindow {
52
+ window_size,
40
53
  weights,
41
54
  min_periods,
42
55
  center,
43
- by,
44
- closed_window: closed.map(|c| c.0),
45
56
  ..Default::default()
46
57
  };
47
58
  self.inner.clone().rolling_min(options).into()
48
59
  }
49
60
 
50
- pub fn rolling_max(
61
+ pub fn rolling_min_by(
51
62
  &self,
63
+ by: &RbExpr,
52
64
  window_size: String,
65
+ min_periods: usize,
66
+ closed: Wrap<ClosedWindow>,
67
+ ) -> Self {
68
+ let options = RollingOptionsDynamicWindow {
69
+ window_size: Duration::parse(&window_size),
70
+ min_periods,
71
+ closed_window: closed.0,
72
+ fn_params: None,
73
+ };
74
+ self.inner
75
+ .clone()
76
+ .rolling_min_by(by.inner.clone(), options)
77
+ .into()
78
+ }
79
+
80
+ pub fn rolling_max(
81
+ &self,
82
+ window_size: usize,
53
83
  weights: Option<Vec<f64>>,
54
84
  min_periods: usize,
55
85
  center: bool,
56
- by: Option<String>,
57
- closed: Option<Wrap<ClosedWindow>>,
58
86
  ) -> Self {
59
- let options = RollingOptions {
60
- window_size: Duration::parse(&window_size),
87
+ let options = RollingOptionsFixedWindow {
88
+ window_size: window_size,
61
89
  weights,
62
90
  min_periods,
63
91
  center,
64
- by,
65
- closed_window: closed.map(|c| c.0),
66
92
  ..Default::default()
67
93
  };
68
94
  self.inner.clone().rolling_max(options).into()
69
95
  }
70
96
 
71
- pub fn rolling_mean(
97
+ pub fn rolling_max_by(
72
98
  &self,
99
+ by: &RbExpr,
73
100
  window_size: String,
101
+ min_periods: usize,
102
+ closed: Wrap<ClosedWindow>,
103
+ ) -> Self {
104
+ let options = RollingOptionsDynamicWindow {
105
+ window_size: Duration::parse(&window_size),
106
+ min_periods,
107
+ closed_window: closed.0,
108
+ fn_params: None,
109
+ };
110
+ self.inner
111
+ .clone()
112
+ .rolling_max_by(by.inner.clone(), options)
113
+ .into()
114
+ }
115
+
116
+ pub fn rolling_mean(
117
+ &self,
118
+ window_size: usize,
74
119
  weights: Option<Vec<f64>>,
75
120
  min_periods: usize,
76
121
  center: bool,
77
- by: Option<String>,
78
- closed: Option<Wrap<ClosedWindow>>,
79
122
  ) -> Self {
80
- let options = RollingOptions {
81
- window_size: Duration::parse(&window_size),
123
+ let options = RollingOptionsFixedWindow {
124
+ window_size,
82
125
  weights,
83
126
  min_periods,
84
127
  center,
85
- by,
86
- closed_window: closed.map(|c| c.0),
87
128
  ..Default::default()
88
129
  };
89
130
 
90
131
  self.inner.clone().rolling_mean(options).into()
91
132
  }
92
133
 
93
- #[allow(clippy::too_many_arguments)]
94
- pub fn rolling_std(
134
+ pub fn rolling_mean_by(
95
135
  &self,
136
+ by: &RbExpr,
96
137
  window_size: String,
138
+ min_periods: usize,
139
+ closed: Wrap<ClosedWindow>,
140
+ ) -> Self {
141
+ let options = RollingOptionsDynamicWindow {
142
+ window_size: Duration::parse(&window_size),
143
+ min_periods,
144
+ closed_window: closed.0,
145
+ fn_params: None,
146
+ };
147
+
148
+ self.inner
149
+ .clone()
150
+ .rolling_mean_by(by.inner.clone(), options)
151
+ .into()
152
+ }
153
+
154
+ pub fn rolling_std(
155
+ &self,
156
+ window_size: usize,
97
157
  weights: Option<Vec<f64>>,
98
158
  min_periods: usize,
99
159
  center: bool,
100
- by: Option<String>,
101
- closed: Option<Wrap<ClosedWindow>>,
102
160
  ddof: u8,
103
- warn_if_unsorted: bool,
104
161
  ) -> Self {
105
- let options = RollingOptions {
106
- window_size: Duration::parse(&window_size),
162
+ let options = RollingOptionsFixedWindow {
163
+ window_size,
107
164
  weights,
108
165
  min_periods,
109
166
  center,
110
- by,
111
- closed_window: closed.map(|c| c.0),
112
167
  fn_params: Some(Arc::new(RollingVarParams { ddof }) as Arc<dyn Any + Send + Sync>),
113
- warn_if_unsorted,
114
168
  };
115
169
 
116
170
  self.inner.clone().rolling_std(options).into()
117
171
  }
118
172
 
119
- #[allow(clippy::too_many_arguments)]
120
- pub fn rolling_var(
173
+ pub fn rolling_std_by(
121
174
  &self,
175
+ by: &RbExpr,
122
176
  window_size: String,
177
+ min_periods: usize,
178
+ closed: Wrap<ClosedWindow>,
179
+ ddof: u8,
180
+ ) -> Self {
181
+ let options = RollingOptionsDynamicWindow {
182
+ window_size: Duration::parse(&window_size),
183
+ min_periods,
184
+ closed_window: closed.0,
185
+ fn_params: Some(Arc::new(RollingVarParams { ddof }) as Arc<dyn Any + Send + Sync>),
186
+ };
187
+
188
+ self.inner
189
+ .clone()
190
+ .rolling_std_by(by.inner.clone(), options)
191
+ .into()
192
+ }
193
+
194
+ pub fn rolling_var(
195
+ &self,
196
+ window_size: usize,
123
197
  weights: Option<Vec<f64>>,
124
198
  min_periods: usize,
125
199
  center: bool,
126
- by: Option<String>,
127
- closed: Option<Wrap<ClosedWindow>>,
128
200
  ddof: u8,
129
- warn_if_unsorted: bool,
130
201
  ) -> Self {
131
- let options = RollingOptions {
132
- window_size: Duration::parse(&window_size),
202
+ let options = RollingOptionsFixedWindow {
203
+ window_size,
133
204
  weights,
134
205
  min_periods,
135
206
  center,
136
- by,
137
- closed_window: closed.map(|c| c.0),
138
207
  fn_params: Some(Arc::new(RollingVarParams { ddof }) as Arc<dyn Any + Send + Sync>),
139
- warn_if_unsorted,
140
208
  };
141
209
 
142
210
  self.inner.clone().rolling_var(options).into()
143
211
  }
144
212
 
145
- #[allow(clippy::too_many_arguments)]
146
- pub fn rolling_median(
213
+ pub fn rolling_var_by(
147
214
  &self,
215
+ by: &RbExpr,
148
216
  window_size: String,
217
+ min_periods: usize,
218
+ closed: Wrap<ClosedWindow>,
219
+ ddof: u8,
220
+ ) -> Self {
221
+ let options = RollingOptionsDynamicWindow {
222
+ window_size: Duration::parse(&window_size),
223
+ min_periods,
224
+ closed_window: closed.0,
225
+ fn_params: Some(Arc::new(RollingVarParams { ddof }) as Arc<dyn Any + Send + Sync>),
226
+ };
227
+
228
+ self.inner
229
+ .clone()
230
+ .rolling_var_by(by.inner.clone(), options)
231
+ .into()
232
+ }
233
+
234
+ pub fn rolling_median(
235
+ &self,
236
+ window_size: usize,
149
237
  weights: Option<Vec<f64>>,
150
238
  min_periods: usize,
151
239
  center: bool,
152
- by: Option<String>,
153
- closed: Option<Wrap<ClosedWindow>>,
154
- warn_if_unsorted: bool,
155
240
  ) -> Self {
156
- let options = RollingOptions {
157
- window_size: Duration::parse(&window_size),
158
- weights,
241
+ let options = RollingOptionsFixedWindow {
242
+ window_size,
159
243
  min_periods,
244
+ weights,
160
245
  center,
161
- by,
162
- closed_window: closed.map(|c| c.0),
163
246
  fn_params: None,
164
- warn_if_unsorted,
165
247
  };
166
248
  self.inner.clone().rolling_median(options).into()
167
249
  }
168
250
 
169
- #[allow(clippy::too_many_arguments)]
251
+ pub fn rolling_median_by(
252
+ &self,
253
+ by: &RbExpr,
254
+ window_size: String,
255
+ min_periods: usize,
256
+ closed: Wrap<ClosedWindow>,
257
+ ) -> Self {
258
+ let options = RollingOptionsDynamicWindow {
259
+ window_size: Duration::parse(&window_size),
260
+ min_periods,
261
+ closed_window: closed.0,
262
+ fn_params: None,
263
+ };
264
+ self.inner
265
+ .clone()
266
+ .rolling_median_by(by.inner.clone(), options)
267
+ .into()
268
+ }
269
+
170
270
  pub fn rolling_quantile(
171
271
  &self,
172
272
  quantile: f64,
173
273
  interpolation: Wrap<QuantileInterpolOptions>,
174
- window_size: String,
274
+ window_size: usize,
175
275
  weights: Option<Vec<f64>>,
176
276
  min_periods: usize,
177
277
  center: bool,
178
- by: Option<String>,
179
- closed: Option<Wrap<ClosedWindow>>,
180
- warn_if_unsorted: bool,
181
278
  ) -> Self {
182
- let options = RollingOptions {
183
- window_size: Duration::parse(&window_size),
279
+ let options = RollingOptionsFixedWindow {
280
+ window_size,
184
281
  weights,
185
282
  min_periods,
186
283
  center,
187
- by,
188
- closed_window: closed.map(|c| c.0),
189
284
  fn_params: None,
190
- warn_if_unsorted,
191
285
  };
192
286
 
193
287
  self.inner
@@ -196,6 +290,28 @@ impl RbExpr {
196
290
  .into()
197
291
  }
198
292
 
293
+ pub fn rolling_quantile_by(
294
+ &self,
295
+ by: &RbExpr,
296
+ quantile: f64,
297
+ interpolation: Wrap<QuantileInterpolOptions>,
298
+ window_size: String,
299
+ min_periods: usize,
300
+ closed: Wrap<ClosedWindow>,
301
+ ) -> Self {
302
+ let options = RollingOptionsDynamicWindow {
303
+ window_size: Duration::parse(&window_size),
304
+ min_periods,
305
+ closed_window: closed.0,
306
+ fn_params: None,
307
+ };
308
+
309
+ self.inner
310
+ .clone()
311
+ .rolling_quantile_by(by.inner.clone(), interpolation.0, quantile, options)
312
+ .into()
313
+ }
314
+
199
315
  pub fn rolling_skew(&self, window_size: usize, bias: bool) -> Self {
200
316
  self.inner.clone().rolling_skew(window_size, bias).into()
201
317
  }
@@ -244,12 +244,12 @@ impl RbExpr {
244
244
  .into()
245
245
  }
246
246
 
247
- pub fn str_to_integer(&self, base: u32, strict: bool) -> Self {
247
+ pub fn str_to_integer(&self, base: &Self, strict: bool) -> Self {
248
248
  self.inner
249
249
  .clone()
250
250
  .str()
251
- .to_integer(base, strict)
252
- .with_fmt("str.parse_int")
251
+ .to_integer(base.inner.clone(), strict)
252
+ .with_fmt("str.to_integer")
253
253
  .into()
254
254
  }
255
255
 
@@ -259,39 +259,18 @@ impl RbExpr {
259
259
  infer_schema_len: Option<usize>,
260
260
  ) -> Self {
261
261
  let dtype = dtype.map(|wrap| wrap.0);
262
-
263
- let output_type = match dtype.clone() {
264
- Some(dtype) => GetOutput::from_type(dtype),
265
- None => GetOutput::from_type(DataType::Unknown),
266
- };
267
-
268
- let function = move |s: Series| {
269
- let ca = s.str()?;
270
- match ca.json_decode(dtype.clone(), infer_schema_len) {
271
- Ok(ca) => Ok(Some(ca.into_series())),
272
- Err(e) => Err(PolarsError::ComputeError(format!("{e:?}").into())),
273
- }
274
- };
275
-
276
- self.clone()
277
- .inner
278
- .map(function, output_type)
279
- .with_fmt("str.json_decode")
262
+ self.inner
263
+ .clone()
264
+ .str()
265
+ .json_decode(dtype, infer_schema_len)
280
266
  .into()
281
267
  }
282
268
 
283
- pub fn str_json_path_match(&self, pat: String) -> Self {
284
- let function = move |s: Series| {
285
- let ca = s.str()?;
286
- match ca.json_path_match(&pat) {
287
- Ok(ca) => Ok(Some(ca.into_series())),
288
- Err(e) => Err(PolarsError::ComputeError(format!("{:?}", e).into())),
289
- }
290
- };
291
- self.clone()
292
- .inner
293
- .map(function, GetOutput::from_type(DataType::String))
294
- .with_fmt("str.json_path_match")
269
+ pub fn str_json_path_match(&self, pat: &Self) -> Self {
270
+ self.inner
271
+ .clone()
272
+ .str()
273
+ .json_path_match(pat.inner.clone())
295
274
  .into()
296
275
  }
297
276
 
@@ -1,20 +1,167 @@
1
- use magnus::{exception, prelude::*, Error, RString, Value};
2
- use polars::io::mmap::MmapBytesReader;
3
1
  use std::fs::File;
4
- use std::io::Cursor;
2
+ use std::io;
3
+ use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write};
5
4
  use std::path::PathBuf;
6
5
 
6
+ use magnus::{exception, prelude::*, Error, RString, Value};
7
+ use polars::io::mmap::MmapBytesReader;
8
+
9
+ use crate::error::RbPolarsErr;
10
+ use crate::prelude::resolve_homedir;
7
11
  use crate::RbResult;
8
12
 
9
- pub fn get_file_like(f: Value, truncate: bool) -> RbResult<File> {
10
- let str_slice = PathBuf::try_convert(f)?;
11
- let f = if truncate {
12
- File::create(str_slice)
13
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?
13
+ #[derive(Clone)]
14
+ pub struct RbFileLikeObject {
15
+ inner: Value,
16
+ }
17
+
18
+ /// Wraps a `Value`, and implements read, seek, and write for it.
19
+ impl RbFileLikeObject {
20
+ /// Creates an instance of a `RbFileLikeObject` from a `Value`.
21
+ /// To assert the object has the required methods methods,
22
+ /// instantiate it with `RbFileLikeObject::require`
23
+ pub fn new(object: Value) -> Self {
24
+ RbFileLikeObject { inner: object }
25
+ }
26
+
27
+ pub fn as_buffer(&self) -> std::io::Cursor<Vec<u8>> {
28
+ let data = self.as_file_buffer().into_inner();
29
+ std::io::Cursor::new(data)
30
+ }
31
+
32
+ pub fn as_file_buffer(&self) -> Cursor<Vec<u8>> {
33
+ let bytes = self
34
+ .inner
35
+ .funcall::<_, _, RString>("read", ())
36
+ .expect("no read method found");
37
+
38
+ let buf = unsafe { bytes.as_slice() }.to_vec();
39
+
40
+ Cursor::new(buf)
41
+ }
42
+
43
+ /// Same as `RbFileLikeObject::new`, but validates that the underlying
44
+ /// ruby object has a `read`, `write`, and `seek` methods in respect to parameters.
45
+ /// Will return a `TypeError` if object does not have `read`, `seek`, and `write` methods.
46
+ pub fn with_requirements(object: Value, read: bool, write: bool, seek: bool) -> RbResult<Self> {
47
+ if read && !object.respond_to("read", false)? {
48
+ return Err(Error::new(
49
+ exception::type_error(),
50
+ "Object does not have a .read() method.",
51
+ ));
52
+ }
53
+
54
+ if seek && !object.respond_to("seek", false)? {
55
+ return Err(Error::new(
56
+ exception::type_error(),
57
+ "Object does not have a .seek() method.",
58
+ ));
59
+ }
60
+
61
+ if write && !object.respond_to("write", false)? {
62
+ return Err(Error::new(
63
+ exception::type_error(),
64
+ "Object does not have a .write() method.",
65
+ ));
66
+ }
67
+
68
+ Ok(RbFileLikeObject::new(object))
69
+ }
70
+ }
71
+
72
+ /// Extracts a string repr from, and returns an IO error to send back to rust.
73
+ fn rberr_to_io_err(e: Error) -> io::Error {
74
+ io::Error::new(io::ErrorKind::Other, e.to_string())
75
+ }
76
+
77
+ impl Read for RbFileLikeObject {
78
+ fn read(&mut self, mut buf: &mut [u8]) -> Result<usize, io::Error> {
79
+ let bytes = self
80
+ .inner
81
+ .funcall::<_, _, RString>("read", (buf.len(),))
82
+ .map_err(rberr_to_io_err)?;
83
+
84
+ buf.write_all(unsafe { bytes.as_slice() })?;
85
+
86
+ Ok(bytes.len())
87
+ }
88
+ }
89
+
90
+ impl Write for RbFileLikeObject {
91
+ fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
92
+ let rbbytes = RString::from_slice(buf);
93
+
94
+ let number_bytes_written = self
95
+ .inner
96
+ .funcall::<_, _, usize>("write", (rbbytes,))
97
+ .map_err(rberr_to_io_err)?;
98
+
99
+ Ok(number_bytes_written)
100
+ }
101
+
102
+ fn flush(&mut self) -> Result<(), io::Error> {
103
+ self.inner
104
+ .funcall::<_, _, Value>("flush", ())
105
+ .map_err(rberr_to_io_err)?;
106
+
107
+ Ok(())
108
+ }
109
+ }
110
+
111
+ impl Seek for RbFileLikeObject {
112
+ fn seek(&mut self, pos: SeekFrom) -> Result<u64, io::Error> {
113
+ let (whence, offset) = match pos {
114
+ SeekFrom::Start(i) => (0, i as i64),
115
+ SeekFrom::Current(i) => (1, i),
116
+ SeekFrom::End(i) => (2, i),
117
+ };
118
+
119
+ let new_position = self
120
+ .inner
121
+ .funcall("seek", (offset, whence))
122
+ .map_err(rberr_to_io_err)?;
123
+
124
+ Ok(new_position)
125
+ }
126
+ }
127
+
128
+ pub trait FileLike: Read + Write + Seek {}
129
+
130
+ impl FileLike for File {}
131
+ impl FileLike for RbFileLikeObject {}
132
+
133
+ pub enum EitherRustRubyFile {
134
+ Rb(RbFileLikeObject),
135
+ Rust(BufReader<File>),
136
+ }
137
+
138
+ ///
139
+ /// # Arguments
140
+ /// * `truncate` - open or create a new file.
141
+ pub fn get_either_file(rb_f: Value, truncate: bool) -> RbResult<EitherRustRubyFile> {
142
+ if let Ok(rstring) = RString::try_convert(rb_f) {
143
+ let s = unsafe { rstring.as_str() }?;
144
+ let file_path = std::path::Path::new(&s);
145
+ let file_path = resolve_homedir(file_path);
146
+ let f = if truncate {
147
+ File::create(file_path).map_err(RbPolarsErr::io)?
148
+ } else {
149
+ polars_utils::open_file(&file_path).map_err(RbPolarsErr::from)?
150
+ };
151
+ let reader = BufReader::new(f);
152
+ Ok(EitherRustRubyFile::Rust(reader))
14
153
  } else {
15
- File::open(str_slice).map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?
16
- };
17
- Ok(f)
154
+ let f = RbFileLikeObject::with_requirements(rb_f, !truncate, truncate, !truncate)?;
155
+ Ok(EitherRustRubyFile::Rb(f))
156
+ }
157
+ }
158
+
159
+ pub fn get_file_like(f: Value, truncate: bool) -> RbResult<Box<dyn FileLike>> {
160
+ use EitherRustRubyFile::*;
161
+ match get_either_file(f, truncate)? {
162
+ Rb(f) => Ok(Box::new(f)),
163
+ Rust(f) => Ok(Box::new(f.into_inner())),
164
+ }
18
165
  }
19
166
 
20
167
  pub fn get_mmap_bytes_reader(rb_f: Value) -> RbResult<Box<dyn MmapBytesReader>> {
@@ -55,9 +55,24 @@ pub fn rolling_cov(
55
55
  .into()
56
56
  }
57
57
 
58
- pub fn arg_sort_by(by: RArray, descending: Vec<bool>) -> RbResult<RbExpr> {
58
+ pub fn arg_sort_by(
59
+ by: RArray,
60
+ descending: Vec<bool>,
61
+ nulls_last: bool,
62
+ multithreaded: bool,
63
+ maintain_order: bool,
64
+ ) -> RbResult<RbExpr> {
59
65
  let by = rb_exprs_to_exprs(by)?;
60
- Ok(dsl::arg_sort_by(by, &descending).into())
66
+ Ok(dsl::arg_sort_by(
67
+ by,
68
+ SortMultipleOptions {
69
+ descending,
70
+ nulls_last,
71
+ multithreaded,
72
+ maintain_order,
73
+ },
74
+ )
75
+ .into())
61
76
  }
62
77
 
63
78
  pub fn arg_where(condition: &RbExpr) -> RbExpr {
@@ -115,6 +130,7 @@ pub fn concat_lf(
115
130
  rechunk,
116
131
  parallel,
117
132
  to_supertypes,
133
+ ..Default::default()
118
134
  },
119
135
  )
120
136
  .map_err(RbPolarsErr::from)?;
@@ -183,6 +199,7 @@ pub fn concat_lf_diagonal(
183
199
  rechunk,
184
200
  parallel,
185
201
  to_supertypes,
202
+ ..Default::default()
186
203
  },
187
204
  )
188
205
  .map_err(RbPolarsErr::from)?;
@@ -324,6 +341,6 @@ pub fn spearman_rank_corr(a: &RbExpr, b: &RbExpr, ddof: u8, propagate_nans: bool
324
341
  }
325
342
 
326
343
  pub fn sql_expr(sql: String) -> RbResult<RbExpr> {
327
- let expr = polars::sql::sql_expr(&sql).map_err(RbPolarsErr::from)?;
344
+ let expr = polars::sql::sql_expr(sql).map_err(RbPolarsErr::from)?;
328
345
  Ok(expr.into())
329
346
  }