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
@@ -1,46 +1,48 @@
1
1
  pub(crate) mod any_value;
2
+ mod categorical;
2
3
  mod chunked_array;
4
+ mod datetime;
3
5
 
6
+ use std::collections::BTreeMap;
4
7
  use std::fmt::{Debug, Display, Formatter};
5
8
  use std::fs::File;
6
9
  use std::hash::{Hash, Hasher};
7
- use std::num::NonZeroUsize;
8
- use std::path::PathBuf;
9
10
 
11
+ pub use categorical::RbCategories;
10
12
  use magnus::{
11
- class, exception, prelude::*, r_hash::ForEach, try_convert::TryConvertOwned, value::Opaque,
12
- IntoValue, Module, RArray, RHash, Ruby, Symbol, TryConvert, Value,
13
+ IntoValue, Module, RArray, RHash, Ruby, Symbol, TryConvert, Value, prelude::*, r_hash::ForEach,
14
+ try_convert::TryConvertOwned, value::Opaque,
13
15
  };
14
16
  use polars::chunked_array::object::PolarsObjectSafe;
15
17
  use polars::chunked_array::ops::{FillNullLimit, FillNullStrategy};
16
18
  use polars::datatypes::AnyValue;
19
+ use polars::frame::PivotColumnNaming;
17
20
  use polars::frame::row::Row;
18
- use polars::frame::NullStrategy;
19
21
  use polars::io::avro::AvroCompression;
20
- use polars::io::cloud::CloudOptions;
22
+ use polars::prelude::default_values::{
23
+ DefaultFieldValues, IcebergIdentityTransformedPartitionFields,
24
+ };
25
+ use polars::prelude::deletion::DeletionFilesList;
21
26
  use polars::prelude::*;
22
27
  use polars::series::ops::NullBehavior;
28
+ use polars_buffer::Buffer;
29
+ use polars_compute::decimal::dec128_verify_prec_scale;
30
+ use polars_core::schema::iceberg::IcebergSchema;
23
31
  use polars_core::utils::arrow::array::Array;
24
32
  use polars_core::utils::materialize_dyn_int;
25
- use polars_plan::plans::ScanSources;
33
+ use polars_plan::dsl::ScanSources;
34
+ use polars_utils::compression::{BrotliLevel, GzipLevel, ZstdLevel};
26
35
  use polars_utils::total_ord::{TotalEq, TotalHash};
27
36
 
28
- use crate::file::{get_ruby_scan_source_input, RubyScanSourceInput};
37
+ use crate::file::{RubyScanSourceInput, get_ruby_scan_source_input};
29
38
  use crate::object::OBJECT_NAME;
30
- use crate::rb_modules::series;
31
- use crate::{RbDataFrame, RbLazyFrame, RbPolarsErr, RbResult, RbSeries, RbTypeError, RbValueError};
32
-
33
- pub(crate) fn slice_extract_wrapped<T>(slice: &[Wrap<T>]) -> &[T] {
34
- // Safety:
35
- // Wrap is transparent.
36
- unsafe { std::mem::transmute(slice) }
37
- }
38
-
39
- pub(crate) fn vec_extract_wrapped<T>(buf: Vec<Wrap<T>>) -> Vec<T> {
40
- // Safety:
41
- // Wrap is transparent.
42
- unsafe { std::mem::transmute(buf) }
43
- }
39
+ use crate::rb_modules::pl_series;
40
+ use crate::ruby::gvl::GvlExt;
41
+ use crate::ruby::utils::TryIntoValue;
42
+ use crate::utils::to_rb_err;
43
+ use crate::{
44
+ RbDataFrame, RbExpr, RbLazyFrame, RbPolarsErr, RbResult, RbSeries, RbTypeError, RbValueError,
45
+ };
44
46
 
45
47
  #[repr(transparent)]
46
48
  pub struct Wrap<T>(pub T);
