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,101 +1,98 @@
1
- use magnus::encoding::{EncodingCapable, Index};
1
+ use magnus::encoding::EncodingCapable;
2
2
  use magnus::{
3
- class, prelude::*, r_hash::ForEach, IntoValue, RArray, RHash, RString, Ruby, TryConvert, Value,
3
+ IntoValue, RArray, RHash, RString, Ruby, Symbol, TryConvert, Value, prelude::*,
4
+ r_hash::ForEach, value::Opaque,
4
5
  };
6
+ use num_traits::ToPrimitive;
7
+ use polars::chunked_array::object::PolarsObjectSafe;
5
8
  use polars::prelude::*;
9
+ use polars_compute::decimal::{DEC128_MAX_PREC, DecimalFmtBuffer, dec128_fits};
6
10
  use polars_core::utils::any_values_to_supertype_and_n_dtypes;
7
11
 
8
- use super::{struct_dict, ObjectValue, Wrap};
12
+ use super::datetime::datetime_to_rb_object;
13
+ use super::{ObjectValue, Wrap, struct_dict};
9
14
 
10
- use crate::exceptions::RbOverflowError;
11
- use crate::rb_modules::utils;
12
- use crate::{RbErr, RbPolarsErr, RbResult, RbSeries};
15
+ use crate::rb_modules::pl_utils;
16
+ use crate::ruby::exceptions::RbOverflowError;
17
+ use crate::ruby::utils::TryIntoValue;
18
+ use crate::{RbErr, RbPolarsErr, RbResult, RbSeries, RbValueError};
13
19
 
14
- impl IntoValue for Wrap<AnyValue<'_>> {
15
- fn into_value_with(self, ruby: &Ruby) -> Value {
20
+ impl TryIntoValue for Wrap<AnyValue<'_>> {
21
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
16
22
  any_value_into_rb_object(self.0, ruby)
17
23
  }
18
24
  }
19
25
 
20
- impl<'s> TryConvert for Wrap<AnyValue<'s>> {
26
+ impl TryConvert for Wrap<AnyValue<'_>> {
21
27
  fn try_convert(ob: Value) -> RbResult<Self> {
22
- rb_object_to_any_value(ob, true).map(Wrap)
28
+ rb_object_to_any_value(ob, true, true).map(Wrap)
23
29
  }
24
30
  }
25
31
 
