polars-df 0.15.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +274 -0
  3. data/Cargo.lock +1465 -867
  4. data/Cargo.toml +3 -0
  5. data/LICENSE.txt +2 -2
  6. data/README.md +87 -37
  7. data/ext/polars/Cargo.toml +47 -16
  8. data/ext/polars/src/c_api/allocator.rs +7 -0
  9. data/ext/polars/src/c_api/mod.rs +1 -0
  10. data/ext/polars/src/catalog/mod.rs +1 -0
  11. data/ext/polars/src/catalog/unity.rs +470 -0
  12. data/ext/polars/src/conversion/any_value.rs +99 -84
  13. data/ext/polars/src/conversion/categorical.rs +30 -0
  14. data/ext/polars/src/conversion/chunked_array.rs +71 -62
  15. data/ext/polars/src/conversion/datetime.rs +63 -0
  16. data/ext/polars/src/conversion/mod.rs +796 -312
  17. data/ext/polars/src/dataframe/construction.rs +6 -18
  18. data/ext/polars/src/dataframe/export.rs +30 -39
  19. data/ext/polars/src/dataframe/general.rs +294 -362
  20. data/ext/polars/src/dataframe/io.rs +33 -150
  21. data/ext/polars/src/dataframe/map.rs +175 -0
  22. data/ext/polars/src/dataframe/mod.rs +37 -5
  23. data/ext/polars/src/dataframe/serde.rs +23 -8
  24. data/ext/polars/src/error.rs +44 -7
  25. data/ext/polars/src/exceptions.rs +21 -8
  26. data/ext/polars/src/expr/array.rs +86 -22
  27. data/ext/polars/src/expr/binary.rs +50 -1
  28. data/ext/polars/src/expr/bitwise.rs +39 -0
  29. data/ext/polars/src/expr/categorical.rs +20 -0
  30. data/ext/polars/src/expr/datatype.rs +51 -0
  31. data/ext/polars/src/expr/datetime.rs +99 -41
  32. data/ext/polars/src/expr/extension.rs +12 -0
  33. data/ext/polars/src/expr/general.rs +252 -128
  34. data/ext/polars/src/expr/list.rs +56 -60
  35. data/ext/polars/src/expr/meta.rs +30 -35
  36. data/ext/polars/src/expr/mod.rs +28 -6
  37. data/ext/polars/src/expr/name.rs +29 -14
  38. data/ext/polars/src/expr/rolling.rs +111 -3
  39. data/ext/polars/src/expr/selector.rs +219 -0
  40. data/ext/polars/src/expr/serde.rs +28 -0
  41. data/ext/polars/src/expr/string.rs +118 -20
  42. data/ext/polars/src/expr/struct.rs +14 -1
  43. data/ext/polars/src/file.rs +194 -86
  44. data/ext/polars/src/functions/aggregation.rs +13 -12
  45. data/ext/polars/src/functions/business.rs +2 -3
  46. data/ext/polars/src/functions/eager.rs +3 -2
  47. data/ext/polars/src/functions/io.rs +90 -18
  48. data/ext/polars/src/functions/lazy.rs +267 -118
  49. data/ext/polars/src/functions/meta.rs +8 -7
  50. data/ext/polars/src/functions/misc.rs +1 -1
  51. data/ext/polars/src/functions/mod.rs +2 -1
  52. data/ext/polars/src/functions/range.rs +88 -31
  53. data/ext/polars/src/functions/strings.rs +6 -0
  54. data/ext/polars/src/functions/utils.rs +8 -0
  55. data/ext/polars/src/interop/arrow/mod.rs +52 -1
  56. data/ext/polars/src/interop/arrow/{to_ruby.rs → to_rb.rs} +37 -7
  57. data/ext/polars/src/interop/arrow/to_rust.rs +43 -0
  58. data/ext/polars/src/interop/numo/to_numo_df.rs +1 -1
  59. data/ext/polars/src/interop/numo/to_numo_series.rs +72 -50
  60. data/ext/polars/src/io/cloud_options.rs +107 -0
  61. data/ext/polars/src/io/mod.rs +4 -0
  62. data/ext/polars/src/io/scan_options.rs +113 -0
  63. data/ext/polars/src/io/sink_options.rs +46 -0
  64. data/ext/polars/src/io/sink_output.rs +21 -0
  65. data/ext/polars/src/lazyframe/exitable.rs +39 -0
  66. data/ext/polars/src/lazyframe/general.rs +846 -368
  67. data/ext/polars/src/lazyframe/mod.rs +58 -5
  68. data/ext/polars/src/lazyframe/optflags.rs +59 -0
  69. data/ext/polars/src/lazyframe/serde.rs +36 -4
  70. data/ext/polars/src/lazyframe/sink.rs +46 -0
  71. data/ext/polars/src/lazygroupby.rs +38 -9
  72. data/ext/polars/src/lib.rs +574 -165
  73. data/ext/polars/src/map/lazy.rs +44 -74
  74. data/ext/polars/src/map/mod.rs +18 -254
  75. data/ext/polars/src/map/series.rs +241 -1087
  76. data/ext/polars/src/on_startup.rs +192 -9
  77. data/ext/polars/src/prelude.rs +1 -0
  78. data/ext/polars/src/rb_modules.rs +10 -57
  79. data/ext/polars/src/ruby/exceptions.rs +26 -0
  80. data/ext/polars/src/ruby/gvl.rs +104 -0
  81. data/ext/polars/src/ruby/lazy.rs +46 -0
  82. data/ext/polars/src/ruby/mod.rs +11 -0
  83. data/ext/polars/src/ruby/numo.rs +52 -0
  84. data/ext/polars/src/ruby/plan_callback.rs +198 -0
  85. data/ext/polars/src/ruby/rb_modules.rs +16 -0
  86. data/ext/polars/src/ruby/ruby_convert_registry.rs +51 -0
  87. data/ext/polars/src/ruby/ruby_function.rs +11 -0
  88. data/ext/polars/src/ruby/ruby_udf.rs +164 -0
  89. data/ext/polars/src/ruby/thread.rs +65 -0
  90. data/ext/polars/src/ruby/utils.rs +39 -0
  91. data/ext/polars/src/series/aggregation.rs +116 -91
  92. data/ext/polars/src/series/arithmetic.rs +16 -22
  93. data/ext/polars/src/series/comparison.rs +101 -222
  94. data/ext/polars/src/series/construction.rs +80 -70
  95. data/ext/polars/src/series/export.rs +98 -56
  96. data/ext/polars/src/series/general.rs +323 -440
  97. data/ext/polars/src/series/import.rs +22 -5
  98. data/ext/polars/src/series/map.rs +103 -0
  99. data/ext/polars/src/series/mod.rs +57 -15
  100. data/ext/polars/src/series/scatter.rs +139 -82
  101. data/ext/polars/src/sql.rs +16 -9
  102. data/ext/polars/src/testing/frame.rs +31 -0
  103. data/ext/polars/src/testing/mod.rs +5 -0
  104. data/ext/polars/src/testing/series.rs +31 -0
  105. data/ext/polars/src/timeout.rs +105 -0
  106. data/ext/polars/src/utils.rs +105 -4
  107. data/lib/polars/array_expr.rb +500 -22
  108. data/lib/polars/array_name_space.rb +384 -10
  109. data/lib/polars/batched_csv_reader.rb +48 -66
  110. data/lib/polars/binary_expr.rb +217 -0
  111. data/lib/polars/binary_name_space.rb +155 -1
  112. data/lib/polars/cat_expr.rb +224 -0
  113. data/lib/polars/cat_name_space.rb +132 -32
  114. data/lib/polars/catalog/unity/catalog_info.rb +20 -0
  115. data/lib/polars/catalog/unity/column_info.rb +31 -0
  116. data/lib/polars/catalog/unity/namespace_info.rb +21 -0
  117. data/lib/polars/catalog/unity/table_info.rb +50 -0
  118. data/lib/polars/catalog.rb +448 -0
  119. data/lib/polars/collect_batches.rb +22 -0
  120. data/lib/polars/config.rb +3 -3
  121. data/lib/polars/convert.rb +201 -36
  122. data/lib/polars/data_frame.rb +2851 -1017
  123. data/lib/polars/data_frame_plot.rb +173 -0
  124. data/lib/polars/data_type_expr.rb +52 -0
  125. data/lib/polars/data_type_group.rb +6 -0
  126. data/lib/polars/data_types.rb +118 -18
  127. data/lib/polars/date_time_expr.rb +426 -84
  128. data/lib/polars/date_time_name_space.rb +384 -111
  129. data/lib/polars/dynamic_group_by.rb +102 -10
  130. data/lib/polars/exceptions.rb +50 -5
  131. data/lib/polars/expr.rb +2159 -915
  132. data/lib/polars/extension_expr.rb +39 -0
  133. data/lib/polars/extension_name_space.rb +39 -0
  134. data/lib/polars/functions/aggregation/horizontal.rb +11 -6
  135. data/lib/polars/functions/aggregation/vertical.rb +2 -3
  136. data/lib/polars/functions/as_datatype.rb +290 -8
  137. data/lib/polars/functions/business.rb +95 -0
  138. data/lib/polars/functions/col.rb +6 -5
  139. data/lib/polars/functions/datatype.rb +62 -0
  140. data/lib/polars/functions/eager.rb +426 -24
  141. data/lib/polars/functions/escape_regex.rb +21 -0
  142. data/lib/polars/functions/lazy.rb +813 -195
  143. data/lib/polars/functions/lit.rb +21 -10
  144. data/lib/polars/functions/range/int_range.rb +74 -2
  145. data/lib/polars/functions/range/linear_space.rb +195 -0
  146. data/lib/polars/functions/range/time_range.rb +1 -1
  147. data/lib/polars/functions/repeat.rb +7 -12
  148. data/lib/polars/functions/whenthen.rb +2 -2
  149. data/lib/polars/group_by.rb +188 -58
  150. data/lib/polars/iceberg_dataset.rb +108 -0
  151. data/lib/polars/in_process_query.rb +37 -0
  152. data/lib/polars/io/cloud.rb +18 -0
  153. data/lib/polars/io/csv.rb +336 -128
  154. data/lib/polars/io/database.rb +19 -4
  155. data/lib/polars/io/delta.rb +134 -0
  156. data/lib/polars/io/iceberg.rb +34 -0
  157. data/lib/polars/io/ipc.rb +63 -63
  158. data/lib/polars/io/json.rb +16 -0
  159. data/lib/polars/io/lines.rb +172 -0
  160. data/lib/polars/io/ndjson.rb +176 -20
  161. data/lib/polars/io/parquet.rb +173 -95
  162. data/lib/polars/io/scan_options.rb +55 -0
  163. data/lib/polars/io/sink_options.rb +27 -0
  164. data/lib/polars/io/utils.rb +17 -0
  165. data/lib/polars/lazy_frame.rb +3017 -622
  166. data/lib/polars/lazy_group_by.rb +436 -2
  167. data/lib/polars/list_expr.rb +551 -59
  168. data/lib/polars/list_name_space.rb +465 -51
  169. data/lib/polars/meta_expr.rb +146 -24
  170. data/lib/polars/name_expr.rb +87 -2
  171. data/lib/polars/query_opt_flags.rb +264 -0
  172. data/lib/polars/rolling_group_by.rb +90 -5
  173. data/lib/polars/scan_cast_options.rb +86 -0
  174. data/lib/polars/schema.rb +128 -0
  175. data/lib/polars/selector.rb +245 -0
  176. data/lib/polars/selectors.rb +1048 -201
  177. data/lib/polars/series.rb +2522 -774
  178. data/lib/polars/series_plot.rb +72 -0
  179. data/lib/polars/slice.rb +1 -1
  180. data/lib/polars/sql_context.rb +13 -6
  181. data/lib/polars/string_cache.rb +19 -72
  182. data/lib/polars/string_expr.rb +561 -107
  183. data/lib/polars/string_name_space.rb +781 -109
  184. data/lib/polars/struct_expr.rb +139 -18
  185. data/lib/polars/struct_name_space.rb +19 -1
  186. data/lib/polars/testing.rb +24 -273
  187. data/lib/polars/utils/constants.rb +2 -0
  188. data/lib/polars/utils/construction/data_frame.rb +410 -0
  189. data/lib/polars/utils/construction/series.rb +350 -0
  190. data/lib/polars/utils/construction/utils.rb +9 -0
  191. data/lib/polars/utils/convert.rb +18 -8
  192. data/lib/polars/utils/deprecation.rb +11 -0
  193. data/lib/polars/utils/parse.rb +62 -9
  194. data/lib/polars/utils/reduce_balanced.rb +43 -0
  195. data/lib/polars/utils/serde.rb +22 -0
  196. data/lib/polars/utils/unstable.rb +19 -0
  197. data/lib/polars/utils/various.rb +86 -1
  198. data/lib/polars/utils.rb +63 -48
  199. data/lib/polars/version.rb +1 -1
  200. data/lib/polars.rb +85 -2
  201. metadata +80 -28
  202. data/ext/polars/src/allocator.rs +0 -13
  203. data/ext/polars/src/batched_csv.rs +0 -138
  204. data/ext/polars/src/functions/string_cache.rs +0 -25
  205. data/ext/polars/src/map/dataframe.rs +0 -338
  206. data/lib/polars/plot.rb +0 -109