@@ -68,24 +70,22 @@ pub(crate) fn get_rbseq(obj: Value) -> RbResult<(RArray, usize)> {
68
70
 
69
71
  pub(crate) fn get_df(obj: Value) -> RbResult<DataFrame> {
70
72
  let rbdf = obj.funcall::<_, _, &RbDataFrame>("_df", ())?;
71
- Ok(rbdf.df.borrow().clone())
73
+ Ok(rbdf.df.read().clone())
72
74
  }
73
75
 
74
76
  pub(crate) fn get_lf(obj: Value) -> RbResult<LazyFrame> {
75
77
  let rbdf = obj.funcall::<_, _, &RbLazyFrame>("_ldf", ())?;
76
- Ok(rbdf.ldf.borrow().clone())
78
+ Ok(rbdf.ldf.read().clone())
77
79
  }
78
80
 
79
81
  pub(crate) fn get_series(obj: Value) -> RbResult<Series> {
80
82
  let rbs = obj.funcall::<_, _, &RbSeries>("_s", ())?;
81
- Ok(rbs.series.borrow().clone())
83
+ Ok(rbs.series.read().clone())
82
84
  }
83
85
 
84
- pub(crate) fn to_series(s: RbSeries) -> Value {
85
- let series = series();
86
- series
87
- .funcall::<_, _, Value>("_from_rbseries", (s,))
88
- .unwrap()
86
+ pub(crate) fn to_series(rb: &Ruby, s: RbSeries) -> RbResult<Value> {
87
+ let series = pl_series(rb);
88
+ series.funcall::<_, _, Value>("_from_rbseries", (s,))
89
89
  }
90
90
 
91
91
  impl TryConvert for Wrap<PlSmallStr> {
@@ -117,151 +117,175 @@ impl TryConvert for Wrap<NullValues> {
117
117
  }
118
118
  }
119
119
 
120
- fn struct_dict<'a>(vals: impl Iterator<Item = AnyValue<'a>>, flds: &[Field]) -> Value {
121
- let dict = RHash::new();
120
+ fn struct_dict<'a>(
121
+ ruby: &Ruby,
122
+ vals: impl Iterator<Item = AnyValue<'a>>,
123
+ flds: &[Field],
124
+ ) -> RbResult<Value> {
125
+ let dict = ruby.hash_new();
122
126
  for (fld, val) in flds.iter().zip(vals) {
123
- dict.aset(fld.name().as_str(), Wrap(val)).unwrap()
127
+ dict.aset(fld.name().as_str(), Wrap(val).try_into_value_with(ruby)?)?;
124
128
  }
125
- dict.into_value()
129
+ Ok(dict.as_value())
126
130
  }
127
131
 
128
- impl IntoValue for Wrap<DataType> {
129
- fn into_value_with(self, _: &Ruby) -> Value {
130
- let pl = crate::rb_modules::polars();
132
+ impl TryIntoValue for Wrap<Series> {
133
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
134
+ to_series(ruby, RbSeries::new(self.0))
135
+ }
136
+ }
137
+
138
+ impl TryIntoValue for Wrap<DataType> {
139
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
140
+ let pl = crate::rb_modules::polars(ruby);
131
141
 
132
142
  match self.0 {
133
143
  DataType::Int8 => {
134
- let class = pl.const_get::<_, Value>("Int8").unwrap();
135
- class.funcall("new", ()).unwrap()
144
+ let class = pl.const_get::<_, Value>("Int8")?;
145
+ class.funcall("new", ())
136
146
  }
137
147
  DataType::Int16 => {
138
- let class = pl.const_get::<_, Value>("Int16").unwrap();
139
- class.funcall("new", ()).unwrap()
148
+ let class = pl.const_get::<_, Value>("Int16")?;
149
+ class.funcall("new", ())
140
150
  }
141
151
  DataType::Int32 => {
142
- let class = pl.const_get::<_, Value>("Int32").unwrap();
143
- class.funcall("new", ()).unwrap()
152
+ let class = pl.const_get::<_, Value>("Int32")?;
153
+ class.funcall("new", ())
144
154
  }
145
155
  DataType::Int64 => {
146
- let class = pl.const_get::<_, Value>("Int64").unwrap();
147
- class.funcall("new", ()).unwrap()
156
+ let class = pl.const_get::<_, Value>("Int64")?;
157
+ class.funcall("new", ())
158
+ }
159
+ DataType::Int128 => {
160
+ let class = pl.const_get::<_, Value>("Int128")?;
161
+ class.funcall("new", ())
148
162
  }
149
163
  DataType::UInt8 => {
150
- let class = pl.const_get::<_, Value>("UInt8").unwrap();
151
- class.funcall("new", ()).unwrap()
164
+ let class = pl.const_get::<_, Value>("UInt8")?;
165
+ class.funcall("new", ())
152
166
  }
153
167
  DataType::UInt16 => {
154
- let class = pl.const_get::<_, Value>("UInt16").unwrap();
155
- class.funcall("new", ()).unwrap()
168
+ let class = pl.const_get::<_, Value>("UInt16")?;
169
+ class.funcall("new", ())
156
170
  }
157
171
  DataType::UInt32 => {
158
- let class = pl.const_get::<_, Value>("UInt32").unwrap();
159
- class.funcall("new", ()).unwrap()
172
+ let class = pl.const_get::<_, Value>("UInt32")?;
173
+ class.funcall("new", ())
160
174
  }
161
175
  DataType::UInt64 => {
162
- let class = pl.const_get::<_, Value>("UInt64").unwrap();
163
- class.funcall("new", ()).unwrap()
176
+ let class = pl.const_get::<_, Value>("UInt64")?;
177
+ class.funcall("new", ())
178
+ }
179
+ DataType::UInt128 => {
180
+ let class = pl.const_get::<_, Value>("UInt128")?;
181
+ class.funcall("new", ())
182
+ }
183
+ DataType::Float16 => {
184
+ let class = pl.const_get::<_, Value>("Float16")?;
185
+ class.funcall("new", ())
164
186
  }
165
187
  DataType::Float32 => {
166
- let class = pl.const_get::<_, Value>("Float32").unwrap();
167
- class.funcall("new", ()).unwrap()
188
+ let class = pl.const_get::<_, Value>("Float32")?;
189
+ class.funcall("new", ())
168
190
  }
169
191
  DataType::Float64 | DataType::Unknown(UnknownKind::Float) => {
170
- let class = pl.const_get::<_, Value>("Float64").unwrap();
171
- class.funcall("new", ()).unwrap()
192
+ let class = pl.const_get::<_, Value>("Float64")?;
193
+ class.funcall("new", ())
172
194
  }
173
195
  DataType::Decimal(precision, scale) => {
174
- let class = pl.const_get::<_, Value>("Decimal").unwrap();
175
- class
176
- .funcall::<_, _, Value>("new", (precision, scale))
177
- .unwrap()
196
+ let class = pl.const_get::<_, Value>("Decimal")?;
197
+ class.funcall::<_, _, Value>("new", (precision, scale))
178
198
  }
179
199
  DataType::Boolean => {
180
- let class = pl.const_get::<_, Value>("Boolean").unwrap();
181
- class.funcall("new", ()).unwrap()
200
+ let class = pl.const_get::<_, Value>("Boolean")?;
201
+ class.funcall("new", ())
182
202
  }
183
203
  DataType::String | DataType::Unknown(UnknownKind::Str) => {
184
- let class = pl.const_get::<_, Value>("String").unwrap();
185
- class.funcall("new", ()).unwrap()
204
+ let class = pl.const_get::<_, Value>("String")?;
205
+ class.funcall("new", ())
186
206
  }
187
207
  DataType::Binary => {
188
- let class = pl.const_get::<_, Value>("Binary").unwrap();
189
- class.funcall("new", ()).unwrap()
208
+ let class = pl.const_get::<_, Value>("Binary")?;
209
+ class.funcall("new", ())
190
210
  }
191
211
  DataType::Array(inner, size) => {
192
- let class = pl.const_get::<_, Value>("Array").unwrap();
193
- let inner = Wrap(*inner);
212
+ let class = pl.const_get::<_, Value>("Array")?;
213
+ let inner = Wrap(*inner).try_into_value_with(ruby)?;
194
214
  let args = (inner, size);
195
- class.funcall::<_, _, Value>("new", args).unwrap()
215
+ class.funcall::<_, _, Value>("new", args)
196
216
  }
197
217
  DataType::List(inner) => {
198
- let class = pl.const_get::<_, Value>("List").unwrap();
199
- let inner = Wrap(*inner);
200
- class.funcall::<_, _, Value>("new", (inner,)).unwrap()
218
+ let class = pl.const_get::<_, Value>("List")?;
219
+ let inner = Wrap(*inner).try_into_value_with(ruby)?;
220
+ class.funcall::<_, _, Value>("new", (inner,))
201
221
  }
202
222
  DataType::Date => {
203
- let class = pl.const_get::<_, Value>("Date").unwrap();
204
- class.funcall("new", ()).unwrap()
223
+ let class = pl.const_get::<_, Value>("Date")?;
224
+ class.funcall("new", ())
205
225
  }
206
226
  DataType::Datetime(tu, tz) => {
207
- let datetime_class = pl.const_get::<_, Value>("Datetime").unwrap();
208
- datetime_class
209
- .funcall::<_, _, Value>("new", (tu.to_ascii(), tz.as_deref()))
210
- .unwrap()
227
+ let datetime_class = pl.const_get::<_, Value>("Datetime")?;
228
+ datetime_class.funcall::<_, _, Value>(
229
+ "new",
230
+ (tu.to_ascii(), tz.as_deref().map(|x| x.as_str())),
231
+ )
211
232
  }
212
233
  DataType::Duration(tu) => {
213
- let duration_class = pl.const_get::<_, Value>("Duration").unwrap();
214
- duration_class
215
- .funcall::<_, _, Value>("new", (tu.to_ascii(),))
216
- .unwrap()
217
- }
218
- DataType::Object(_, _) => {
219
- let class = pl.const_get::<_, Value>("Object").unwrap();
220
- class.funcall("new", ()).unwrap()
221
- }
222
- DataType::Categorical(_, ordering) => {
223
- let class = pl.const_get::<_, Value>("Categorical").unwrap();
224
- class.funcall("new", (Wrap(ordering),)).unwrap()
225
- }
226
- DataType::Enum(rev_map, _) => {
227
- // we should always have an initialized rev_map coming from rust
228
- let categories = rev_map.as_ref().unwrap().get_categories();
229
- let class = pl.const_get::<_, Value>("Enum").unwrap();
230
- let s =
231
- Series::from_arrow(PlSmallStr::from_static("category"), categories.to_boxed())
232
- .unwrap();
233
- let series = to_series(s.into());
234
- class.funcall::<_, _, Value>("new", (series,)).unwrap()
234
+ let duration_class = pl.const_get::<_, Value>("Duration")?;
235
+ duration_class.funcall::<_, _, Value>("new", (tu.to_ascii(),))
236
+ }
237
+ DataType::Object(_) => {
238
+ let class = pl.const_get::<_, Value>("Object")?;
239
+ class.funcall("new", ())
240
+ }
241
+ DataType::Categorical(cats, _) => {
242
+ let categories_class = pl.const_get::<_, Value>("Categories")?;
243
+ let categorical_class = pl.const_get::<_, Value>("Categorical")?;
244
+ let categories: Value = categories_class
245
+ .funcall("_from_rb_categories", (RbCategories::from(cats.clone()),))?;
246
+ let kwargs = ruby.hash_new();
247
+ kwargs.aset(ruby.to_symbol("categories"), categories)?;
248
+ categorical_class.funcall("new", (kwargs,))
249
+ }
250
+ DataType::Enum(_, mapping) => {
251
+ let categories = unsafe {
252
+ StringChunked::from_chunks(
253
+ PlSmallStr::from_static("category"),
254
+ vec![mapping.to_arrow(true)],
255
+ )
256
+ };
257
+ let class = pl.const_get::<_, Value>("Enum")?;
258
+ let series = to_series(ruby, categories.into_series().into())?;
259
+ class.funcall::<_, _, Value>("new", (series,))
235
260
  }
236
261
  DataType::Time => {
237
- let class = pl.const_get::<_, Value>("Time").unwrap();
238
- class.funcall("new", ()).unwrap()
262
+ let class = pl.const_get::<_, Value>("Time")?;
263
+ class.funcall("new", ())
239
264
  }
240
265
  DataType::Struct(fields) => {
241
- let field_class = pl.const_get::<_, Value>("Field").unwrap();
266
+ let field_class = pl.const_get::<_, Value>("Field")?;
242
267
  let iter = fields.iter().map(|fld| {
243
268
  let name = fld.name().as_str();
244
- let dtype = Wrap(fld.dtype().clone());
245
- field_class
246
- .funcall::<_, _, Value>("new", (name, dtype))
247
- .unwrap()
269
+ let dtype = Wrap(fld.dtype().clone()).try_into_value_with(ruby);
270
+ dtype.and_then(|dt| field_class.funcall::<_, _, Value>("new", (name, dt)))
248
271
  });
249
- let fields = RArray::from_iter(iter);
250
- let struct_class = pl.const_get::<_, Value>("Struct").unwrap();
251
- struct_class
252
- .funcall::<_, _, Value>("new", (fields,))
253
- .unwrap()
272
+ let fields = ruby.ary_try_from_iter(iter)?;
273
+ let struct_class = pl.const_get::<_, Value>("Struct")?;
274
+ struct_class.funcall::<_, _, Value>("new", (fields,))
254
275
  }
255
276
  DataType::Null => {
256
- let class = pl.const_get::<_, Value>("Null").unwrap();
257
- class.funcall("new", ()).unwrap()
277
+ let class = pl.const_get::<_, Value>("Null")?;
278
+ class.funcall("new", ())
279
+ }
280
+ DataType::Extension(_typ, _storage) => {
281
+ todo!();
258
282
  }
259
283
  DataType::Unknown(UnknownKind::Int(v)) => {
260
- Wrap(materialize_dyn_int(v).dtype()).into_value()
284
+ Wrap(materialize_dyn_int(v).dtype()).try_into_value_with(ruby)
261
285
  }
262
286
  DataType::Unknown(_) => {
263
- let class = pl.const_get::<_, Value>("Unknown").unwrap();
264
- class.funcall("new", ()).unwrap()
287
+ let class = pl.const_get::<_, Value>("Unknown")?;
288
+ class.funcall("new", ())
265
289
  }
266
290
  DataType::BinaryOffset => {
267
291
  unimplemented!()
@@ -270,24 +294,24 @@ impl IntoValue for Wrap<DataType> {
270
294
  }
271
295
  }
272
296
 
297
+ enum CategoricalOrdering {
298
+ Lexical,
299
+ }
300
+
273
301
  impl IntoValue for Wrap<CategoricalOrdering> {
274
- fn into_value_with(self, _: &Ruby) -> Value {
275
- let ordering = match self.0 {
276
- CategoricalOrdering::Physical => "physical",
277
- CategoricalOrdering::Lexical => "lexical",
278
- };
279
- ordering.into_value()
302
+ fn into_value_with(self, ruby: &Ruby) -> Value {
303
+ "lexical".into_value_with(ruby)
280
304
  }
281
305
  }
282
306
 
283
307
  impl IntoValue for Wrap<TimeUnit> {
284
- fn into_value_with(self, _: &Ruby) -> Value {
308
+ fn into_value_with(self, ruby: &Ruby) -> Value {
285
309
  let tu = match self.0 {
286
310
  TimeUnit::Nanoseconds => "ns",
287
311
  TimeUnit::Microseconds => "us",
288
312
  TimeUnit::Milliseconds => "ms",
289
313
  };
290
- tu.into_value()
314
+ tu.into_value_with(ruby)
291
315
  }
292
316
  }
293
317
 
@@ -301,93 +325,120 @@ impl TryConvert for Wrap<Field> {
301
325
 
302
326
  impl TryConvert for Wrap<DataType> {
303
327
  fn try_convert(ob: Value) -> RbResult<Self> {
304
- let dtype = if ob.is_kind_of(class::class()) {
328
+ let ruby = Ruby::get_with(ob);
329
+ let dtype = if ob.is_kind_of(ruby.class_class()) {
305
330
  let name = ob.funcall::<_, _, String>("name", ())?;
306
331
  match name.as_str() {
307
- "Polars::UInt8" => DataType::UInt8,
308
- "Polars::UInt16" => DataType::UInt16,
309
- "Polars::UInt32" => DataType::UInt32,
310
- "Polars::UInt64" => DataType::UInt64,
311
332
  "Polars::Int8" => DataType::Int8,
312
333
  "Polars::Int16" => DataType::Int16,
313
334
  "Polars::Int32" => DataType::Int32,
314
335
  "Polars::Int64" => DataType::Int64,
336
+ "Polars::Int128" => DataType::Int64,
337
+ "Polars::UInt8" => DataType::UInt8,
338
+ "Polars::UInt16" => DataType::UInt16,
339
+ "Polars::UInt32" => DataType::UInt32,
340
+ "Polars::UInt64" => DataType::UInt64,
341
+ "Polars::UInt128" => DataType::UInt128,
342
+ "Polars::Float16" => DataType::Float16,
343
+ "Polars::Float32" => DataType::Float32,
344
+ "Polars::Float64" => DataType::Float64,
345
+ "Polars::Boolean" => DataType::Boolean,
315
346
  "Polars::String" => DataType::String,
316
347
  "Polars::Binary" => DataType::Binary,
317
- "Polars::Boolean" => DataType::Boolean,
318
- "Polars::Categorical" => DataType::Categorical(None, Default::default()),
319
- "Polars::Enum" => DataType::Enum(None, Default::default()),
348
+ "Polars::Categorical" => DataType::from_categories(Categories::global()),
349
+ "Polars::Enum" => {
350
+ DataType::from_frozen_categories(FrozenCategories::new([]).unwrap())
351
+ }
320
352
  "Polars::Date" => DataType::Date,
321
- "Polars::Datetime" => DataType::Datetime(TimeUnit::Microseconds, None),
322
353
  "Polars::Time" => DataType::Time,
354
+ "Polars::Datetime" => DataType::Datetime(TimeUnit::Microseconds, None),
323
355
  "Polars::Duration" => DataType::Duration(TimeUnit::Microseconds),
324
- "Polars::Decimal" => DataType::Decimal(None, None),
325
- "Polars::Float32" => DataType::Float32,
326
- "Polars::Float64" => DataType::Float64,
327
- "Polars::Object" => DataType::Object(OBJECT_NAME, None),
356
+ "Polars::Decimal" => {
357
+ return Err(RbTypeError::new_err(
358
+ "Decimal without precision/scale set is not a valid Polars datatype",
359
+ ));
360
+ }
328
361
  "Polars::List" => DataType::List(Box::new(DataType::Null)),
362
+ "Polars::Array" => DataType::Array(Box::new(DataType::Null), 0),
363
+ "Polars::Struct" => DataType::Struct(vec![]),
329
364
  "Polars::Null" => DataType::Null,
365
+ "Polars::Object" => DataType::Object(OBJECT_NAME),
330
366
  "Polars::Unknown" => DataType::Unknown(Default::default()),
331
367
  dt => {
332
- return Err(RbValueError::new_err(format!(
333
- "{dt} is not a correct polars DataType.",
334
- )))
368
+ return Err(RbTypeError::new_err(format!(
369
+ "'{dt}' is not a Polars data type",
370
+ )));
335
371
  }
336
372
  }
337
- } else if String::try_convert(ob).is_err() {
338
- let name = unsafe { ob.class().name() }.into_owned();
373
+ } else {
374
+ let cls = ob.class();
375
+ let name = unsafe { cls.name() }.into_owned();
339
376
  match name.as_str() {
340
377
  "Polars::Int8" => DataType::Int8,
341
378
  "Polars::Int16" => DataType::Int16,
342
379
  "Polars::Int32" => DataType::Int32,
343
380
  "Polars::Int64" => DataType::Int64,
381
+ "Polars::Int128" => DataType::Int128,
344
382
  "Polars::UInt8" => DataType::UInt8,
345
383
  "Polars::UInt16" => DataType::UInt16,
346
384
  "Polars::UInt32" => DataType::UInt32,
347
385
  "Polars::UInt64" => DataType::UInt64,
386
+ "Polars::UInt128" => DataType::UInt128,
387
+ "Polars::Float16" => DataType::Float16,
388
+ "Polars::Float32" => DataType::Float32,
389
+ "Polars::Float64" => DataType::Float64,
390
+ "Polars::Boolean" => DataType::Boolean,
348
391
  "Polars::String" => DataType::String,
349
392
  "Polars::Binary" => DataType::Binary,
350
- "Polars::Boolean" => DataType::Boolean,
351
393
  "Polars::Categorical" => {
352
- let ordering = ob
353
- .funcall::<_, _, Wrap<CategoricalOrdering>>("ordering", ())?
354
- .0;
355
- DataType::Categorical(None, ordering)
394
+ let categories: Value = ob.funcall("categories", ())?;
395
+ let rb_categories: &RbCategories = categories.funcall("_categories", ())?;
396
+ DataType::from_categories(rb_categories.categories().clone())
356
397
  }
357
398
  "Polars::Enum" => {
358
- let categories = ob.funcall("categories", ()).unwrap();
399
+ let categories: Value = ob.funcall("categories", ())?;
359
400
  let s = get_series(categories)?;
360
401
  let ca = s.str().map_err(RbPolarsErr::from)?;
361
402
  let categories = ca.downcast_iter().next().unwrap().clone();
362
- create_enum_dtype(categories)
403
+ assert!(!categories.has_nulls());
404
+ DataType::from_frozen_categories(
405
+ FrozenCategories::new(categories.values_iter()).unwrap(),
406
+ )
363
407
  }
364
408
  "Polars::Date" => DataType::Date,
365
409
  "Polars::Time" => DataType::Time,
366
- "Polars::Float32" => DataType::Float32,
367
- "Polars::Float64" => DataType::Float64,
368
- "Polars::Null" => DataType::Null,
369
- "Polars::Unknown" => DataType::Unknown(Default::default()),
370
- "Polars::Duration" => {
371
- let time_unit: Value = ob.funcall("time_unit", ()).unwrap();
372
- let time_unit = Wrap::<TimeUnit>::try_convert(time_unit)?.0;
373
- DataType::Duration(time_unit)
374
- }
375
410
  "Polars::Datetime" => {
376
- let time_unit: Value = ob.funcall("time_unit", ()).unwrap();
411
+ let time_unit: Value = ob.funcall("time_unit", ())?;
377
412
  let time_unit = Wrap::<TimeUnit>::try_convert(time_unit)?.0;
378
413
  let time_zone: Option<String> = ob.funcall("time_zone", ())?;
379
- DataType::Datetime(time_unit, time_zone.as_deref().map(|x| x.into()))
414
+ DataType::Datetime(
415
+ time_unit,
416
+ TimeZone::opt_try_new(time_zone.as_deref()).map_err(RbPolarsErr::from)?,
417
+ )
418
+ }
419
+ "Polars::Duration" => {
420
+ let time_unit: Value = ob.funcall("time_unit", ())?;
421
+ let time_unit = Wrap::<TimeUnit>::try_convert(time_unit)?.0;
422
+ DataType::Duration(time_unit)
380
423
  }
381
424
  "Polars::Decimal" => {
382
425
  let precision = ob.funcall("precision", ())?;
383
426
  let scale = ob.funcall("scale", ())?;
384
- DataType::Decimal(precision, Some(scale))
427
+ dec128_verify_prec_scale(precision, scale).map_err(to_rb_err)?;
428
+ DataType::Decimal(precision, scale)
385
429
  }
386
430
  "Polars::List" => {
387
- let inner: Value = ob.funcall("inner", ()).unwrap();
431
+ let inner: Value = ob.funcall("inner", ())?;
388
432
  let inner = Wrap::<DataType>::try_convert(inner)?;
389
433
  DataType::List(Box::new(inner.0))
390
434
  }
435
+ "Polars::Array" => {
436
+ let inner: Value = ob.funcall("inner", ())?;
437
+ let size: Value = ob.funcall("size", ())?;
438
+ let inner = Wrap::<DataType>::try_convert(inner)?;
439
+ let size = usize::try_convert(size)?;
440
+ DataType::Array(Box::new(inner.0), size)
441
+ }
391
442
  "Polars::Struct" => {
392
443
  let arr: RArray = ob.funcall("fields", ())?;
393
444
  let mut fields = Vec::with_capacity(arr.len());
@@ -396,42 +447,13 @@ impl TryConvert for Wrap<DataType> {
396
447
  }
397
448
  DataType::Struct(fields)
398
449
  }
450
+ "Polars::Null" => DataType::Null,
451
+ "Polars::Object" => DataType::Object(OBJECT_NAME),
452
+ "Polars::Unknown" => DataType::Unknown(Default::default()),
399
453
  dt => {
400
454
  return Err(RbTypeError::new_err(format!(
401
- "A {dt} object is not a correct polars DataType. \
402
- Hint: use the class without instantiating it.",
403
- )))
404
- }
405
- }
406
- } else {
407
- match String::try_convert(ob)?.as_str() {
408
- "u8" => DataType::UInt8,
409
- "u16" => DataType::UInt16,
410
- "u32" => DataType::UInt32,
411
- "u64" => DataType::UInt64,
412
- "i8" => DataType::Int8,
413
- "i16" => DataType::Int16,
414
- "i32" => DataType::Int32,
415
- "i64" => DataType::Int64,
416
- "str" => DataType::String,
417
- "bin" => DataType::Binary,
418
- "bool" => DataType::Boolean,
419
- "cat" => DataType::Categorical(None, Default::default()),
420
- "date" => DataType::Date,
421
- "datetime" => DataType::Datetime(TimeUnit::Microseconds, None),
422
- "f32" => DataType::Float32,
423
- "time" => DataType::Time,
424
- "dur" => DataType::Duration(TimeUnit::Microseconds),
425
- "f64" => DataType::Float64,
426
- "obj" => DataType::Object(OBJECT_NAME, None),
427
- "list" => DataType::List(Box::new(DataType::Boolean)),
428
- "null" => DataType::Null,
429
- "unk" => DataType::Unknown(Default::default()),
430
- _ => {
431
- return Err(RbValueError::new_err(format!(
432
- "{} is not a supported DataType.",
433
- ob
434
- )))
455
+ "'{dt}' is not a Polars data type",
456
+ )));
435
457
  }
436
458
  }
437
459
  };
@@ -455,7 +477,7 @@ impl TryConvert for Wrap<StatisticsOptions> {
455
477
  _ => {
456
478
  return Err(RbTypeError::new_err(format!(
457
479
  "'{key}' is not a valid statistic option",
458
- )))
480
+ )));
459
481
  }
460
482
  }
461
483
  Ok(ForEach::Continue)
@@ -478,7 +500,7 @@ impl<'s> TryConvert for Wrap<Row<'s>> {
478
500
 
479
501
  impl TryConvert for Wrap<Schema> {
480
502
  fn try_convert(ob: Value) -> RbResult<Self> {
481
- let dict = RHash::try_convert(ob)?;
503
+ let dict: RHash = ob.funcall("to_h", ())?;
482
504
 
483
505
  let mut schema = Vec::new();
484
506
  dict.foreach(|key: String, val: Wrap<DataType>| {
@@ -490,6 +512,60 @@ impl TryConvert for Wrap<Schema> {
490
512
  }
491
513
  }
492
514
 
515
+ impl TryConvert for Wrap<ArrowSchema> {
516
+ fn try_convert(ob: Value) -> RbResult<Self> {
517
+ let ruby = Ruby::get_with(ob);
518
+ // TODO improve
519
+ let ob = RHash::try_convert(ob)?;
520
+ let fields: RArray = ob.aref(ruby.to_symbol("fields"))?;
521
+ let mut arrow_schema = ArrowSchema::with_capacity(fields.len());
522
+ for f in fields {
523
+ let f = RHash::try_convert(f)?;
524
+ let name: String = f.aref(ruby.to_symbol("name"))?;
525
+ let rb_dtype: String = f.aref(ruby.to_symbol("type"))?;
526
+ let dtype = match rb_dtype.as_str() {
527
+ "null" => ArrowDataType::Null,
528
+ "boolean" => ArrowDataType::Boolean,
529
+ "int8" => ArrowDataType::Int8,
530
+ "int16" => ArrowDataType::Int16,
531
+ "int32" => ArrowDataType::Int32,
532
+ "int64" => ArrowDataType::Int64,
533
+ "uint8" => ArrowDataType::UInt8,
534
+ "uint16" => ArrowDataType::UInt16,
535
+ "uint32" => ArrowDataType::UInt32,
536
+ "uint64" => ArrowDataType::UInt64,
537
+ "float16" => ArrowDataType::Float16,
538
+ "float32" => ArrowDataType::Float32,
539
+ "float64" => ArrowDataType::Float64,
540
+ "date32" => ArrowDataType::Date32,
541
+ "date64" => ArrowDataType::Date64,
542
+ "binary" => ArrowDataType::Binary,
543
+ "large_binary" => ArrowDataType::LargeBinary,
544
+ "string" => ArrowDataType::Utf8,
545
+ "large_string" => ArrowDataType::LargeUtf8,
546
+ "binary_view" => ArrowDataType::BinaryView,
547
+ "string_view" => ArrowDataType::Utf8View,
548
+ "unknown" => ArrowDataType::Unknown,
549
+ _ => todo!(),
550
+ };
551
+ let is_nullable = f.aref(ruby.to_symbol("nullable"))?;
552
+ let rb_metadata: RHash = f.aref(ruby.to_symbol("metadata"))?;
553
+ let mut metadata = BTreeMap::new();
554
+ rb_metadata.foreach(|k: String, v: String| {
555
+ metadata.insert(k.into(), v.into());
556
+ Ok(ForEach::Continue)
557
+ })?;
558
+ arrow_schema
559
+ .try_insert(
560
+ name.clone().into(),
561
+ ArrowField::new(name.into(), dtype, is_nullable).with_metadata(metadata),
562
+ )
563
+ .map_err(to_rb_err)?;
564
+ }
565
+ Ok(Wrap(arrow_schema))
566
+ }
567
+ }
568
+
493
569
  impl TryConvert for Wrap<ScanSources> {
494
570
  fn try_convert(ob: Value) -> RbResult<Self> {
495
571
  let list = RArray::try_convert(ob)?;
@@ -499,9 +575,9 @@ impl TryConvert for Wrap<ScanSources> {
499
575
  }
500
576
 
501
577
  enum MutableSources {
502
- Paths(Vec<PathBuf>),
578
+ Paths(Vec<PlRefPath>),
503
579
  Files(Vec<File>),
504
- Buffers(Vec<bytes::Bytes>),
580
+ Buffers(Vec<Buffer<u8>>),
505
581
  }
506
582
 
507
583
  let num_items = list.len();
@@ -540,7 +616,7 @@ impl TryConvert for Wrap<ScanSources> {
540
616
  return Err(RbTypeError::new_err(
541
617
  "Cannot combine in-memory bytes, paths and files for scan sources"
542
618
  .to_string(),
543
- ))
619
+ ));
544
620
  }
545
621
  }
546
622
  }
@@ -553,6 +629,16 @@ impl TryConvert for Wrap<ScanSources> {
553
629
  }
554
630
  }
555
631
 
632
+ impl TryIntoValue for Wrap<Schema> {
633
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
634
+ let dict = ruby.hash_new();
635
+ for (k, v) in self.0.iter() {
636
+ dict.aset(k.as_str(), Wrap(v.clone()).try_into_value_with(ruby)?)?;
637
+ }
638
+ Ok(dict.as_value())
639
+ }
640
+ }
641
+
556
642
  #[derive(Clone)]
557
643
  pub struct ObjectValue {
558
644
  pub inner: Opaque<Value>,
@@ -560,18 +646,17 @@ pub struct ObjectValue {
560
646
 
561
647
  impl Debug for ObjectValue {
562
648
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
563
- f.debug_struct("ObjectValue")
564
- .field("inner", &self.to_value())
565
- .finish()
649
+ write!(f, "{}", self)
566
650
  }
567
651
  }
568
652
 
569
653
  impl Hash for ObjectValue {
570
654
  fn hash<H: Hasher>(&self, state: &mut H) {
571
- let h = self
572
- .to_value()
573
- .funcall::<_, _, isize>("hash", ())
574
- .expect("should be hashable");
655
+ let h = Ruby::attach(|rb| {
656
+ rb.get_inner(self.inner)
657
+ .funcall::<_, _, isize>("hash", ())
658
+ .expect("should be hashable")
659
+ });
575
660
  state.write_isize(h)
576
661
  }
577
662
  }
@@ -580,7 +665,11 @@ impl Eq for ObjectValue {}
580
665
 
581
666
  impl PartialEq for ObjectValue {
582
667
  fn eq(&self, other: &Self) -> bool {
583
- self.to_value().eql(other.to_value()).unwrap_or(false)
668
+ Ruby::attach(|ruby| {
669
+ ruby.get_inner(self.inner)
670
+ .eql(ruby.get_inner(other.inner))
671
+ .unwrap_or(false)
672
+ })
584
673
  }
585
674
  }
586
675
 
@@ -601,7 +690,10 @@ impl TotalHash for ObjectValue {
601
690
 
602
691
  impl Display for ObjectValue {
603
692
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
604
- write!(f, "{}", self.to_value())
693
+ Ruby::attach(|rb| {
694
+ let v = rb.get_inner(self.inner);
695
+ write!(f, "{}", v)
696
+ })
605
697
  }
606
698
  }
607
699
 
@@ -629,12 +721,6 @@ impl From<&dyn PolarsObjectSafe> for &ObjectValue {
629
721
  }
630
722
  }
631
723
 
632
- impl ObjectValue {
633
- pub fn to_value(&self) -> Value {
634
- self.clone().into_value()
635
- }
636
- }
637
-
638
724
  impl IntoValue for ObjectValue {
639
725
  fn into_value_with(self, ruby: &Ruby) -> Value {
640
726
  ruby.get_inner(self.inner)
@@ -643,9 +729,9 @@ impl IntoValue for ObjectValue {
643
729
 
644
730
  impl Default for ObjectValue {
645
731
  fn default() -> Self {
646
- ObjectValue {
647
- inner: Ruby::get().unwrap().qnil().as_value().into(),
648
- }
732
+ Ruby::attach(|rb| ObjectValue {
733
+ inner: rb.qnil().as_value().into(),
734
+ })
649
735
  }
650
736
  }
651
737
 
@@ -658,7 +744,7 @@ impl TryConvert for Wrap<AsofStrategy> {
658
744
  v => {
659
745
  return Err(RbValueError::new_err(format!(
660
746
  "asof `strategy` must be one of {{'backward', 'forward', 'nearest'}}, got {v}",
661
- )))
747
+ )));
662
748
  }
663
749
  };
664
750
  Ok(Wrap(parsed))
@@ -673,7 +759,7 @@ impl TryConvert for Wrap<InterpolationMethod> {
673
759
  v => {
674
760
  return Err(RbValueError::new_err(format!(
675
761
  "method must be one of {{'linear', 'nearest'}}, got {v}",
676
- )))
762
+ )));
677
763
  }
678
764
  };
679
765
  Ok(Wrap(parsed))
@@ -688,9 +774,8 @@ impl TryConvert for Wrap<Option<AvroCompression>> {
688
774
  "deflate" => Some(AvroCompression::Deflate),
689
775
  v => {
690
776
  return Err(RbValueError::new_err(format!(
691
- "compression must be one of {{'uncompressed', 'snappy', 'deflate'}}, got {}",
692
- v
693
- )))
777
+ "compression must be one of {{'uncompressed', 'snappy', 'deflate'}}, got {v}"
778
+ )));
694
779
  }
