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