@@ -3,7 +3,7 @@ use polars_core::datatypes::{TimeUnit, TimeZone};
3
3
 
4
4
  use crate::conversion::Wrap;
5
5
  use crate::prelude::*;
6
- use crate::RbExpr;
6
+ use crate::{RbDataTypeExpr, RbExpr, RbPolarsErr, RbResult};
7
7
 
8
8
  pub fn int_range(start: &RbExpr, end: &RbExpr, step: i64, dtype: Wrap<DataType>) -> RbExpr {
9
9
  let start = start.inner.clone();
@@ -12,16 +12,20 @@ pub fn int_range(start: &RbExpr, end: &RbExpr, step: i64, dtype: Wrap<DataType>)
12
12
  dsl::int_range(start, end, step, dtype).into()
13
13
  }
14
14
 
15
- pub fn int_ranges(start: &RbExpr, end: &RbExpr, step: &RbExpr, dtype: Wrap<DataType>) -> RbExpr {
16
- let dtype = dtype.0;
17
-
18
- let mut result = dsl::int_ranges(start.inner.clone(), end.inner.clone(), step.inner.clone());
19
-
20
- if dtype != DataType::Int64 {
21
- result = result.cast(DataType::List(Box::new(dtype)))
22
- }
23
-
24
- result.into()
15
+ pub fn int_ranges(
16
+ start: &RbExpr,
17
+ end: &RbExpr,
18
+ step: &RbExpr,
19
+ dtype: &RbDataTypeExpr,
20
+ ) -> RbResult<RbExpr> {
21
+ let dtype = dtype.inner.clone();
22
+ Ok(dsl::int_ranges(
23
+ start.inner.clone(),
24
+ end.inner.clone(),
25
+ step.inner.clone(),
26
+ dtype,
27
+ )
28
+ .into())
25
29
  }
26
30
 
27
31
  pub fn date_range(
@@ -29,12 +33,14 @@ pub fn date_range(
29
33
  end: &RbExpr,
30
34
  interval: String,
31
35
  closed: Wrap<ClosedWindow>,
32
- ) -> RbExpr {
36
+ ) -> RbResult<RbExpr> {
33
37
  let start = start.inner.clone();
34
38
  let end = end.inner.clone();
35
39
  let interval = Duration::parse(&interval);
36
40
  let closed = closed.0;
37
- dsl::date_range(start, end, interval, closed).into()
41
+ let out = dsl::date_range(Some(start), Some(end), Some(interval), None, closed)
42
+ .map_err(RbPolarsErr::from)?;
43
+ Ok(out.into())
38
44
  }
39
45
 
40
46
  pub fn date_ranges(
@@ -42,46 +48,68 @@ pub fn date_ranges(
42
48
  end: &RbExpr,
43
49
  interval: String,
44
50
  closed: Wrap<ClosedWindow>,
45
- ) -> RbExpr {
51
+ ) -> RbResult<RbExpr> {
46
52
  let start = start.inner.clone();
47
53
  let end = end.inner.clone();
48
54
  let interval = Duration::parse(&interval);
49
55
  let closed = closed.0;
50
- dsl::date_ranges(start, end, interval, closed).into()
56
+ let out = dsl::date_ranges(Some(start), Some(end), Some(interval), None, closed)
57
+ .map_err(RbPolarsErr::from)?;
58
+ Ok(out.into())
51
59
  }
52
60
 
53
61
  pub fn datetime_range(
54
62
  start: &RbExpr,
55
63
  end: &RbExpr,
56
- every: String,
64
+ interval: String,
57
65
  closed: Wrap<ClosedWindow>,
58
66
  time_unit: Option<Wrap<TimeUnit>>,
59
- time_zone: Option<Wrap<TimeZone>>,
60
- ) -> RbExpr {
67
+ time_zone: Wrap<Option<TimeZone>>,
68
+ ) -> RbResult<RbExpr> {
61
69
  let start = start.inner.clone();
62
70
  let end = end.inner.clone();
63
- let every = Duration::parse(&every);
71
+ let interval = Duration::try_parse(&interval).map_err(RbPolarsErr::from)?;
64
72
  let closed = closed.0;
65
73
  let time_unit = time_unit.map(|x| x.0);
66
- let time_zone = time_zone.map(|x| x.0);
67
- dsl::datetime_range(start, end, every, closed, time_unit, time_zone).into()
74
+ let time_zone = time_zone.0;
75
+ let out = dsl::datetime_range(
76
+ Some(start),
77
+ Some(end),
78
+ Some(interval),
79
+ None,
80
+ closed,
81
+ time_unit,
82
+ time_zone,
83
+ )
84
+ .map_err(RbPolarsErr::from)?;
85
+ Ok(out.into())
68
86
  }
69
87
 
70
88
  pub fn datetime_ranges(
71
89
  start: &RbExpr,
72
90
  end: &RbExpr,
73
- every: String,
91
+ interval: String,
74
92
  closed: Wrap<ClosedWindow>,
75
93
  time_unit: Option<Wrap<TimeUnit>>,
76
- time_zone: Option<Wrap<TimeZone>>,
77
- ) -> RbExpr {
94
+ time_zone: Wrap<Option<TimeZone>>,
95
+ ) -> RbResult<RbExpr> {
78
96
  let start = start.inner.clone();
79
97
  let end = end.inner.clone();
80
- let every = Duration::parse(&every);
98
+ let interval = Duration::try_parse(&interval).map_err(RbPolarsErr::from)?;
81
99
  let closed = closed.0;
82
100
  let time_unit = time_unit.map(|x| x.0);
83
- let time_zone = time_zone.map(|x| x.0);
84
- dsl::datetime_ranges(start, end, every, closed, time_unit, time_zone).into()
101
+ let time_zone = time_zone.0;
102
+ let out = dsl::datetime_ranges(
103
+ Some(start),
104
+ Some(end),
105
+ Some(interval),
106
+ None,
107
+ closed,
108
+ time_unit,
109
+ time_zone,
110
+ )
111
+ .map_err(RbPolarsErr::from)?;
112
+ Ok(out.into())
85
113
  }
86
114
 
87
115
  pub fn time_range(
@@ -89,12 +117,12 @@ pub fn time_range(
89
117
  end: &RbExpr,
90
118
  every: String,
91
119
  closed: Wrap<ClosedWindow>,
92
- ) -> RbExpr {
120
+ ) -> RbResult<RbExpr> {
93
121
  let start = start.inner.clone();
94
122
  let end = end.inner.clone();
95
123
  let every = Duration::parse(&every);
96
124
  let closed = closed.0;
97
- dsl::time_range(start, end, every, closed).into()
125
+ Ok(dsl::time_range(start, end, every, closed).into())
98
126
  }
99
127
 
100
128
  pub fn time_ranges(
@@ -102,10 +130,39 @@ pub fn time_ranges(
102
130
  end: &RbExpr,
103
131
  every: String,
104
132
  closed: Wrap<ClosedWindow>,
105
- ) -> RbExpr {
133
+ ) -> RbResult<RbExpr> {
106
134
  let start = start.inner.clone();
107
135
  let end = end.inner.clone();
108
136
  let every = Duration::parse(&every);
109
137
  let closed = closed.0;
110
- dsl::time_ranges(start, end, every, closed).into()
138
+ Ok(dsl::time_ranges(start, end, every, closed).into())
139
+ }
140
+
141
+ pub fn linear_space(
142
+ start: &RbExpr,
143
+ end: &RbExpr,
144
+ num_samples: &RbExpr,
145
+ closed: Wrap<ClosedInterval>,
146
+ ) -> RbResult<RbExpr> {
147
+ let start = start.inner.clone();
148
+ let end = end.inner.clone();
149
+ let num_samples = num_samples.inner.clone();
150
+ let closed = closed.0;
151
+ Ok(dsl::linear_space(start, end, num_samples, closed).into())
152
+ }
153
+
154
+ pub fn linear_spaces(
155
+ start: &RbExpr,
156
+ end: &RbExpr,
157
+ num_samples: &RbExpr,
158
+ closed: Wrap<ClosedInterval>,
159
+ as_array: bool,
160
+ ) -> RbResult<RbExpr> {
161
+ let start = start.inner.clone();
162
+ let end = end.inner.clone();
163
+ let num_samples = num_samples.inner.clone();
164
+ let closed = closed.0;
165
+ let out =
166
+ dsl::linear_spaces(start, end, num_samples, closed, as_array).map_err(RbPolarsErr::from)?;
167
+ Ok(out.into())
111
168
  }
@@ -0,0 +1,6 @@
1
+ use crate::RbResult;
2
+
3
+ pub fn escape_regex(s: String) -> RbResult<String> {
4
+ let escaped_s = polars_ops::chunked_array::strings::escape_regex_str(&s);
5
+ Ok(escaped_s)
6
+ }
@@ -0,0 +1,8 @@
1
+ use crate::RbResult;
2
+
3
+ pub fn rb_get_engine_affinity() -> RbResult<String> {
4
+ Ok(polars_config::config()
5
+ .engine_affinity()
6
+ .as_static_str()
7
+ .to_string())
8
+ }
@@ -1 +1,52 @@
1
- pub mod to_ruby;
1
+ use magnus::prelude::*;
2
+ use magnus::{RHash, Ruby, Value};
3
+ use polars::prelude::{ArrowDataType, DataType};
4
+ use polars_error::polars_err;
5
+
6
+ use crate::interop::arrow::to_rust::normalize_arrow_fields;
7
+ use crate::prelude::Wrap;
8
+ use crate::ruby::utils::TryIntoValue;
9
+ use crate::series::import_schema_rbcapsule;
10
+ use crate::utils::to_rb_err;
11
+ use crate::{RbResult, RbValueError};
12
+
13
+ pub mod to_rb;
14
+ pub mod to_rust;
15
+
16
+ pub fn init_polars_schema_from_arrow_c_schema(
17
+ polars_schema: RHash,
18
+ schema_object: Value,
19
+ ) -> RbResult<()> {
20
+ let ruby = &Ruby::get_with(polars_schema);
21
+ let schema_capsule = schema_object.funcall("arrow_c_schema", ())?;
22
+
23
+ let field = import_schema_rbcapsule(schema_capsule)?;
24
+ let field = normalize_arrow_fields(&field);
25
+
26
+ let ArrowDataType::Struct(fields) = field.dtype else {
27
+ return Err(RbValueError::new_err(format!(
28
+ "arrow_c_schema of object passed to Polars::Schema did not return struct dtype: \
29
+ object: {}, dtype: {:?}",
30
+ schema_object, &field.dtype
31
+ )));
32
+ };
33
+
34
+ for field in fields {
35
+ let dtype = DataType::from_arrow_field(&field);
36
+
37
+ let name = field.name.as_str();
38
+ let dtype = Wrap(dtype).try_into_value_with(ruby)?;
39
+
40
+ if polars_schema.get(name).is_some() {
41
+ return Err(to_rb_err(polars_err!(
42
+ Duplicate:
43
+ "arrow schema contained duplicate name: {}",
44
+ name
45
+ )));
46
+ }
47
+
48
+ polars_schema.aset(name, dtype)?;
49
+ }
50
+
51
+ Ok(())
52
+ }
@@ -1,6 +1,6 @@
1
1
  use arrow::datatypes::ArrowDataType;
2
2
  use arrow::ffi;
3
- use magnus::{IntoValue, Value};
3
+ use magnus::{IntoValue, Ruby, Value};
4
4
  use polars::datatypes::CompatLevel;
5
5
  use polars::frame::DataFrame;
6
6
  use polars::prelude::{ArrayRef, ArrowField, PlSmallStr, PolarsResult, SchemaExt};
@@ -9,9 +9,9 @@ use polars_core::utils::arrow;
9
9
 
10
10
  use crate::RbResult;
11
11
 
12
- #[magnus::wrap(class = "Polars::RbArrowArrayStream")]
12
+ #[magnus::wrap(class = "Polars::ArrowArrayStream")]
13
13
  pub struct RbArrowArrayStream {
14
- stream: ffi::ArrowArrayStream,
14
+ pub(crate) stream: ffi::ArrowArrayStream,
15
15
  }
16
16
 
17
17
  impl RbArrowArrayStream {
@@ -20,11 +20,41 @@ impl RbArrowArrayStream {
20
20
  }
21
21
  }
22
22
 
23
- pub(crate) fn dataframe_to_stream(df: &DataFrame) -> RbResult<Value> {
23
+ #[magnus::wrap(class = "Polars::ArrowSchema")]
24
+ pub struct RbArrowSchema {
25
+ pub(crate) schema: ffi::ArrowSchema,
26
+ }
27
+
28
+ impl RbArrowSchema {
29
+ pub fn to_i(&self) -> usize {
30
+ (&self.schema as *const _) as usize
31
+ }
32
+ }
33
+
34
+ pub(crate) fn dataframe_to_stream(df: &DataFrame, ruby: &Ruby) -> RbResult<Value> {
24
35
  let iter = Box::new(DataFrameStreamIterator::new(df));
25
36
  let field = iter.field();
26
37
  let stream = ffi::export_iterator(iter, field);
27
- Ok(RbArrowArrayStream { stream }.into_value())
38
+ Ok(RbArrowArrayStream { stream }.into_value_with(ruby))
39
+ }
40
+
41
+ pub(crate) fn polars_schema_to_rbcapsule(
42
+ ruby: &Ruby,
43
+ schema: crate::prelude::Wrap<polars::prelude::Schema>,
44
+ ) -> RbResult<Value> {
45
+ let schema: arrow::ffi::ArrowSchema = arrow::ffi::export_field_to_c(&ArrowField::new(
46
+ PlSmallStr::EMPTY,
47
+ ArrowDataType::Struct(
48
+ schema
49
+ .0
50
+ .iter_fields()
51
+ .map(|x| x.to_arrow(CompatLevel::newest()))
52
+ .collect(),
53
+ ),
54
+ false,
55
+ ));
56
+
57
+ Ok(RbArrowSchema { schema }.into_value_with(ruby))
28
58
  }
29
59
 
30
60
  pub struct DataFrameStreamIterator {
@@ -41,13 +71,13 @@ impl DataFrameStreamIterator {
41
71
 
42
72
  Self {
43
73
  columns: df
44
- .get_columns()
74
+ .columns()
45
75
  .iter()
46
76
  .map(|v| v.as_materialized_series().clone())
47
77
  .collect(),
48
78
  dtype,
49
79
  idx: 0,
50
- n_chunks: df.n_chunks(),
80
+ n_chunks: df.first_col_n_chunks(),
51
81
  }
52
82
  }
53
83
 
@@ -0,0 +1,43 @@
1
+ use polars_core::prelude::*;
2
+
3
+ pub(crate) fn normalize_arrow_fields(field: &ArrowField) -> ArrowField {
4
+ // normalize fields with extension dtypes that are otherwise standard dtypes associated
5
+ // with (for us) irrelevant metadata; recreate the field using the inner (standard) dtype
6
+ match field {
7
+ ArrowField {
8
+ dtype: ArrowDataType::Struct(fields),
9
+ ..
10
+ } => {
11
+ let mut normalized = false;
12
+ let normalized_fields: Vec<_> = fields
13
+ .iter()
14
+ .map(|f| {
15
+ // note: google bigquery column data is returned as a standard arrow dtype, but the
16
+ // sql type it was loaded from is associated as metadata (resulting in an extension dtype)
17
+ if let ArrowDataType::Extension(ext_type) = &f.dtype
18
+ && ext_type.name.starts_with("google:sqlType:")
19
+ {
20
+ normalized = true;
21
+ return ArrowField::new(
22
+ f.name.clone(),
23
+ ext_type.inner.clone(),
24
+ f.is_nullable,
25
+ );
26
+ }
27
+ f.clone()
28
+ })
29
+ .collect();
30
+
31
+ if normalized {
32
+ ArrowField::new(
33
+ field.name.clone(),
34
+ ArrowDataType::Struct(normalized_fields),
35
+ field.is_nullable,
36
+ )
37
+ } else {
38
+ field.clone()
39
+ }
40
+ }
41
+ _ => field.clone(),
42
+ }
43
+ }
@@ -6,7 +6,7 @@ use crate::dataframe::RbDataFrame;
6
6
  impl RbDataFrame {
7
7
  pub fn to_numo(&self) -> Option<Value> {
8
8
  let mut st = None;
9
- for s in self.df.borrow().iter() {
9
+ for s in self.df.read().columns().iter() {
10
10
  let dt_i = s.dtype();
11
11
  match st {
12
12
  None => st = Some(dt_i.clone()),
@@ -1,61 +1,83 @@
1
- use magnus::{class, prelude::*, Module, RArray, RClass, RModule, Value};
2
- use polars::series::BitRepr;
1
+ use magnus::{Ruby, Value};
2
+ use num_traits::{Float, NumCast};
3
3
  use polars_core::prelude::*;
4
4
 
5
+ use crate::RbResult;
5
6
  use crate::error::RbPolarsErr;
6
7
  use crate::raise_err;
8
+ use crate::ruby::numo::{Element, RbArray1};
7
9
  use crate::series::RbSeries;
8
- use crate::RbResult;
9
10
 
10
11
  impl RbSeries {
11
- /// For numeric types, this should only be called for Series with null types.
12
- /// This will cast to floats so that `nil = NAN`
13
- pub fn to_numo(&self) -> RbResult<Value> {
14
- let s = &self.series.borrow();
15
- match s.dtype() {
16
- DataType::String => {
17
- let ca = s.str().unwrap();
12
+ /// Convert this Series to a Numo array.
13
+ pub fn to_numo(rb: &Ruby, self_: &Self) -> RbResult<Value> {
14
+ series_to_numo(rb, &self_.series.read())
15
+ }
16
+ }
18
17
 
19
- // TODO make more efficient
20
- let np_arr = RArray::from_iter(ca);
21
- class::object()
22
- .const_get::<_, RModule>("Numo")?
23
- .const_get::<_, RClass>("RObject")?
24
- .funcall("cast", (np_arr,))
25
- }
26
- dt if dt.is_numeric() => {
27
- if let Some(BitRepr::Large(_)) = s.bit_repr() {
28
- let s = s.cast(&DataType::Float64).unwrap();
29
- let ca = s.f64().unwrap();
30
- // TODO make more efficient
31
- let np_arr = RArray::from_iter(ca.into_iter().map(|opt_v| match opt_v {
32
- Some(v) => v,
33
- None => f64::NAN,
34
- }));
35
- class::object()
36
- .const_get::<_, RModule>("Numo")?
37
- .const_get::<_, RClass>("DFloat")?
38
- .funcall("cast", (np_arr,))
39
- } else {
40
- let s = s.cast(&DataType::Float32).unwrap();
41
- let ca = s.f32().unwrap();
42
- // TODO make more efficient
43
- let np_arr = RArray::from_iter(ca.into_iter().map(|opt_v| match opt_v {
44
- Some(v) => v,
45
- None => f32::NAN,
46
- }));
47
- class::object()
48
- .const_get::<_, RModule>("Numo")?
49
- .const_get::<_, RClass>("SFloat")?
50
- .funcall("cast", (np_arr,))
51
- }
52
- }
53
- dt => {
54
- raise_err!(
55
- format!("'to_numo' not supported for dtype: {dt:?}"),
56
- ComputeError
57
- );
58
- }
18
+ /// Convert a Series to a Numo array.
19
+ fn series_to_numo(rb: &Ruby, s: &Series) -> RbResult<Value> {
20
+ series_to_numo_with_copy(rb, s)
21
+ }
22
+
23
+ /// Convert a Series to a Numo array, copying data in the process.
24
+ fn series_to_numo_with_copy(rb: &Ruby, s: &Series) -> RbResult<Value> {
25
+ use DataType::*;
26
+ match s.dtype() {
27
+ Int8 => numeric_series_to_numo::<Int8Type, f32>(rb, s),
28
+ Int16 => numeric_series_to_numo::<Int16Type, f32>(rb, s),
29
+ Int32 => numeric_series_to_numo::<Int32Type, f64>(rb, s),
30
+ Int64 => numeric_series_to_numo::<Int64Type, f64>(rb, s),
31
+ UInt8 => numeric_series_to_numo::<UInt8Type, f32>(rb, s),
32
+ UInt16 => numeric_series_to_numo::<UInt16Type, f32>(rb, s),
33
+ UInt32 => numeric_series_to_numo::<UInt32Type, f64>(rb, s),
34
+ UInt64 => numeric_series_to_numo::<UInt64Type, f64>(rb, s),
35
+ Float32 => numeric_series_to_numo::<Float32Type, f32>(rb, s),
36
+ Float64 => numeric_series_to_numo::<Float64Type, f64>(rb, s),
37
+ Boolean => boolean_series_to_numo(rb, s),
38
+ String => {
39
+ let ca = s.str().unwrap();
40
+ let values = ca.iter();
41
+ RbArray1::from_iter(rb, values)
42
+ }
43
+ dt => {
44
+ raise_err!(
45
+ format!("'to_numo' not supported for dtype: {dt:?}"),
46
+ ComputeError
47
+ );
59
48
  }
60
49
  }
61
50
  }
51
+
52
+ /// Convert numeric types to f32 or f64 with NaN representing a null value.
53
+ fn numeric_series_to_numo<T, U>(rb: &Ruby, s: &Series) -> RbResult<Value>
54
+ where
55
+ T: PolarsNumericType,
56
+ T::Native: Element,
57
+ U: Float + Element,
58
+ {
59
+ let ca: &ChunkedArray<T> = s.as_ref().as_ref();
60
+ if s.null_count() == 0 {
61
+ let values = ca.into_no_null_iter();
62
+ RbArray1::<T::Native>::from_iter(rb, values)
63
+ } else {
64
+ let mapper = |opt_v: Option<T::Native>| match opt_v {
65
+ Some(v) => NumCast::from(v).unwrap(),
66
+ None => U::nan(),
67
+ };
68
+ let values = ca.iter().map(mapper);
69
+ RbArray1::from_iter(rb, values)
70
+ }
71
+ }
72
+
73
+ /// Convert booleans to bit if no nulls are present, otherwise convert to objects.
74
+ fn boolean_series_to_numo(rb: &Ruby, s: &Series) -> RbResult<Value> {
75
+ let ca = s.bool().unwrap();
76
+ if s.null_count() == 0 {
77
+ let values = ca.no_null_iter();
78
+ RbArray1::<bool>::from_iter(rb, values)
79
+ } else {
80
+ let values = ca.iter();
81
+ RbArray1::from_iter(rb, values)
82
+ }
83
+ }
@@ -0,0 +1,107 @@
1
+ use std::time::Duration;
2
+
3
+ use magnus::{RHash, TryConvert, Value, r_hash::ForEach, value::ReprValue};
4
+ use polars::prelude::CloudScheme;
5
+ use polars_io::cloud::{CloudOptions, CloudRetryConfig};
6
+ use polars_utils::total_ord::TotalOrdWrap;
7
+
8
+ use crate::utils::to_rb_err;
9
+ use crate::{RbResult, RbValueError};
10
+
11
+ pub struct OptRbCloudOptions(Value);
12
+
13
+ impl TryConvert for OptRbCloudOptions {
14
+ fn try_convert(ob: Value) -> RbResult<Self> {
15
+ Ok(Self(ob))
16
+ }
17
+ }
18
+
19
+ impl OptRbCloudOptions {
20
+ pub fn extract_opt_cloud_options(
21
+ &self,
22
+ cloud_scheme: Option<CloudScheme>,
23
+ credential_provider: Option<Value>,
24
+ ) -> RbResult<Option<CloudOptions>> {
25
+ if self.0.is_nil() && credential_provider.is_none() {
26
+ return Ok(None);
27
+ }
28
+
29
+ let mut storage_options: Vec<(String, String)> = vec![];
30
+ let mut file_cache_ttl: u64 = 2;
31
+ let mut retry_config = CloudRetryConfig::default();
32
+
33
+ let storage_options_dict = Option::<RHash>::try_convert(self.0)?;
34
+
35
+ if let Some(storage_options_dict) = storage_options_dict {
36
+ storage_options.reserve(storage_options_dict.len());
37
+
38
+ storage_options_dict.foreach(|key: Value, value: Value| {
39
+ let key: String = key.funcall("to_s", ())?;
40
+
41
+ macro_rules! expected_type {
42
+ ($key_name:expr, $type_name:expr) => {{
43
+ |_| {
44
+ let key_name = $key_name;
45
+ let type_name = $type_name;
46
+ RbValueError::new_err(format!(
47
+ "invalid value for '{key_name}': '{value}' (expected {type_name})"
48
+ ))
49
+ }
50
+ }};
51
+ }
52
+
53
+ match &*key {
54
+ "file_cache_ttl" => {
55
+ file_cache_ttl = u64::try_convert(value)
56
+ .map_err(expected_type!("file_cache_ttl", "int"))?;
57
+ }
58
+ "max_retries" => {
59
+ retry_config.max_retries = Option::<usize>::try_convert(value)
60
+ .map_err(expected_type!("max_retries", "int"))?;
61
+ }
62
+ "retry_timeout_ms" => {
63
+ retry_config.retry_timeout = Some(Duration::from_millis(
64
+ u64::try_convert(value)
65
+ .map_err(expected_type!("retry_timeout", "int"))?,
66
+ ));
67
+ }
68
+ "retry_init_backoff_ms" => {
69
+ retry_config.retry_init_backoff = Some(Duration::from_millis(
70
+ u64::try_convert(value)
71
+ .map_err(expected_type!("retry_init_backoff", "int"))?,
72
+ ));
73
+ }
74
+ "retry_max_backoff_ms" => {
75
+ retry_config.retry_max_backoff = Some(Duration::from_millis(
76
+ u64::try_convert(value)
77
+ .map_err(expected_type!("retry_max_backoff", "int"))?,
78
+ ));
79
+ }
80
+ "retry_base_multiplier" => {
81
+ retry_config.retry_base_multiplier = Some(TotalOrdWrap(
82
+ f64::try_convert(value)
83
+ .map_err(expected_type!("retry_base_multiplier", "float"))?,
84
+ ));
85
+ }
86
+ _ => {
87
+ let value =
88
+ String::try_convert(value).map_err(expected_type!(&key, "str"))?;
89
+ storage_options.push((key, value))
90
+ }
91
+ }
92
+
93
+ Ok(ForEach::Continue)
94
+ })?;
95
+ }
96
+
97
+ let mut cloud_options = CloudOptions::from_untyped_config(cloud_scheme, storage_options)
98
+ .map_err(to_rb_err)?
99
+ .with_retry_config(retry_config);
100
+
101
+ if file_cache_ttl > 0 {
102
+ cloud_options.file_cache_ttl = file_cache_ttl;
103
+ }
104
+
105
+ Ok(Some(cloud_options))
106
+ }
107
+ }
@@ -0,0 +1,4 @@
1
+ pub mod cloud_options;
2
+ pub mod scan_options;
3
+ pub mod sink_options;
4
+ pub mod sink_output;