695
780
  };
696
781
  Ok(Wrap(parsed))
@@ -700,13 +785,18 @@ impl TryConvert for Wrap<Option<AvroCompression>> {
700
785
  impl TryConvert for Wrap<CategoricalOrdering> {
701
786
  fn try_convert(ob: Value) -> RbResult<Self> {
702
787
  let parsed = match String::try_convert(ob)?.as_str() {
703
- "physical" => CategoricalOrdering::Physical,
704
788
  "lexical" => CategoricalOrdering::Lexical,
789
+ "physical" => {
790
+ polars_warn!(
791
+ Deprecation,
792
+ "physical ordering is deprecated, will use lexical ordering instead"
793
+ );
794
+ CategoricalOrdering::Lexical
795
+ }
705
796
  v => {
706
797
  return Err(RbValueError::new_err(format!(
707
- "ordering must be one of {{'physical', 'lexical'}}, got {}",
708
- v
709
- )))
798
+ "ordering must be one of {{'physical', 'lexical'}}, got {v}"
799
+ )));
710
800
  }
711
801
  };
712
802
  Ok(Wrap(parsed))
@@ -722,7 +812,7 @@ impl TryConvert for Wrap<StartBy> {
722
812
  v => {
723
813
  return Err(RbValueError::new_err(format!(
724
814
  "closed must be one of {{'window', 'datapoint', 'monday'}}, got {v}",
725
- )))
815
+ )));
726
816
  }
