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
@@ -3,16 +3,49 @@ use std::io;
3
3
  use std::io::{Cursor, Read, Seek, SeekFrom, Write};
4
4
  use std::path::PathBuf;
5
5
 
6
- use magnus::{exception, prelude::*, Error, RString, Value};
6
+ use magnus::{Error, RString, Ruby, Value, prelude::*, value::Opaque};
7
7
  use polars::io::mmap::MmapBytesReader;
8
+ use polars::prelude::PlRefPath;
9
+ use polars::prelude::file::{Writeable, WriteableTrait};
10
+ use polars_buffer::Buffer;
11
+ use polars_utils::create_file;
8
12
 
9
13
  use crate::error::RbPolarsErr;
10
14
  use crate::prelude::resolve_homedir;
11
- use crate::RbResult;
15
+ use crate::ruby::gvl::GvlExt;
16
+ use crate::ruby::thread::{is_non_ruby_thread, run_in_ruby_thread, start_background_ruby_thread};
17
+ use crate::utils::to_rb_err;
18
+ use crate::{RbErr, RbResult};
12
19
 
13
- #[derive(Clone)]
14
20
  pub struct RbFileLikeObject {
15
- inner: Value,
21
+ inner: Opaque<Value>,
22
+ expects_str: bool,
23
+ has_flush: bool,
24
+ }
25
+
26
+ impl WriteableTrait for RbFileLikeObject {
27
+ fn close(&mut self) -> io::Result<()> {
28
+ Ok(())
29
+ }
30
+
31
+ fn sync_all(&self) -> std::io::Result<()> {
32
+ self.flush()
33
+ }
34
+
35
+ fn sync_data(&self) -> std::io::Result<()> {
36
+ self.flush()
37
+ }
38
+ }
39
+
40
+ impl Clone for RbFileLikeObject {
41
+ fn clone(&self) -> Self {
42
+ // Skip attach
43
+ Self {
44
+ inner: self.inner,
45
+ expects_str: self.expects_str,
46
+ has_flush: self.has_flush,
47
+ }
48
+ }
16
49
  }
17
50
 
18
51
  /// Wraps a `Value`, and implements read, seek, and write for it.
