1use std::borrow::Cow;
2
3use polars_row::{
4 RowEncodingContext, RowEncodingField, RowEncodingLayout, RowEncodingOptions, RowEncodingOutput,
5 convert_columns,
6};
7use polars_utils::itertools::Itertools;
8use rayon::prelude::*;
9
10use crate::prelude::*;
11use crate::runtime::RAYON;
12use crate::utils::_split_offsets;
13
14pub fn encode_rows_vertical_par_unordered(by: &[Column]) -> PolarsResult<BinaryOffsetChunked> {
15 encode_binary_offset_par(by, &unordered_opts(by.len()), false)
16}
17
18pub fn encode_rows_vertical_par_unordered_propagate_nulls(
19 by: &[Column],
20) -> PolarsResult<BinaryOffsetChunked> {
21 encode_binary_offset_par(by, &unordered_opts(by.len()), true)
22}
23
24pub fn get_row_encoding_context(dtype: &DataType) -> Option<RowEncodingContext> {
29 match dtype {
30 DataType::Boolean
31 | DataType::UInt8
32 | DataType::UInt16
33 | DataType::UInt32
34 | DataType::UInt64
35 | DataType::UInt128
36 | DataType::Int8
37 | DataType::Int16
38 | DataType::Int32
39 | DataType::Int64
40 | DataType::Int128
41 | DataType::Float16
42 | DataType::Float32
43 | DataType::Float64
44 | DataType::String
45 | DataType::Binary
46 | DataType::BinaryOffset
47 | DataType::Null
48 | DataType::Time
49 | DataType::Date
50 | DataType::Datetime(_, _)
51 | DataType::Duration(_) => None,
52
53 #[cfg(feature = "dtype-categorical")]
54 DataType::Categorical(_, mapping) | DataType::Enum(_, mapping) => {
55 use polars_row::RowEncodingCategoricalContext;
56
57 Some(RowEncodingContext::Categorical(
58 RowEncodingCategoricalContext {
59 is_enum: matches!(dtype, DataType::Enum(_, _)),
60 mapping: mapping.clone(),
61 },
62 ))
63 },
64
65 DataType::Unknown(_) => panic!("Unsupported in row encoding"),
66
67 #[cfg(feature = "object")]
68 DataType::Object(_) => panic!("Unsupported in row encoding"),
69
70 #[cfg(feature = "dtype-decimal")]
71 DataType::Decimal(precision, _) => Some(RowEncodingContext::Decimal(*precision)),
72
73 #[cfg(feature = "dtype-array")]
74 DataType::Array(dtype, _) => get_row_encoding_context(dtype),
75 DataType::List(dtype) => get_row_encoding_context(dtype),
76 #[cfg(feature = "dtype-struct")]
77 DataType::Struct(fs) => {
78 let mut ctxts = Vec::new();
79
80 for (i, f) in fs.iter().enumerate() {
81 if let Some(ctxt) = get_row_encoding_context(f.dtype()) {
82 ctxts.reserve(fs.len());
83 ctxts.extend(std::iter::repeat_n(None, i));
84 ctxts.push(Some(ctxt));
85 break;
86 }
87 }
88
89 if ctxts.is_empty() {
90 return None;
91 }
92
93 ctxts.extend(
94 fs[ctxts.len()..]
95 .iter()
96 .map(|f| get_row_encoding_context(f.dtype())),
97 );
98
99 Some(RowEncodingContext::Struct(ctxts))
100 },
101
102 #[cfg(feature = "dtype-map")]
103 DataType::Map(key, value) => {
104 let ctxts = vec![
105 get_row_encoding_context(key),
106 get_row_encoding_context(value),
107 ];
108
109 if ctxts.iter().all(Option::is_none) {
110 return None;
111 }
112
113 Some(RowEncodingContext::Struct(ctxts))
114 },
115
116 #[cfg(feature = "dtype-extension")]
117 DataType::Extension(_, storage) => get_row_encoding_context(storage),
118 }
119}
120
121pub fn get_row_encoding_layout(
124 dtypes: &[DataType],
125 opts: &[RowEncodingOptions],
126 output: RowEncodingOutput,
127) -> RowEncodingLayout {
128 assert_eq!(dtypes.len(), opts.len());
129 let fields = dtypes
130 .iter()
131 .zip(opts)
132 .map(|(dtype, opt)| RowEncodingField {
133 dtype: dtype.to_physical().to_arrow(CompatLevel::newest()),
134 opt: *opt,
135 ctx: get_row_encoding_context(dtype),
136 })
137 .collect_vec();
138 let layout = RowEncodingLayout::new(fields, output);
139 if *layout.dtype() == ArrowDataType::UInt128 && !cfg!(feature = "dtype-u128") {
140 return RowEncodingLayout::new(
141 layout.fields().to_vec(),
142 RowEncodingOutput::AlwaysBinaryOffset,
143 );
144 }
145 layout
146}
147
148pub fn rows_dtype(layout: &RowEncodingLayout) -> DataType {
150 match layout.dtype() {
151 ArrowDataType::LargeBinary => DataType::BinaryOffset,
152 dtype => DataType::from_arrow_dtype(dtype),
153 }
154}
155
156fn encode_columns(
157 by: &[Column],
158 layout: &RowEncodingLayout,
159 propagate_nulls: bool,
160) -> PolarsResult<ArrayRef> {
161 let num_rows = by.first().map_or(0, |c| c.len());
164
165 let mut cols = Vec::with_capacity(by.len());
166 for (by, field) in by.iter().zip(layout.fields()) {
167 polars_ensure!(
168 field.opt.encode_nulls() || propagate_nulls || !by.has_nulls(),
169 ComputeError: "row encoding: column '{}' contains nulls, but its nulls are not encoded", by.name()
170 );
171
172 let by = by
173 .trim_lists_to_normalized_offsets()
174 .map_or(Cow::Borrowed(by), Cow::Owned);
175 let by = by.propagate_nulls().map_or(by, Cow::Owned);
176 let by = by.as_materialized_series();
177 cols.push(by.to_physical_repr().rechunk().chunks()[0].to_boxed());
178 }
179 Ok(convert_columns(num_rows, &cols, layout, propagate_nulls))
180}
181
182fn unordered_opts(n: usize) -> Vec<RowEncodingOptions> {
183 vec![RowEncodingOptions::new_unsorted(); n]
184}
185
186pub(crate) fn ordered_opts(descending: &[bool], nulls_last: &[bool]) -> Vec<RowEncodingOptions> {
187 debug_assert_eq!(descending.len(), nulls_last.len());
188 descending
189 .iter()
190 .zip(nulls_last)
191 .map(|(desc, null_last)| RowEncodingOptions::new_sorted(*desc, *null_last))
192 .collect_vec()
193}
194
195pub fn get_binary_offset_row_encoding_layout(
197 by: &[Column],
198 opts: &[RowEncodingOptions],
199) -> RowEncodingLayout {
200 let dtypes = by.iter().map(|c| c.dtype().clone()).collect_vec();
201 get_row_encoding_layout(&dtypes, opts, RowEncodingOutput::AlwaysBinaryOffset)
202}
203
204fn encode_binary_offset(
205 by: &[Column],
206 opts: &[RowEncodingOptions],
207 propagate_nulls: bool,
208) -> PolarsResult<BinaryArray<i64>> {
209 let rows = encode_columns(
210 by,
211 &get_binary_offset_row_encoding_layout(by, opts),
212 propagate_nulls,
213 )?;
214 Ok(rows
215 .as_any()
216 .downcast_ref::<BinaryArray<i64>>()
217 .unwrap()
218 .clone())
219}
220
221fn encode_binary_offset_par(
222 by: &[Column],
223 opts: &[RowEncodingOptions],
224 propagate_nulls: bool,
225) -> PolarsResult<BinaryOffsetChunked> {
226 let rows = encode_rows_with_layout_par(
227 by,
228 &get_binary_offset_row_encoding_layout(by, opts),
229 propagate_nulls,
230 )?;
231 Ok(rows.binary_offset()?.clone())
232}
233
234pub fn encode_rows_with_layout(
237 by: &[Column],
238 layout: &RowEncodingLayout,
239 propagate_nulls: bool,
240) -> PolarsResult<Series> {
241 let rows = encode_columns(by, layout, propagate_nulls)?;
242 Ok(unsafe {
244 Series::from_chunks_and_dtype_unchecked(PlSmallStr::EMPTY, vec![rows], &rows_dtype(layout))
245 })
246}
247
248const ROW_ENCODE_PAR_MIN_ROWS: usize = if cfg!(debug_assertions) { 4 } else { 1 << 16 };
251
252pub fn encode_rows_with_layout_par(
254 by: &[Column],
255 layout: &RowEncodingLayout,
256 propagate_nulls: bool,
257) -> PolarsResult<Series> {
258 let len = by.first().map_or(0, |c| c.len());
259 let n_splits = RAYON.current_num_threads().min(len).max(1);
260 if len < ROW_ENCODE_PAR_MIN_ROWS || n_splits == 1 {
261 return encode_rows_with_layout(by, layout, propagate_nulls);
262 }
263 let chunks = _split_offsets(len, n_splits)
264 .into_par_iter()
265 .map(|(offset, len)| {
266 let sliced = by
267 .iter()
268 .map(|c| c.slice(offset as i64, len))
269 .collect::<Vec<_>>();
270 encode_columns(&sliced, layout, propagate_nulls)
271 });
272 let chunks = RAYON.install(|| chunks.collect::<PolarsResult<Vec<_>>>())?;
273 Ok(unsafe {
275 Series::from_chunks_and_dtype_unchecked(PlSmallStr::EMPTY, chunks, &rows_dtype(layout))
276 })
277}
278
279pub fn get_optimal_row_encoding_layout(
282 by: &[Column],
283 others: &[&[Column]],
284 opts: &[RowEncodingOptions],
285) -> RowEncodingLayout {
286 let dtypes = by.iter().map(|c| c.dtype().clone()).collect_vec();
287 let opts = opts
288 .iter()
289 .enumerate()
290 .map(|(i, opt)| {
291 let encode_nulls = by[i].has_nulls() || others.iter().any(|o| o[i].has_nulls());
292 let mut opt = *opt;
293 opt.set(RowEncodingOptions::SKIP_NULL_ENCODING, !encode_nulls);
294 opt
295 })
296 .collect_vec();
297 get_row_encoding_layout(&dtypes, &opts, RowEncodingOutput::Optimal)
298}
299
300pub fn encode_rows_unordered(by: &[Column]) -> PolarsResult<BinaryOffsetChunked> {
301 let rows = _get_rows_encoded_unordered(by, false)?;
302 Ok(BinaryOffsetChunked::with_chunk(PlSmallStr::EMPTY, rows))
303}
304
305pub fn _get_rows_encoded_unordered(
307 by: &[Column],
308 propagate_nulls: bool,
309) -> PolarsResult<BinaryArray<i64>> {
310 encode_binary_offset(by, &unordered_opts(by.len()), propagate_nulls)
311}
312
313pub fn _get_rows_encoded(
314 by: &[Column],
315 descending: &[bool],
316 nulls_last: &[bool],
317) -> PolarsResult<BinaryArray<i64>> {
318 _get_rows_encoded_arr(by, descending, nulls_last, false)
319}
320
321pub fn _get_rows_encoded_ca(
322 name: PlSmallStr,
323 by: &[Column],
324 descending: &[bool],
325 nulls_last: &[bool],
326 propagate_nulls: bool,
327) -> PolarsResult<BinaryOffsetChunked> {
328 let rows_arr = _get_rows_encoded_arr(by, descending, nulls_last, propagate_nulls)?;
329 Ok(BinaryOffsetChunked::with_chunk(name, rows_arr))
330}
331
332pub fn _get_rows_encoded_arr(
333 by: &[Column],
334 descending: &[bool],
335 nulls_last: &[bool],
336 propagate_nulls: bool,
337) -> PolarsResult<BinaryArray<i64>> {
338 debug_assert_eq!(by.len(), descending.len());
339 encode_binary_offset(by, &ordered_opts(descending, nulls_last), propagate_nulls)
340}
341
342pub fn _get_rows_encoded_ca_unordered(
343 name: PlSmallStr,
344 by: &[Column],
345) -> PolarsResult<BinaryOffsetChunked> {
346 _get_rows_encoded_unordered(by, false).map(|rows| BinaryOffsetChunked::with_chunk(name, rows))
347}
348
349#[cfg(feature = "dtype-struct")]
351pub fn row_encoding_decode(
352 rows: &Series,
353 fields: &[Field],
354 layout: &RowEncodingLayout,
355) -> PolarsResult<StructChunked> {
356 let expected = rows_dtype(layout);
357 polars_ensure!(
358 rows.dtype() == &expected,
359 SchemaMismatch: "row decoding expected rows of type {}, got {}", expected, rows.dtype()
360 );
361 polars_ensure!(
362 layout.is_pass_through() || !rows.has_nulls(),
363 InvalidOperation: "cannot row decode null rows"
364 );
365
366 let struct_arrow_dtype = ArrowDataType::Struct(
367 fields
368 .iter()
369 .map(|v| v.to_physical().to_arrow(CompatLevel::newest()))
370 .collect(),
371 );
372
373 let chunks = rows
374 .chunks()
375 .iter()
376 .map(|array| {
377 let decoded_arrays =
378 unsafe { polars_row::decode::decode_rows_from_array(array.as_ref(), layout) };
379 assert_eq!(decoded_arrays.len(), fields.len());
380
381 StructArray::new(
382 struct_arrow_dtype.clone(),
383 array.len(),
384 decoded_arrays,
385 None,
386 )
387 .to_boxed()
388 })
389 .collect::<Vec<_>>();
390
391 Ok(unsafe {
392 StructChunked::from_chunks_and_dtype(
393 rows.name().clone(),
394 chunks,
395 DataType::Struct(fields.to_vec()),
396 )
397 })
398}