727
817
  };
728
818
  Ok(Wrap(parsed))
@@ -738,9 +828,23 @@ impl TryConvert for Wrap<ClosedWindow> {
738
828
  "none" => ClosedWindow::None,
739
829
  v => {
740
830
  return Err(RbValueError::new_err(format!(
741
- "closed must be one of {{'left', 'right', 'both', 'none'}}, got {}",
742
- v
743
- )))
831
+ "closed must be one of {{'left', 'right', 'both', 'none'}}, got {v}"
832
+ )));
833
+ }
834
+ };
835
+ Ok(Wrap(parsed))
836
+ }
837
+ }
838
+
839
+ impl TryConvert for Wrap<RoundMode> {
840
+ fn try_convert(ob: Value) -> RbResult<Self> {
841
+ let parsed = match String::try_convert(ob)?.as_str() {
842
+ "half_to_even" => RoundMode::HalfToEven,
843
+ "half_away_from_zero" => RoundMode::HalfAwayFromZero,
844
+ v => {
845
+ return Err(RbValueError::new_err(format!(
846
+ "`mode` must be one of {{'half_to_even', 'half_away_from_zero'}}, got {v}",
847
+ )));
744
848
  }
745
849
  };
746
850
  Ok(Wrap(parsed))
@@ -754,9 +858,8 @@ impl TryConvert for Wrap<CsvEncoding> {
754
858
  "utf8-lossy" => CsvEncoding::LossyUtf8,
755
859
  v => {
756
860
  return Err(RbValueError::new_err(format!(
757
- "encoding must be one of {{'utf8', 'utf8-lossy'}}, got {}",
758
- v
759
- )))
861
+ "encoding must be one of {{'utf8', 'utf8-lossy'}}, got {v}"
862
+ )));
760
863
  }