@@ -20,107 +53,142 @@ impl RbFileLikeObject {
20
53
  /// Creates an instance of a `RbFileLikeObject` from a `Value`.
21
54
  /// To assert the object has the required methods methods,
22
55
  /// instantiate it with `RbFileLikeObject::require`
23
- pub fn new(object: Value) -> Self {
24
- RbFileLikeObject { inner: object }
25
- }
26
-
27
- pub fn as_bytes(&self) -> bytes::Bytes {
28
- self.as_file_buffer().into_inner().into()
56
+ pub fn new(object: Value, expects_str: bool, has_flush: bool) -> Self {
57
+ RbFileLikeObject {
58
+ inner: object.into(),
59
+ expects_str,
60
+ has_flush,
61
+ }
29
62
  }
30
63
 
31
- pub fn as_file_buffer(&self) -> Cursor<Vec<u8>> {
32
- let bytes = self
33
- .inner
34
- .funcall::<_, _, RString>("read", ())
35
- .expect("no read method found");
36
-
37
- let buf = unsafe { bytes.as_slice() }.to_vec();
64
+ pub(crate) fn to_buffer(&self) -> Buffer<u8> {
65
+ Ruby::attach(|rb| {
66
+ let bytes = rb
67
+ .get_inner(self.inner)
68
+ .funcall::<_, _, RString>("read", ())
69
+ .expect("no read method found");
38
70
 
39
- Cursor::new(buf)
71
+ let b = unsafe { bytes.as_slice() }.to_vec();
72
+ Buffer::from_vec(b)
73
+ })
40
74
  }
41
75
 
42
76
  /// Same as `RbFileLikeObject::new`, but validates that the underlying
43
77
  /// ruby object has a `read`, `write`, and `seek` methods in respect to parameters.
44
78
  /// Will return a `TypeError` if object does not have `read`, `seek`, and `write` methods.
45
- pub fn with_requirements(object: Value, read: bool, write: bool, seek: bool) -> RbResult<Self> {
79
+ pub fn ensure_requirements(object: Value, read: bool, write: bool, seek: bool) -> RbResult<()> {
80
+ let ruby = Ruby::get_with(object);
81
+
46
82
  if read && !object.respond_to("read", false)? {
47
83
  return Err(Error::new(
48
- exception::type_error(),
84
+ ruby.exception_type_error(),
49
85
  "Object does not have a .read() method.",
50
86
  ));
51
87
  }
52
88
 
53
89
  if seek && !object.respond_to("seek", false)? {
54
90
  return Err(Error::new(
55
- exception::type_error(),
91
+ ruby.exception_type_error(),
56
92
  "Object does not have a .seek() method.",
57
93
  ));
58
94
  }
59
95
 
60
96
  if write && !object.respond_to("write", false)? {
61
97
  return Err(Error::new(
62
- exception::type_error(),
98
+ ruby.exception_type_error(),
63
99
  "Object does not have a .write() method.",
64
100
  ));
65
101
  }
66
102
 
67
- Ok(RbFileLikeObject::new(object))
103
+ Ok(())
104
+ }
105
+
106
+ fn flush(&self) -> std::io::Result<()> {
107
+ if self.has_flush {
108
+ if is_non_ruby_thread() {
109
+ let self2 = self.clone();
110
+ return run_in_ruby_thread(move || self2.flush());
111
+ }
112
+
113
+ Ruby::attach(|rb| {
114
+ rb.get_inner(self.inner)
115
+ .funcall::<_, _, Value>("flush", ())
116
+ .map_err(rberr_to_io_err)
117
+ })?;
118
+ }
119
+
120
+ Ok(())
68
121
  }
69
122
  }
70
123
 
71
124
  /// Extracts a string repr from, and returns an IO error to send back to rust.
72
- fn rberr_to_io_err(e: Error) -> io::Error {
73
- io::Error::new(io::ErrorKind::Other, e.to_string())
125
+ fn rberr_to_io_err(e: RbErr) -> io::Error {
126
+ Ruby::attach(|_rb| io::Error::other(e.to_string()))
74
127
  }
75
128
 
76
129
  impl Read for RbFileLikeObject {
77
130
  fn read(&mut self, mut buf: &mut [u8]) -> Result<usize, io::Error> {
78
- let bytes = self
79
- .inner
80
- .funcall::<_, _, RString>("read", (buf.len(),))
81
- .map_err(rberr_to_io_err)?;
131
+ Ruby::attach(|rb| {
132
+ let bytes = rb
133
+ .get_inner(self.inner)
134
+ .funcall::<_, _, RString>("read", (buf.len(),))
135
+ .map_err(rberr_to_io_err)?;
82
136
 
83
- buf.write_all(unsafe { bytes.as_slice() })?;
137
+ buf.write_all(unsafe { bytes.as_slice() })?;
84
138
 
85
- Ok(bytes.len())
139
+ Ok(bytes.len())
140
+ })
86
141
  }
87
142
  }
88
143
 
89
144
  impl Write for RbFileLikeObject {
90
145
  fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
91
- let rbbytes = RString::from_slice(buf);
146
+ if is_non_ruby_thread() {
147
+ let mut self2 = self.clone();
148
+ let buf2 = buf.to_vec();
149
+ return run_in_ruby_thread(move || self2.write(&buf2));
150
+ }
151
+
152
+ let expects_str = self.expects_str;
153
+
154
+ Ruby::attach(|rb| {
155
+ if expects_str {
156
+ todo!();
157
+ }
158
+
159
+ let rbbytes = rb.str_from_slice(buf);
92
160
 
93
- let number_bytes_written = self
94
- .inner
95
- .funcall::<_, _, usize>("write", (rbbytes,))
96
- .map_err(rberr_to_io_err)?;
161
+ let number_bytes_written = rb
162
+ .get_inner(self.inner)
163
+ .funcall::<_, _, usize>("write", (rbbytes,))
164
+ .map_err(rberr_to_io_err)?;
97
165
 
98
- Ok(number_bytes_written)
166
+ Ok(number_bytes_written)
167
+ })
99
168
  }
100
169
 
101
170
  fn flush(&mut self) -> Result<(), io::Error> {
102
- self.inner
103
- .funcall::<_, _, Value>("flush", ())
104
- .map_err(rberr_to_io_err)?;
105
-
106
- Ok(())
171
+ Self::flush(self)
107
172
  }
108
173
  }
109
174
 
110
175
  impl Seek for RbFileLikeObject {
111
176
  fn seek(&mut self, pos: SeekFrom) -> Result<u64, io::Error> {
112
- let (whence, offset) = match pos {
113
- SeekFrom::Start(i) => (0, i as i64),
114
- SeekFrom::Current(i) => (1, i),
115
- SeekFrom::End(i) => (2, i),
116
- };
177
+ Ruby::attach(|rb| {
178
+ let (whence, offset) = match pos {
179
+ SeekFrom::Start(i) => (0, i as i64),
180
+ SeekFrom::Current(i) => (1, i),
181
+ SeekFrom::End(i) => (2, i),
182
+ };
117
183
 
118
- let new_position = self
119
- .inner
120
- .funcall("seek", (offset, whence))
121
- .map_err(rberr_to_io_err)?;
184
+ let inner = rb.get_inner(self.inner);
122
185
 
123
- Ok(new_position)
186
+ inner
187
+ .funcall::<_, _, Value>("seek", (offset, whence))
188
+ .map_err(rberr_to_io_err)?;
189
+
190
+ inner.funcall("tell", ()).map_err(rberr_to_io_err)
191
+ })
124
192
  }
125
193
  }
126
194
 
@@ -131,7 +199,7 @@ impl FileLike for RbFileLikeObject {}
131
199
 
132
200
  pub enum EitherRustRubyFile {
133
201
  Rb(RbFileLikeObject),
134
- Rust(File),
202
+ Rust(std::fs::File),
135
203
  }
136
204
 
137
205
  impl EitherRustRubyFile {
@@ -141,43 +209,85 @@ impl EitherRustRubyFile {
141
209
  EitherRustRubyFile::Rust(f) => Box::new(f),
142
210
  }
143
211
  }
212
+
213
+ fn into_scan_source_input(self) -> RubyScanSourceInput {
214
+ match self {
215
+ EitherRustRubyFile::Rb(f) => RubyScanSourceInput::Buffer(f.to_buffer()),
216
+ EitherRustRubyFile::Rust(f) => RubyScanSourceInput::File(f),
217
+ }
218
+ }
219
+
220
+ pub(crate) fn into_writeable(self) -> Writeable {
221
+ match self {
222
+ Self::Rb(f) => Writeable::Dyn(Box::new(f)),
223
+ Self::Rust(f) => Writeable::Local(f),
224
+ }
225
+ }
144
226
  }
145
227
 
146
228
  pub enum RubyScanSourceInput {
147
- Buffer(bytes::Bytes),
148
- Path(PathBuf),
149
- #[allow(dead_code)]
150
- File(File),
229
+ Buffer(Buffer<u8>),
230
+ Path(PlRefPath),
231
+ File(std::fs::File),
151
232
  }
152
233
 
153
- pub fn get_ruby_scan_source_input(rb_f: Value, write: bool) -> RbResult<RubyScanSourceInput> {
154
- if let Ok(file_path) = PathBuf::try_convert(rb_f) {
155
- // TODO resolve_homedir
156
- Ok(RubyScanSourceInput::Path(file_path))
157
- } else {
158
- let f = RbFileLikeObject::with_requirements(rb_f, !write, write, !write)?;
159
- Ok(RubyScanSourceInput::Buffer(f.as_bytes()))
234
+ pub(crate) fn try_get_rbfile(
235
+ rb: &Ruby,
236
+ rb_f: Value,
237
+ write: bool,
238
+ ) -> RbResult<(EitherRustRubyFile, Option<PathBuf>)> {
239
+ RbFileLikeObject::ensure_requirements(rb_f, !write, write, !write)?;
240
+ let expects_str = false;
241
+ let has_flush = rb_f.respond_to("flush", false)?;
242
+ let f = RbFileLikeObject::new(rb_f, expects_str, has_flush);
243
+
244
+ // TODO move
245
+ if write {
246
+ start_background_ruby_thread(rb);
160
247
  }
248
+
249
+ Ok((EitherRustRubyFile::Rb(f), None))
250
+ }
251
+
252
+ pub fn get_ruby_scan_source_input(rb_f: Value, write: bool) -> RbResult<RubyScanSourceInput> {
253
+ Ruby::attach(|rb| {
254
+ if let Ok(s) = PathBuf::try_convert(rb_f) {
255
+ let file_path =
256
+ PlRefPath::try_from_path(resolve_homedir(&s).as_ref()).map_err(to_rb_err)?;
257
+
258
+ Ok(RubyScanSourceInput::Path(file_path))
259
+ } else {
260
+ Ok(try_get_rbfile(rb, rb_f, write)?.0.into_scan_source_input())
261
+ }
262
+ })
263
+ }
264
+
265
+ pub fn get_either_buffer_or_path(
266
+ rb_f: Value,
267
+ write: bool,
268
+ ) -> RbResult<(EitherRustRubyFile, Option<PathBuf>)> {
269
+ Ruby::attach(|rb| {
270
+ if let Ok(rstring) = RString::try_convert(rb_f) {
271
+ let s = unsafe { rstring.as_str() }?;
272
+ let file_path = std::path::Path::new(&s);
273
+ let file_path = resolve_homedir(&file_path);
274
+ let f = if write {
275
+ create_file(&file_path).map_err(RbPolarsErr::from)?
276
+ } else {
277
+ polars_utils::open_file(&file_path).map_err(RbPolarsErr::from)?
278
+ };
279
+ Ok((EitherRustRubyFile::Rust(f), Some(file_path.into_owned())))
280
+ } else {
281
+ try_get_rbfile(rb, rb_f, write)
282
+ }
283
+ })
161
284
  }
162
285
 
163
286
  ///
164
287
  /// # Arguments
165
- /// * `truncate` - open or create a new file.
166
- pub fn get_either_file(rb_f: Value, truncate: bool) -> RbResult<EitherRustRubyFile> {
167
- if let Ok(rstring) = RString::try_convert(rb_f) {
168
- let s = unsafe { rstring.as_str() }?;
169
- let file_path = std::path::Path::new(&s);
170
- let file_path = resolve_homedir(file_path);
171
- let f = if truncate {
172
- File::create(file_path).map_err(RbPolarsErr::from)?
173
- } else {
174
- polars_utils::open_file(&file_path).map_err(RbPolarsErr::from)?
175
- };
176
- Ok(EitherRustRubyFile::Rust(f))
177
- } else {
178
- let f = RbFileLikeObject::with_requirements(rb_f, !truncate, truncate, !truncate)?;
179
- Ok(EitherRustRubyFile::Rb(f))
180
- }
288
+ /// * `write` - open for writing; will truncate existing file and create new file if not.
289
+ pub fn get_either_file(rb_f: Value, write: bool) -> RbResult<EitherRustRubyFile> {
290
+ Ok(get_either_buffer_or_path(rb_f, write)?.0)
181
291
  }
182
292
 
183
293
  pub fn get_file_like(f: Value, truncate: bool) -> RbResult<Box<dyn FileLike>> {
@@ -204,11 +314,9 @@ pub fn get_mmap_bytes_reader_and_path<'a>(
204
314
  ) -> RbResult<(Box<dyn MmapBytesReader + 'a>, Option<PathBuf>)> {
205
315
  match rb_f {
206
316
  RbReadBytes::Bytes(v) => Ok((Box::new(Cursor::new(unsafe { v.as_slice() })), None)),
207
- RbReadBytes::Other(v) => {
208
- let path = PathBuf::try_convert(*v)?;
209
- let f = File::open(&path)
210
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
211
- Ok((Box::new(f), Some(path)))
212
- }
317
+ RbReadBytes::Other(v) => match get_either_buffer_or_path(*v, false)? {
318
+ (EitherRustRubyFile::Rust(f), path) => Ok((Box::new(f), path)),
319
+ (EitherRustRubyFile::Rb(f), path) => Ok((Box::new(Cursor::new(f.to_buffer())), path)),
320
+ },
213
321
  }
214
322
  }
@@ -1,41 +1,42 @@
1
1
  use magnus::RArray;
2
2
  use polars::lazy::dsl;
3
3
 
4
- use crate::rb_exprs_to_exprs;
5
- use crate::{RbExpr, RbPolarsErr, RbResult};
4
+ use crate::error::RbPolarsErr;
5
+ use crate::expr::ToExprs;
6
+ use crate::{RbExpr, RbResult};
6
7
 
7
8
  pub fn all_horizontal(exprs: RArray) -> RbResult<RbExpr> {
8
- let exprs = rb_exprs_to_exprs(exprs)?;
9
+ let exprs = exprs.to_exprs()?;
9
10
  let e = dsl::all_horizontal(exprs).map_err(RbPolarsErr::from)?;
10
11
  Ok(e.into())
11
12
  }
12
13
 
13
14
  pub fn any_horizontal(exprs: RArray) -> RbResult<RbExpr> {
14
- let exprs = rb_exprs_to_exprs(exprs)?;
15
+ let exprs = exprs.to_exprs()?;
15
16
  let e = dsl::any_horizontal(exprs).map_err(RbPolarsErr::from)?;
16
17
  Ok(e.into())
17
18
  }
18
19
 
19
20
  pub fn max_horizontal(exprs: RArray) -> RbResult<RbExpr> {
20
- let exprs = rb_exprs_to_exprs(exprs)?;
21
+ let exprs = exprs.to_exprs()?;
21
22
  let e = dsl::max_horizontal(exprs).map_err(RbPolarsErr::from)?;
22
23
  Ok(e.into())
23
24
  }
24
25
 
25
26
  pub fn min_horizontal(exprs: RArray) -> RbResult<RbExpr> {
26
- let exprs = rb_exprs_to_exprs(exprs)?;
27
+ let exprs = exprs.to_exprs()?;
27
28
  let e = dsl::min_horizontal(exprs).map_err(RbPolarsErr::from)?;
28
29
  Ok(e.into())
29
30
  }
30
31
 
31
- pub fn sum_horizontal(exprs: RArray) -> RbResult<RbExpr> {
32
- let exprs = rb_exprs_to_exprs(exprs)?;
33
- let e = dsl::sum_horizontal(exprs).map_err(RbPolarsErr::from)?;
32
+ pub fn sum_horizontal(exprs: RArray, ignore_nulls: bool) -> RbResult<RbExpr> {
33
+ let exprs = exprs.to_exprs()?;
34
+ let e = dsl::sum_horizontal(exprs, ignore_nulls).map_err(RbPolarsErr::from)?;
34
35
  Ok(e.into())
35
36
  }
36
37
 
37
- pub fn mean_horizontal(exprs: RArray) -> RbResult<RbExpr> {
38
- let exprs = rb_exprs_to_exprs(exprs)?;
39
- let e = dsl::mean_horizontal(exprs).map_err(RbPolarsErr::from)?;
38
+ pub fn mean_horizontal(exprs: RArray, ignore_nulls: bool) -> RbResult<RbExpr> {
39
+ let exprs = exprs.to_exprs()?;
40
+ let e = dsl::mean_horizontal(exprs, ignore_nulls).map_err(RbPolarsErr::from)?;
40
41
  Ok(e.into())
41
42
  }
@@ -2,14 +2,13 @@ use polars::lazy::dsl;
2
2
 
3
3
  use crate::RbExpr;
4
4
 
5
- // TODO add to Ruby
6
5
  pub fn business_day_count(
7
6
  start: &RbExpr,
8
7
  end: &RbExpr,
9
8
  week_mask: [bool; 7],
10
- holidays: Vec<i32>,
9
+ holidays: &RbExpr,
11
10
  ) -> RbExpr {
12
11
  let start = start.inner.clone();
13
12
  let end = end.inner.clone();
14
- dsl::business_day_count(start, end, week_mask, holidays).into()
13
+ dsl::business_day_count(start, end, week_mask, holidays.inner.clone()).into()
15
14
  }
@@ -59,11 +59,12 @@ pub fn concat_df_diagonal(seq: RArray) -> RbResult<RbDataFrame> {
59
59
  Ok(df.into())
60
60
  }
61
61
 
62
- pub fn concat_df_horizontal(seq: RArray) -> RbResult<RbDataFrame> {
62
+ pub fn concat_df_horizontal(seq: RArray, strict: bool) -> RbResult<RbDataFrame> {
63
63
  let mut dfs = Vec::new();
64
64
  for item in seq.into_iter() {
65
65
  dfs.push(get_df(item)?);
66
66
  }
67
- let df = functions::concat_df_horizontal(&dfs, true).map_err(RbPolarsErr::from)?;
67
+ let df =
68
+ functions::concat_df_horizontal(&dfs, true, strict, false).map_err(RbPolarsErr::from)?;
68
69
  Ok(df.into())
69
70
  }
@@ -1,19 +1,80 @@
1
- use magnus::{RHash, Value};
1
+ use std::io::BufReader;
2
+
3
+ use magnus::{RHash, Ruby, Value};
4
+ use polars::prelude::ArrowSchema;
5
+ use polars::prelude::CloudScheme;
2
6
 
3
7
  use crate::conversion::Wrap;
4
- use crate::file::get_file_like;
5
- use crate::prelude::DataType;
8
+ use crate::file::{EitherRustRubyFile, get_either_file};
9
+ use crate::io::cloud_options::OptRbCloudOptions;
10
+ use crate::ruby::gvl::GvlExt;
11
+ use crate::ruby::utils::TryIntoValue;
6
12
  use crate::{RbPolarsErr, RbResult};
7
13
 
8
- pub fn read_ipc_schema(rb_f: Value) -> RbResult<RHash> {
9
- use polars_core::export::arrow::io::ipc::read::read_file_metadata;
10
- let mut r = get_file_like(rb_f, false)?;
11
- let metadata = read_file_metadata(&mut r).map_err(RbPolarsErr::from)?;
14
+ pub fn read_ipc_schema(rb: &Ruby, rb_f: Value) -> RbResult<RHash> {
15
+ use arrow::io::ipc::read::read_file_metadata;
16
+ let metadata = match get_either_file(rb_f, false)? {
17
+ EitherRustRubyFile::Rust(r) => {
18
+ read_file_metadata(&mut BufReader::new(r)).map_err(RbPolarsErr::from)?
19
+ }
20
+ EitherRustRubyFile::Rb(mut r) => read_file_metadata(&mut r).map_err(RbPolarsErr::from)?,
21
+ };
22
+
23
+ let dict = rb.hash_new();
24
+ fields_to_rbdict(&metadata.schema, &dict)?;
25
+ Ok(dict)
26
+ }
27
+
28
+ pub fn read_parquet_metadata(
29
+ rb: &Ruby,
30
+ rb_f: Value,
31
+ storage_options: OptRbCloudOptions,
32
+ credential_provider: Option<Value>,
33
+ ) -> RbResult<RHash> {
34
+ use std::io::Cursor;
35
+
36
+ use polars_parquet::read::read_metadata;
37
+ use polars_parquet::read::schema::read_custom_key_value_metadata;
38
+
39
+ use crate::file::{RubyScanSourceInput, get_ruby_scan_source_input};
12
40
 
13
- let dict = RHash::new();
14
- for field in metadata.schema.iter_values() {
15
- let dt: Wrap<DataType> = Wrap((&field.dtype).into());
16
- dict.aset(field.name.as_str(), dt)?;
41
+ let metadata = match get_ruby_scan_source_input(rb_f, false)? {
42
+ RubyScanSourceInput::Buffer(buf) => {
43
+ read_metadata(&mut Cursor::new(buf)).map_err(RbPolarsErr::from)?
44
+ }
45
+ RubyScanSourceInput::Path(p) => {
46
+ let cloud_options = storage_options.extract_opt_cloud_options(
47
+ CloudScheme::from_path(p.as_str()),
48
+ credential_provider,
49
+ )?;
50
+
51
+ if p.has_scheme() {
52
+ use polars::prelude::ParquetObjectStore;
53
+ use polars_error::PolarsResult;
54
+
55
+ rb.detach(|| {
56
+ polars_core::runtime::ASYNC.block_on(async {
57
+ let mut reader =
58
+ ParquetObjectStore::from_uri(p, cloud_options.as_ref(), None).await?;
59
+ let result = reader.get_metadata().await?;
60
+ PolarsResult::Ok((**result).clone())
61
+ })
62
+ })
63
+ .map_err(RbPolarsErr::from)?
64
+ } else {
65
+ let file = polars_utils::open_file(p.as_std_path()).map_err(RbPolarsErr::from)?;
66
+ read_metadata(&mut BufReader::new(file)).map_err(RbPolarsErr::from)?
67
+ }
68
+ }
69
+ RubyScanSourceInput::File(f) => {
70
+ read_metadata(&mut BufReader::new(f)).map_err(RbPolarsErr::from)?
71
+ }
72
+ };
73
+
74
+ let key_value_metadata = read_custom_key_value_metadata(metadata.key_value_metadata());
75
+ let dict = rb.hash_new();
76
+ for (key, value) in key_value_metadata.into_iter() {
77
+ dict.aset(key.as_str(), value.as_str())?;
17
78
  }
18
79
  Ok(dict)
19
80
  }
@@ -21,14 +82,25 @@ pub fn read_ipc_schema(rb_f: Value) -> RbResult<RHash> {
21
82
  pub fn read_parquet_schema(rb_f: Value) -> RbResult<RHash> {
22
83
  use polars_parquet::read::{infer_schema, read_metadata};
23
84
 
24
- let mut r = get_file_like(rb_f, false)?;
25
- let metadata = read_metadata(&mut r).map_err(RbPolarsErr::from)?;
85
+ let metadata = match get_either_file(rb_f, false)? {
86
+ EitherRustRubyFile::Rust(r) => {
87
+ read_metadata(&mut BufReader::new(r)).map_err(RbPolarsErr::from)?
88
+ }
89
+ EitherRustRubyFile::Rb(mut r) => read_metadata(&mut r).map_err(RbPolarsErr::from)?,
90
+ };
26
91
  let arrow_schema = infer_schema(&metadata).map_err(RbPolarsErr::from)?;
27
92
 
28
- let dict = RHash::new();
29
- for field in arrow_schema.iter_values() {
30
- let dt: Wrap<DataType> = Wrap((&field.dtype).into());
31
- dict.aset(field.name.as_str(), dt)?;
32
- }
93
+ let ruby = Ruby::get_with(rb_f);
94
+ let dict = ruby.hash_new();
95
+ fields_to_rbdict(&arrow_schema, &dict)?;
33
96
  Ok(dict)
34
97
  }
98
+
99
+ fn fields_to_rbdict(schema: &ArrowSchema, dict: &RHash) -> RbResult<()> {
100
+ let ruby = &Ruby::get_with(*dict);
101
+ for field in schema.iter_values() {
102
+ let dt = Wrap(polars::prelude::DataType::from_arrow_field(field));
103
+ dict.aset(field.name.as_str(), dt.try_into_value_with(ruby)?)?;
104
+ }
105
+ Ok(())
106
+ }