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,13 +1,30 @@
1
- use magnus::prelude::*;
1
+ use arrow::array::Array;
2
+ use arrow::ffi;
3
+ use arrow::ffi::{ArrowArrayStream, ArrowArrayStreamReader};
2
4
  use magnus::Value;
3
- use polars::export::arrow::array::Array;
4
- use polars::export::arrow::ffi::{ArrowArrayStream, ArrowArrayStreamReader};
5
+ use magnus::prelude::*;
5
6
  use polars::prelude::*;
6
7
 
7
8
  use super::RbSeries;
8
9
 
9
- use crate::exceptions::RbValueError;
10
10
  use crate::RbResult;
11
+ use crate::ruby::exceptions::RbValueError;
12
+
13
+ pub(crate) fn import_schema_rbcapsule(schema_capsule: Value) -> RbResult<arrow::datatypes::Field> {
14
+ let capsule_pointer: usize = schema_capsule.funcall("to_i", ())?;
15
+
16
+ // # Safety
17
+ // schema_capsule holds a valid C ArrowSchema pointer, as defined by the Arrow PyCapsule
18
+ // Interface
19
+ unsafe {
20
+ let schema_ptr = (capsule_pointer as *const ffi::ArrowSchema)
21
+ .as_ref()
22
+ .unwrap();
23
+ let field = ffi::import_field_from_c(schema_ptr).unwrap();
24
+
25
+ Ok(field)
26
+ }
27
+ }
11
28
 
12
29
  /// Import `arrow_c_stream` across Ruby boundary.