761
864
  };
762
865
  Ok(Wrap(parsed))
@@ -768,12 +871,11 @@ impl TryConvert for Wrap<Option<IpcCompression>> {
768
871
  let parsed = match String::try_convert(ob)?.as_str() {
769
872
  "uncompressed" => None,
770
873
  "lz4" => Some(IpcCompression::LZ4),
771
- "zstd" => Some(IpcCompression::ZSTD),
874
+ "zstd" => Some(IpcCompression::ZSTD(Default::default())),
772
875
  v => {
773
876
  return Err(RbValueError::new_err(format!(
774
- "compression must be one of {{'uncompressed', 'lz4', 'zstd'}}, got {}",
775
- v
776
- )))
877
+ "compression must be one of {{'uncompressed', 'lz4', 'zstd'}}, got {v}"
878
+ )));
777
879
  }
778
880
  };
779
881
  Ok(Wrap(parsed))
@@ -791,9 +893,8 @@ impl TryConvert for Wrap<JoinType> {
791
893
  "cross" => JoinType::Cross,
792
894
  v => {
793
895
  return Err(RbValueError::new_err(format!(
794
- "how must be one of {{'inner', 'left', 'full', 'semi', 'anti', 'cross'}}, got {}",
795
- v
796
- )))
896
+ "how must be one of {{'inner', 'left', 'full', 'semi', 'anti', 'cross'}}, got {v}"
897
+ )));
797
898
  }
798
899
  };
799
900
  Ok(Wrap(parsed))
@@ -809,7 +910,7 @@ impl TryConvert for Wrap<Label> {
809
910
  v => {
810
911
  return Err(RbValueError::new_err(format!(
811
912
  "`label` must be one of {{'left', 'right', 'datapoint'}}, got {v}",
812
- )))
913
+ )));
813
914
  }
814
915
  };
815
916
  Ok(Wrap(parsed))
@@ -823,9 +924,8 @@ impl TryConvert for Wrap<ListToStructWidthStrategy> {
823
924
  "max_width" => ListToStructWidthStrategy::MaxWidth,
824
925
  v => {
825
926
  return Err(RbValueError::new_err(format!(
826
- "n_field_strategy must be one of {{'first_non_null', 'max_width'}}, got {}",
827
- v
828
- )))
927
+ "n_field_strategy must be one of {{'first_non_null', 'max_width'}}, got {v}"
928
+ )));
829
929
  }
830
930
  };
831
931
  Ok(Wrap(parsed))
@@ -840,7 +940,7 @@ impl TryConvert for Wrap<NonExistent> {
840
940
  v => {
841
941
  return Err(RbValueError::new_err(format!(
842
942
  "`non_existent` must be one of {{'null', 'raise'}}, got {v}",
843
- )))
943
+ )));
844
944
  }
845
945
  };
846
946
  Ok(Wrap(parsed))
@@ -854,9 +954,8 @@ impl TryConvert for Wrap<NullBehavior> {
854
954
  "ignore" => NullBehavior::Ignore,
855
955
  v => {
856
956
  return Err(RbValueError::new_err(format!(
857
- "null behavior must be one of {{'drop', 'ignore'}}, got {}",
858
- v
859
- )))
957
+ "null behavior must be one of {{'drop', 'ignore'}}, got {v}"
958
+ )));
860
959
  }
861
960
  };
862
961
  Ok(Wrap(parsed))
@@ -870,9 +969,8 @@ impl TryConvert for Wrap<NullStrategy> {
870
969
  "propagate" => NullStrategy::Propagate,
871
970
  v => {
872
971
  return Err(RbValueError::new_err(format!(
873
- "null strategy must be one of {{'ignore', 'propagate'}}, got {}",
874
- v
875
- )))
972
+ "null strategy must be one of {{'ignore', 'propagate'}}, got {v}"
973
+ )));
876
974
  }
877
975
  };
878
976
  Ok(Wrap(parsed))
