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,52 +1,90 @@
1
- use magnus::{r_hash::ForEach, typed_data::Obj, IntoValue, RArray, RHash, TryConvert, Value};
2
- use polars::io::{HiveOptions, RowIndex};
1
+ use arrow::ffi::export_iterator;
2
+ use magnus::{
3
+ IntoValue, RArray, RHash, Ruby, TryConvert, Value, r_hash::ForEach, value::ReprValue,
4
+ };
5
+ use parking_lot::Mutex;
6
+ use polars::frame::PivotColumnNaming;
7
+ use polars::io::RowIndex;
3
8
  use polars::lazy::frame::LazyFrame;
4
9
  use polars::prelude::*;
5
- use polars_plan::plans::ScanSources;
6
- use std::cell::RefCell;
7
- use std::io::BufWriter;
10
+ use polars_core::query_result::QueryResult;
11
+ use polars_plan::dsl::ScanSources;
12
+ use polars_plan::plans::{HintIR, Sorted};
8
13
  use std::num::NonZeroUsize;
9
- use std::path::PathBuf;
10
14
 
15
+ use super::{RbLazyFrame, RbOptFlags};
11
16
  use crate::conversion::*;
12
- use crate::expr::rb_exprs_to_exprs;
13
- use crate::file::get_file_like;
14
- use crate::{RbDataFrame, RbExpr, RbLazyFrame, RbLazyGroupBy, RbPolarsErr, RbResult, RbValueError};
15
-
16
- fn rbobject_to_first_path_and_scan_sources(obj: Value) -> RbResult<(Option<PathBuf>, ScanSources)> {
17
- use crate::file::{get_ruby_scan_source_input, RubyScanSourceInput};
17
+ use crate::expr::ToExprs;
18
+ use crate::expr::selector::RbSelector;
19
+ use crate::io::cloud_options::OptRbCloudOptions;
20
+ use crate::io::scan_options::RbScanOptions;
21
+ use crate::io::sink_options::RbSinkOptions;
22
+ use crate::io::sink_output::RbFileSinkDestination;
23
+ use crate::ruby::gvl::GvlExt;
24
+ use crate::ruby::lazy::RubyUdfLazyFrameExt;
25
+ use crate::ruby::plan_callback::PlanCallbackExt;
26
+ use crate::ruby::ruby_function::RubyObject;
27
+ use crate::ruby::utils::TryIntoValue;
28
+ use crate::utils::{EnterPolarsExt, to_rb_err};
29
+ use crate::{
30
+ RbArrowArrayStream, RbDataFrame, RbExpr, RbLazyGroupBy, RbPolarsErr, RbResult, RbTypeError,
31
+ RbValueError,
32
+ };
33
+
34
+ fn rbobject_to_first_path_and_scan_sources(
35
+ obj: Value,
36
+ ) -> RbResult<(Option<PlRefPath>, ScanSources)> {
37
+ use crate::file::{RubyScanSourceInput, get_ruby_scan_source_input};
18
38
  Ok(match get_ruby_scan_source_input(obj, false)? {
19
- RubyScanSourceInput::Path(path) => (Some(path.clone()), ScanSources::Paths([path].into())),
39
+ RubyScanSourceInput::Path(path) => (
40
+ Some(path.clone()),
41
+ ScanSources::Paths(FromIterator::from_iter([path])),
42
+ ),
20
43
  RubyScanSourceInput::File(file) => (None, ScanSources::Files([file].into())),
21
44
  RubyScanSourceInput::Buffer(buff) => (None, ScanSources::Buffers([buff].into())),
22
45
  })
23
46
  }
24
47
 
25
48
  impl RbLazyFrame {
26
- #[allow(clippy::too_many_arguments)]
27
- pub fn new_from_ndjson(
28
- source: Option<Value>,
29
- sources: Wrap<ScanSources>,
30
- infer_schema_length: Option<usize>,
31
- batch_size: Option<Wrap<NonZeroUsize>>,
32
- n_rows: Option<usize>,
33
- low_memory: bool,
34
- rechunk: bool,
35
- row_index: Option<(String, IdxSize)>,
36
- ) -> RbResult<Self> {
37
- let batch_size = batch_size.map(|v| v.0);
49
+ pub fn new_from_ndjson(arguments: &[Value]) -> RbResult<Self> {
50
+ let source = Option::<Value>::try_convert(arguments[0])?;
51
+ let sources = Wrap::<ScanSources>::try_convert(arguments[1])?;
52
+ let infer_schema_length = Option::<usize>::try_convert(arguments[2])?;
53
+ let schema = Option::<Wrap<Schema>>::try_convert(arguments[3])?;
54
+ let schema_overrides = Option::<Wrap<Schema>>::try_convert(arguments[4])?;
55
+ let batch_size = Option::<NonZeroUsize>::try_convert(arguments[5])?;
56
+ let n_rows = Option::<usize>::try_convert(arguments[6])?;
57
+ let low_memory = bool::try_convert(arguments[7])?;
58
+ let rechunk = bool::try_convert(arguments[8])?;
59
+ let row_index = Option::<(String, IdxSize)>::try_convert(arguments[9])?;
60
+ let ignore_errors = bool::try_convert(arguments[10])?;
61
+ let include_file_paths = Option::<String>::try_convert(arguments[11])?;
62
+ let cloud_options = OptRbCloudOptions::try_convert(arguments[12])?;
63
+ let credential_provider = Option::<Value>::try_convert(arguments[13])?;
64
+
38
65
  let row_index = row_index.map(|(name, offset)| RowIndex {
39
66
  name: name.into(),
40
67
  offset,
41
68
  });
42
69
 
43
70
  let sources = sources.0;
44
- let (_first_path, sources) = match source {
45
- None => (sources.first_path().map(|p| p.to_path_buf()), sources),
71
+ let (first_path, sources) = match source {
72
+ None => (sources.first_path().cloned(), sources),
46
73
  Some(source) => rbobject_to_first_path_and_scan_sources(source)?,
47
74
  };
48
75
 
49
- let r = LazyJsonLineReader::new_with_sources(sources);
76
+ let mut r = LazyJsonLineReader::new_with_sources(sources);
77
+
78
+ if let Some(first_path) = first_path {
79
+ let first_path_url = first_path.as_str();
80
+
81
+ let cloud_options = cloud_options.extract_opt_cloud_options(
82
+ CloudScheme::from_path(first_path_url),
83
+ credential_provider,
84
+ )?;
85
+
86
+ r = r.with_cloud_options(cloud_options);
87
+ };
50
88
 
51
89
  let lf = r
52
90
  .with_infer_schema_length(infer_schema_length.and_then(NonZeroUsize::new))
@@ -54,11 +92,11 @@ impl RbLazyFrame {
54
92
  .with_n_rows(n_rows)
55
93
  .low_memory(low_memory)
56
94
  .with_rechunk(rechunk)
57
- // .with_schema(schema.map(|schema| Arc::new(schema.0)))
58
- // .with_schema_overwrite(schema_overrides.map(|x| Arc::new(x.0)))
95
+ .with_schema(schema.map(|schema| Arc::new(schema.0)))
96
+ .with_schema_overwrite(schema_overrides.map(|x| Arc::new(x.0)))
59
97
  .with_row_index(row_index)
60
- // .with_ignore_errors(ignore_errors)
61
- // .with_include_file_paths(include_file_paths.map(|x| x.into()))
98
+ .with_ignore_errors(ignore_errors)
99
+ .with_include_file_paths(include_file_paths.map(|x| x.into()))
62
100
  .finish()
63
101
  .map_err(RbPolarsErr::from)?;
64
102
 
@@ -69,27 +107,37 @@ impl RbLazyFrame {
69
107
  // start arguments
70
108
  // this pattern is needed for more than 16
71
109
  let source = Option::<Value>::try_convert(arguments[0])?;
72
- let sources = Wrap::<ScanSources>::try_convert(arguments[21])?;
73
- let separator = String::try_convert(arguments[1])?;
74
- let has_header = bool::try_convert(arguments[2])?;
75
- let ignore_errors = bool::try_convert(arguments[3])?;
76
- let skip_rows = usize::try_convert(arguments[4])?;
77
- let n_rows = Option::<usize>::try_convert(arguments[5])?;
78
- let cache = bool::try_convert(arguments[6])?;
79
- let overwrite_dtype = Option::<Vec<(String, Wrap<DataType>)>>::try_convert(arguments[7])?;
80
- let low_memory = bool::try_convert(arguments[8])?;
81
- let comment_prefix = Option::<String>::try_convert(arguments[9])?;
82
- let quote_char = Option::<String>::try_convert(arguments[10])?;
83
- let null_values = Option::<Wrap<NullValues>>::try_convert(arguments[11])?;
84
- let infer_schema_length = Option::<usize>::try_convert(arguments[12])?;
85
- let with_schema_modify = Option::<Value>::try_convert(arguments[13])?;
86
- let rechunk = bool::try_convert(arguments[14])?;
87
- let skip_rows_after_header = usize::try_convert(arguments[15])?;
88
- let encoding = Wrap::<CsvEncoding>::try_convert(arguments[16])?;
89
- let row_index = Option::<(String, IdxSize)>::try_convert(arguments[17])?;
90
- let try_parse_dates = bool::try_convert(arguments[18])?;
91
- let eol_char = String::try_convert(arguments[19])?;
92
- let truncate_ragged_lines = bool::try_convert(arguments[20])?;
110
+ let sources = Wrap::<ScanSources>::try_convert(arguments[1])?;
111
+ let separator = String::try_convert(arguments[2])?;
112
+ let has_header = bool::try_convert(arguments[3])?;
113
+ let ignore_errors = bool::try_convert(arguments[4])?;
114
+ let skip_rows = usize::try_convert(arguments[5])?;
115
+ let skip_lines = usize::try_convert(arguments[6])?;
116
+ let n_rows = Option::<usize>::try_convert(arguments[7])?;
117
+ let cache = bool::try_convert(arguments[8])?;
118
+ let overwrite_dtype = Option::<Vec<(String, Wrap<DataType>)>>::try_convert(arguments[9])?;
119
+ let low_memory = bool::try_convert(arguments[10])?;
120
+ let comment_prefix = Option::<String>::try_convert(arguments[11])?;
121
+ let quote_char = Option::<String>::try_convert(arguments[12])?;
122
+ let null_values = Option::<Wrap<NullValues>>::try_convert(arguments[13])?;
123
+ let missing_utf8_is_empty_string = bool::try_convert(arguments[14])?;
124
+ let infer_schema_length = Option::<usize>::try_convert(arguments[15])?;
125
+ let with_schema_modify = Option::<Value>::try_convert(arguments[16])?;
126
+ let rechunk = bool::try_convert(arguments[17])?;
127
+ let skip_rows_after_header = usize::try_convert(arguments[18])?;
128
+ let encoding = Wrap::<CsvEncoding>::try_convert(arguments[19])?;
129
+ let row_index = Option::<(String, IdxSize)>::try_convert(arguments[20])?;
130
+ let try_parse_dates = bool::try_convert(arguments[21])?;
131
+ let eol_char = String::try_convert(arguments[22])?;
132
+ let raise_if_empty = bool::try_convert(arguments[23])?;
133
+ let truncate_ragged_lines = bool::try_convert(arguments[24])?;
134
+ let decimal_comma = bool::try_convert(arguments[25])?;
135
+ let glob = bool::try_convert(arguments[26])?;
136
+ let schema = Option::<Wrap<Schema>>::try_convert(arguments[27])?;
137
+ let cloud_options = OptRbCloudOptions::try_convert(arguments[28])?;
138
+ let credential_provider = Option::<Value>::try_convert(arguments[29])?;
139
+ let include_file_paths = Option::<String>::try_convert(arguments[30])?;
140
+ let missing_columns = Option::<Wrap<MissingColumnsPolicy>>::try_convert(arguments[31])?;
93
141
  // end arguments
94
142
 
95
143
  let null_values = null_values.map(|w| w.0);
@@ -109,23 +157,33 @@ impl RbLazyFrame {
109
157
  });
110
158
 
111
159
  let sources = sources.0;
112
- let (_first_path, sources) = match source {
113
- None => (sources.first_path().map(|p| p.to_path_buf()), sources),
160
+ let (first_path, sources) = match source {
161
+ None => (sources.first_path().cloned(), sources),
114
162
  Some(source) => rbobject_to_first_path_and_scan_sources(source)?,
115
163
  };
116
164
 
117
- let r = LazyCsvReader::new_with_sources(sources);
165
+ let mut r = LazyCsvReader::new_with_sources(sources);
118
166
 
119
- let r = r
167
+ if let Some(first_path) = first_path {
168
+ let first_path_url = first_path.as_str();
169
+ let cloud_options = cloud_options.extract_opt_cloud_options(
170
+ CloudScheme::from_path(first_path_url),
171
+ credential_provider,
172
+ )?;
173
+ r = r.with_cloud_options(cloud_options);
174
+ }
175
+
176
+ let mut r = r
120
177
  .with_infer_schema_length(infer_schema_length)
121
178
  .with_separator(separator)
122
179
  .with_has_header(has_header)
123
180
  .with_ignore_errors(ignore_errors)
124
181
  .with_skip_rows(skip_rows)
182
+ .with_skip_lines(skip_lines)
125
183
  .with_n_rows(n_rows)
126
184
  .with_cache(cache)
127
185
  .with_dtype_overwrite(overwrite_dtype.map(Arc::new))
128
- // TODO add with_schema
186
+ .with_schema(schema.map(|schema| Arc::new(schema.0)))
129
187
  .with_low_memory(low_memory)
130
188
  .with_comment_prefix(comment_prefix.map(|x| x.into()))
131
189
  .with_quote_char(quote_char)
@@ -136,182 +194,136 @@ impl RbLazyFrame {
136
194
  .with_row_index(row_index)
137
195
  .with_try_parse_dates(try_parse_dates)
138
196
  .with_null_values(null_values)
139
- // TODO add with_missing_is_null
140
- .with_truncate_ragged_lines(truncate_ragged_lines);
141
-
142
- if let Some(_lambda) = with_schema_modify {
143
- todo!();
197
+ .with_missing_is_null(!missing_utf8_is_empty_string)
198
+ .with_truncate_ragged_lines(truncate_ragged_lines)
199
+ .with_decimal_comma(decimal_comma)
200
+ .with_glob(glob)
201
+ .with_raise_if_empty(raise_if_empty)
202
+ .with_include_file_paths(include_file_paths.map(|x| x.into()))
203
+ .with_missing_columns_policy(missing_columns.map(|x| x.0));
204
+
205
+ if let Some(lambda) = with_schema_modify {
206
+ let f = |schema: Schema| {
207
+ let iter = schema.iter_names().map(|s| s.as_str());
208
+ Ruby::attach(|rb| {
209
+ let names = rb.ary_from_iter(iter);
210
+
211
+ let out = lambda
212
+ .funcall("call", (names,))
213
+ .expect("ruby function failed");
214
+ let new_names = Vec::<String>::try_convert(out)
215
+ .expect("ruby function should return Array[String]");
216
+ polars_ensure!(new_names.len() == schema.len(),
217
+ ShapeMismatch: "The length of the new names list should be equal to or less than the original column length",
218
+ );
219
+ Ok(schema
220
+ .iter_values()
221
+ .zip(new_names)
222
+ .map(|(dtype, name)| Field::new(name.into(), dtype.clone()))
223
+ .collect())
224
+ })
225
+ };
226
+ r = r.with_schema_modify(f).map_err(RbPolarsErr::from)?
144
227
  }
145
228
 
146
229
  Ok(r.finish().map_err(RbPolarsErr::from)?.into())
147
230
  }
148
231
 
149
- pub fn new_from_parquet(arguments: &[Value]) -> RbResult<Self> {
150
- let source = Option::<Value>::try_convert(arguments[0])?;
151
- let sources = Wrap::<ScanSources>::try_convert(arguments[1])?;
152
- let n_rows = Option::<usize>::try_convert(arguments[2])?;
153
- let cache = bool::try_convert(arguments[3])?;
154
- let parallel = Wrap::<ParallelStrategy>::try_convert(arguments[4])?;
155
- let rechunk = bool::try_convert(arguments[5])?;
156
- let row_index = Option::<(String, IdxSize)>::try_convert(arguments[6])?;
157
- let low_memory = bool::try_convert(arguments[7])?;
158
- let cloud_options = Option::<Vec<(String, String)>>::try_convert(arguments[8])?;
159
- let _credential_provider = Option::<Value>::try_convert(arguments[9])?;
160
- let use_statistics = bool::try_convert(arguments[10])?;
161
- let hive_partitioning = Option::<bool>::try_convert(arguments[11])?;
162
- let schema = Option::<Wrap<Schema>>::try_convert(arguments[12])?;
163
- let hive_schema = Option::<Wrap<Schema>>::try_convert(arguments[13])?;
164
- let try_parse_hive_dates = bool::try_convert(arguments[14])?;
165
- let retries = usize::try_convert(arguments[15])?;
166
- let glob = bool::try_convert(arguments[16])?;
167
- let include_file_paths = Option::<String>::try_convert(arguments[17])?;
168
- let allow_missing_columns = bool::try_convert(arguments[18])?;
232
+ pub fn new_from_parquet(
233
+ sources: Wrap<ScanSources>,
234
+ schema: Option<Wrap<Schema>>,
235
+ scan_options: RbScanOptions,
236
+ parallel: Wrap<ParallelStrategy>,
237
+ low_memory: bool,
238
+ use_statistics: bool,
239
+ ) -> RbResult<Self> {
240
+ use crate::utils::to_rb_err;
169
241
 
170
242
  let parallel = parallel.0;
171
- let hive_schema = hive_schema.map(|s| Arc::new(s.0));
172
-
173
- let row_index = row_index.map(|(name, offset)| RowIndex {
174
- name: name.into(),
175
- offset,
176
- });
177
-
178
- let hive_options = HiveOptions {
179
- enabled: hive_partitioning,
180
- hive_start_idx: 0,
181
- schema: hive_schema,
182
- try_parse_dates: try_parse_hive_dates,
183
- };
184
243
 
185
- let mut args = ScanArgsParquet {
186
- n_rows,
187
- cache,
244
+ let options = ParquetOptions {
245
+ schema: schema.map(|x| Arc::new(x.0)),
188
246
  parallel,
189
- rechunk,
190
- row_index,
191
247
  low_memory,
192
- cloud_options: None,
193
248
  use_statistics,
194
- schema: schema.map(|x| Arc::new(x.0)),
195
- hive_options,
196
- glob,
197
- include_file_paths: include_file_paths.map(|x| x.into()),
198
- allow_missing_columns,
199
249
  };
200
250
 
201
251
  let sources = sources.0;
202
- let (first_path, sources) = match source {
203
- None => (sources.first_path().map(|p| p.to_path_buf()), sources),
204
- Some(source) => rbobject_to_first_path_and_scan_sources(source)?,
205
- };
252
+ let first_path = sources.first_path();
206
253
 
207
- if let Some(first_path) = first_path {
208
- let first_path_url = first_path.to_string_lossy();
209
- let cloud_options =
210
- parse_cloud_options(&first_path_url, cloud_options.unwrap_or_default())?;
211
- args.cloud_options = Some(cloud_options.with_max_retries(retries));
212
- }
254
+ let unified_scan_args =
255
+ scan_options.extract_unified_scan_args(first_path.and_then(|x| x.scheme()))?;
213
256
 
214
- let lf = LazyFrame::scan_parquet_sources(sources, args).map_err(RbPolarsErr::from)?;
257
+ let lf: LazyFrame = DslBuilder::scan_parquet(sources, options, unified_scan_args)
258
+ .map_err(to_rb_err)?
259
+ .build()
260
+ .into();
215
261
 
216
262
  Ok(lf.into())
217
263
  }
218
264
 
219
- #[allow(clippy::too_many_arguments)]
220
265
  pub fn new_from_ipc(
221
- source: Option<Value>,
222
266
  sources: Wrap<ScanSources>,
223
- n_rows: Option<usize>,
224
- cache: bool,
225
- rechunk: bool,
226
- row_index: Option<(String, IdxSize)>,
227
- hive_partitioning: Option<bool>,
228
- hive_schema: Option<Wrap<Schema>>,
229
- try_parse_hive_dates: bool,
230
- include_file_paths: Option<String>,
267
+ record_batch_statistics: bool,
268
+ scan_options: RbScanOptions,
231
269
  ) -> RbResult<Self> {
232
- let row_index = row_index.map(|(name, offset)| RowIndex {
233
- name: name.into(),
234
- offset,
235
- });
236
-
237
- let hive_options = HiveOptions {
238
- enabled: hive_partitioning,
239
- hive_start_idx: 0,
240
- schema: hive_schema.map(|x| Arc::new(x.0)),
241
- try_parse_dates: try_parse_hive_dates,
270
+ let options = IpcScanOptions {
271
+ record_batch_statistics,
272
+ checked: Default::default(),
242
273
  };
243
274
 
244
- let args = ScanArgsIpc {
245
- n_rows,
246
- cache,
247
- rechunk,
248
- row_index,
249
- cloud_options: None,
250
- hive_options,
251
- include_file_paths: include_file_paths.map(|x| x.into()),
252
- };
275
+ let sources = sources.0;
276
+ let first_path = sources.first_path().cloned();
277
+
278
+ let unified_scan_args =
279
+ scan_options.extract_unified_scan_args(first_path.as_ref().and_then(|x| x.scheme()))?;
280
+
281
+ let lf = LazyFrame::scan_ipc_sources(sources, options, unified_scan_args)
282
+ .map_err(RbPolarsErr::from)?;
283
+ Ok(lf.into())
284
+ }
253
285
 
286
+ pub fn new_from_scan_lines(
287
+ sources: Wrap<ScanSources>,
288
+ scan_options: RbScanOptions,
289
+ name: String,
290
+ ) -> RbResult<Self> {
254
291
  let sources = sources.0;
255
- let (_first_path, sources) = match source {
256
- None => (sources.first_path().map(|p| p.to_path_buf()), sources),
257
- Some(source) => rbobject_to_first_path_and_scan_sources(source)?,
258
- };
292
+ let first_path = sources.first_path();
293
+
294
+ let unified_scan_args =
295
+ scan_options.extract_unified_scan_args(first_path.and_then(|x| x.scheme()))?;
296
+
297
+ let dsl: DslPlan = DslBuilder::scan_lines(sources, unified_scan_args, (&*name).into())
298
+ .map_err(to_rb_err)?
299
+ .build();
300
+ let lf: LazyFrame = dsl.into();
259
301
 
260
- let lf = LazyFrame::scan_ipc_sources(sources, args).map_err(RbPolarsErr::from)?;
261
302
  Ok(lf.into())
262
303
  }
263
304
 
264
- pub fn write_json(&self, rb_f: Value) -> RbResult<()> {
265
- let file = BufWriter::new(get_file_like(rb_f, true)?);
266
- serde_json::to_writer(file, &self.ldf.borrow().logical_plan)
267
- .map_err(|err| RbValueError::new_err(format!("{:?}", err)))?;
268
- Ok(())
305
+ pub fn describe_plan(rb: &Ruby, self_: &Self) -> RbResult<String> {
306
+ rb.enter_polars(|| self_.ldf.read().describe_plan())
269
307
  }
270
308
 
271
- pub fn describe_plan(&self) -> RbResult<String> {
272
- self.ldf
273
- .borrow()
274
- .describe_plan()
275
- .map_err(RbPolarsErr::from)
276
- .map_err(Into::into)
309
+ pub fn describe_optimized_plan(rb: &Ruby, self_: &Self) -> RbResult<String> {
310
+ rb.enter_polars(|| self_.ldf.read().describe_optimized_plan())
277
311
  }
278
312
 
279
- pub fn describe_optimized_plan(&self) -> RbResult<String> {
280
- let result = self
281
- .ldf
282
- .borrow()
283
- .describe_optimized_plan()
284
- .map_err(RbPolarsErr::from)?;
285
- Ok(result)
313
+ pub fn describe_plan_tree(rb: &Ruby, self_: &Self) -> RbResult<String> {
314
+ rb.enter_polars(|| self_.ldf.read().describe_plan_tree())
286
315
  }
287
316
 
288
- #[allow(clippy::too_many_arguments)]
289
- pub fn optimization_toggle(
290
- &self,
291
- type_coercion: bool,
292
- predicate_pushdown: bool,
293
- projection_pushdown: bool,
294
- simplify_expr: bool,
295
- slice_pushdown: bool,
296
- comm_subplan_elim: bool,
297
- comm_subexpr_elim: bool,
298
- allow_streaming: bool,
299
- _eager: bool,
300
- ) -> RbLazyFrame {
301
- let ldf = self.ldf.borrow().clone();
302
- let mut ldf = ldf
303
- .with_type_coercion(type_coercion)
304
- .with_predicate_pushdown(predicate_pushdown)
305
- .with_simplify_expr(simplify_expr)
306
- .with_slice_pushdown(slice_pushdown)
307
- .with_streaming(allow_streaming)
308
- ._with_eager(_eager)
309
- .with_projection_pushdown(projection_pushdown);
310
-
311
- ldf = ldf.with_comm_subplan_elim(comm_subplan_elim);
312
- ldf = ldf.with_comm_subexpr_elim(comm_subexpr_elim);
313
-
314
- ldf.into()
317
+ pub fn describe_optimized_plan_tree(rb: &Ruby, self_: &Self) -> RbResult<String> {
318
+ rb.enter_polars(|| self_.ldf.read().describe_optimized_plan_tree())
319
+ }
320
+
321
+ pub fn to_dot(rb: &Ruby, self_: &Self, optimized: bool) -> RbResult<String> {
322
+ rb.enter_polars(|| self_.ldf.read().to_dot(optimized))
323
+ }
324
+
325
+ pub fn to_dot_streaming_phys(rb: &Ruby, self_: &Self, optimized: bool) -> RbResult<String> {
326
+ rb.enter_polars(|| self_.ldf.read().to_dot_streaming_phys(optimized))
315
327
  }
316
328
 
317
329
  pub fn sort(
@@ -322,7 +334,7 @@ impl RbLazyFrame {
322
334
  maintain_order: bool,
323
335
  multithreaded: bool,
324
336
  ) -> Self {
325
- let ldf = self.ldf.borrow().clone();
337
+ let ldf = self.ldf.read().clone();
326
338
  ldf.sort(
327
339
  [&by_column],
328
340
  SortMultipleOptions {
@@ -330,6 +342,7 @@ impl RbLazyFrame {
330
342
  nulls_last: vec![nulls_last],
331
343
  multithreaded,
332
344
  maintain_order,
345
+ limit: None,
333
346
  },
334
347
  )
335
348
  .into()
@@ -343,8 +356,8 @@ impl RbLazyFrame {
343
356
  maintain_order: bool,
344
357
  multithreaded: bool,
345
358
  ) -> RbResult<Self> {
346
- let ldf = self.ldf.borrow().clone();
347
- let exprs = rb_exprs_to_exprs(by)?;
359
+ let ldf = self.ldf.read().clone();
360
+ let exprs = by.to_exprs()?;
348
361
  Ok(ldf
349
362
  .sort_by_exprs(
350
363
  exprs,
@@ -353,33 +366,101 @@ impl RbLazyFrame {
353
366
  nulls_last,
354
367
  maintain_order,
355
368
  multithreaded,
369
+ limit: None,
356
370
  },
357
371
  )
358
372
  .into())
359
373
  }
360
374
 
375
+ pub fn top_k(&self, k: IdxSize, by: RArray, reverse: Vec<bool>) -> RbResult<Self> {
376
+ let ldf = self.ldf.read().clone();
377
+ let exprs = by.to_exprs()?;
378
+ Ok(ldf
379
+ .top_k(
380
+ k,
381
+ exprs,
382
+ SortMultipleOptions::new().with_order_descending_multi(reverse),
383
+ )
384
+ .into())
385
+ }
386
+
387
+ pub fn bottom_k(&self, k: IdxSize, by: RArray, reverse: Vec<bool>) -> RbResult<Self> {
388
+ let ldf = self.ldf.read().clone();
389
+ let exprs = by.to_exprs()?;
390
+ Ok(ldf
391
+ .bottom_k(
392
+ k,
393
+ exprs,
394
+ SortMultipleOptions::new().with_order_descending_multi(reverse),
395
+ )
396
+ .into())
397
+ }
398
+
361
399
  pub fn cache(&self) -> Self {
362
- let ldf = self.ldf.borrow().clone();
400
+ let ldf = self.ldf.read().clone();
363
401
  ldf.cache().into()
364
402
  }
365
403
 
366
- pub fn collect(&self) -> RbResult<RbDataFrame> {
367
- let ldf = self.ldf.borrow().clone();
368
- let df = ldf.collect().map_err(RbPolarsErr::from)?;
369
- Ok(df.into())
404
+ pub fn with_optimizations(&self, optflags: &RbOptFlags) -> Self {
405
+ let ldf = self.ldf.read().clone();
406
+ ldf.with_optimizations(optflags.clone().inner.into_inner())
407
+ .into()
408
+ }
409
+
410
+ pub fn profile(rb: &Ruby, self_: &Self) -> RbResult<(RbDataFrame, RbDataFrame)> {
411
+ let (df, time_df) = rb.enter_polars(|| {
412
+ let ldf = self_.ldf.read().clone();
413
+ ldf.profile()
414
+ })?;
415
+ Ok((df.into(), time_df.into()))
416
+ }
417
+
418
+ pub fn collect(rb: &Ruby, self_: &Self, engine: Wrap<Engine>) -> RbResult<RbDataFrame> {
419
+ rb.enter_polars_df(|| {
420
+ let ldf = self_.ldf.read().clone();
421
+ ldf.collect_with_engine(engine.0).map(|r| match r {
422
+ QueryResult::Single(df) => df,
423
+ // TODO: Should return query results
424
+ QueryResult::Multiple(_) => DataFrame::empty(),
425
+ })
426
+ })
427
+ }
428
+
429
+ pub fn collect_batches(
430
+ rb: &Ruby,
431
+ self_: &Self,
432
+ engine: Wrap<Engine>,
433
+ maintain_order: bool,
434
+ chunk_size: Option<NonZeroUsize>,
435
+ lazy: bool,
436
+ ) -> RbResult<RbCollectBatches> {
437
+ rb.enter_polars(|| {
438
+ let ldf = self_.ldf.read().clone();
439
+
440
+ let collect_batches =
441
+ ldf.clone()
442
+ .collect_batches(engine.0, maintain_order, chunk_size, lazy)?;
443
+
444
+ PolarsResult::Ok(RbCollectBatches {
445
+ inner: Arc::new(Mutex::new(collect_batches)),
446
+ ldf,
447
+ })
448
+ })
370
449
  }
371
450
 
372
- #[allow(clippy::too_many_arguments)]
373
451
  pub fn sink_parquet(
374
- &self,
375
- path: PathBuf,
452
+ rb: &Ruby,
453
+ self_: &Self,
454
+ target: RbFileSinkDestination,
455
+ sink_options: RbSinkOptions,
376
456
  compression: String,
377
457
  compression_level: Option<i32>,
378
458
  statistics: Wrap<StatisticsOptions>,
379
459
  row_group_size: Option<usize>,
380
460
  data_page_size: Option<usize>,
381
- maintain_order: bool,
382
- ) -> RbResult<()> {
461
+ metadata: Wrap<Option<KeyValueMetadata>>,
462
+ arrow_schema: Option<Wrap<ArrowSchema>>,
463
+ ) -> RbResult<RbLazyFrame> {
383
464
  let compression = parse_parquet_compression(&compression, compression_level)?;
384
465
 
385
466
  let options = ParquetWriteOptions {
@@ -387,120 +468,190 @@ impl RbLazyFrame {
387
468
  statistics: statistics.0,
388
469
  row_group_size,
389
470
  data_page_size,
390
- maintain_order,
471
+ key_value_metadata: metadata.0,
472
+ arrow_schema: arrow_schema.map(|x| Arc::new(x.0)),
473
+ compat_level: None,
391
474
  };
392
475
 
393
- let ldf = self.ldf.borrow().clone();
394
- ldf.sink_parquet(path, options).map_err(RbPolarsErr::from)?;
395
- Ok(())
476
+ let target = target.extract_file_sink_destination()?;
477
+ let unified_sink_args = sink_options.extract_unified_sink_args(target.cloud_scheme())?;
478
+
479
+ rb.enter_polars(|| {
480
+ self_.ldf.read().clone().sink(
481
+ target,
482
+ FileWriteFormat::Parquet(Arc::new(options)),
483
+ unified_sink_args,
484
+ )
485
+ })
486
+ .map(Into::into)
396
487
  }
397
488
 
398
489
  pub fn sink_ipc(
399
- &self,
400
- path: PathBuf,
401
- compression: Option<Wrap<IpcCompression>>,
402
- maintain_order: bool,
403
- ) -> RbResult<()> {
490
+ rb: &Ruby,
491
+ self_: &Self,
492
+ target: RbFileSinkDestination,
493
+ sink_options: RbSinkOptions,
494
+ compression: Wrap<Option<IpcCompression>>,
495
+ compat_level: RbCompatLevel,
496
+ record_batch_size: Option<usize>,
497
+ record_batch_statistics: bool,
498
+ ) -> RbResult<RbLazyFrame> {
404
499
  let options = IpcWriterOptions {
405
- compression: compression.map(|c| c.0),
406
- maintain_order,
500
+ compression: compression.0,
501
+ compat_level: compat_level.0,
502
+ record_batch_size,
503
+ record_batch_statistics,
407
504
  };
408
505
 
409
- let ldf = self.ldf.borrow().clone();
410
- ldf.sink_ipc(path, options).map_err(RbPolarsErr::from)?;
411
- Ok(())
506
+ let target = target.extract_file_sink_destination()?;
507
+ let unified_sink_args = sink_options.extract_unified_sink_args(target.cloud_scheme())?;
508
+
509
+ rb.enter_polars(|| {
510
+ self_
511
+ .ldf
512
+ .read()
513
+ .clone()
514
+ .sink(target, FileWriteFormat::Ipc(options), unified_sink_args)
515
+ })
516
+ .map(Into::into)
412
517
  }
413
518
 
414
- #[allow(clippy::too_many_arguments)]
415
- pub fn sink_csv(
416
- &self,
417
- path: PathBuf,
418
- include_bom: bool,
419
- include_header: bool,
420
- separator: u8,
421
- line_terminator: String,
422
- quote_char: u8,
423
- batch_size: Wrap<NonZeroUsize>,
424
- datetime_format: Option<String>,
425
- date_format: Option<String>,
426
- time_format: Option<String>,
427
- float_scientific: Option<bool>,
428
- float_precision: Option<usize>,
429
- null_value: Option<String>,
430
- quote_style: Option<Wrap<QuoteStyle>>,
431
- maintain_order: bool,
432
- ) -> RbResult<()> {
519
+ pub fn sink_csv(rb: &Ruby, self_: &Self, arguments: &[Value]) -> RbResult<RbLazyFrame> {
520
+ let target = RbFileSinkDestination::try_convert(arguments[0])?;
521
+ let sink_options = RbSinkOptions::try_convert(arguments[1])?;
522
+ let include_bom = bool::try_convert(arguments[2])?;
523
+ let compression = String::try_convert(arguments[3])?;
524
+ let compression_level = Option::<u32>::try_convert(arguments[4])?;
525
+ let check_extension = bool::try_convert(arguments[5])?;
526
+ let include_header = bool::try_convert(arguments[6])?;
527
+ let separator = u8::try_convert(arguments[7])?;
528
+ let line_terminator = Wrap::<PlSmallStr>::try_convert(arguments[8])?;
529
+ let quote_char = u8::try_convert(arguments[9])?;
530
+ let batch_size = NonZeroUsize::try_convert(arguments[10])?;
531
+ let datetime_format = Option::<Wrap<PlSmallStr>>::try_convert(arguments[11])?;
532
+ let date_format = Option::<Wrap<PlSmallStr>>::try_convert(arguments[12])?;
533
+ let time_format = Option::<Wrap<PlSmallStr>>::try_convert(arguments[13])?;
534
+ let float_scientific = Option::<bool>::try_convert(arguments[14])?;
535
+ let float_precision = Option::<usize>::try_convert(arguments[15])?;
536
+ let decimal_comma = bool::try_convert(arguments[16])?;
537
+ let null_value = Option::<Wrap<PlSmallStr>>::try_convert(arguments[17])?;
538
+ let quote_style = Option::<Wrap<QuoteStyle>>::try_convert(arguments[18])?;
539
+
433
540
  let quote_style = quote_style.map_or(QuoteStyle::default(), |wrap| wrap.0);
434
- let null_value = null_value.unwrap_or(SerializeOptions::default().null);
541
+ let null_value = null_value
542
+ .map(|x| x.0)
543
+ .unwrap_or(SerializeOptions::default().null);
435
544
 
436
545
  let serialize_options = SerializeOptions {
437
- date_format,
438
- time_format,
439
- datetime_format,
546
+ date_format: date_format.map(|x| x.0),
547
+ time_format: time_format.map(|x| x.0),
548
+ datetime_format: datetime_format.map(|x| x.0),
440
549
  float_scientific,
441
550
  float_precision,
551
+ decimal_comma,
442
552
  separator,
443
553
  quote_char,
444
554
  null: null_value,
445
- line_terminator,
555
+ line_terminator: line_terminator.0,
446
556
  quote_style,
447
557
  };
448
558
 
449
559
  let options = CsvWriterOptions {
450
560
  include_bom,
561
+ compression: ExternalCompression::try_from(&compression, compression_level)
562
+ .map_err(RbPolarsErr::from)?,
563
+ check_extension,
451
564
  include_header,
452
- maintain_order,
453
- batch_size: batch_size.0,
454
- serialize_options,
565
+ batch_size,
566
+ serialize_options: serialize_options.into(),
455
567
  };
456
568
 
457
- let ldf = self.ldf.borrow().clone();
458
- ldf.sink_csv(path, options).map_err(RbPolarsErr::from)?;
459
- Ok(())
569
+ let target = target.extract_file_sink_destination()?;
570
+ let unified_sink_args = sink_options.extract_unified_sink_args(target.cloud_scheme())?;
571
+
572
+ rb.enter_polars(|| {
573
+ self_
574
+ .ldf
575
+ .read()
576
+ .clone()
577
+ .sink(target, FileWriteFormat::Csv(options), unified_sink_args)
578
+ })
579
+ .map(Into::into)
460
580
  }
461
581
 
462
- pub fn sink_json(&self, path: PathBuf, maintain_order: bool) -> RbResult<()> {
463
- let options = JsonWriterOptions { maintain_order };
582
+ pub fn sink_ndjson(
583
+ rb: &Ruby,
584
+ self_: &Self,
585
+ target: RbFileSinkDestination,
586
+ compression: String,
587
+ compression_level: Option<u32>,
588
+ check_extension: bool,
589
+ sink_options: RbSinkOptions,
590
+ ) -> RbResult<RbLazyFrame> {
591
+ let options = NDJsonWriterOptions {
592
+ compression: ExternalCompression::try_from(&compression, compression_level)
593
+ .map_err(RbPolarsErr::from)?,
594
+ check_extension,
595
+ };
464
596
 
465
- let ldf = self.ldf.borrow().clone();
466
- ldf.sink_json(path, options).map_err(RbPolarsErr::from)?;
467
- Ok(())
597
+ let target = target.extract_file_sink_destination()?;
598
+ let unified_sink_args = sink_options.extract_unified_sink_args(target.cloud_scheme())?;
599
+
600
+ rb.enter_polars(|| {
601
+ self_.ldf.read().clone().sink(
602
+ target,
603
+ FileWriteFormat::NDJson(options),
604
+ unified_sink_args,
605
+ )
606
+ })
607
+ .map(Into::into)
468
608
  }
469
609
 
470
- pub fn fetch(&self, n_rows: usize) -> RbResult<RbDataFrame> {
471
- let ldf = self.ldf.borrow().clone();
472
- let df = ldf.fetch(n_rows).map_err(RbPolarsErr::from)?;
473
- Ok(df.into())
610
+ pub fn sink_batches(
611
+ rb: &Ruby,
612
+ self_: &Self,
613
+ function: Value,
614
+ maintain_order: bool,
615
+ chunk_size: Option<NonZeroUsize>,
616
+ ) -> RbResult<RbLazyFrame> {
617
+ let ldf = self_.ldf.read().clone();
618
+ // ensure new_ruby is called with GVL
619
+ let callback = PlanCallback::new_ruby(RubyObject::from(function));
620
+ rb.enter_polars(|| ldf.sink_batches(callback, maintain_order, chunk_size))
621
+ .map(Into::into)
474
622
  }
475
623
 
476
624
  pub fn filter(&self, predicate: &RbExpr) -> Self {
477
- let ldf = self.ldf.borrow().clone();
625
+ let ldf = self.ldf.read().clone();
478
626
  ldf.filter(predicate.inner.clone()).into()
479
627
  }
480
628
 
629
+ pub fn remove(&self, predicate: &RbExpr) -> Self {
630
+ let ldf = self.ldf.read().clone();
631
+ ldf.remove(predicate.inner.clone()).into()
632
+ }
633
+
481
634
  pub fn select(&self, exprs: RArray) -> RbResult<Self> {
482
- let ldf = self.ldf.borrow().clone();
483
- let exprs = rb_exprs_to_exprs(exprs)?;
635
+ let ldf = self.ldf.read().clone();
636
+ let exprs = exprs.to_exprs()?;
484
637
  Ok(ldf.select(exprs).into())
485
638
  }
486
639
 
487
640
  pub fn select_seq(&self, exprs: RArray) -> RbResult<Self> {
488
- let ldf = self.ldf.borrow().clone();
489
- let exprs = rb_exprs_to_exprs(exprs)?;
641
+ let ldf = self.ldf.read().clone();
642
+ let exprs = exprs.to_exprs()?;
490
643
  Ok(ldf.select_seq(exprs).into())
491
644
  }
492
645
 
493
646
  pub fn group_by(&self, by: RArray, maintain_order: bool) -> RbResult<RbLazyGroupBy> {
494
- let ldf = self.ldf.borrow().clone();
495
- let by = rb_exprs_to_exprs(by)?;
647
+ let ldf = self.ldf.read().clone();
648
+ let by = by.to_exprs()?;
496
649
  let lazy_gb = if maintain_order {
497
650
  ldf.group_by_stable(by)
498
651
  } else {
499
652
  ldf.group_by(by)
500
653
  };
501
- Ok(RbLazyGroupBy {
502
- lgb: RefCell::new(Some(lazy_gb)),
503
- })
654
+ Ok(RbLazyGroupBy { lgb: Some(lazy_gb) })
504
655
  }
505
656
 
506
657
  pub fn rolling(
@@ -512,8 +663,8 @@ impl RbLazyFrame {
512
663
  by: RArray,
513
664
  ) -> RbResult<RbLazyGroupBy> {
514
665
  let closed_window = closed.0;
515
- let ldf = self.ldf.borrow().clone();
516
- let by = rb_exprs_to_exprs(by)?;
666
+ let ldf = self.ldf.read().clone();
667
+ let by = by.to_exprs()?;
517
668
  let lazy_gb = ldf.rolling(
518
669
  index_column.inner.clone(),
519
670
  by,
@@ -525,12 +676,9 @@ impl RbLazyFrame {
525
676
  },
526
677
  );
527
678
 
528
- Ok(RbLazyGroupBy {
529
- lgb: RefCell::new(Some(lazy_gb)),
530
- })
679
+ Ok(RbLazyGroupBy { lgb: Some(lazy_gb) })
531
680
  }
532
681
 
533
- #[allow(clippy::too_many_arguments)]
534
682
  pub fn group_by_dynamic(
535
683
  &self,
536
684
  index_column: &RbExpr,
@@ -544,8 +692,8 @@ impl RbLazyFrame {
544
692
  start_by: Wrap<StartBy>,
545
693
  ) -> RbResult<RbLazyGroupBy> {
546
694
  let closed_window = closed.0;
547
- let by = rb_exprs_to_exprs(by)?;
548
- let ldf = self.ldf.borrow().clone();
695
+ let by = by.to_exprs()?;
696
+ let ldf = self.ldf.read().clone();
549
697
  let lazy_gb = ldf.group_by_dynamic(
550
698
  index_column.inner.clone(),
551
699
  by,
@@ -561,21 +709,9 @@ impl RbLazyFrame {
561
709
  },
562
710
  );
563
711
 
564
- Ok(RbLazyGroupBy {
565
- lgb: RefCell::new(Some(lazy_gb)),
566
- })
712
+ Ok(RbLazyGroupBy { lgb: Some(lazy_gb) })
567
713
  }
568
714
 
569
- pub fn with_context(&self, contexts: RArray) -> RbResult<Self> {
570
- let contexts = contexts.typecheck::<Obj<RbLazyFrame>>()?;
571
- let contexts = contexts
572
- .into_iter()
573
- .map(|ldf| ldf.ldf.borrow().clone())
574
- .collect::<Vec<_>>();
575
- Ok(self.ldf.borrow().clone().with_context(contexts).into())
576
- }
577
-
578
- #[allow(clippy::too_many_arguments)]
579
715
  pub fn join_asof(
580
716
  &self,
581
717
  other: &RbLazyFrame,
@@ -590,14 +726,16 @@ impl RbLazyFrame {
590
726
  tolerance: Option<Wrap<AnyValue<'_>>>,
591
727
  tolerance_str: Option<String>,
592
728
  coalesce: bool,
729
+ allow_eq: bool,
730
+ check_sortedness: bool,
593
731
  ) -> RbResult<Self> {
594
732
  let coalesce = if coalesce {
595
733
  JoinCoalesce::CoalesceColumns
596
734
  } else {
597
735
  JoinCoalesce::KeepColumns
598
736
  };
599
- let ldf = self.ldf.borrow().clone();
600
- let other = other.ldf.borrow().clone();
737
+ let ldf = self.ldf.read().clone();
738
+ let other = other.ldf.read().clone();
601
739
  let left_on = left_on.inner.clone();
602
740
  let right_on = right_on.inner.clone();
603
741
  Ok(ldf
@@ -608,19 +746,24 @@ impl RbLazyFrame {
608
746
  .allow_parallel(allow_parallel)
609
747
  .force_parallel(force_parallel)
610
748
  .coalesce(coalesce)
611
- .how(JoinType::AsOf(AsOfOptions {
749
+ .how(JoinType::AsOf(Box::new(AsOfOptions {
612
750
  strategy: strategy.0,
613
751
  left_by: left_by.map(strings_to_pl_smallstr),
614
752
  right_by: right_by.map(strings_to_pl_smallstr),
615
- tolerance: tolerance.map(|t| t.0.into_static()),
753
+ tolerance: tolerance.map(|t| {
754
+ let av = t.0.into_static();
755
+ let dtype = av.dtype();
756
+ Scalar::new(dtype, av)
757
+ }),
616
758
  tolerance_str: tolerance_str.map(|s| s.into()),
617
- }))
759
+ allow_eq,
760
+ check_sortedness,
761
+ })))
618
762
  .suffix(suffix)
619
763
  .finish()
620
764
  .into())
621
765
  }
622
766
 
623
- #[allow(clippy::too_many_arguments)]
624
767
  pub fn join(
625
768
  &self,
626
769
  other: &RbLazyFrame,
@@ -632,6 +775,7 @@ impl RbLazyFrame {
632
775
  how: Wrap<JoinType>,
633
776
  suffix: String,
634
777
  validate: Wrap<JoinValidation>,
778
+ maintain_order: Wrap<MaintainOrderJoin>,
635
779
  coalesce: Option<bool>,
636
780
  ) -> RbResult<Self> {
637
781
  let coalesce = match coalesce {
@@ -639,10 +783,10 @@ impl RbLazyFrame {
639
783
  Some(true) => JoinCoalesce::CoalesceColumns,
640
784
  Some(false) => JoinCoalesce::KeepColumns,
641
785
  };
642
- let ldf = self.ldf.borrow().clone();
643
- let other = other.ldf.borrow().clone();
644
- let left_on = rb_exprs_to_exprs(left_on)?;
645
- let right_on = rb_exprs_to_exprs(right_on)?;
786
+ let ldf = self.ldf.read().clone();
787
+ let other = other.ldf.read().clone();
788
+ let left_on = left_on.to_exprs()?;
789
+ let right_on = right_on.to_exprs()?;
646
790
 
647
791
  Ok(ldf
648
792
  .join_builder()
@@ -655,38 +799,180 @@ impl RbLazyFrame {
655
799
  .how(how.0)
656
800
  .validate(validate.0)
657
801
  .coalesce(coalesce)
802
+ .maintain_order(maintain_order.0)
658
803
  .suffix(suffix)
659
804
  .finish()
660
805
  .into())
661
806
  }
662
807
 
808
+ pub fn join_where(&self, other: &Self, predicates: RArray, suffix: String) -> RbResult<Self> {
809
+ let ldf = self.ldf.read().clone();
810
+ let other = other.ldf.read().clone();
811
+
812
+ let predicates = predicates.to_exprs()?;
813
+
814
+ Ok(ldf
815
+ .join_builder()
816
+ .with(other)
817
+ .suffix(suffix)
818
+ .join_where(predicates)
819
+ .into())
820
+ }
821
+
822
+ pub fn gather(&self, idxs: &Self, null_on_oob: bool) -> Self {
823
+ let ldf = self.ldf.read().clone();
824
+ let idxs = idxs.clone().ldf.into_inner();
825
+ ldf.gather(idxs, null_on_oob).into()
826
+ }
827
+
663
828
  pub fn with_column(&self, expr: &RbExpr) -> Self {
664
- let ldf = self.ldf.borrow().clone();
829
+ let ldf = self.ldf.read().clone();
665
830
  ldf.with_column(expr.inner.clone()).into()
666
831
  }
667
832
 
668
833
  pub fn with_columns(&self, exprs: RArray) -> RbResult<Self> {
669
- let ldf = self.ldf.borrow().clone();
670
- Ok(ldf.with_columns(rb_exprs_to_exprs(exprs)?).into())
834
+ let ldf = self.ldf.read().clone();
835
+ Ok(ldf.with_columns(exprs.to_exprs()?).into())
671
836
  }
672
837
 
673
838
  pub fn with_columns_seq(&self, exprs: RArray) -> RbResult<Self> {
674
- let ldf = self.ldf.borrow().clone();
675
- Ok(ldf.with_columns_seq(rb_exprs_to_exprs(exprs)?).into())
839
+ let ldf = self.ldf.read().clone();
840
+ Ok(ldf.with_columns_seq(exprs.to_exprs()?).into())
841
+ }
842
+
843
+ pub fn match_to_schema(
844
+ &self,
845
+ schema: Wrap<Schema>,
846
+ missing_columns: Value,
847
+ missing_struct_fields: Value,
848
+ extra_columns: Wrap<ExtraColumnsPolicy>,
849
+ extra_struct_fields: Value,
850
+ integer_cast: Value,
851
+ float_cast: Value,
852
+ ) -> RbResult<Self> {
853
+ fn parse_missing_columns(
854
+ schema: &Schema,
855
+ missing_columns: Value,
856
+ ) -> RbResult<Vec<MissingColumnsPolicyOrExpr>> {
857
+ let mut out = Vec::with_capacity(schema.len());
858
+ if let Ok(policy) = Wrap::<MissingColumnsPolicyOrExpr>::try_convert(missing_columns) {
859
+ out.extend(std::iter::repeat_n(policy.0, schema.len()));
860
+ } else if let Ok(dict) = RHash::try_convert(missing_columns) {
861
+ out.extend(std::iter::repeat_n(
862
+ MissingColumnsPolicyOrExpr::Raise,
863
+ schema.len(),
864
+ ));
865
+ dict.foreach(|key: String, value: Wrap<MissingColumnsPolicyOrExpr>| {
866
+ out[schema.try_index_of(&key).map_err(to_rb_err)?] = value.0;
867
+ Ok(ForEach::Continue)
868
+ })?;
869
+ } else {
870
+ return Err(RbTypeError::new_err("Invalid value for `missing_columns`"));
871
+ }
872
+ Ok(out)
873
+ }
874
+ fn parse_missing_struct_fields(
875
+ schema: &Schema,
876
+ missing_struct_fields: Value,
877
+ ) -> RbResult<Vec<MissingColumnsPolicy>> {
878
+ let mut out = Vec::with_capacity(schema.len());
879
+ if let Ok(policy) = Wrap::<MissingColumnsPolicy>::try_convert(missing_struct_fields) {
880
+ out.extend(std::iter::repeat_n(policy.0, schema.len()));
881
+ } else if let Ok(dict) = RHash::try_convert(missing_struct_fields) {
882
+ out.extend(std::iter::repeat_n(
883
+ MissingColumnsPolicy::Raise,
884
+ schema.len(),
885
+ ));
886
+ dict.foreach(|key: String, value: Wrap<MissingColumnsPolicy>| {
887
+ out[schema.try_index_of(&key).map_err(to_rb_err)?] = value.0;
888
+ Ok(ForEach::Continue)
889
+ })?;
890
+ } else {
891
+ return Err(RbTypeError::new_err(
892
+ "Invalid value for `missing_struct_fields`",
893
+ ));
894
+ }
895
+ Ok(out)
896
+ }
897
+ fn parse_extra_struct_fields(
898
+ schema: &Schema,
899
+ extra_struct_fields: Value,
900
+ ) -> RbResult<Vec<ExtraColumnsPolicy>> {
901
+ let mut out = Vec::with_capacity(schema.len());
902
+ if let Ok(policy) = Wrap::<ExtraColumnsPolicy>::try_convert(extra_struct_fields) {
903
+ out.extend(std::iter::repeat_n(policy.0, schema.len()));
904
+ } else if let Ok(dict) = RHash::try_convert(extra_struct_fields) {
905
+ out.extend(std::iter::repeat_n(ExtraColumnsPolicy::Raise, schema.len()));
906
+ dict.foreach(|key: String, value: Wrap<ExtraColumnsPolicy>| {
907
+ out[schema.try_index_of(&key).map_err(to_rb_err)?] = value.0;
908
+ Ok(ForEach::Continue)
909
+ })?;
910
+ } else {
911
+ return Err(RbTypeError::new_err(
912
+ "Invalid value for `extra_struct_fields`",
913
+ ));
914
+ }
915
+ Ok(out)
916
+ }
917
+ fn parse_cast(schema: &Schema, cast: Value) -> RbResult<Vec<UpcastOrForbid>> {
918
+ let mut out = Vec::with_capacity(schema.len());
919
+ if let Ok(policy) = Wrap::<UpcastOrForbid>::try_convert(cast) {
920
+ out.extend(std::iter::repeat_n(policy.0, schema.len()));
921
+ } else if let Ok(dict) = RHash::try_convert(cast) {
922
+ out.extend(std::iter::repeat_n(UpcastOrForbid::Forbid, schema.len()));
923
+ dict.foreach(|key: String, value: Wrap<UpcastOrForbid>| {
924
+ out[schema.try_index_of(&key).map_err(to_rb_err)?] = value.0;
925
+ Ok(ForEach::Continue)
926
+ })?;
927
+ } else {
928
+ return Err(RbTypeError::new_err(
929
+ "Invalid value for `integer_cast` / `float_cast`",
930
+ ));
931
+ }
932
+ Ok(out)
933
+ }
934
+
935
+ let missing_columns = parse_missing_columns(&schema.0, missing_columns)?;
936
+ let missing_struct_fields = parse_missing_struct_fields(&schema.0, missing_struct_fields)?;
937
+ let extra_struct_fields = parse_extra_struct_fields(&schema.0, extra_struct_fields)?;
938
+ let integer_cast = parse_cast(&schema.0, integer_cast)?;
939
+ let float_cast = parse_cast(&schema.0, float_cast)?;
940
+
941
+ let per_column = (0..schema.0.len())
942
+ .map(|i| MatchToSchemaPerColumn {
943
+ missing_columns: missing_columns[i].clone(),
944
+ missing_struct_fields: missing_struct_fields[i],
945
+ extra_struct_fields: extra_struct_fields[i],
946
+ integer_cast: integer_cast[i],
947
+ float_cast: float_cast[i],
948
+ })
949
+ .collect();
950
+
951
+ let ldf = self.ldf.read().clone();
952
+ Ok(ldf
953
+ .match_to_schema(Arc::new(schema.0), per_column, extra_columns.0)
954
+ .into())
955
+ }
956
+
957
+ pub fn pipe_with_schema(&self, callback: Value) -> Self {
958
+ let ldf = self.ldf.read().clone();
959
+ let function = RubyObject::from(callback);
960
+ ldf.pipe_with_schema(PlanCallback::new_ruby(function))
961
+ .into()
676
962
  }
677
963
 
678
964
  pub fn rename(&self, existing: Vec<String>, new: Vec<String>, strict: bool) -> Self {
679
- let ldf = self.ldf.borrow().clone();
965
+ let ldf = self.ldf.read().clone();
680
966
  ldf.rename(existing, new, strict).into()
681
967
  }
682
968
 
683
969
  pub fn reverse(&self) -> Self {
684
- let ldf = self.ldf.borrow().clone();
970
+ let ldf = self.ldf.read().clone();
685
971
  ldf.reverse().into()
686
972
  }
687
973
 
688
974
  pub fn shift(&self, n: &RbExpr, fill_value: Option<&RbExpr>) -> Self {
689
- let lf = self.ldf.borrow().clone();
975
+ let lf = self.ldf.read().clone();
690
976
  let out = match fill_value {
691
977
  Some(v) => lf.shift_and_fill(n.inner.clone(), v.inner.clone()),
692
978
  None => lf.shift(n.inner.clone()),
@@ -695,76 +981,88 @@ impl RbLazyFrame {
695
981
  }
696
982
 
697
983
  pub fn fill_nan(&self, fill_value: &RbExpr) -> Self {
698
- let ldf = self.ldf.borrow().clone();
984
+ let ldf = self.ldf.read().clone();
699
985
  ldf.fill_nan(fill_value.inner.clone()).into()
700
986
  }
701
987
 
702
988
  pub fn min(&self) -> Self {
703
- let ldf = self.ldf.borrow().clone();
989
+ let ldf = self.ldf.read().clone();
704
990
  let out = ldf.min();
705
991
  out.into()
706
992
  }
707
993
 
708
994
  pub fn max(&self) -> Self {
709
- let ldf = self.ldf.borrow().clone();
995
+ let ldf = self.ldf.read().clone();
710
996
  let out = ldf.max();
711
997
  out.into()
712
998
  }
713
999
 
714
1000
  pub fn sum(&self) -> Self {
715
- let ldf = self.ldf.borrow().clone();
1001
+ let ldf = self.ldf.read().clone();
716
1002
  let out = ldf.sum();
717
1003
  out.into()
718
1004
  }
719
1005
 
720
1006
  pub fn mean(&self) -> Self {
721
- let ldf = self.ldf.borrow().clone();
1007
+ let ldf = self.ldf.read().clone();
722
1008
  let out = ldf.mean();
723
1009
  out.into()
724
1010
  }
725
1011
 
726
1012
  pub fn std(&self, ddof: u8) -> Self {
727
- let ldf = self.ldf.borrow().clone();
1013
+ let ldf = self.ldf.read().clone();
728
1014
  let out = ldf.std(ddof);
729
1015
  out.into()
730
1016
  }
731
1017
 
732
1018
  pub fn var(&self, ddof: u8) -> Self {
733
- let ldf = self.ldf.borrow().clone();
1019
+ let ldf = self.ldf.read().clone();
734
1020
  let out = ldf.var(ddof);
735
1021
  out.into()
736
1022
  }
737
1023
 
738
1024
  pub fn median(&self) -> Self {
739
- let ldf = self.ldf.borrow().clone();
1025
+ let ldf = self.ldf.read().clone();
740
1026
  let out = ldf.median();
741
1027
  out.into()
742
1028
  }
743
1029
 
744
1030
  pub fn quantile(&self, quantile: &RbExpr, interpolation: Wrap<QuantileMethod>) -> Self {
745
- let ldf = self.ldf.borrow().clone();
1031
+ let ldf = self.ldf.read().clone();
746
1032
  let out = ldf.quantile(quantile.inner.clone(), interpolation.0);
747
1033
  out.into()
748
1034
  }
749
1035
 
750
- pub fn explode(&self, column: RArray) -> RbResult<Self> {
751
- let ldf = self.ldf.borrow().clone();
752
- let column = rb_exprs_to_exprs(column)?;
753
- Ok(ldf.explode(column).into())
1036
+ pub fn explode(&self, subset: &RbSelector, empty_as_null: bool, keep_nulls: bool) -> Self {
1037
+ self.ldf
1038
+ .read()
1039
+ .clone()
1040
+ .explode(
1041
+ subset.inner.clone(),
1042
+ ExplodeOptions {
1043
+ empty_as_null,
1044
+ keep_nulls,
1045
+ },
1046
+ )
1047
+ .into()
754
1048
  }
755
1049
 
756
1050
  pub fn null_count(&self) -> Self {
757
- let ldf = self.ldf.borrow().clone();
1051
+ let ldf = self.ldf.read().clone();
758
1052
  ldf.null_count().into()
759
1053
  }
760
1054
 
761
1055
  pub fn unique(
762
1056
  &self,
763
1057
  maintain_order: bool,
764
- subset: Option<Vec<String>>,
1058
+ subset: Option<RArray>,
765
1059
  keep: Wrap<UniqueKeepStrategy>,
766
1060
  ) -> RbResult<Self> {
767
- let ldf = self.ldf.borrow().clone();
1061
+ let ldf = self.ldf.read().clone();
1062
+ let subset = match subset {
1063
+ Some(e) => Some(e.to_exprs()?),
1064
+ None => None,
1065
+ };
768
1066
  Ok(match maintain_order {
769
1067
  true => ldf.unique_stable_generic(subset, keep.0),
770
1068
  false => ldf.unique_generic(subset, keep.0),
@@ -772,50 +1070,110 @@ impl RbLazyFrame {
772
1070
  .into())
773
1071
  }
774
1072
 
775
- pub fn drop_nulls(&self, subset: Option<Vec<String>>) -> Self {
776
- let ldf = self.ldf.borrow().clone();
777
- ldf.drop_nulls(subset.map(|v| v.into_iter().map(|s| col(&s)).collect()))
1073
+ pub fn drop_nans(&self, subset: Option<&RbSelector>) -> Self {
1074
+ self.ldf
1075
+ .read()
1076
+ .clone()
1077
+ .drop_nans(subset.map(|e| e.inner.clone()))
1078
+ .into()
1079
+ }
1080
+
1081
+ pub fn drop_nulls(&self, subset: Option<&RbSelector>) -> Self {
1082
+ self.ldf
1083
+ .read()
1084
+ .clone()
1085
+ .drop_nulls(subset.map(|e| e.inner.clone()))
778
1086
  .into()
779
1087
  }
780
1088
 
781
1089
  pub fn slice(&self, offset: i64, len: Option<IdxSize>) -> Self {
782
- let ldf = self.ldf.borrow().clone();
1090
+ let ldf = self.ldf.read().clone();
783
1091
  ldf.slice(offset, len.unwrap_or(IdxSize::MAX)).into()
784
1092
  }
785
1093
 
786
1094
  pub fn tail(&self, n: IdxSize) -> Self {
787
- let ldf = self.ldf.borrow().clone();
1095
+ let ldf = self.ldf.read().clone();
788
1096
  ldf.tail(n).into()
789
1097
  }
790
1098
 
1099
+ pub fn pivot(
1100
+ &self,
1101
+ on: &RbSelector,
1102
+ on_columns: &RbDataFrame,
1103
+ index: &RbSelector,
1104
+ values: &RbSelector,
1105
+ agg: &RbExpr,
1106
+ maintain_order: bool,
1107
+ separator: String,
1108
+ column_naming: Wrap<PivotColumnNaming>,
1109
+ ) -> Self {
1110
+ let ldf = self.ldf.read().clone();
1111
+ ldf.pivot(
1112
+ on.inner.clone(),
1113
+ Arc::new(on_columns.df.read().clone()),
1114
+ index.inner.clone(),
1115
+ values.inner.clone(),
1116
+ agg.inner.clone(),
1117
+ maintain_order,
1118
+ separator.into(),
1119
+ column_naming.0,
1120
+ )
1121
+ .into()
1122
+ }
1123
+
791
1124
  pub fn unpivot(
792
1125
  &self,
793
- on: RArray,
794
- index: RArray,
1126
+ on: Option<&RbSelector>,
1127
+ index: &RbSelector,
795
1128
  value_name: Option<String>,
796
1129
  variable_name: Option<String>,
797
1130
  ) -> RbResult<Self> {
798
- let on = rb_exprs_to_exprs(on)?;
799
- let index = rb_exprs_to_exprs(index)?;
800
1131
  let args = UnpivotArgsDSL {
801
- on: on.into_iter().map(|e| e.into()).collect(),
802
- index: index.into_iter().map(|e| e.into()).collect(),
1132
+ on: on.map(|on| on.inner.clone()),
1133
+ index: index.inner.clone(),
803
1134
  value_name: value_name.map(|s| s.into()),
804
1135
  variable_name: variable_name.map(|s| s.into()),
805
1136
  };
806
1137
 
807
- let ldf = self.ldf.borrow().clone();
1138
+ let ldf = self.ldf.read().clone();
808
1139
  Ok(ldf.unpivot(args).into())
809
1140
  }
810
1141
 
811
1142
  pub fn with_row_index(&self, name: String, offset: Option<IdxSize>) -> Self {
812
- let ldf = self.ldf.borrow().clone();
1143
+ let ldf = self.ldf.read().clone();
813
1144
  ldf.with_row_index(&name, offset).into()
814
1145
  }
815
1146
 
816
- pub fn drop(&self, cols: Vec<String>) -> Self {
817
- let ldf = self.ldf.borrow().clone();
818
- ldf.drop(cols).into()
1147
+ pub fn map_batches(
1148
+ &self,
1149
+ function: Value,
1150
+ predicate_pushdown: bool,
1151
+ projection_pushdown: bool,
1152
+ slice_pushdown: bool,
1153
+ streamable: bool,
1154
+ schema: Option<Wrap<Schema>>,
1155
+ validate_output: bool,
1156
+ ) -> Self {
1157
+ let mut opt = OptFlags::default();
1158
+ opt.set(OptFlags::PREDICATE_PUSHDOWN, predicate_pushdown);
1159
+ opt.set(OptFlags::PROJECTION_PUSHDOWN, projection_pushdown);
1160
+ opt.set(OptFlags::SLICE_PUSHDOWN, slice_pushdown);
1161
+ opt.set(OptFlags::STREAMING, streamable);
1162
+
1163
+ self.ldf
1164
+ .read()
1165
+ .clone()
1166
+ .map_ruby(
1167
+ function.into(),
1168
+ opt,
1169
+ schema.map(|s| Arc::new(s.0)),
1170
+ validate_output,
1171
+ )
1172
+ .into()
1173
+ }
1174
+
1175
+ pub fn drop(&self, columns: &RbSelector) -> Self {
1176
+ self.ldf.read().clone().drop(columns.inner.clone()).into()
819
1177
  }
820
1178
 
821
1179
  pub fn cast(&self, rb_dtypes: RHash, strict: bool) -> RbResult<Self> {
@@ -826,52 +1184,172 @@ impl RbLazyFrame {
826
1184
  })?;
827
1185
  let mut cast_map = PlHashMap::with_capacity(dtypes.len());
828
1186
  cast_map.extend(dtypes.iter().map(|(k, v)| (k.as_ref(), v.clone())));
829
- Ok(self.ldf.borrow().clone().cast(cast_map, strict).into())
1187
+ Ok(self.ldf.read().clone().cast(cast_map, strict).into())
830
1188
  }
831
1189
 
832
1190
  pub fn cast_all(&self, dtype: Wrap<DataType>, strict: bool) -> Self {
833
- self.ldf.borrow().clone().cast_all(dtype.0, strict).into()
1191
+ self.ldf.read().clone().cast_all(dtype.0, strict).into()
834
1192
  }
835
1193
 
836
1194
  pub fn clone(&self) -> Self {
837
- self.ldf.borrow().clone().into()
1195
+ self.ldf.read().clone().into()
838
1196
  }
839
1197
 
840
- pub fn collect_schema(&self) -> RbResult<RHash> {
841
- let schema = self
842
- .ldf
843
- .borrow_mut()
844
- .collect_schema()
845
- .map_err(RbPolarsErr::from)?;
1198
+ pub fn collect_schema(rb: &Ruby, self_: &Self) -> RbResult<RHash> {
1199
+ let schema = rb.enter_polars(|| self_.ldf.write().collect_schema())?;
846
1200
 
847
- let schema_dict = RHash::new();
848
- schema.iter_fields().for_each(|fld| {
849
- schema_dict
850
- .aset::<String, Value>(
851
- fld.name().to_string(),
852
- Wrap(fld.dtype().clone()).into_value(),
853
- )
854
- .unwrap();
855
- });
1201
+ let schema_dict = rb.hash_new();
1202
+ for fld in schema.iter_fields() {
1203
+ schema_dict.aset(
1204
+ fld.name().to_string(),
1205
+ Wrap(fld.dtype().clone()).try_into_value_with(rb)?,
1206
+ )?;
1207
+ }
856
1208
  Ok(schema_dict)
857
1209
  }
858
1210
 
859
- pub fn unnest(&self, cols: Vec<String>) -> Self {
860
- self.ldf.borrow().clone().unnest(cols).into()
1211
+ pub fn unnest(&self, columns: &RbSelector, separator: Option<String>) -> Self {
1212
+ self.ldf
1213
+ .read()
1214
+ .clone()
1215
+ .unnest(
1216
+ columns.inner.clone(),
1217
+ separator.as_deref().map(PlSmallStr::from_str),
1218
+ )
1219
+ .into()
861
1220
  }
862
1221
 
863
1222
  pub fn count(&self) -> Self {
864
- let ldf = self.ldf.borrow().clone();
1223
+ let ldf = self.ldf.read().clone();
865
1224
  ldf.count().into()
866
1225
  }
867
1226
 
868
- pub fn merge_sorted(&self, other: &Self, key: String) -> RbResult<Self> {
1227
+ pub fn merge_sorted(&self, other: &Self, key: String, maintain_order: bool) -> RbResult<Self> {
869
1228
  let out = self
870
1229
  .ldf
871
- .borrow()
1230
+ .read()
872
1231
  .clone()
873
- .merge_sorted(other.ldf.borrow().clone(), &key)
1232
+ .merge_sorted(other.ldf.read().clone(), &key, maintain_order)
874
1233
  .map_err(RbPolarsErr::from)?;
875
1234
  Ok(out.into())
876
1235
  }
1236
+
1237
+ pub fn hint_sorted(
1238
+ &self,
1239
+ columns: Vec<String>,
1240
+ descending: Vec<bool>,
1241
+ nulls_last: Vec<bool>,
1242
+ ) -> RbResult<Self> {
1243
+ if columns.len() != descending.len() && descending.len() != 1 {
1244
+ return Err(RbValueError::new_err(
1245
+ "`set_sorted` expects the same amount of `columns` as `descending` values.",
1246
+ ));
1247
+ }
1248
+ if columns.len() != nulls_last.len() && nulls_last.len() != 1 {
1249
+ return Err(RbValueError::new_err(
1250
+ "`set_sorted` expects the same amount of `columns` as `nulls_last` values.",
1251
+ ));
1252
+ }
1253
+
1254
+ let mut sorted = columns
1255
+ .iter()
1256
+ .map(|c| Sorted {
1257
+ column: PlSmallStr::from_str(c.as_str()),
1258
+ descending: Some(false),
1259
+ nulls_last: Some(false),
1260
+ })
1261
+ .collect::<Vec<_>>();
1262
+
1263
+ if !columns.is_empty() {
1264
+ if descending.len() != 1 {
1265
+ sorted
1266
+ .iter_mut()
1267
+ .zip(descending)
1268
+ .for_each(|(s, d)| s.descending = Some(d));
1269
+ } else if descending[0] {
1270
+ sorted.iter_mut().for_each(|s| s.descending = Some(true));
1271
+ }
1272
+
1273
+ if nulls_last.len() != 1 {
1274
+ sorted
1275
+ .iter_mut()
1276
+ .zip(nulls_last)
1277
+ .for_each(|(s, d)| s.nulls_last = Some(d));
1278
+ } else if nulls_last[0] {
1279
+ sorted.iter_mut().for_each(|s| s.nulls_last = Some(true));
1280
+ }
1281
+ }
1282
+
1283
+ let out = self
1284
+ .ldf
1285
+ .read()
1286
+ .clone()
1287
+ .hint(HintIR::Sorted(sorted.into()))
1288
+ .map_err(RbPolarsErr::from)?;
1289
+ Ok(out.into())
1290
+ }
1291
+ }
1292
+
1293
+ #[magnus::wrap(class = "Polars::RbCollectBatches")]
1294
+ pub struct RbCollectBatches {
1295
+ inner: Arc<Mutex<CollectBatches>>,
1296
+ ldf: LazyFrame,
1297
+ }
1298
+
1299
+ impl RbCollectBatches {
1300
+ pub fn next(rb: &Ruby, slf: &Self) -> RbResult<Option<RbDataFrame>> {
1301
+ let inner = Arc::clone(&slf.inner);
1302
+ rb.enter_polars(|| PolarsResult::Ok(inner.lock().next().transpose()?.map(RbDataFrame::new)))
1303
+ }
1304
+
1305
+ pub fn __arrow_c_stream__(rb: &Ruby, self_: &Self) -> RbResult<Value> {
1306
+ let mut ldf = self_.ldf.clone();
1307
+ let schema = ldf
1308
+ .collect_schema()
1309
+ .map_err(RbPolarsErr::from)?
1310
+ .to_arrow(CompatLevel::newest());
1311
+
1312
+ let dtype = ArrowDataType::Struct(schema.into_iter_values().collect());
1313
+
1314
+ let iter = Box::new(ArrowStreamIterator::new(self_.inner.clone(), dtype.clone()));
1315
+ let field = ArrowField::new(PlSmallStr::EMPTY, dtype, false);
1316
+ let stream = export_iterator(iter, field);
1317
+ Ok(RbArrowArrayStream { stream }.into_value_with(rb))
1318
+ }
1319
+ }
1320
+
1321
+ pub struct ArrowStreamIterator {
1322
+ inner: Arc<Mutex<CollectBatches>>,
1323
+ dtype: ArrowDataType,
1324
+ }
1325
+
1326
+ impl ArrowStreamIterator {
1327
+ fn new(inner: Arc<Mutex<CollectBatches>>, schema: ArrowDataType) -> Self {
1328
+ Self {
1329
+ inner,
1330
+ dtype: schema,
1331
+ }
1332
+ }
1333
+ }
1334
+
1335
+ impl Iterator for ArrowStreamIterator {
1336
+ type Item = PolarsResult<ArrayRef>;
1337
+
1338
+ fn next(&mut self) -> Option<Self::Item> {
1339
+ let next = self.inner.lock().next();
1340
+ match next {
1341
+ None => None,
1342
+ Some(Err(err)) => Some(Err(err)),
1343
+ Some(Ok(df)) => {
1344
+ let height = df.height();
1345
+ let arrays = df.rechunk_into_arrow(CompatLevel::newest());
1346
+ Some(Ok(Box::new(arrow::array::StructArray::new(
1347
+ self.dtype.clone(),
1348
+ height,
1349
+ arrays,
1350
+ None,
1351
+ ))))
1352
+ }
1353
+ }
1354
+ }
877
1355
  }