13
30
  fn call_arrow_c_stream(ob: Value) -> RbResult<Value> {
@@ -39,7 +56,7 @@ pub(crate) fn import_stream_rbcapsule(capsule: Value) -> RbResult<RbSeries> {
39
56
 
40
57
  // Series::try_from fails for an empty vec of chunks
41
58
  let s = if produced_arrays.is_empty() {
42
- let polars_dt = DataType::from_arrow(stream.field().dtype(), false);
59
+ let polars_dt = DataType::from_arrow_field(stream.field());
43
60
  Series::new_empty(stream.field().name.clone(), &polars_dt)
44
61
  } else {
45
62
  Series::try_from((stream.field(), produced_arrays)).unwrap()
@@ -0,0 +1,103 @@
1
+ use magnus::{IntoValue, Ruby, Value, value::Opaque, value::ReprValue};
2
+
3
+ use super::RbSeries;
4
+ use crate::map::series::ApplyLambdaGeneric;
5
+ use crate::prelude::*;
6
+ use crate::ruby::gvl::GvlExt;
7
+ use crate::ruby::utils::TryIntoValue;
8
+ use crate::series::construction::series_from_objects;
9
+ use crate::{RbPolarsErr, RbResult};
10
+ use crate::{apply_all_polars_dtypes, raise_err};
11
+
12
+ impl RbSeries {
13
+ pub fn map_elements(
14
+ &self,
15
+ function: Value,
16
+ return_dtype: Option<Wrap<DataType>>,
17
+ skip_nulls: bool,
18
+ ) -> RbResult<Self> {
19
+ let series = &self.series.read().clone(); // Clone so we don't deadlock on re-entrance.
20
+
21
+ if return_dtype.is_none() {
22
+ polars_warn!(
23
+ MapWithoutReturnDtypeWarning,
24
+ "Calling `map_elements` without specifying `return_dtype` can lead to unpredictable results. \
25
+ Specify `return_dtype` to silence this warning."
26
+ )
27
+ }
28
+
29
+ if skip_nulls && (series.null_count() == series.len()) {
30
+ if let Some(return_dtype) = return_dtype {
31
+ return Ok(
32
+ Series::full_null(series.name().clone(), series.len(), &return_dtype.0).into(),
33
+ );
34
+ }
35
+ let msg = "The output type of the 'map_elements' function cannot be determined.\n\
36
+ The function was never called because 'skip_nulls: true' and all values are null.\n\
37
+ Consider setting 'skip_nulls: false' or setting the 'return_dtype'.";
38
+ raise_err!(msg, ComputeError)
39
+ }
40
+
41
+ let return_dtype = return_dtype.map(|dt| dt.0);
42
+
43
+ Ruby::attach(|rb| {
44
+ let s = match &return_dtype {
45
+ Some(DataType::Object(_)) => {
46
+ // If the return dtype is Object we should not go through AnyValue.
47
+ call_and_collect_objects(
48
+ rb,
49
+ series.name().clone(),
50
+ function,
51
+ series.len(),
52
+ series.iter().map(|av| av.null_to_none().map(Wrap)),
53
+ skip_nulls,
54
+ )
55
+ }
56
+ Some(return_dtype) => {
57
+ apply_all_polars_dtypes!(
58
+ series,
59
+ apply_generic_with_dtype,
60
+ rb,
61
+ function,
62
+ return_dtype,
63
+ skip_nulls
64
+ )
65
+ }
66
+ None => apply_all_polars_dtypes!(series, apply_generic, rb, function, skip_nulls),
67
+ };
68
+ s.map(RbSeries::from)
69
+ })
70
+ }
71
+ }
72
+
73
+ fn call_and_collect_objects<T, I>(
74
+ rb: &Ruby,
75
+ name: PlSmallStr,
76
+ lambda: Value,
77
+ len: usize,
78
+ iter: I,
79
+ skip_nulls: bool,
80
+ ) -> RbResult<Series>
81
+ where
82
+ T: TryIntoValue,
83
+ I: Iterator<Item = Option<T>>,
84
+ {
85
+ let mut objects = Vec::with_capacity(len);
86
+ for opt_val in iter {
87
+ let arg = match opt_val {
88
+ None if skip_nulls => {
89
+ objects.push(ObjectValue {
90
+ inner: Opaque::from(rb.qnil().into_value_with(rb)),
91
+ });
92
+ continue;
93
+ }
94
+ None => rb.qnil().into_value_with(rb),
95
+ Some(val) => val.try_into_value_with(rb)?,
96
+ };
97
+ let out: Value = lambda.funcall("call", (arg,))?;
98
+ objects.push(ObjectValue {
99
+ inner: Opaque::from(out),
100
+ });
101
+ }
102
+ Ok(series_from_objects(rb, name, objects))
103
+ }
@@ -1,33 +1,45 @@
1
1
  mod aggregation;
2
2
  mod arithmetic;
3
3
  mod comparison;
4
- mod construction;
4
+ pub(crate) mod construction;
5
5
  mod export;
6
6
  mod general;
7
7
  mod import;
8
+ mod map;
8
9
  mod scatter;
9
10
 
10
- use magnus::{prelude::*, RArray};
11
+ pub(crate) use import::import_schema_rbcapsule;
12
+
13
+ use magnus::{DataTypeFunctions, RArray, Ruby, TypedData, gc, prelude::*};
14
+ use parking_lot::RwLock;
11
15
  use polars::prelude::*;
12
- use std::cell::RefCell;
13
16
 
14
- use crate::RbResult;
17
+ use crate::{ObjectValue, RbResult};
15
18
 
16
- #[magnus::wrap(class = "Polars::RbSeries")]
19
+ #[derive(TypedData)]
20
+ #[magnus(class = "Polars::RbSeries", mark)]
17
21
  pub struct RbSeries {
18
- pub series: RefCell<Series>,
22
+ pub series: RwLock<Series>,
23
+ }
24
+
25
+ impl Clone for RbSeries {
26
+ fn clone(&self) -> Self {
27
+ Self {
28
+ series: RwLock::new(self.series.read().clone()),
29
+ }
30
+ }
19
31
  }
20
32
 
21
33
  impl From<Series> for RbSeries {
22
34
  fn from(series: Series) -> Self {
23
- RbSeries::new(series)
35
+ Self::new(series)
24
36
  }
25
37
  }
26
38
 
27
39
  impl RbSeries {
28
40
  pub fn new(series: Series) -> Self {
29
41
  RbSeries {
30
- series: RefCell::new(series),
42
+ series: RwLock::new(series),
31
43
  }
32
44
  }
33
45
  }
@@ -35,15 +47,45 @@ impl RbSeries {
35
47
  pub fn to_series(rs: RArray) -> RbResult<Vec<Series>> {
36
48
  let mut series = Vec::new();
37
49
  for item in rs.into_iter() {
38
- series.push(<&RbSeries>::try_convert(item)?.series.borrow().clone());
50
+ series.push(<&RbSeries>::try_convert(item)?.series.read().clone());
39
51
  }
40
52
  Ok(series)
41
53
  }
42
54
 
43
- pub fn to_rbseries(s: Vec<Column>) -> RArray {
44
- RArray::from_iter(
45
- s.into_iter()
46
- .map(|c| c.take_materialized_series())
47
- .map(RbSeries::new),
48
- )
55
+ pub(crate) trait ToRbSeries {
56
+ fn to_rbseries(self, rb: &Ruby) -> RArray;
57
+ }
58
+
59
+ impl ToRbSeries for Vec<Column> {
60
+ fn to_rbseries(self, rb: &Ruby) -> RArray {
61
+ rb.ary_from_iter(
62
+ self.into_iter()
63
+ .map(|c| c.take_materialized_series())
64
+ .map(RbSeries::new),
65
+ )
66
+ }
67
+ }
68
+
69
+ pub fn mark_series(marker: &gc::Marker, series: &Series) {
70
+ if let DataType::Object(_) = series.dtype() {
71
+ for i in 0..series.len() {
72
+ let obj: Option<&ObjectValue> = series.get_object(i).map(|any| any.into());
73
+ if let Some(o) = obj {
74
+ marker.mark(o.inner);
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ impl DataTypeFunctions for RbSeries {
81
+ fn mark(&self, marker: &gc::Marker) {
82
+ // this is not ideal, as objects will not be marked if unable to borrow
83
+ // this should never happen, but log for now to avoid panic,
84
+ // as most series will not use Object datatype
85
+ if let Some(s) = &self.series.try_read() {
86
+ mark_series(marker, s);
87
+ } else {
88
+ eprintln!("[polars] Could not borrow!");
89
+ }
90
+ }
49
91
  }
@@ -1,107 +1,164 @@
1
- use polars::export::arrow::array::Array;
1
+ use arrow::array::Array;
2
2
  use polars::prelude::*;
3
3
 
4
- use crate::error::RbPolarsErr;
5
- use crate::{RbErr, RbResult, RbSeries};
4
+ use crate::utils::EnterPolarsExt;
5
+ use crate::{RbResult, RbSeries, Ruby};
6
6
 
7
7
  impl RbSeries {
8
- pub fn scatter(&self, idx: &RbSeries, values: &RbSeries) -> RbResult<()> {
9
- let mut s = self.series.borrow_mut();
10
- match scatter(s.clone(), &idx.series.borrow(), &values.series.borrow()) {
11
- Ok(out) => {
12
- *s = out;
13
- Ok(())
8
+ pub fn scatter(rb: &Ruby, self_: &Self, idx: &RbSeries, values: &RbSeries) -> RbResult<()> {
9
+ rb.enter_polars(|| {
10
+ // We take the value because we want a ref count of 1 so that we can
11
+ // have mutable access cheaply via _get_inner_mut().
12
+ let mut lock = self_.series.write();
13
+ let s = std::mem::take(&mut *lock);
14
+ let result = scatter(
15
+ s,
16
+ &idx.clone().series.into_inner(),
17
+ &values.clone().series.into_inner(),
18
+ );
19
+ match result {
20
+ Ok(out) => {
21
+ *lock = out;
22
+ Ok(())
23
+ }
24
+ Err((s, e)) => {
25
+ *lock = s; // Restore original series.
26
+ Err(e)
27
+ }
14
28
  }
15
- Err(e) => Err(RbErr::from(RbPolarsErr::from(e))),
16
- }
29
+ })
17
30
  }
18
31
  }
19
32
 
20
- fn scatter(mut s: Series, idx: &Series, values: &Series) -> PolarsResult<Series> {
33
+ fn scatter(s: Series, idx: &Series, values: &Series) -> Result<Series, (Series, PolarsError)> {
21
34
  let logical_dtype = s.dtype().clone();
22
- let idx = idx.cast(&IDX_DTYPE)?;
23
- let idx = idx.rechunk();
24
- let idx = idx.idx().unwrap();
25
- let idx = idx.downcast_iter().next().unwrap();
35
+ let converted_values;
36
+ let values = if logical_dtype.is_categorical() || logical_dtype.is_enum() {
37
+ if matches!(
38
+ values.dtype(),
39
+ DataType::Categorical(_, _) | DataType::Enum(_, _) | DataType::String | DataType::Null
40
+ ) {
41
+ converted_values = values.strict_cast(&logical_dtype);
42
+ match converted_values {
43
+ Ok(ref values) => values,
44
+ Err(err) => return Err((s, err)),
45
+ }
46
+ } else {
47
+ return Err((
48
+ s,
49
+ polars_err!(InvalidOperation: "invalid values dtype '{}' for scattering into dtype '{}'", values.dtype(), logical_dtype),
50
+ ));
51
+ }
52
+ } else if logical_dtype.is_decimal() {
53
+ if values.dtype().is_numeric() {
54
+ converted_values = values.strict_cast(&logical_dtype);
55
+ match converted_values {
56
+ Ok(ref values) => values,
57
+ Err(err) => return Err((s, err)),
58
+ }
59
+ } else {
60
+ return Err((
61
+ s,
62
+ polars_err!(InvalidOperation: "invalid values dtype '{}' for scattering into dtype '{}'", values.dtype(), logical_dtype),
63
+ ));
64
+ }
65
+ } else {
66
+ values
67
+ };
26
68
 
27
- if idx.null_count() > 0 {
28
- return Err(PolarsError::ComputeError(
29
- "index values should not be null".into(),
69
+ let null_on_oob = false;
70
+ let idx = match polars_ops::prelude::convert_and_bound_index(idx, s.len(), null_on_oob) {
71
+ Ok(idx) => idx,
72
+ Err(err) => return Err((s, err)),
73
+ };
74
+ let idx = idx.rechunk();
75
+ let idx = idx.downcast_as_array();
76
+ if idx.has_nulls() {
77
+ return Err((
78
+ s,
79
+ PolarsError::ComputeError("index values should not be null".into()),
30
80
  ));
31
81
  }
32
-
33
82
  let idx = idx.values().as_slice();
34
83
 
35
- let values = values.to_physical_repr().cast(&s.dtype().to_physical())?;
84
+ let mut values = match values.to_physical_repr().cast(&s.dtype().to_physical()) {
85
+ Ok(values) => values,
86
+ Err(err) => return Err((s, err)),
87
+ };
88
+
89
+ // Broadcast values input.
90
+ if values.len() == 1 && idx.len() > 1 {
91
+ values = values.new_from_index(0, idx.len());
92
+ }
93
+
94
+ let mut phys = s.to_physical_repr().into_owned();
95
+ drop(s); // Reduce refcount to make use of in-place mutation of possible.
96
+ let ret = scatter_impl(&mut phys, &logical_dtype, idx, &values);
97
+ match ret {
98
+ Ok(s) => Ok(unsafe { s.from_physical_unchecked(&logical_dtype).unwrap() }),
99
+ Err(e) => Err((
100
+ unsafe { phys.from_physical_unchecked(&logical_dtype).unwrap() },
101
+ e,
102
+ )),
103
+ }
104
+ }
36
105
 
37
- // do not shadow, otherwise s is not dropped immediately
38
- // and we want to have mutable access
39
- s = s.to_physical_repr().into_owned();
106
+ macro_rules! with_match_physical_numeric_polars_type {(
107
+ $key_type:expr, | $_:tt $T:ident | $($body:tt)*
108
+ ) => ({
109
+ macro_rules! __with_ty__ {( $_ $T:ident ) => ( $($body)* )}
110
+ use polars_core::datatypes::DataType::*;
111
+ match $key_type {
112
+ Int8 => __with_ty__! { Int8Type },
113
+ Int16 => __with_ty__! { Int16Type },
114
+ Int32 => __with_ty__! { Int32Type },
115
+ Int64 => __with_ty__! { Int64Type },
116
+ Int128 => __with_ty__! { Int128Type },
117
+ UInt8 => __with_ty__! { UInt8Type },
118
+ UInt16 => __with_ty__! { UInt16Type },
119
+ UInt32 => __with_ty__! { UInt32Type },
120
+ UInt64 => __with_ty__! { UInt64Type },
121
+ UInt128 => __with_ty__! { UInt128Type },
122
+ Float16 => __with_ty__! { Float16Type },
123
+ Float32 => __with_ty__! { Float32Type },
124
+ Float64 => __with_ty__! { Float64Type },
125
+ dt => panic!("not implemented for dtype {:?}", dt),
126
+ }
127
+ })}
128
+
129
+ fn scatter_impl(
130
+ s: &mut Series,
131
+ logical_dtype: &DataType,
132
+ idx: &[IdxSize],
133
+ values: &Series,
134
+ ) -> PolarsResult<Series> {
40
135
  let mutable_s = s._get_inner_mut();
41
136
 
42
- let s = match logical_dtype.to_physical() {
43
- DataType::Int8 => {
44
- let ca: &mut ChunkedArray<Int8Type> = mutable_s.as_mut();
45
- let values = values.i8()?;
46
- std::mem::take(ca).scatter(idx, values)
47
- }
48
- DataType::Int16 => {
49
- let ca: &mut ChunkedArray<Int16Type> = mutable_s.as_mut();
50
- let values = values.i16()?;
51
- std::mem::take(ca).scatter(idx, values)
52
- }
53
- DataType::Int32 => {
54
- let ca: &mut ChunkedArray<Int32Type> = mutable_s.as_mut();
55
- let values = values.i32()?;
56
- std::mem::take(ca).scatter(idx, values)
57
- }
58
- DataType::Int64 => {
59
- let ca: &mut ChunkedArray<Int64Type> = mutable_s.as_mut();
60
- let values = values.i64()?;
61
- std::mem::take(ca).scatter(idx, values)
62
- }
63
- DataType::UInt8 => {
64
- let ca: &mut ChunkedArray<UInt8Type> = mutable_s.as_mut();
65
- let values = values.u8()?;
66
- std::mem::take(ca).scatter(idx, values)
67
- }
68
- DataType::UInt16 => {
69
- let ca: &mut ChunkedArray<UInt16Type> = mutable_s.as_mut();
70
- let values = values.u16()?;
71
- std::mem::take(ca).scatter(idx, values)
72
- }
73
- DataType::UInt32 => {
74
- let ca: &mut ChunkedArray<UInt32Type> = mutable_s.as_mut();
75
- let values = values.u32()?;
76
- std::mem::take(ca).scatter(idx, values)
77
- }
78
- DataType::UInt64 => {
79
- let ca: &mut ChunkedArray<UInt64Type> = mutable_s.as_mut();
80
- let values = values.u64()?;
81
- std::mem::take(ca).scatter(idx, values)
82
- }
83
- DataType::Float32 => {
84
- let ca: &mut ChunkedArray<Float32Type> = mutable_s.as_mut();
85
- let values = values.f32()?;
86
- std::mem::take(ca).scatter(idx, values)
87
- }
88
- DataType::Float64 => {
89
- let ca: &mut ChunkedArray<Float64Type> = mutable_s.as_mut();
90
- let values = values.f64()?;
91
- std::mem::take(ca).scatter(idx, values)
137
+ match mutable_s.dtype() {
138
+ dt if dt.is_primitive_numeric() => {
139
+ with_match_physical_numeric_polars_type!(dt, |$T| {
140
+ let ca: &mut ChunkedArray<$T> = mutable_s.as_mut();
141
+ let values: &ChunkedArray<$T> = values.as_ref().as_ref();
142
+ ca.scatter(idx, values.iter())
143
+ })
92
144
  }
93
145
  DataType::Boolean => {
94
- let ca = s.bool()?;
146
+ let ca: &mut ChunkedArray<BooleanType> = mutable_s.as_mut();
95
147
  let values = values.bool()?;
96
- ca.scatter(idx, values)
148
+ ca.scatter(idx, values.iter())
149
+ }
150
+ DataType::Binary => {
151
+ let ca: &mut ChunkedArray<BinaryType> = mutable_s.as_mut();
152
+ let values = values.binary()?;
153
+ ca.scatter(idx, values.iter())
97
154
  }
98
155
  DataType::String => {
99
- let ca = s.str()?;
156
+ let ca: &mut ChunkedArray<StringType> = mutable_s.as_mut();
100
157
  let values = values.str()?;
101
- ca.scatter(idx, values)
158
+ ca.scatter(idx, values.iter())
102
159
  }
103
- _ => panic!("not yet implemented for dtype: {}", logical_dtype),
104
- };
105
-
106
- s.and_then(|s| s.cast(&logical_dtype))
160
+ _ => Err(PolarsError::ComputeError(
161
+ format!("not yet implemented for dtype: {logical_dtype}").into(),
162
+ )),
163
+ }
107
164
  }
@@ -1,13 +1,20 @@
1
+ use parking_lot::RwLock;
1
2
  use polars::sql::SQLContext;
2
- use std::cell::RefCell;
3
3
 
4
4
  use crate::{RbLazyFrame, RbPolarsErr, RbResult};
5
5
 
6
6
  #[magnus::wrap(class = "Polars::RbSQLContext")]
7
7
  #[repr(transparent)]
8
- #[derive(Clone)]
9
8
  pub struct RbSQLContext {
10
- pub context: RefCell<SQLContext>,
9
+ pub context: RwLock<SQLContext>,
10
+ }
11
+
12
+ impl Clone for RbSQLContext {
13
+ fn clone(&self) -> Self {
14
+ Self {
15
+ context: RwLock::new(self.context.read().clone()),
16
+ }
17
+ }
11
18
  }
12
19
 
13
20
  #[allow(
@@ -19,30 +26,30 @@ impl RbSQLContext {
19
26
  #[allow(clippy::new_without_default)]
20
27
  pub fn new() -> RbSQLContext {
21
28
  RbSQLContext {
22
- context: SQLContext::new().into(),
29
+ context: RwLock::new(SQLContext::new()),
23
30
  }
24
31
  }
25
32
 
26
33
  pub fn execute(&self, query: String) -> RbResult<RbLazyFrame> {
27
34
  Ok(self
28
35
  .context
29
- .borrow_mut()
36
+ .write()
30
37
  .execute(&query)
31
38
  .map_err(RbPolarsErr::from)?
32
39
  .into())
33
40
  }
34
41
 
35
42
  pub fn get_tables(&self) -> RbResult<Vec<String>> {
36
- Ok(self.context.borrow().get_tables())
43
+ Ok(self.context.read().get_tables())
37
44
  }
38
45
 
39
46
  pub fn register(&self, name: String, lf: &RbLazyFrame) {
40
47
  self.context
41
- .borrow_mut()
42
- .register(&name, lf.ldf.borrow().clone())
48
+ .write()
49
+ .register(&name, lf.clone().ldf.into_inner())
43
50
  }
44
51
 
45
52
  pub fn unregister(&self, name: String) {
46
- self.context.borrow_mut().unregister(&name)
53
+ self.context.write().unregister(&name)
47
54
  }
48
55
  }
@@ -0,0 +1,31 @@
1
+ use polars_testing::asserts::{DataFrameEqualOptions, assert_dataframe_equal};
2
+
3
+ use crate::error::RbPolarsErr;
4
+ use crate::{RbDataFrame, RbResult};
5
+
6
+ pub fn assert_dataframe_equal_rb(
7
+ left: &RbDataFrame,
8
+ right: &RbDataFrame,
9
+ check_row_order: bool,
10
+ check_column_order: bool,
11
+ check_dtypes: bool,
12
+ check_exact: bool,
13
+ rel_tol: f64,
14
+ abs_tol: f64,
15
+ categorical_as_str: bool,
16
+ ) -> RbResult<()> {
17
+ let left_df = &left.df.read();
18
+ let right_df = &right.df.read();
19
+
20
+ let options = DataFrameEqualOptions {
21
+ check_row_order,
22
+ check_column_order,
23
+ check_dtypes,
24
+ check_exact,
25
+ rel_tol,
26
+ abs_tol,
27
+ categorical_as_str,
28
+ };
29
+
30
+ assert_dataframe_equal(left_df, right_df, options).map_err(|e| RbPolarsErr::from(e).into())
31
+ }
@@ -0,0 +1,5 @@
1
+ mod frame;
2
+ mod series;
3
+
4
+ pub use frame::*;
5
+ pub use series::*;
@@ -0,0 +1,31 @@
1
+ use polars_testing::asserts::{SeriesEqualOptions, assert_series_equal};
2
+
3
+ use crate::error::RbPolarsErr;
4
+ use crate::{RbResult, RbSeries};
5
+
6
+ pub fn assert_series_equal_rb(
7
+ left: &RbSeries,
8
+ right: &RbSeries,
9
+ check_dtypes: bool,
10
+ check_names: bool,
11
+ check_order: bool,
12
+ check_exact: bool,
13
+ rel_tol: f64,
14
+ abs_tol: f64,
15
+ categorical_as_str: bool,
16
+ ) -> RbResult<()> {
17
+ let left_series = &left.series.read();
18
+ let right_series = &right.series.read();
19
+
20
+ let options = SeriesEqualOptions {
21
+ check_dtypes,
22
+ check_names,
23
+ check_order,
24
+ check_exact,
25
+ rel_tol,
26
+ abs_tol,
27
+ categorical_as_str,
28
+ };
29
+
30
+ assert_series_equal(left_series, right_series, options).map_err(|e| RbPolarsErr::from(e).into())
31
+ }