@@ -888,9 +986,8 @@ impl TryConvert for Wrap<ParallelStrategy> {
888
986
  "none" => ParallelStrategy::None,
889
987
  v => {
890
988
  return Err(RbValueError::new_err(format!(
891
- "parallel must be one of {{'auto', 'columns', 'row_groups', 'none'}}, got {}",
892
- v
893
- )))
989
+ "parallel must be one of {{'auto', 'columns', 'row_groups', 'none'}}, got {v}"
990
+ )));
894
991
  }
895
992
  };
896
993
  Ok(Wrap(parsed))
@@ -907,9 +1004,8 @@ impl TryConvert for Wrap<QuantileMethod> {
907
1004
  "midpoint" => QuantileMethod::Midpoint,
908
1005
  v => {
909
1006
  return Err(RbValueError::new_err(format!(
910
- "interpolation must be one of {{'lower', 'higher', 'nearest', 'linear', 'midpoint'}}, got {}",
911
- v
912
- )))
1007
+ "interpolation must be one of {{'lower', 'higher', 'nearest', 'linear', 'midpoint'}}, got {v}"
1008
+ )));
913
1009
  }
914
1010
  };
915
1011
  Ok(Wrap(parsed))
@@ -927,9 +1023,42 @@ impl TryConvert for Wrap<RankMethod> {
927
1023
  "random" => RankMethod::Random,
928
1024
  v => {
929
1025
  return Err(RbValueError::new_err(format!(
930
- "method must be one of {{'min', 'max', 'average', 'dense', 'ordinal', 'random'}}, got {}",
931
- v
932
- )))
1026
+ "method must be one of {{'min', 'max', 'average', 'dense', 'ordinal', 'random'}}, got {v}"
1027
+ )));
1028
+ }
1029
+ };
1030
+ Ok(Wrap(parsed))
1031
+ }
1032
+ }
1033
+
1034
+ impl TryConvert for Wrap<RollingRankMethod> {
1035
+ fn try_convert(ob: Value) -> RbResult<Self> {
1036
+ let parsed = match String::try_convert(ob)?.as_str() {
1037
+ "min" => RollingRankMethod::Min,
1038
+ "max" => RollingRankMethod::Max,
1039
+ "average" => RollingRankMethod::Average,
1040
+ "dense" => RollingRankMethod::Dense,
1041
+ "random" => RollingRankMethod::Random,
1042
+ v => {
1043
+ return Err(RbValueError::new_err(format!(
1044
+ "rank `method` must be one of {{'min', 'max', 'average', 'dense', 'random'}}, got {v}",
1045
+ )));
1046
+ }
1047
+ };
1048
+ Ok(Wrap(parsed))
1049
+ }
1050
+ }
1051
+
1052
+ impl TryConvert for Wrap<Roll> {
1053
+ fn try_convert(ob: Value) -> RbResult<Self> {
1054
+ let parsed = match String::try_convert(ob)?.as_str() {
1055
+ "raise" => Roll::Raise,
1056
+ "forward" => Roll::Forward,
1057
+ "backward" => Roll::Backward,
1058
+ v => {
1059
+ return Err(RbValueError::new_err(format!(
1060
+ "`roll` must be one of {{'raise', 'forward', 'backward'}}, got {v}",
1061
+ )));
933
1062
  }
934
1063
  };
935
1064
  Ok(Wrap(parsed))
@@ -944,57 +1073,58 @@ impl TryConvert for Wrap<TimeUnit> {
944
1073
  "ms" => TimeUnit::Milliseconds,
945
1074
  v => {
946
1075
  return Err(RbValueError::new_err(format!(
947
- "time unit must be one of {{'ns', 'us', 'ms'}}, got {}",
948
- v
949
- )))
1076
+ "time unit must be one of {{'ns', 'us', 'ms'}}, got {v}"
1077
+ )));
950
1078
  }
951
1079
  };
952
1080
  Ok(Wrap(parsed))
953
1081
  }
954
1082
  }
955
1083
 
1084
+ unsafe impl TryConvertOwned for Wrap<TimeUnit> {}
1085
+
956
1086
  impl TryConvert for Wrap<UniqueKeepStrategy> {
957
1087
  fn try_convert(ob: Value) -> RbResult<Self> {
958
1088
  let parsed = match String::try_convert(ob)?.as_str() {
959
1089
  "first" => UniqueKeepStrategy::First,
960
1090
  "last" => UniqueKeepStrategy::Last,
1091
+ "none" => UniqueKeepStrategy::None,
1092
+ "any" => UniqueKeepStrategy::Any,
961
1093
  v => {
962
1094
  return Err(RbValueError::new_err(format!(
963
- "keep must be one of {{'first', 'last'}}, got {}",
964
- v
965
- )))
1095
+ "`keep` must be one of {{'first', 'last', 'any', 'none'}}, got {v}",
1096
+ )));
966
1097
  }
967
1098
  };
968
1099
  Ok(Wrap(parsed))
969
1100
  }
970
1101
  }
971
1102
 
972
- impl TryConvert for Wrap<IpcCompression> {
1103
+ impl TryConvert for Wrap<SearchSortedSide> {
973
1104
  fn try_convert(ob: Value) -> RbResult<Self> {
974
1105
  let parsed = match String::try_convert(ob)?.as_str() {
975
- "lz4" => IpcCompression::LZ4,
976
- "zstd" => IpcCompression::ZSTD,
1106
+ "any" => SearchSortedSide::Any,
1107
+ "left" => SearchSortedSide::Left,
1108
+ "right" => SearchSortedSide::Right,
977
1109
  v => {
978
1110
  return Err(RbValueError::new_err(format!(
979
- "compression must be one of {{'lz4', 'zstd'}}, got {}",
980
- v
981
- )))
1111
+ "side must be one of {{'any', 'left', 'right'}}, got {v}",
1112
+ )));
982
1113
  }
983
1114
  };
984
1115
  Ok(Wrap(parsed))
985
1116
  }
986
1117
  }
987
1118
 
988
- impl TryConvert for Wrap<SearchSortedSide> {
1119
+ impl TryConvert for Wrap<PivotColumnNaming> {
989
1120
  fn try_convert(ob: Value) -> RbResult<Self> {
990
1121
  let parsed = match String::try_convert(ob)?.as_str() {
991
- "any" => SearchSortedSide::Any,
992
- "left" => SearchSortedSide::Left,
993
- "right" => SearchSortedSide::Right,
1122
+ "auto" => PivotColumnNaming::Auto,
1123
+ "combine" => PivotColumnNaming::Combine,
994
1124
  v => {
995
1125
  return Err(RbValueError::new_err(format!(
996
- "side must be one of {{'any', 'left', 'right'}}, got {v}",
997
- )))
1126
+ "`column_naming` must be one of {{'auto', 'combine'}}, got {v}",
1127
+ )));
998
1128
  }
999
1129
  };
1000
1130
  Ok(Wrap(parsed))
@@ -1011,7 +1141,7 @@ impl TryConvert for Wrap<ClosedInterval> {
1011
1141
  v => {
1012
1142
  return Err(RbValueError::new_err(format!(
1013
1143
  "`closed` must be one of {{'both', 'left', 'right', 'none'}}, got {v}",
1014
- )))
1144
+ )));
1015
1145
  }
1016
1146
  };
1017
1147
  Ok(Wrap(parsed))
@@ -1026,8 +1156,8 @@ impl TryConvert for Wrap<WindowMapping> {
1026
1156
  "explode" => WindowMapping::Explode,
1027
1157
  v => {
1028
1158
  return Err(RbValueError::new_err(format!(
1029
- "`mapping_strategy` must be one of {{'group_to_rows', 'join', 'explode'}}, got {v}",
1030
- )))
1159
+ "`mapping_strategy` must be one of {{'group_to_rows', 'join', 'explode'}}, got {v}",
1160
+ )));
1031
1161
  }
1032
1162
  };
1033
1163
  Ok(Wrap(parsed))
@@ -1044,7 +1174,25 @@ impl TryConvert for Wrap<JoinValidation> {
1044
1174
  v => {
1045
1175
  return Err(RbValueError::new_err(format!(
1046
1176
  "`validate` must be one of {{'m:m', 'm:1', '1:m', '1:1'}}, got {v}",
1047
- )))
1177
+ )));
1178
+ }
1179
+ };
1180
+ Ok(Wrap(parsed))
1181
+ }
1182
+ }
1183
+
1184
+ impl TryConvert for Wrap<MaintainOrderJoin> {
1185
+ fn try_convert(ob: Value) -> RbResult<Self> {
1186
+ let parsed = match String::try_convert(ob)?.as_str() {
1187
+ "none" => MaintainOrderJoin::None,
1188
+ "left" => MaintainOrderJoin::Left,
1189
+ "right" => MaintainOrderJoin::Right,
1190
+ "left_right" => MaintainOrderJoin::LeftRight,
1191
+ "right_left" => MaintainOrderJoin::RightLeft,
1192
+ v => {
1193
+ return Err(RbValueError::new_err(format!(
1194
+ "`maintain_order` must be one of {{'none', 'left', 'right', 'left_right', 'right_left'}}, got {v}",
1195
+ )));
1048
1196
  }
1049
1197
  };
1050
1198
  Ok(Wrap(parsed))
@@ -1061,16 +1209,163 @@ impl TryConvert for Wrap<QuoteStyle> {
1061
1209
  v => {
1062
1210
  return Err(RbValueError::new_err(format!(
1063
1211
  "`quote_style` must be one of {{'always', 'necessary', 'non_numeric', 'never'}}, got {v}",
1064
- )))
1065
- },
1212
+ )));
1213
+ }
1066
1214
  };