26
- pub(crate) fn any_value_into_rb_object(av: AnyValue, ruby: &Ruby) -> Value {
27
- match av {
32
+ pub(crate) fn any_value_into_rb_object(av: AnyValue, ruby: &Ruby) -> RbResult<Value> {
33
+ let rb_object = match av {
28
34
  AnyValue::UInt8(v) => ruby.into_value(v),
29
35
  AnyValue::UInt16(v) => ruby.into_value(v),
30
36
  AnyValue::UInt32(v) => ruby.into_value(v),
31
37
  AnyValue::UInt64(v) => ruby.into_value(v),
38
+ AnyValue::UInt128(v) => ruby.into_value(v),
32
39
  AnyValue::Int8(v) => ruby.into_value(v),
33
40
  AnyValue::Int16(v) => ruby.into_value(v),
34
41
  AnyValue::Int32(v) => ruby.into_value(v),
35
42
  AnyValue::Int64(v) => ruby.into_value(v),
43
+ AnyValue::Int128(v) => ruby.into_value(v),
44
+ AnyValue::Float16(v) => ruby.into_value(v.to_f32()),
36
45
  AnyValue::Float32(v) => ruby.into_value(v),
37
46
  AnyValue::Float64(v) => ruby.into_value(v),
38
47
  AnyValue::Null => ruby.qnil().as_value(),
39
48
  AnyValue::Boolean(v) => ruby.into_value(v),
40
49
  AnyValue::String(v) => ruby.into_value(v),
41
50
  AnyValue::StringOwned(v) => ruby.into_value(v.as_str()),
42
- AnyValue::Categorical(idx, rev, arr) | AnyValue::Enum(idx, rev, arr) => {
43
- let s = if arr.is_null() {
44
- rev.get(idx)
45
- } else {
46
- unsafe { arr.deref_unchecked().value(idx as usize) }
47
- };
48
- s.into_value()
49
- }
50
- AnyValue::CategoricalOwned(idx, rev, arr) | AnyValue::EnumOwned(idx, rev, arr) => {
51
- let s = if arr.is_null() {
52
- rev.get(idx)
53
- } else {
54
- unsafe { arr.deref_unchecked().value(idx as usize) }
55
- };
56
- s.into_value()
57
- }
58
- AnyValue::Date(v) => utils().funcall("_to_ruby_date", (v,)).unwrap(),
51
+ AnyValue::Categorical(cat, map) | AnyValue::Enum(cat, map) => unsafe {
52
+ map.cat_to_str_unchecked(cat).into_value_with(ruby)
53
+ },
54
+ AnyValue::CategoricalOwned(cat, map) | AnyValue::EnumOwned(cat, map) => unsafe {
55
+ map.cat_to_str_unchecked(cat).into_value_with(ruby)
56
+ },
57
+ AnyValue::Date(v) => pl_utils(ruby).funcall("_to_ruby_date", (v,))?,
59
58
  AnyValue::Datetime(v, time_unit, time_zone) => {
60
- datetime_to_rb_object(v, time_unit, time_zone)
59
+ datetime_to_rb_object(ruby, v, time_unit, time_zone)?
61
60
  }
62
61
  AnyValue::DatetimeOwned(v, time_unit, time_zone) => {
63
- datetime_to_rb_object(v, time_unit, time_zone.as_ref().map(AsRef::as_ref))
62
+ datetime_to_rb_object(ruby, v, time_unit, time_zone.as_ref().map(AsRef::as_ref))?
64
63
  }
65
64
  AnyValue::Duration(v, time_unit) => {
66
65
  let time_unit = time_unit.to_ascii();
67
- utils()
68
- .funcall("_to_ruby_duration", (v, time_unit))
69
- .unwrap()
66
+ pl_utils(ruby).funcall("_to_ruby_duration", (v, time_unit))?
70
67
  }
71
- AnyValue::Time(v) => utils().funcall("_to_ruby_time", (v,)).unwrap(),
72
- AnyValue::Array(v, _) | AnyValue::List(v) => RbSeries::new(v).to_a().into_value(),
73
- ref av @ AnyValue::Struct(_, _, flds) => struct_dict(av._iter_struct_av(), flds),
74
- AnyValue::StructOwned(payload) => struct_dict(payload.0.into_iter(), &payload.1),
68
+ AnyValue::Time(v) => pl_utils(ruby).funcall("_to_ruby_time", (v,))?,
69
+ AnyValue::Array(v, _) | AnyValue::List(v) => RbSeries::to_a(ruby, &RbSeries::new(v))?,
70
+ ref av @ AnyValue::Struct(_, _, flds) => struct_dict(ruby, av._iter_struct_av(), flds)?,
71
+ AnyValue::StructOwned(payload) => struct_dict(ruby, payload.0.into_iter(), &payload.1)?,
75
72
  AnyValue::Object(v) => {
76
73
  let object = v.as_any().downcast_ref::<ObjectValue>().unwrap();
77
- object.to_value()
74
+ object.clone().into_value_with(ruby)
78
75
  }
79
76
  AnyValue::ObjectOwned(v) => {
80
77
  let object = v.0.as_any().downcast_ref::<ObjectValue>().unwrap();
81
- object.to_value()
78
+ object.clone().into_value_with(ruby)
82
79
  }
83
- AnyValue::Binary(v) => RString::from_slice(v).into_value(),
84
- AnyValue::BinaryOwned(v) => RString::from_slice(&v).into_value(),
85
- AnyValue::Decimal(v, scale) => utils()
86
- .funcall("_to_ruby_decimal", (v.to_string(), -(scale as i32)))
87
- .unwrap(),
88
- }
89
- }
90
-
91
- fn datetime_to_rb_object(v: i64, tu: TimeUnit, tz: Option<&TimeZone>) -> Value {
92
- let tu = tu.to_ascii();
93
- utils()
94
- .funcall("_to_ruby_datetime", (v, tu, tz.map(|v| v.to_string())))
95
- .unwrap()
80
+ AnyValue::Binary(v) => ruby.str_from_slice(v).as_value(),
81
+ AnyValue::BinaryOwned(v) => ruby.str_from_slice(&v).as_value(),
82
+ AnyValue::Decimal(v, prec, scale) => {
83
+ let mut buf = DecimalFmtBuffer::new();
84
+ let s = buf.format_dec128(v, scale, false, false);
85
+ pl_utils(ruby).funcall("_to_ruby_decimal", (prec, s))?
86
+ }
87
+ };
88
+ Ok(rb_object)
96
89
  }
97
90
 
98
- pub(crate) fn rb_object_to_any_value<'s>(ob: Value, strict: bool) -> RbResult<AnyValue<'s>> {
91
+ pub(crate) fn rb_object_to_any_value<'s>(
92
+ ob: Value,
93
+ strict: bool,
94
+ allow_object: bool,
95
+ ) -> RbResult<AnyValue<'s>> {
99
96
  // Conversion functions.
100
97
  fn get_null(_ob: Value, _strict: bool) -> RbResult<AnyValue<'static>> {
101
98
  Ok(AnyValue::Null)
@@ -109,8 +106,12 @@ pub(crate) fn rb_object_to_any_value<'s>(ob: Value, strict: bool) -> RbResult<An
109
106
  fn get_int(ob: Value, strict: bool) -> RbResult<AnyValue<'static>> {
110
107
  if let Ok(v) = i64::try_convert(ob) {
111
108
  Ok(AnyValue::Int64(v))
109
+ } else if let Ok(v) = i128::try_convert(ob) {
110
+ Ok(AnyValue::Int128(v))
112
111
  } else if let Ok(v) = u64::try_convert(ob) {
113
112
  Ok(AnyValue::UInt64(v))
113
+ } else if let Ok(v) = u128::try_convert(ob) {
114
+ Ok(AnyValue::UInt128(v))
114
115
  } else if !strict {
115
116
  let f = f64::try_convert(ob)?;
116
117
  Ok(AnyValue::Float64(f))
@@ -126,8 +127,9 @@ pub(crate) fn rb_object_to_any_value<'s>(ob: Value, strict: bool) -> RbResult<An
126
127
  }
127
128
 
128
129
  fn get_str(ob: Value, _strict: bool) -> RbResult<AnyValue<'static>> {
129
- let v = RString::from_value(ob).unwrap();
130
- if v.enc_get() == Index::utf8() {
130
+ let ruby = Ruby::get_with(ob);
131
+ let v = RString::try_convert(ob)?;
132
+ if v.enc_get() == ruby.utf8_encindex() {
131
133
  Ok(AnyValue::StringOwned(v.to_string()?.into()))
132
134
  } else {
133
135
  Ok(AnyValue::BinaryOwned(unsafe { v.as_slice() }.to_vec()))
@@ -135,7 +137,7 @@ pub(crate) fn rb_object_to_any_value<'s>(ob: Value, strict: bool) -> RbResult<An
135
137
  }
136
138
 
137
139
  fn get_list(ob: Value, _strict: bool) -> RbResult<AnyValue<'static>> {
138
- let v = RArray::from_value(ob).unwrap();
140
+ let v = RArray::try_convert(ob)?;
139
141
  if v.is_empty() {
140
142
  Ok(AnyValue::List(Series::new_empty(
141
143
  PlSmallStr::EMPTY,
@@ -172,11 +174,16 @@ pub(crate) fn rb_object_to_any_value<'s>(ob: Value, strict: bool) -> RbResult<An
172
174
  }
173
175
 
174
176
  fn get_struct(ob: Value, _strict: bool) -> RbResult<AnyValue<'static>> {
175
- let dict = RHash::from_value(ob).unwrap();
177
+ let dict = RHash::try_convert(ob)?;
176
178
  let len = dict.len();
177
179
  let mut keys = Vec::with_capacity(len);
178
180
  let mut vals = Vec::with_capacity(len);
179
- dict.foreach(|key: String, val: Wrap<AnyValue>| {
181
+ dict.foreach(|key: Value, val: Wrap<AnyValue>| {
182
+ let key = if let Some(s) = Symbol::from_value(key) {
183
+ s.name()?.to_string()
184
+ } else {
185
+ String::try_convert(key)?
186
+ };
180
187
  let val = val.0;
181
188
  let dtype = DataType::from(&val);
182
189
  keys.push(Field::new(key.into(), dtype));
@@ -186,6 +193,14 @@ pub(crate) fn rb_object_to_any_value<'s>(ob: Value, strict: bool) -> RbResult<An
186
193
  Ok(AnyValue::StructOwned(Box::new((vals, keys))))
187
194
  }
188
195
 
196
+ fn get_object(ob: Value, _strict: bool) -> RbResult<AnyValue<'static>> {
197
+ // This is slow, but hey don't use objects.
198
+ let v = &ObjectValue {
199
+ inner: Opaque::from(ob),
200
+ };
201
+ Ok(AnyValue::ObjectOwned(OwnedObject(v.to_boxed())))
202
+ }
203
+
189
204
  fn get_date(ob: Value, _strict: bool) -> RbResult<AnyValue<'static>> {
190
205
  // convert to DateTime for UTC
191
206
  let v = ob
@@ -204,14 +219,8 @@ pub(crate) fn rb_object_to_any_value<'s>(ob: Value, strict: bool) -> RbResult<An
204
219
  Ok(AnyValue::Datetime(v, TimeUnit::Nanoseconds, None))
205
220
  }
206
221
 
207
- fn get_datetime(ob: Value, _strict: bool) -> RbResult<AnyValue<'static>> {
208
- let sec: i64 = ob.funcall("to_i", ())?;
209
- let nsec: i64 = ob.funcall("nsec", ())?;
210
- Ok(AnyValue::Datetime(
211
- sec * 1_000_000_000 + nsec,
212
- TimeUnit::Nanoseconds,
213
- None,
214
- ))
222
+ fn get_datetime(ob: Value, strict: bool) -> RbResult<AnyValue<'static>> {
223
+ get_time(ob.funcall("to_time", ())?, strict)
215
224
  }
216
225
 
217
226
  fn get_decimal(ob: Value, _strict: bool) -> RbResult<AnyValue<'static>> {
@@ -220,20 +229,18 @@ pub(crate) fn rb_object_to_any_value<'s>(ob: Value, strict: bool) -> RbResult<An
220
229
  match digits.parse::<i128>() {
221
230
  Ok(mut v) => {
222
231
  let scale = if exp > 0 {
223
- v = 10_i128
224
- .checked_pow(exp as u32)
225
- .and_then(|factor| v.checked_mul(factor))?;
232
+ v = 10_i128.checked_pow(exp as u32)?.checked_mul(v)?;
226
233
  0
227
234
  } else {
228
235
  (-exp) as usize
229
236
  };
230
- Some((v, scale))
237
+ dec128_fits(v, DEC128_MAX_PREC).then_some((v, scale))
231
238
  }
232
239
  Err(_) => None,
233
240
  }
234
241
  }
235
242
 
236
- let (sign, digits, _, exp): (i8, String, i32, i32) = ob.funcall("split", ()).unwrap();
243
+ let (sign, digits, _, exp): (i8, String, i32, i32) = ob.funcall("split", ())?;
237
244
  let (mut v, scale) = abs_decimal_from_digits(digits, exp).ok_or_else(|| {
238
245
  RbErr::from(RbPolarsErr::Other(
239
246
  "BigDecimal is too large to fit in Decimal128".into(),
@@ -243,35 +250,43 @@ pub(crate) fn rb_object_to_any_value<'s>(ob: Value, strict: bool) -> RbResult<An
243
250
  // TODO better error
244
251
  v = v.checked_neg().unwrap();
245
252
  }
246
- Ok(AnyValue::Decimal(v, scale))
253
+ Ok(AnyValue::Decimal(v, DEC128_MAX_PREC, scale))
247
254
  }
248
255
 
256
+ let ruby = Ruby::get_with(ob);
249
257
  if ob.is_nil() {
250
258
  get_null(ob, strict)
251
- } else if ob.is_kind_of(class::true_class()) || ob.is_kind_of(class::false_class()) {
259
+ } else if ob.is_kind_of(ruby.class_true_class()) || ob.is_kind_of(ruby.class_false_class()) {
252
260
  get_bool(ob, strict)
253
- } else if ob.is_kind_of(class::integer()) {
261
+ } else if ob.is_kind_of(ruby.class_integer()) {
254
262
  get_int(ob, strict)
255
- } else if ob.is_kind_of(class::float()) {
263
+ } else if ob.is_kind_of(ruby.class_float()) {
256
264
  get_float(ob, strict)
257
- } else if ob.is_kind_of(class::string()) {
265
+ } else if ob.is_kind_of(ruby.class_string()) {
258
266
  get_str(ob, strict)
259
- } else if ob.is_kind_of(class::array()) {
267
+ } else if ob.is_kind_of(ruby.class_array()) {
260
268
  get_list(ob, strict)
261
- } else if ob.is_kind_of(class::hash()) {
269
+ } else if ob.is_kind_of(ruby.class_hash()) {
262
270
  get_struct(ob, strict)
263
271
  } else if ob.respond_to("_s", true)? {
264
272
  get_list_from_series(ob, strict)
265
273
  // call is_a? for ActiveSupport::TimeWithZone
266
- } else if ob.funcall::<_, _, bool>("is_a?", (class::time(),))? {
274
+ } else if ob.funcall::<_, _, bool>("is_a?", (ruby.class_time(),))? {
267
275
  get_time(ob, strict)
268
- } else if ob.is_kind_of(crate::rb_modules::datetime()) {
276
+ } else if ob.is_kind_of(crate::ruby::rb_modules::datetime(&ruby)) {
269
277
  get_datetime(ob, strict)
270
- } else if ob.is_kind_of(crate::rb_modules::date()) {
278
+ } else if ob.is_kind_of(crate::ruby::rb_modules::date(&ruby)) {
271
279
  get_date(ob, strict)
272
- } else if ob.is_kind_of(crate::rb_modules::bigdecimal()) {
280
+ } else if crate::ruby::rb_modules::bigdecimal(&ruby)
281
+ .map(|cls| ob.is_kind_of(cls))
282
+ .unwrap_or(false)
283
+ {
273
284
  get_decimal(ob, strict)
274
285
  } else {
275
- Err(RbPolarsErr::Other(format!("object type not supported {:?}", ob)).into())
286
+ if allow_object {
287
+ get_object(ob, strict)
288
+ } else {
289
+ Err(RbValueError::new_err(format!("Cannot convert {ob}")))
290
+ }
276
291
  }
277
292
  }
@@ -0,0 +1,30 @@
1
+ use std::sync::Arc;
2
+
3
+ use polars_dtype::categorical::Categories;
4
+
5
+ #[magnus::wrap(class = "Polars::RbCategories")]
6
+ #[repr(transparent)]
7
+ #[derive(Clone)]
8
+ pub struct RbCategories {
9
+ categories: Arc<Categories>,
10
+ }
11
+
12
+ impl RbCategories {
13
+ pub fn categories(&self) -> &Arc<Categories> {
14
+ &self.categories
15
+ }
16
+ }
17
+
18
+ impl RbCategories {
19
+ pub fn global_categories() -> Self {
20
+ Self {
21
+ categories: Categories::global(),
22
+ }
23
+ }
24
+ }
25
+
26
+ impl From<Arc<Categories>> for RbCategories {
27
+ fn from(categories: Arc<Categories>) -> Self {
28
+ Self { categories }
29
+ }
30
+ }
@@ -1,10 +1,13 @@
1
- use magnus::{prelude::*, IntoValue, RArray, RString, Ruby, TryConvert, Value};
1
+ use magnus::{IntoValue, RArray, RString, Ruby, TryConvert, Value, prelude::*};
2
2
  use polars::prelude::*;
3
+ use polars_compute::decimal::DecimalFmtBuffer;
3
4
 
4
- use super::{get_rbseq, struct_dict, Wrap};
5
+ use super::datetime::datetime_to_rb_object;
6
+ use super::{Wrap, get_rbseq, struct_dict};
5
7
 
6
- use crate::rb_modules::utils;
7
8
  use crate::RbResult;
9
+ use crate::rb_modules::pl_utils;
10
+ use crate::ruby::utils::TryIntoValue;
8
11
 
9
12
  impl TryConvert for Wrap<StringChunked> {
10
13
  fn try_convert(obj: Value) -> RbResult<Self> {
@@ -39,100 +42,106 @@ impl TryConvert for Wrap<BinaryChunked> {
39
42
  }
40
43
 
41
44
  impl IntoValue for Wrap<&StringChunked> {
42
- fn into_value_with(self, _: &Ruby) -> Value {
43
- let iter = self.0.into_iter();
44
- RArray::from_iter(iter).into_value()
45
+ fn into_value_with(self, ruby: &Ruby) -> Value {
46
+ let iter = self.0.iter();
47
+ ruby.ary_from_iter(iter).as_value()
45
48
  }
46
49
  }
47
50
 
48
51
  impl IntoValue for Wrap<&BinaryChunked> {
49
- fn into_value_with(self, _: &Ruby) -> Value {
52
+ fn into_value_with(self, ruby: &Ruby) -> Value {
50
53
  let iter = self
51
54
  .0
52
- .into_iter()
53
- .map(|opt_bytes| opt_bytes.map(RString::from_slice));
54
- RArray::from_iter(iter).into_value()
55
+ .iter()
56
+ .map(|opt_bytes| opt_bytes.map(|v| ruby.str_from_slice(v)));
57
+ ruby.ary_from_iter(iter).as_value()
55
58
  }
56
59
  }
57
60
 
58
- impl IntoValue for Wrap<&StructChunked> {
59
- fn into_value_with(self, ruby: &Ruby) -> Value {
61
+ impl TryIntoValue for Wrap<&StructChunked> {
62
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
60
63
  let s = self.0.clone().into_series();
61
64
  // todo! iterate its chunks and flatten.
62
65
  // make series::iter() accept a chunk index.
63
66
  let s = s.rechunk();
64
67
  let iter = s.iter().map(|av| match av {
65
- AnyValue::Struct(_, _, flds) => struct_dict(av._iter_struct_av(), flds),
66
- AnyValue::Null => ruby.qnil().as_value(),
68
+ AnyValue::Struct(_, _, flds) => struct_dict(ruby, av._iter_struct_av(), flds),
69
+ AnyValue::Null => Ok(ruby.qnil().as_value()),
67
70
  _ => unreachable!(),
68
71
  });
69
72
 
70
- RArray::from_iter(iter).into_value()
73
+ ruby.ary_try_from_iter(iter).map(|v| v.as_value())
71
74
  }
72
75
  }
73
76
 
74
- impl IntoValue for Wrap<&DurationChunked> {
75
- fn into_value_with(self, _: &Ruby) -> Value {
76
- let utils = utils();
77
- let time_unit = Wrap(self.0.time_unit()).into_value();
78
- let iter = self.0.into_iter().map(|opt_v| {
79
- opt_v.map(|v| {
80
- utils
81
- .funcall::<_, _, Value>("_to_ruby_duration", (v, time_unit))
82
- .unwrap()
83
- })
77
+ impl TryIntoValue for Wrap<&DurationChunked> {
78
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
79
+ let utils = pl_utils(ruby);
80
+ let time_unit = Wrap(self.0.time_unit()).into_value_with(ruby);
81
+ let iter = self.0.physical().iter().map(|opt_v| {
82
+ opt_v
83
+ .map(|v| utils.funcall::<_, _, Value>("_to_ruby_duration", (v, time_unit)))
84
+ .transpose()
84
85
  });
85
- RArray::from_iter(iter).into_value()
86
+ ruby.ary_try_from_iter(iter).map(|v| v.as_value())
86
87
  }
87
88
  }
88
89
 
89
- impl IntoValue for Wrap<&DatetimeChunked> {
90
- fn into_value_with(self, _: &Ruby) -> Value {
91
- let utils = utils();
92
- let time_unit = Wrap(self.0.time_unit()).into_value();
93
- let time_zone = self.0.time_zone().as_deref().map(|v| v.into_value());
94
- let iter = self.0.into_iter().map(|opt_v| {
95
- opt_v.map(|v| {
96
- utils
97
- .funcall::<_, _, Value>("_to_ruby_datetime", (v, time_unit, time_zone))
98
- .unwrap()
99
- })
90
+ impl TryIntoValue for Wrap<&DatetimeChunked> {
91
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
92
+ let time_zone = self.0.time_zone().as_ref();
93
+ let time_unit = self.0.time_unit();
94
+ let iter = self.0.physical().iter().map(|opt_v| {
95
+ opt_v
96
+ .map(|v| datetime_to_rb_object(ruby, v, time_unit, time_zone))
97
+ .transpose()
100
98
  });
101
- RArray::from_iter(iter).into_value()
99
+ ruby.ary_try_from_iter(iter).map(|v| v.as_value())
102
100
  }
103
101
  }
104
102
 
105
- impl IntoValue for Wrap<&TimeChunked> {
106
- fn into_value_with(self, _: &Ruby) -> Value {
107
- let utils = utils();
108
- let iter = self.0.into_iter().map(|opt_v| {
109
- opt_v.map(|v| utils.funcall::<_, _, Value>("_to_ruby_time", (v,)).unwrap())
103
+ impl TryIntoValue for Wrap<&TimeChunked> {
104
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
105
+ let utils = pl_utils(ruby);
106
+ let iter = self.0.physical().iter().map(|opt_v| {
107
+ opt_v
108
+ .map(|v| utils.funcall::<_, _, Value>("_to_ruby_time", (v,)))
109
+ .transpose()
110
110
  });
111
- RArray::from_iter(iter).into_value()
111
+ ruby.ary_try_from_iter(iter).map(|v| v.as_value())
112
112
  }
113
113
  }
114
114
 
115
- impl IntoValue for Wrap<&DateChunked> {
116
- fn into_value_with(self, _: &Ruby) -> Value {
117
- let utils = utils();
118
- let iter = self.0.into_iter().map(|opt_v| {
119
- opt_v.map(|v| utils.funcall::<_, _, Value>("_to_ruby_date", (v,)).unwrap())
115
+ impl TryIntoValue for Wrap<&DateChunked> {
116
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
117
+ let utils = pl_utils(ruby);
118
+ let iter = self.0.physical().iter().map(|opt_v| {
119
+ opt_v
120
+ .map(|v| utils.funcall::<_, _, Value>("_to_ruby_date", (v,)))
121
+ .transpose()
120
122
  });
121
- RArray::from_iter(iter).into_value()
123
+ ruby.ary_try_from_iter(iter).map(|v| v.as_value())
122
124
  }
123
125
  }
124
126
 
125
- impl IntoValue for Wrap<&DecimalChunked> {
126
- fn into_value_with(self, _: &Ruby) -> Value {
127
- let utils = utils();
128
- let rb_scale = (-(self.0.scale() as i32)).into_value();
129
- let iter = self.0.into_iter().map(|opt_v| {
130
- opt_v.map(|v| {
131
- utils
132
- .funcall::<_, _, Value>("_to_ruby_decimal", (v.to_string(), rb_scale))
133
- .unwrap()
134
- })
135
- });
136
- RArray::from_iter(iter).into_value()
127
+ impl TryIntoValue for Wrap<&DecimalChunked> {
128
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
129
+ let iter = decimal_to_rbobject_iter(ruby, self.0)?;
130
+ Ok(iter.as_value())
137
131
  }
138
132
  }
133
+
134
+ pub(crate) fn decimal_to_rbobject_iter(ruby: &Ruby, ca: &DecimalChunked) -> RbResult<RArray> {
135
+ let utils = pl_utils(ruby);
136
+ let rb_precision = ca.precision().into_value_with(ruby);
137
+ let mut buf = DecimalFmtBuffer::new();
138
+ let iter = ca.physical().iter().map(move |opt_v| {
139
+ opt_v
140
+ .map(|v| {
141
+ let s = buf.format_dec128(v, ca.scale(), false, false);
142
+ utils.funcall::<_, _, Value>("_to_ruby_decimal", (rb_precision, s))
143
+ })
144
+ .transpose()
145
+ });
146
+ ruby.ary_try_from_iter(iter)
147
+ }
@@ -0,0 +1,63 @@
1
+ //! Utilities for converting dates, times, datetimes, and so on.
2
+
3
+ use std::str::FromStr;
4
+
5
+ use chrono::{DateTime, Datelike, FixedOffset, NaiveDateTime, TimeDelta, TimeZone as _};
6
+ use chrono_tz::Tz;
7
+ use magnus::{IntoValue, Ruby, Value, prelude::*};
8
+ use polars::prelude::*;
9
+
10
+ use crate::rb_modules::pl_utils;
11
+ use crate::{RbPolarsErr, RbResult};
12
+
13
+ pub fn elapsed_offset_to_timedelta(elapsed: i64, time_unit: TimeUnit) -> TimeDelta {
14
+ let (in_second, nano_multiplier) = match time_unit {
15
+ TimeUnit::Nanoseconds => (1_000_000_000, 1),
16
+ TimeUnit::Microseconds => (1_000_000, 1_000),
17
+ TimeUnit::Milliseconds => (1_000, 1_000_000),
18
+ };
19
+ let mut elapsed_sec = elapsed / in_second;
20
+ let mut elapsed_nanos = nano_multiplier * (elapsed % in_second);
21
+ if elapsed_nanos < 0 {
22
+ // TimeDelta expects nanos to always be positive.
23
+ elapsed_sec -= 1;
24
+ elapsed_nanos += 1_000_000_000;
25
+ }
26
+ TimeDelta::new(elapsed_sec, elapsed_nanos as u32).unwrap()
27
+ }
28
+
29
+ /// Convert time-units-since-epoch to a more structured object.
30
+ pub fn timestamp_to_naive_datetime(since_epoch: i64, time_unit: TimeUnit) -> NaiveDateTime {
31
+ DateTime::UNIX_EPOCH.naive_utc() + elapsed_offset_to_timedelta(since_epoch, time_unit)
32
+ }
33
+
34
+ pub fn datetime_to_rb_object(
35
+ ruby: &Ruby,
36
+ v: i64,
37
+ tu: TimeUnit,
38
+ tz: Option<&TimeZone>,
39
+ ) -> RbResult<Value> {
40
+ if let Some(time_zone) = tz {
41
+ if let Ok(tz) = Tz::from_str(time_zone) {
42
+ let utc_datetime = DateTime::UNIX_EPOCH + elapsed_offset_to_timedelta(v, tu);
43
+ if utc_datetime.year() >= 2100 {
44
+ // chrono-tz does not support dates after 2100
45
+ // https://github.com/chronotope/chrono-tz/issues/135
46
+ pl_utils(ruby).funcall("_to_ruby_datetime", (v, tu.to_ascii(), time_zone.as_str()))
47
+ } else {
48
+ let datetime = utc_datetime.with_timezone(&tz);
49
+ Ok(datetime.fixed_offset().into_value_with(ruby))
50
+ }
51
+ } else if let Ok(tz) = FixedOffset::from_str(time_zone) {
52
+ let naive_datetime = timestamp_to_naive_datetime(v, tu);
53
+ let datetime = tz.from_utc_datetime(&naive_datetime);
54
+ Ok(datetime.into_value_with(ruby))
55
+ } else {
56
+ Err(RbPolarsErr::Other(format!("Could not parse timezone: {time_zone}")).into())
57
+ }
58
+ } else {
59
+ Ok(timestamp_to_naive_datetime(v, tu)
60
+ .and_utc()
61
+ .into_value_with(ruby))
62
+ }
63
+ }