Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-plan/src/plans/ir/format.rs
8430 views
1
use std::fmt::{self, Display, Formatter, Write};
2
3
use polars_core::frame::DataFrame;
4
use polars_core::schema::Schema;
5
use polars_io::RowIndex;
6
use polars_utils::format_list_truncated;
7
use polars_utils::slice_enum::Slice;
8
use recursive::recursive;
9
10
use self::ir::dot::ScanSourcesDisplay;
11
use crate::dsl::deletion::DeletionFilesList;
12
use crate::prelude::*;
13
14
const INDENT_INCREMENT: usize = 2;
15
16
pub struct IRDisplay<'a> {
17
lp: IRPlanRef<'a>,
18
}
19
20
#[derive(Clone, Copy)]
21
pub struct ExprIRDisplay<'a> {
22
pub(crate) node: Node,
23
pub(crate) output_name: &'a OutputName,
24
pub(crate) expr_arena: &'a Arena<AExpr>,
25
}
26
27
impl<'a> ExprIRDisplay<'a> {
28
pub fn display_node(node: Node, expr_arena: &'a Arena<AExpr>) -> Self {
29
Self {
30
node,
31
output_name: &OutputName::None,
32
expr_arena,
33
}
34
}
35
}
36
37
/// Utility structure to display several [`ExprIR`]'s in a nice way
38
pub(crate) struct ExprIRSliceDisplay<'a, T: AsExpr> {
39
pub(crate) exprs: &'a [T],
40
pub(crate) expr_arena: &'a Arena<AExpr>,
41
}
42
43
pub(crate) trait AsExpr {
44
fn node(&self) -> Node;
45
fn output_name(&self) -> &OutputName;
46
}
47
48
impl AsExpr for Node {
49
fn node(&self) -> Node {
50
*self
51
}
52
fn output_name(&self) -> &OutputName {
53
&OutputName::None
54
}
55
}
56
57
impl AsExpr for ExprIR {
58
fn node(&self) -> Node {
59
self.node()
60
}
61
fn output_name(&self) -> &OutputName {
62
self.output_name_inner()
63
}
64
}
65
66
#[allow(clippy::too_many_arguments)]
67
fn write_scan(
68
f: &mut dyn fmt::Write,
69
name: &str,
70
sources: &ScanSources,
71
indent: usize,
72
n_columns: i64,
73
total_columns: usize,
74
row_estimation: Option<usize>,
75
predicate: &Option<ExprIRDisplay<'_>>,
76
pre_slice: Option<Slice>,
77
row_index: Option<&RowIndex>,
78
deletion_files: Option<&DeletionFilesList>,
79
) -> fmt::Result {
80
write!(
81
f,
82
"{:indent$}{name} SCAN {}",
83
"",
84
ScanSourcesDisplay(sources),
85
)?;
86
87
let total_columns = total_columns - usize::from(row_index.is_some());
88
if n_columns > 0 {
89
write!(
90
f,
91
"\n{:indent$}PROJECT {n_columns}/{total_columns} COLUMNS",
92
"",
93
)?;
94
} else {
95
write!(f, "\n{:indent$}PROJECT */{total_columns} COLUMNS", "")?;
96
}
97
if let Some(predicate) = predicate {
98
write!(f, "\n{:indent$}SELECTION: {predicate}", "")?;
99
}
100
if let Some(pre_slice) = pre_slice {
101
write!(f, "\n{:indent$}SLICE: {pre_slice:?}", "")?;
102
}
103
if let Some(row_index) = row_index {
104
write!(f, "\n{:indent$}ROW_INDEX: {}", "", row_index.name)?;
105
if row_index.offset != 0 {
106
write!(f, " (offset: {})", row_index.offset)?;
107
}
108
}
109
if let Some(deletion_files) = deletion_files {
110
write!(f, "\n{deletion_files}")?;
111
}
112
if let Some(row_estimation) = row_estimation {
113
write!(f, "\n{:indent$}ESTIMATED ROWS: {row_estimation}", "")?;
114
}
115
Ok(())
116
}
117
118
impl<'a> IRDisplay<'a> {
119
pub fn new(lp: IRPlanRef<'a>) -> Self {
120
Self { lp }
121
}
122
123
fn root(&self) -> &IR {
124
self.lp.root()
125
}
126
127
fn with_root(&self, root: Node) -> Self {
128
Self {
129
lp: self.lp.with_root(root),
130
}
131
}
132
133
fn display_expr(&self, root: &'a ExprIR) -> ExprIRDisplay<'a> {
134
ExprIRDisplay {
135
node: root.node(),
136
output_name: root.output_name_inner(),
137
expr_arena: self.lp.expr_arena,
138
}
139
}
140
141
fn display_expr_slice(&self, exprs: &'a [ExprIR]) -> ExprIRSliceDisplay<'a, ExprIR> {
142
ExprIRSliceDisplay {
143
exprs,
144
expr_arena: self.lp.expr_arena,
145
}
146
}
147
148
#[recursive]
149
fn _format(&self, f: &mut Formatter, indent: usize) -> fmt::Result {
150
if indent != 0 {
151
writeln!(f)?;
152
}
153
154
let sub_indent = indent + INDENT_INCREMENT;
155
use IR::*;
156
157
let ir_node = self.root();
158
let output_schema = ir_node.schema(self.lp.lp_arena);
159
let output_schema = output_schema.as_ref();
160
match ir_node {
161
Union { inputs, options } => {
162
write_ir_non_recursive(f, ir_node, self.lp.expr_arena, output_schema, indent)?;
163
let name = if let Some(slice) = options.slice {
164
format!("SLICED UNION: {slice:?}")
165
} else {
166
"UNION".to_string()
167
};
168
169
// 3 levels of indentation
170
// - 0 => UNION ... END UNION
171
// - 1 => PLAN 0, PLAN 1, ... PLAN N
172
// - 2 => actual formatting of plans
173
let sub_sub_indent = sub_indent + INDENT_INCREMENT;
174
for (i, plan) in inputs.iter().enumerate() {
175
write!(f, "\n{:sub_indent$}PLAN {i}:", "")?;
176
self.with_root(*plan)._format(f, sub_sub_indent)?;
177
}
178
write!(f, "\n{:indent$}END {name}", "")
179
},
180
HConcat { inputs, .. } => {
181
let sub_sub_indent = sub_indent + INDENT_INCREMENT;
182
write_ir_non_recursive(f, ir_node, self.lp.expr_arena, output_schema, indent)?;
183
for (i, plan) in inputs.iter().enumerate() {
184
write!(f, "\n{:sub_indent$}PLAN {i}:", "")?;
185
self.with_root(*plan)._format(f, sub_sub_indent)?;
186
}
187
write!(f, "\n{:indent$}END HCONCAT", "")
188
},
189
GroupBy { input, .. } => {
190
write_ir_non_recursive(f, ir_node, self.lp.expr_arena, output_schema, indent)?;
191
write!(f, "\n{:sub_indent$}FROM", "")?;
192
self.with_root(*input)._format(f, sub_indent)?;
193
Ok(())
194
},
195
Join {
196
input_left,
197
input_right,
198
left_on,
199
right_on,
200
options,
201
..
202
} => {
203
let left_on = self.display_expr_slice(left_on);
204
let right_on = self.display_expr_slice(right_on);
205
206
// Fused cross + filter (show as nested loop join)
207
if let Some(JoinTypeOptionsIR::CrossAndFilter { predicate }) = &options.options {
208
let predicate = self.display_expr(predicate);
209
let name = "NESTED LOOP";
210
write!(f, "{:indent$}{name} JOIN ON {predicate}:", "")?;
211
write!(f, "\n{:indent$}LEFT PLAN:", "")?;
212
self.with_root(*input_left)._format(f, sub_indent)?;
213
write!(f, "\n{:indent$}RIGHT PLAN:", "")?;
214
self.with_root(*input_right)._format(f, sub_indent)?;
215
write!(f, "\n{:indent$}END {name} JOIN", "")
216
} else {
217
let how = &options.args.how;
218
write!(f, "{:indent$}{how} JOIN:", "")?;
219
write!(f, "\n{:indent$}LEFT PLAN ON: {left_on}", "")?;
220
self.with_root(*input_left)._format(f, sub_indent)?;
221
write!(f, "\n{:indent$}RIGHT PLAN ON: {right_on}", "")?;
222
self.with_root(*input_right)._format(f, sub_indent)?;
223
write!(f, "\n{:indent$}END {how} JOIN", "")
224
}
225
},
226
MapFunction { input, .. } => {
227
write_ir_non_recursive(f, ir_node, self.lp.expr_arena, output_schema, indent)?;
228
self.with_root(*input)._format(f, sub_indent)
229
},
230
SinkMultiple { inputs } => {
231
write_ir_non_recursive(f, ir_node, self.lp.expr_arena, output_schema, indent)?;
232
233
// 3 levels of indentation
234
// - 0 => SINK_MULTIPLE ... END SINK_MULTIPLE
235
// - 1 => PLAN 0, PLAN 1, ... PLAN N
236
// - 2 => actual formatting of plans
237
let sub_sub_indent = sub_indent + 2;
238
for (i, plan) in inputs.iter().enumerate() {
239
write!(f, "\n{:sub_indent$}PLAN {i}:", "")?;
240
self.with_root(*plan)._format(f, sub_sub_indent)?;
241
}
242
write!(f, "\n{:indent$}END SINK_MULTIPLE", "")
243
},
244
#[cfg(feature = "merge_sorted")]
245
MergeSorted {
246
input_left,
247
input_right,
248
key: _,
249
} => {
250
write_ir_non_recursive(f, ir_node, self.lp.expr_arena, output_schema, indent)?;
251
write!(f, ":")?;
252
253
write!(f, "\n{:indent$}LEFT PLAN:", "")?;
254
self.with_root(*input_left)._format(f, sub_indent)?;
255
write!(f, "\n{:indent$}RIGHT PLAN:", "")?;
256
self.with_root(*input_right)._format(f, sub_indent)?;
257
write!(f, "\n{:indent$}END MERGE_SORTED", "")
258
},
259
ir_node => {
260
write_ir_non_recursive(f, ir_node, self.lp.expr_arena, output_schema, indent)?;
261
for input in ir_node.inputs() {
262
self.with_root(input)._format(f, sub_indent)?;
263
}
264
Ok(())
265
},
266
}
267
}
268
}
269
270
impl<'a> ExprIRDisplay<'a> {
271
fn with_slice<T: AsExpr>(&self, exprs: &'a [T]) -> ExprIRSliceDisplay<'a, T> {
272
ExprIRSliceDisplay {
273
exprs,
274
expr_arena: self.expr_arena,
275
}
276
}
277
278
fn with_root<T: AsExpr>(&self, root: &'a T) -> Self {
279
Self {
280
node: root.node(),
281
output_name: root.output_name(),
282
expr_arena: self.expr_arena,
283
}
284
}
285
}
286
287
impl Display for IRDisplay<'_> {
288
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
289
self._format(f, 0)
290
}
291
}
292
293
impl fmt::Debug for IRDisplay<'_> {
294
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
295
Display::fmt(&self, f)
296
}
297
}
298
299
impl<T: AsExpr> Display for ExprIRSliceDisplay<'_, T> {
300
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
301
// Display items in slice delimited by a comma
302
303
use std::fmt::Write;
304
305
let mut iter = self.exprs.iter();
306
307
f.write_char('[')?;
308
if let Some(fst) = iter.next() {
309
let fst = ExprIRDisplay {
310
node: fst.node(),
311
output_name: fst.output_name(),
312
expr_arena: self.expr_arena,
313
};
314
write!(f, "{fst}")?;
315
}
316
317
for expr in iter {
318
let expr = ExprIRDisplay {
319
node: expr.node(),
320
output_name: expr.output_name(),
321
expr_arena: self.expr_arena,
322
};
323
write!(f, ", {expr}")?;
324
}
325
326
f.write_char(']')?;
327
328
Ok(())
329
}
330
}
331
332
impl<T: AsExpr> fmt::Debug for ExprIRSliceDisplay<'_, T> {
333
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
334
Display::fmt(self, f)
335
}
336
}
337
338
impl Display for ExprIRDisplay<'_> {
339
#[recursive]
340
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
341
let root = self.expr_arena.get(self.node);
342
343
use AExpr::*;
344
match root {
345
Element => f.write_str("element()"),
346
#[cfg(feature = "dynamic_group_by")]
347
Rolling {
348
function,
349
index_column,
350
period,
351
offset,
352
closed_window: _,
353
} => {
354
let function = self.with_root(function);
355
let index_column = self.with_root(index_column);
356
write!(
357
f,
358
"{function}.rolling(by='{index_column}', offset={offset}, period={period})",
359
)
360
},
361
Over {
362
function,
363
partition_by,
364
order_by,
365
mapping: _,
366
} => {
367
let function = self.with_root(function);
368
let partition_by = self.with_slice(partition_by);
369
if let Some((order_by, _)) = order_by {
370
let order_by = self.with_root(order_by);
371
write!(
372
f,
373
"{function}.over(partition_by: {partition_by}, order_by: {order_by})"
374
)
375
} else {
376
write!(f, "{function}.over({partition_by})")
377
}
378
},
379
Len => write!(f, "len()"),
380
Explode { expr, options } => {
381
let expr = self.with_root(expr);
382
write!(f, "{expr}.explode(")?;
383
match (options.empty_as_null, options.keep_nulls) {
384
(true, true) => {},
385
(true, false) => f.write_str("keep_nulls=false")?,
386
(false, true) => f.write_str("empty_as_null=false")?,
387
(false, false) => f.write_str("empty_as_null=false, keep_nulls=false")?,
388
}
389
f.write_char(')')
390
},
391
Column(name) => write!(f, "col(\"{name}\")"),
392
#[cfg(feature = "dtype-struct")]
393
StructField(name) => write!(f, "field(\"{name}\")"),
394
Literal(v) => write!(f, "{v:?}"),
395
BinaryExpr { left, op, right } => {
396
let left = self.with_root(left);
397
let right = self.with_root(right);
398
write!(f, "[({left}) {op:?} ({right})]")
399
},
400
Sort { expr, options } => {
401
let expr = self.with_root(expr);
402
if options.descending {
403
write!(f, "{expr}.sort(desc)")
404
} else {
405
write!(f, "{expr}.sort(asc)")
406
}
407
},
408
SortBy {
409
expr,
410
by,
411
sort_options,
412
} => {
413
let expr = self.with_root(expr);
414
let by = self.with_slice(by);
415
write!(f, "{expr}.sort_by(by={by}, sort_option={sort_options:?})",)
416
},
417
Filter { input, by } => {
418
let input = self.with_root(input);
419
let by = self.with_root(by);
420
421
write!(f, "{input}.filter({by})")
422
},
423
Gather {
424
expr,
425
idx,
426
returns_scalar,
427
null_on_oob: _,
428
} => {
429
let expr = self.with_root(expr);
430
let idx = self.with_root(idx);
431
expr.fmt(f)?;
432
433
if *returns_scalar {
434
write!(f, ".get({idx})")
435
} else {
436
write!(f, ".gather({idx})")
437
}
438
},
439
Agg(agg) => {
440
use IRAggExpr::*;
441
match agg {
442
Min {
443
input,
444
propagate_nans,
445
} => {
446
self.with_root(input).fmt(f)?;
447
if *propagate_nans {
448
write!(f, ".nan_min()")
449
} else {
450
write!(f, ".min()")
451
}
452
},
453
Max {
454
input,
455
propagate_nans,
456
} => {
457
self.with_root(input).fmt(f)?;
458
if *propagate_nans {
459
write!(f, ".nan_max()")
460
} else {
461
write!(f, ".max()")
462
}
463
},
464
Median(expr) => write!(f, "{}.median()", self.with_root(expr)),
465
Mean(expr) => write!(f, "{}.mean()", self.with_root(expr)),
466
First(expr) => write!(f, "{}.first()", self.with_root(expr)),
467
FirstNonNull(expr) => write!(f, "{}.first_non_null()", self.with_root(expr)),
468
Last(expr) => write!(f, "{}.last()", self.with_root(expr)),
469
LastNonNull(expr) => write!(f, "{}.last_non_null()", self.with_root(expr)),
470
Item { input, allow_empty } => {
471
self.with_root(input).fmt(f)?;
472
if *allow_empty {
473
write!(f, ".item(allow_empty=true)")
474
} else {
475
write!(f, ".item()")
476
}
477
},
478
Implode(expr) => write!(f, "{}.implode()", self.with_root(expr)),
479
NUnique(expr) => write!(f, "{}.n_unique()", self.with_root(expr)),
480
Sum(expr) => write!(f, "{}.sum()", self.with_root(expr)),
481
AggGroups(expr) => write!(f, "{}.groups()", self.with_root(expr)),
482
Count {
483
input,
484
include_nulls: false,
485
} => write!(f, "{}.count()", self.with_root(input)),
486
Count {
487
input,
488
include_nulls: true,
489
} => write!(f, "{}.len()", self.with_root(input)),
490
Var(expr, _) => write!(f, "{}.var()", self.with_root(expr)),
491
Std(expr, _) => write!(f, "{}.std()", self.with_root(expr)),
492
Quantile {
493
expr,
494
quantile,
495
method,
496
} => write!(
497
f,
498
"{}.quantile({}, interpolation='{}')",
499
self.with_root(expr),
500
self.with_root(quantile),
501
<&'static str>::from(method),
502
),
503
}
504
},
505
Cast {
506
expr,
507
dtype,
508
options,
509
} => {
510
self.with_root(expr).fmt(f)?;
511
if options.is_strict() {
512
write!(f, ".strict_cast({dtype:?})")
513
} else {
514
write!(f, ".cast({dtype:?})")
515
}
516
},
517
Ternary {
518
predicate,
519
truthy,
520
falsy,
521
} => {
522
let predicate = self.with_root(predicate);
523
let truthy = self.with_root(truthy);
524
let falsy = self.with_root(falsy);
525
write!(f, "when({predicate}).then({truthy}).otherwise({falsy})",)
526
},
527
Function {
528
input, function, ..
529
} => {
530
let fst = self.with_root(&input[0]);
531
fst.fmt(f)?;
532
if input.len() >= 2 {
533
write!(f, ".{function}({})", self.with_slice(&input[1..]))
534
} else {
535
write!(f, ".{function}()")
536
}
537
},
538
AnonymousFunction { input, fmt_str, .. } | AnonymousAgg { input, fmt_str, .. } => {
539
let fst = self.with_root(&input[0]);
540
fst.fmt(f)?;
541
if input.len() >= 2 {
542
write!(f, ".{fmt_str}({})", self.with_slice(&input[1..]))
543
} else {
544
write!(f, ".{fmt_str}()")
545
}
546
},
547
Eval {
548
expr,
549
evaluation,
550
variant,
551
} => {
552
let expr = self.with_root(expr);
553
let evaluation = self.with_root(evaluation);
554
match variant {
555
EvalVariant::List => write!(f, "{expr}.list.eval({evaluation})"),
556
EvalVariant::ListAgg => write!(f, "{expr}.list.agg({evaluation})"),
557
EvalVariant::Array { as_list: false } => {
558
write!(f, "{expr}.array.eval({evaluation})")
559
},
560
EvalVariant::Array { as_list: true } => {
561
write!(f, "{expr}.array.eval({evaluation}, as_list=true)")
562
},
563
EvalVariant::ArrayAgg => write!(f, "{expr}.array.agg({evaluation})"),
564
EvalVariant::Cumulative { min_samples } => write!(
565
f,
566
"{expr}.cumulative_eval({evaluation}, min_samples={min_samples})"
567
),
568
}
569
},
570
#[cfg(feature = "dtype-struct")]
571
StructEval { expr, evaluation } => {
572
let expr = self.with_root(expr);
573
let evaluation = self.with_slice(evaluation);
574
write!(f, "{expr}.struct.with_fields({evaluation})")
575
},
576
Slice {
577
input,
578
offset,
579
length,
580
} => {
581
let input = self.with_root(input);
582
let offset = self.with_root(offset);
583
let length = self.with_root(length);
584
585
write!(f, "{input}.slice(offset={offset}, length={length})")
586
},
587
}?;
588
589
match self.output_name {
590
OutputName::None => {},
591
OutputName::LiteralLhs(_) => {},
592
OutputName::ColumnLhs(_) => {},
593
#[cfg(feature = "dtype-struct")]
594
OutputName::Field(_) => {},
595
OutputName::Alias(name) => {
596
if root.to_name(self.expr_arena) != name {
597
write!(f, r#".alias("{name}")"#)?;
598
}
599
},
600
}
601
602
Ok(())
603
}
604
}
605
606
impl fmt::Debug for ExprIRDisplay<'_> {
607
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
608
Display::fmt(self, f)
609
}
610
}
611
612
pub(crate) struct ColumnsDisplay<'a>(pub(crate) &'a Schema);
613
614
impl fmt::Display for ColumnsDisplay<'_> {
615
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616
let len = self.0.len();
617
let mut iter_names = self.0.iter_names().enumerate();
618
619
const MAX_LEN: usize = 32;
620
const ADD_PER_ITEM: usize = 4;
621
622
let mut current_len = 0;
623
624
if let Some((_, fst)) = iter_names.next() {
625
write!(f, "\"{fst}\"")?;
626
627
current_len += fst.len() + ADD_PER_ITEM;
628
}
629
630
for (i, col) in iter_names {
631
current_len += col.len() + ADD_PER_ITEM;
632
633
if current_len > MAX_LEN {
634
write!(f, ", ... {} other ", len - i)?;
635
if len - i == 1 {
636
f.write_str("column")?;
637
} else {
638
f.write_str("columns")?;
639
}
640
641
break;
642
}
643
644
write!(f, ", \"{col}\"")?;
645
}
646
647
Ok(())
648
}
649
}
650
651
impl fmt::Debug for Operator {
652
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653
Display::fmt(self, f)
654
}
655
}
656
657
impl fmt::Debug for LiteralValue {
658
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
659
use LiteralValue::*;
660
661
match self {
662
Self::Scalar(sc) => write!(f, "{}", sc.value()),
663
Self::Series(s) => {
664
let name = s.name();
665
if name.is_empty() {
666
write!(f, "Series")
667
} else {
668
write!(f, "Series[{name}]")
669
}
670
},
671
Range(range) => fmt::Debug::fmt(range, f),
672
Dyn(d) => fmt::Debug::fmt(d, f),
673
}
674
}
675
}
676
677
impl fmt::Debug for DynLiteralValue {
678
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
679
match self {
680
Self::Int(v) => write!(f, "dyn int: {v}"),
681
Self::Float(v) => write!(f, "dyn float: {v}"),
682
Self::Str(v) => write!(f, "dyn str: {v}"),
683
Self::List(_) => todo!(),
684
}
685
}
686
}
687
688
impl fmt::Debug for RangeLiteralValue {
689
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
690
write!(f, "range({}, {})", self.low, self.high)
691
}
692
}
693
694
pub fn write_ir_non_recursive(
695
f: &mut dyn fmt::Write,
696
ir: &IR,
697
expr_arena: &Arena<AExpr>,
698
output_schema: &Schema,
699
indent: usize,
700
) -> fmt::Result {
701
match ir {
702
#[cfg(feature = "python")]
703
IR::PythonScan { options } => {
704
let total_columns = options.schema.len();
705
let n_columns = options
706
.with_columns
707
.as_ref()
708
.map(|s| s.len() as i64)
709
.unwrap_or(-1);
710
711
let predicate = match &options.predicate {
712
PythonPredicate::Polars(e) => Some(e.display(expr_arena)),
713
PythonPredicate::PyArrow(_) => None,
714
PythonPredicate::None => None,
715
};
716
717
write_scan(
718
f,
719
"PYTHON",
720
&ScanSources::default(),
721
indent,
722
n_columns,
723
total_columns,
724
None,
725
&predicate,
726
options
727
.n_rows
728
.map(|len| polars_utils::slice_enum::Slice::Positive { offset: 0, len }),
729
None,
730
None,
731
)
732
},
733
IR::Slice {
734
input: _,
735
offset,
736
len,
737
} => {
738
write!(f, "{:indent$}SLICE[offset: {offset}, len: {len}]", "")
739
},
740
IR::Filter {
741
input: _,
742
predicate,
743
} => {
744
let predicate = predicate.display(expr_arena);
745
// this one is writeln because we don't increase indent (which inserts a line)
746
write!(f, "{:indent$}FILTER {predicate}", "")?;
747
write!(f, "\n{:indent$}FROM", "")
748
},
749
IR::Scan {
750
sources,
751
file_info,
752
predicate,
753
predicate_file_skip_applied: _,
754
scan_type,
755
unified_scan_args,
756
hive_parts: _,
757
output_schema: _,
758
} => {
759
let n_columns = unified_scan_args
760
.projection
761
.as_ref()
762
.map(|columns| columns.len() as i64)
763
.unwrap_or(-1);
764
765
let row_estimation = if file_info.row_estimation.1 != usize::MAX {
766
Some(file_info.row_estimation.1)
767
} else {
768
None
769
};
770
771
let predicate = predicate.as_ref().map(|p| p.display(expr_arena));
772
773
write_scan(
774
f,
775
(&**scan_type).into(),
776
sources,
777
indent,
778
n_columns,
779
file_info.schema.len(),
780
row_estimation,
781
&predicate,
782
unified_scan_args.pre_slice.clone(),
783
unified_scan_args.row_index.as_ref(),
784
unified_scan_args.deletion_files.as_ref(),
785
)
786
},
787
IR::DataFrameScan {
788
df: _,
789
schema,
790
output_schema,
791
} => {
792
let total_columns = schema.len();
793
let (n_columns, projected) = if let Some(schema) = output_schema {
794
(
795
format!("{}", schema.len()),
796
format_list_truncated!(schema.iter_names(), 4, '"'),
797
)
798
} else {
799
("*".to_string(), "".to_string())
800
};
801
write!(
802
f,
803
"{:indent$}DF {}; PROJECT{} {}/{} COLUMNS",
804
"",
805
format_list_truncated!(schema.iter_names(), 4, '"'),
806
projected,
807
n_columns,
808
total_columns,
809
)
810
},
811
IR::SimpleProjection { input: _, columns } => {
812
let num_columns = columns.as_ref().len();
813
let total_columns = output_schema.len();
814
815
let columns = ColumnsDisplay(columns.as_ref());
816
write!(
817
f,
818
"{:indent$}simple π {num_columns}/{total_columns} [{columns}]",
819
""
820
)
821
},
822
IR::Select {
823
input: _,
824
expr,
825
schema: _,
826
options: _,
827
} => {
828
// @NOTE: Maybe there should be a clear delimiter here?
829
let exprs = ExprIRSliceDisplay {
830
exprs: expr,
831
expr_arena,
832
};
833
write!(f, "{:indent$}SELECT {exprs}", "")?;
834
Ok(())
835
},
836
IR::Sort {
837
input: _,
838
by_column,
839
slice,
840
sort_options,
841
} => {
842
write!(f, "{:indent$}", "")?;
843
844
f.write_str("SORT BY ")?;
845
846
if slice.is_some()
847
|| sort_options.maintain_order
848
|| sort_options.descending.iter().any(|v| *v)
849
|| sort_options.nulls_last.iter().any(|v| *v)
850
{
851
f.write_char('[')?;
852
853
let mut comma = false;
854
if let Some((o, l)) = slice {
855
write!(f, "slice: ({o}, {l})")?;
856
comma = true;
857
}
858
if sort_options.maintain_order {
859
if comma {
860
f.write_str(", ")?;
861
}
862
f.write_str("maintain_order: true")?;
863
comma = true;
864
}
865
if sort_options.descending.iter().any(|v| *v) {
866
if comma {
867
f.write_str(", ")?;
868
}
869
write!(f, "descending: {:?}", sort_options.descending.as_slice())?;
870
comma = true;
871
}
872
if sort_options.nulls_last.iter().any(|v| *v) {
873
if comma {
874
f.write_str(", ")?;
875
}
876
write!(f, "nulls_last: {:?}", sort_options.nulls_last.as_slice())?;
877
}
878
879
f.write_str("] ")?;
880
}
881
882
write!(
883
f,
884
"{}",
885
ExprIRSliceDisplay {
886
exprs: by_column,
887
expr_arena,
888
}
889
)
890
},
891
IR::Cache { input: _, id } => write!(f, "{:indent$}CACHE[id: {id}]", ""),
892
IR::GroupBy {
893
input: _,
894
keys,
895
aggs,
896
schema: _,
897
maintain_order,
898
options: _,
899
apply,
900
} => write_group_by(
901
f,
902
indent,
903
expr_arena,
904
keys,
905
aggs,
906
apply.as_ref(),
907
*maintain_order,
908
),
909
IR::Join {
910
input_left: _,
911
input_right: _,
912
schema: _,
913
left_on,
914
right_on,
915
options,
916
} => {
917
let left_on = ExprIRSliceDisplay {
918
exprs: left_on,
919
expr_arena,
920
};
921
let right_on = ExprIRSliceDisplay {
922
exprs: right_on,
923
expr_arena,
924
};
925
926
// Fused cross + filter (show as nested loop join)
927
if let Some(JoinTypeOptionsIR::CrossAndFilter { predicate }) = &options.options {
928
let predicate = predicate.display(expr_arena);
929
write!(f, "{:indent$}NESTED_LOOP JOIN ON {predicate}", "")?;
930
} else {
931
let how = &options.args.how;
932
write!(f, "{:indent$}{how} JOIN", "")?;
933
write!(f, "\n{:indent$}LEFT PLAN ON: {left_on}", "")?;
934
write!(f, "\n{:indent$}RIGHT PLAN ON: {right_on}", "")?;
935
}
936
937
Ok(())
938
},
939
IR::HStack {
940
input: _,
941
exprs,
942
schema: _,
943
options: _,
944
} => {
945
// @NOTE: Maybe there should be a clear delimiter here?
946
let exprs = ExprIRSliceDisplay { exprs, expr_arena };
947
948
write!(f, "{:indent$} WITH_COLUMNS:", "",)?;
949
write!(f, "\n{:indent$} {exprs} ", "")
950
},
951
IR::Distinct { input: _, options } => {
952
write!(
953
f,
954
"{:indent$}UNIQUE[maintain_order: {:?}, keep_strategy: {:?}] BY {:?}",
955
"", options.maintain_order, options.keep_strategy, options.subset
956
)
957
},
958
IR::MapFunction { input: _, function } => write!(f, "{:indent$}{function}", ""),
959
IR::Union { inputs: _, options } => {
960
let name = if let Some(slice) = options.slice {
961
format!("SLICED UNION: {slice:?}")
962
} else {
963
"UNION".to_string()
964
};
965
write!(f, "{:indent$}{name}", "")
966
},
967
IR::HConcat {
968
inputs: _,
969
schema: _,
970
options: _,
971
} => write!(f, "{:indent$}HCONCAT", ""),
972
IR::ExtContext {
973
input: _,
974
contexts: _,
975
schema: _,
976
} => write!(f, "{:indent$}EXTERNAL_CONTEXT", ""),
977
IR::Sink { input: _, payload } => {
978
let name = match payload {
979
SinkTypeIR::Memory => "SINK (memory)",
980
SinkTypeIR::Callback { .. } => "SINK (callback)",
981
SinkTypeIR::File { .. } => "SINK (file)",
982
SinkTypeIR::Partitioned { .. } => "SINK (partition)",
983
};
984
write!(f, "{:indent$}{name}", "")
985
},
986
IR::SinkMultiple { inputs: _ } => write!(f, "{:indent$}SINK_MULTIPLE", ""),
987
#[cfg(feature = "merge_sorted")]
988
IR::MergeSorted {
989
input_left: _,
990
input_right: _,
991
key,
992
} => write!(f, "{:indent$}MERGE SORTED ON '{key}'", ""),
993
IR::Invalid => write!(f, "{:indent$}INVALID", ""),
994
}
995
}
996
997
pub fn write_group_by(
998
f: &mut dyn fmt::Write,
999
indent: usize,
1000
expr_arena: &Arena<AExpr>,
1001
keys: &[ExprIR],
1002
aggs: &[ExprIR],
1003
apply: Option<&PlanCallback<DataFrame, DataFrame>>,
1004
maintain_order: bool,
1005
) -> fmt::Result {
1006
let sub_indent = indent + INDENT_INCREMENT;
1007
let keys = ExprIRSliceDisplay {
1008
exprs: keys,
1009
expr_arena,
1010
};
1011
write!(
1012
f,
1013
"{:indent$}AGGREGATE[maintain_order: {}]",
1014
"", maintain_order
1015
)?;
1016
if apply.is_some() {
1017
write!(f, "\n{:sub_indent$}MAP_GROUPS BY {keys}", "")?;
1018
write!(f, "\n{:sub_indent$}FROM", "")?;
1019
} else {
1020
let aggs = ExprIRSliceDisplay {
1021
exprs: aggs,
1022
expr_arena,
1023
};
1024
write!(f, "\n{:sub_indent$}{aggs} BY {keys}", "")?;
1025
}
1026
1027
Ok(())
1028
}
1029
1030