1067
1215
  Ok(Wrap(parsed))
1068
1216
  }
1069
1217
  }
1070
1218
 
1071
- pub(crate) fn parse_cloud_options(uri: &str, kv: Vec<(String, String)>) -> RbResult<CloudOptions> {
1072
- let out = CloudOptions::from_untyped_config(uri, kv).map_err(RbPolarsErr::from)?;
1073
- Ok(out)
1219
+ impl TryConvert for Wrap<SetOperation> {
1220
+ fn try_convert(ob: Value) -> RbResult<Self> {
1221
+ let parsed = match String::try_convert(ob)?.as_str() {
1222
+ "union" => SetOperation::Union,
1223
+ "difference" => SetOperation::Difference,
1224
+ "intersection" => SetOperation::Intersection,
1225
+ "symmetric_difference" => SetOperation::SymmetricDifference,
1226
+ v => {
1227
+ return Err(RbValueError::new_err(format!(
1228
+ "set operation must be one of {{'union', 'difference', 'intersection', 'symmetric_difference'}}, got {v}",
1229
+ )));
1230
+ }
1231
+ };
1232
+ Ok(Wrap(parsed))
1233
+ }
1234
+ }
1235
+
1236
+ impl TryConvert for Wrap<CastColumnsPolicy> {
1237
+ fn try_convert(ob: Value) -> RbResult<Self> {
1238
+ if ob.is_nil() {
1239
+ let out = Wrap(CastColumnsPolicy::ERROR_ON_MISMATCH);
1240
+ return Ok(out);
1241
+ }
1242
+
1243
+ let mut integer_upcast = false;
1244
+ let mut integer_to_float_cast = false;
1245
+
1246
+ let integer_cast_object: Value = ob.funcall("integer_cast", ())?;
1247
+
1248
+ parse_multiple_options("integer_cast", integer_cast_object, |v| {
1249
+ match v {
1250
+ "upcast" => integer_upcast = true,
1251
+ "allow-float" => integer_to_float_cast = true,
1252
+ "forbid" => {}
1253
+ v => {
1254
+ return Err(RbValueError::new_err(format!(
1255
+ "unknown option for integer_cast: {v}"
1256
+ )));
1257
+ }
1258
+ }
1259
+
1260
+ Ok(())
1261
+ })?;
1262
+
1263
+ let mut float_upcast = false;
1264
+ let mut float_downcast = false;
1265
+
1266
+ let float_cast_object: Value = ob.funcall("float_cast", ())?;
1267
+
1268
+ parse_multiple_options("float_cast", float_cast_object, |v| {
1269
+ match v {
1270
+ "forbid" => {}
1271
+ "upcast" => float_upcast = true,
1272
+ "downcast" => float_downcast = true,
1273
+ v => {
1274
+ return Err(RbValueError::new_err(format!(
1275
+ "unknown option for float_cast: {v}"
1276
+ )));
1277
+ }
1278
+ }
1279
+
1280
+ Ok(())
1281
+ })?;
1282
+
1283
+ let mut datetime_nanoseconds_downcast = false;
1284
+ let mut datetime_convert_timezone = false;
1285
+
1286
+ let datetime_cast_object: Value = ob.funcall("datetime_cast", ())?;
1287
+
1288
+ parse_multiple_options("datetime_cast", datetime_cast_object, |v| {
1289
+ match v {
1290
+ "forbid" => {}
1291
+ "nanosecond-downcast" => datetime_nanoseconds_downcast = true,
1292
+ "convert-timezone" => datetime_convert_timezone = true,
1293
+ v => {
1294
+ return Err(RbValueError::new_err(format!(
1295
+ "unknown option for datetime_cast: {v}"
1296
+ )));
1297
+ }
1298
+ };
1299
+
1300
+ Ok(())
1301
+ })?;
1302
+
1303
+ let missing_struct_fields =
1304
+ match &*ob.funcall::<_, _, String>("missing_struct_fields", ())? {
1305
+ "insert" => MissingColumnsPolicy::Insert,
1306
+ "raise" => MissingColumnsPolicy::Raise,
1307
+ v => {
1308
+ return Err(RbValueError::new_err(format!(
1309
+ "unknown option for missing_struct_fields: {v}"
1310
+ )));
1311
+ }
1312
+ };
1313
+
1314
+ let extra_struct_fields = match &*ob.funcall::<_, _, String>("extra_struct_fields", ())? {
1315
+ "ignore" => ExtraColumnsPolicy::Ignore,
1316
+ "raise" => ExtraColumnsPolicy::Raise,
1317
+ v => {
1318
+ return Err(RbValueError::new_err(format!(
1319
+ "unknown option for extra_struct_fields: {v}"
1320
+ )));
1321
+ }
1322
+ };
1323
+
1324
+ let categorical_to_string =
1325
+ match &*ob.funcall::<_, _, String>("categorical_to_string", ())? {
1326
+ "allow" => true,
1327
+ "forbid" => false,
1328
+ v => {
1329
+ return Err(RbValueError::new_err(format!(
1330
+ "unknown option for categorical_to_string: {v}"
1331
+ )));
1332
+ }
1333
+ };
1334
+
1335
+ return Ok(Wrap(CastColumnsPolicy {
1336
+ integer_upcast,
1337
+ integer_to_float_cast,
1338
+ float_upcast,
1339
+ float_downcast,
1340
+ datetime_nanoseconds_downcast,
1341
+ datetime_microseconds_downcast: false,
1342
+ datetime_convert_timezone,
1343
+ null_upcast: true,
1344
+ categorical_to_string,
1345
+ missing_struct_fields,
1346
+ extra_struct_fields,
1347
+ }));
1348
+
1349
+ fn parse_multiple_options(
1350
+ parameter_name: &'static str,
1351
+ rb_object: Value,
1352
+ mut parser_func: impl FnMut(&str) -> RbResult<()>,
1353
+ ) -> RbResult<()> {
1354
+ if let Ok(v) = String::try_convert(rb_object) {
1355
+ parser_func(&v)?;
1356
+ } else if let Ok(v) = RArray::try_convert(rb_object) {
1357
+ for v in v {
1358
+ parser_func(&String::try_convert(v)?)?;
1359
+ }
1360
+ } else {
1361
+ return Err(RbValueError::new_err(format!(
1362
+ "unknown type for {parameter_name}: {rb_object}"
1363
+ )));
1364
+ }
1365
+
1366
+ Ok(())
1367
+ }
1368
+ }
1074
1369
  }
1075
1370
 
1076
1371
  pub fn parse_fill_null_strategy(
@@ -1086,10 +1381,9 @@ pub fn parse_fill_null_strategy(
1086
1381
  "zero" => FillNullStrategy::Zero,
1087
1382
  "one" => FillNullStrategy::One,
1088
1383
  e => {
1089
- return Err(magnus::Error::new(exception::runtime_error(), format!(
1090
- "strategy must be one of {{'forward', 'backward', 'min', 'max', 'mean', 'zero', 'one'}}, got {}",
1091
- e,
1092
- )))
1384
+ return Err(RbValueError::new_err(format!(
1385
+ "`strategy` must be one of {{'forward', 'backward', 'min', 'max', 'mean', 'zero', 'one'}}, got {e}",
1386
+ )));
1093
1387
  }
1094
1388
  };
1095
1389
  Ok(parsed)
@@ -1106,16 +1400,15 @@ pub fn parse_parquet_compression(
1106
1400
  compression_level
1107
1401
  .map(|lvl| {
1108
1402
  GzipLevel::try_new(lvl as u8)
1109
- .map_err(|e| RbValueError::new_err(format!("{:?}", e)))
1403
+ .map_err(|e| RbValueError::new_err(format!("{e:?}")))
1110
1404
  })
1111
1405
  .transpose()?,
1112
1406
  ),
1113
- "lzo" => ParquetCompression::Lzo,
1114
1407
  "brotli" => ParquetCompression::Brotli(
1115
1408
  compression_level
1116
1409
  .map(|lvl| {
1117
1410
  BrotliLevel::try_new(lvl as u32)
1118
- .map_err(|e| RbValueError::new_err(format!("{:?}", e)))
1411
+ .map_err(|e| RbValueError::new_err(format!("{e:?}")))
1119
1412
  })
1120
1413
  .transpose()?,
1121
1414
  ),
@@ -1123,30 +1416,19 @@ pub fn parse_parquet_compression(
1123
1416
  "zstd" => ParquetCompression::Zstd(
1124
1417
  compression_level
1125
1418
  .map(|lvl| {
1126
- ZstdLevel::try_new(lvl)
1127
- .map_err(|e| RbValueError::new_err(format!("{:?}", e)))
1419
+ ZstdLevel::try_new(lvl).map_err(|e| RbValueError::new_err(format!("{e:?}")))
1128
1420
  })
1129
1421
  .transpose()?,
1130
1422
  ),
1131
1423
  e => {
1132
1424
  return Err(RbValueError::new_err(format!(
1133
- "compression must be one of {{'uncompressed', 'snappy', 'gzip', 'lzo', 'brotli', 'lz4', 'zstd'}}, got {}",
1134
- e
1135
- )))
1425
+ "compression must be one of {{'uncompressed', 'snappy', 'gzip', 'lzo', 'brotli', 'lz4', 'zstd'}}, got {e}"
1426
+ )));
1136
1427
  }
1137
1428
  };
1138
1429
  Ok(parsed)
1139
1430
  }
1140
1431
 
1141
- impl TryConvert for Wrap<NonZeroUsize> {
1142
- fn try_convert(ob: Value) -> RbResult<Self> {
1143
- let v = usize::try_convert(ob)?;
1144
- NonZeroUsize::new(v)
1145
- .map(Wrap)
1146
- .ok_or(RbValueError::new_err("must be non-zero"))
1147
- }
1148
- }
1149
-
1150
1432
  pub(crate) fn strings_to_pl_smallstr<I, S>(container: I) -> Vec<PlSmallStr>
1151
1433
  where
1152
1434
  I: IntoIterator<Item = S>,
@@ -1182,3 +1464,205 @@ impl TryConvert for RbCompatLevel {
1182
1464
  }))
1183
1465
  }
1184
1466
  }
1467
+
1468
+ impl TryConvert for Wrap<UnicodeForm> {
1469
+ fn try_convert(ob: Value) -> RbResult<Self> {
1470
+ let parsed = match String::try_convert(ob)?.as_str() {
1471
+ "NFC" => UnicodeForm::NFC,
1472
+ "NFKC" => UnicodeForm::NFKC,
1473
+ "NFD" => UnicodeForm::NFD,
1474
+ "NFKD" => UnicodeForm::NFKD,
1475
+ v => {
1476
+ return Err(RbValueError::new_err(format!(
1477
+ "`form` must be one of {{'NFC', 'NFKC', 'NFD', 'NFKD'}}, got {v}",
1478
+ )));
1479
+ }
1480
+ };
1481
+ Ok(Wrap(parsed))
1482
+ }
1483
+ }
1484
+
1485
+ impl TryConvert for Wrap<Option<KeyValueMetadata>> {
1486
+ fn try_convert(ob: Value) -> RbResult<Self> {
1487
+ if ob.is_nil() {
1488
+ return Ok(Wrap(None));
1489
+ }
1490
+ todo!();
1491
+ }
1492
+ }
1493
+
1494
+ unsafe impl TryConvertOwned for Wrap<Option<KeyValueMetadata>> {}
1495
+
1496
+ impl TryConvert for Wrap<Option<TimeZone>> {
1497
+ fn try_convert(ob: Value) -> RbResult<Self> {
1498
+ let tz = Option::<Wrap<PlSmallStr>>::try_convert(ob)?;
1499
+
1500
+ let tz = tz.map(|x| x.0);
1501
+
1502
+ Ok(Wrap(TimeZone::opt_try_new(tz).map_err(RbPolarsErr::from)?))
1503
+ }
1504
+ }
1505
+
1506
+ unsafe impl TryConvertOwned for Wrap<Option<TimeZone>> {}
1507
+
1508
+ impl TryConvert for Wrap<UpcastOrForbid> {
1509
+ fn try_convert(ob: Value) -> RbResult<Self> {
1510
+ let parsed = match String::try_convert(ob)?.as_str() {
1511
+ "upcast" => UpcastOrForbid::Upcast,
1512
+ "forbid" => UpcastOrForbid::Forbid,
1513
+ v => {
1514
+ return Err(RbValueError::new_err(format!(
1515
+ "cast parameter must be one of {{'upcast', 'forbid'}}, got {v}",
1516
+ )));
1517
+ }
1518
+ };
1519
+ Ok(Wrap(parsed))
1520
+ }
1521
+ }
1522
+
1523
+ impl TryConvert for Wrap<ExtraColumnsPolicy> {
1524
+ fn try_convert(ob: Value) -> RbResult<Self> {
1525
+ let parsed = match String::try_convert(ob)?.as_str() {
1526
+ "ignore" => ExtraColumnsPolicy::Ignore,
1527
+ "raise" => ExtraColumnsPolicy::Raise,
1528
+ v => {
1529
+ return Err(RbValueError::new_err(format!(
1530
+ "extra column/field parameter must be one of {{'ignore', 'raise'}}, got {v}",
1531
+ )));
1532
+ }
1533
+ };
1534
+ Ok(Wrap(parsed))
1535
+ }
1536
+ }
1537
+
1538
+ impl TryConvert for Wrap<MissingColumnsPolicy> {
1539
+ fn try_convert(ob: Value) -> RbResult<Self> {
1540
+ let parsed = match String::try_convert(ob)?.as_str() {
1541
+ "insert" => MissingColumnsPolicy::Insert,
1542
+ "raise" => MissingColumnsPolicy::Raise,
1543
+ v => {
1544
+ return Err(RbValueError::new_err(format!(
1545
+ "missing column/field parameter must be one of {{'insert', 'raise'}}, got {v}",
1546
+ )));
1547
+ }
1548
+ };
1549
+ Ok(Wrap(parsed))
1550
+ }
1551
+ }
1552
+
1553
+ impl TryConvert for Wrap<MissingColumnsPolicyOrExpr> {
1554
+ fn try_convert(ob: Value) -> RbResult<Self> {
1555
+ if let Ok(rbexpr) = <&RbExpr>::try_convert(ob) {
1556
+ return Ok(Wrap(MissingColumnsPolicyOrExpr::InsertWith(
1557
+ rbexpr.inner.clone(),
1558
+ )));
1559
+ }
1560
+
1561
+ let parsed = match String::try_convert(ob)?.as_str() {
1562
+ "insert" => MissingColumnsPolicyOrExpr::Insert,
1563
+ "raise" => MissingColumnsPolicyOrExpr::Raise,
1564
+ v => {
1565
+ return Err(RbValueError::new_err(format!(
1566
+ "missing column/field parameter must be one of {{'insert', 'raise', expression}}, got {v}",
1567
+ )));
1568
+ }
1569
+ };
1570
+ Ok(Wrap(parsed))
1571
+ }
1572
+ }
1573
+
1574
+ impl TryConvert for Wrap<ColumnMapping> {
1575
+ fn try_convert(ob: Value) -> RbResult<Self> {
1576
+ let (column_mapping_type, ob) = <(String, Value)>::try_convert(ob)?;
1577
+
1578
+ Ok(Wrap(match column_mapping_type.as_str() {
1579
+ "iceberg-column-mapping" => {
1580
+ let arrow_schema = Wrap::<ArrowSchema>::try_convert(ob)?;
1581
+ ColumnMapping::Iceberg(Arc::new(
1582
+ IcebergSchema::from_arrow_schema(&arrow_schema.0).map_err(to_rb_err)?,
1583
+ ))
1584
+ }
1585
+
1586
+ v => {
1587
+ return Err(RbValueError::new_err(format!(
1588
+ "unknown column mapping type: {v}"
1589
+ )));
1590
+ }
1591
+ }))
1592
+ }
1593
+ }
1594
+
1595
+ impl TryConvert for Wrap<DeletionFilesList> {
1596
+ fn try_convert(ob: Value) -> RbResult<Self> {
1597
+ let (deletion_file_type, ob) = <(String, Value)>::try_convert(ob)?;
1598
+
1599
+ Ok(Wrap(match deletion_file_type.as_str() {
1600
+ "iceberg-position-delete" => {
1601
+ let dict = RHash::try_convert(ob)?;
1602
+
1603
+ let mut out = PlIndexMap::new();
1604
+
1605
+ dict.foreach(|k: usize, v: RArray| {
1606
+ let files = v
1607
+ .into_iter()
1608
+ .map(|x| {
1609
+ let x = String::try_convert(x)?;
1610
+ Ok(x)
1611
+ })
1612
+ .collect::<RbResult<Arc<[String]>>>()?;
1613
+
1614
+ if !files.is_empty() {
1615
+ out.insert(k, files);
1616
+ }
1617
+
1618
+ Ok(ForEach::Continue)
1619
+ })?;
1620
+
1621
+ DeletionFilesList::IcebergPositionDelete(Arc::new(out))
1622
+ }
1623
+
1624
+ v => {
1625
+ return Err(RbValueError::new_err(format!(
1626
+ "unknown deletion file type: {v}"
1627
+ )));
1628
+ }
1629
+ }))
1630
+ }
1631
+ }
1632
+
1633
+ impl TryConvert for Wrap<DefaultFieldValues> {
1634
+ fn try_convert(ob: Value) -> RbResult<Self> {
1635
+ let (default_values_type, ob) = <(String, Value)>::try_convert(ob)?;
1636
+
1637
+ Ok(Wrap(match &*default_values_type {
1638
+ "iceberg" => {
1639
+ let dict = RHash::try_convert(ob)?;
1640
+
1641
+ let mut out = PlIndexMap::new();
1642
+
1643
+ dict.foreach(|k: u32, v: Value| {
1644
+ let v: Result<Column, String> = if let Ok(s) = get_series(v) {
1645
+ Ok(s.into_column())
1646
+ } else {
1647
+ let err_msg = String::try_convert(v)?;
1648
+ Err(err_msg)
1649
+ };
1650
+
1651
+ out.insert(k, v);
1652
+
1653
+ Ok(ForEach::Continue)
1654
+ })?;
1655
+
1656
+ DefaultFieldValues::Iceberg(Arc::new(IcebergIdentityTransformedPartitionFields(
1657
+ out,
1658
+ )))
1659
+ }
1660
+
1661
+ v => {
1662
+ return Err(RbValueError::new_err(format!(
1663
+ "unknown deletion file type: {v}"
1664
+ )));
1665
+ }
1666
+ }))
1667
+ }
1668
+ }