1use std::num::NonZeroUsize;
2use std::sync::Arc;
3
4use polars_core::frame::DataFrame;
5#[cfg(any(
6 feature = "dtype-date",
7 feature = "dtype-datetime",
8 feature = "dtype-time"
9))]
10use polars_core::prelude::DataType;
11use polars_core::prelude::{IdxSize, InitHashMaps, PlHashMap, PlIndexMap, SortMultipleOptions};
12use polars_core::schema::{Schema, SchemaRef};
13use polars_error::PolarsResult;
14use polars_io::RowIndex;
15use polars_io::cloud::CloudOptions;
16use polars_ops::frame::JoinArgs;
17#[cfg(any(
18 feature = "dtype-date",
19 feature = "dtype-datetime",
20 feature = "dtype-time"
21))]
22use polars_plan::dsl::StrptimeOptions;
23use polars_plan::dsl::deletion::DeletionFilesList;
24use polars_plan::dsl::{
25 CastColumnsPolicy, ColumnsUdf, FileSinkOptions, JoinTypeOptionsIR, MissingColumnsPolicy,
26 PartitionedSinkOptionsIR, PredicateFileSkip, ScanSources, TableStatistics,
27};
28use polars_plan::plans::expr_ir::ExprIR;
29use polars_plan::plans::hive::HivePartitionsDf;
30use polars_plan::plans::{AExpr, DataFrameUdf, DynamicPred, FunctionArgMap, IR};
31
32mod fmt;
33mod io;
34mod lower_expr;
35mod lower_group_by;
36mod lower_ir;
37mod to_graph;
38
39pub use fmt::{NodeStyle, visualize_plan};
40use polars_plan::prelude::PlanCallback;
41#[cfg(feature = "dynamic_group_by")]
42use polars_time::DynamicGroupOptions;
43use polars_time::{ClosedWindow, Duration};
44use polars_utils::arena::{Arena, Node};
45use polars_utils::pl_str::PlSmallStr;
46use polars_utils::slice_enum::Slice;
47use polars_utils::{UnitVec, unitvec};
48use slotmap::{SecondaryMap, SlotMap};
49pub use to_graph::physical_plan_to_graph;
50
51pub use self::lower_ir::StreamingLowerIRContext;
52use crate::nodes::io_sources::multi_scan::components::forbid_extra_columns::ForbidExtraColumns;
53use crate::nodes::io_sources::multi_scan::components::projection::builder::ProjectionBuilder;
54use crate::nodes::io_sources::multi_scan::reader_interface::builder::FileReaderBuilder;
55use crate::physical_plan::lower_expr::ExprCache;
56
57slotmap::new_key_type! {
58 pub struct PhysNodeKey;
60}
61
62impl PhysNodeKey {
63 pub fn as_ffi(&self) -> u64 {
64 self.0.as_ffi()
65 }
66}
67
68#[derive(Clone, Debug)]
73pub struct PhysNode {
74 output_schemas: UnitVec<Arc<Schema>>,
75 kind: PhysNodeKind,
76}
77
78impl PhysNode {
79 pub fn new(output_schema: Arc<Schema>, kind: PhysNodeKind) -> Self {
80 Self {
81 output_schemas: unitvec![output_schema],
82 kind,
83 }
84 }
85
86 pub fn new_multi_output(output_schemas: UnitVec<Arc<Schema>>, kind: PhysNodeKind) -> Self {
87 Self {
88 output_schemas,
89 kind,
90 }
91 }
92
93 pub fn output_schema(&self, port_idx: usize) -> &Arc<Schema> {
94 &self.output_schemas[port_idx]
95 }
96
97 pub fn output_schema_mut(&mut self, port_idx: usize) -> &mut Arc<Schema> {
98 &mut self.output_schemas[port_idx]
99 }
100
101 pub fn kind(&self) -> &PhysNodeKind {
102 &self.kind
103 }
104}
105
106#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
110pub struct PhysStream {
111 pub node: PhysNodeKey,
112 pub port: usize,
113}
114
115impl PhysStream {
116 #[allow(unused)]
117 pub fn new(node: PhysNodeKey, port: usize) -> Self {
118 Self { node, port }
119 }
120
121 pub fn first(node: PhysNodeKey) -> Self {
123 Self { node, port: 0 }
124 }
125
126 pub fn output_schema<'sm>(&self, sm: &'sm SlotMap<PhysNodeKey, PhysNode>) -> &'sm Arc<Schema> {
127 sm[self.node].output_schema(self.port)
128 }
129
130 pub fn output_schema_mut<'sm>(
131 &self,
132 sm: &'sm mut SlotMap<PhysNodeKey, PhysNode>,
133 ) -> &'sm mut Arc<Schema> {
134 sm[self.node].output_schema_mut(self.port)
135 }
136}
137
138#[derive(Clone, Debug, Copy)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142#[cfg_attr(
143 feature = "physical_plan_visualization_schema",
144 derive(schemars::JsonSchema)
145)]
146pub enum ZipBehavior {
147 NullExtend,
149 Broadcast,
151 Strict,
153}
154
155#[derive(Clone, Debug)]
156pub enum PhysNodeKind {
157 InMemorySource {
158 df: Arc<DataFrame>,
159 disable_morsel_split: bool,
160 },
161
162 Select {
163 input: PhysStream,
164 selectors: Vec<ExprIR>,
165 extend_original: bool,
166 },
167
168 InputIndependentSelect {
169 selectors: Vec<ExprIR>,
170 },
171
172 WithRowIndex {
173 input: PhysStream,
174 name: PlSmallStr,
175 offset: Option<IdxSize>,
176 },
177
178 Reduce {
179 input: PhysStream,
180 exprs: Vec<ExprIR>,
181 },
182
183 StreamingSlice {
184 input: PhysStream,
185 offset: usize,
186 length: usize,
187 },
188
189 NegativeSlice {
190 input: PhysStream,
191 offset: i64,
192 length: usize,
193 },
194
195 DynamicSlice {
196 input: PhysStream,
197 offset: PhysStream,
198 length: PhysStream,
199 },
200
201 Shift {
202 input: PhysStream,
203 offset: PhysStream,
204 fill: Option<PhysStream>,
205 },
206
207 Filter {
208 input: PhysStream,
209 predicate: ExprIR,
210 },
211
212 SimpleProjection {
213 input: PhysStream,
214 columns: PlIndexMap<PlSmallStr, PlSmallStr>,
216 },
217
218 InMemorySink {
219 input: PhysStream,
220 },
221
222 CallbackSink {
223 input: PhysStream,
224 function: PlanCallback<DataFrame, bool>,
225 maintain_order: bool,
226 chunk_size: Option<NonZeroUsize>,
227 },
228
229 FileSink {
230 input: PhysStream,
231 options: FileSinkOptions,
232 },
233
234 PartitionedSink {
235 input: PhysStream,
236 options: PartitionedSinkOptionsIR,
237 },
238
239 SinkMultiple {
240 sinks: Vec<PhysNodeKey>,
241 },
242
243 InMemoryMap {
247 input: PhysStream,
248 map: Arc<dyn DataFrameUdf>,
249 format_str: Option<String>,
251 },
252
253 Map {
254 input: PhysStream,
255 map: Arc<dyn DataFrameUdf>,
256 format_str: Option<String>,
258 },
259
260 ColumnarFunction {
261 inputs: Vec<PhysStream>,
262 func: Arc<dyn ColumnsUdf>,
263 arg_map: Option<FunctionArgMap>,
264 output_name: PlSmallStr,
265 format_str: Option<String>,
266 },
267
268 #[cfg(any(
270 feature = "dtype-date",
271 feature = "dtype-datetime",
272 feature = "dtype-time"
273 ))]
274 StrptimeInfer {
275 input: PhysStream,
276 dtype: DataType,
277 options: StrptimeOptions,
278
279 ambiguous_is_raise: bool,
286 },
287
288 SortedGroupBy {
289 input: PhysStream,
290 key: PlSmallStr,
291 aggs: Vec<ExprIR>,
292 slice: Option<(IdxSize, IdxSize)>,
293 },
294
295 Sort {
296 input: PhysStream,
297 by_column: Vec<ExprIR>,
298 slice: Option<(i64, usize)>,
299 sort_options: SortMultipleOptions,
300 },
301
302 TopK {
303 input: PhysStream,
304 k: PhysStream,
305 by_column: Vec<ExprIR>,
306 reverse: Vec<bool>,
307 nulls_last: Vec<bool>,
308 dyn_pred: Option<DynamicPred>,
309 },
310
311 Repeat {
312 value: PhysStream,
313 repeats: PhysStream,
314 },
315
316 #[cfg(feature = "cum_agg")]
317 CumAgg {
318 input: PhysStream,
319 kind: crate::nodes::cum_agg::CumAggKind,
320 },
321
322 GatherEvery {
324 input: PhysStream,
325 n: usize,
326 offset: usize,
327 },
328 ForwardFill {
329 input: PhysStream,
330 limit: Option<IdxSize>,
331 },
332 BackwardFill {
333 input: PhysStream,
334 limit: Option<IdxSize>,
335 },
336 #[cfg(feature = "interpolate")]
337 Interpolate {
338 input: PhysStream,
339 method: polars_ops::series::InterpolationMethod,
340 },
341 Rle(PhysStream),
342 RleId(PhysStream),
343 SortedUnique {
344 input: PhysStream,
345 keys: Vec<PlSmallStr>,
346 },
347 PeakMinMax {
348 input: PhysStream,
349 is_peak_max: bool,
350 },
351
352 OrderedUnion {
353 inputs: Vec<PhysStream>,
354 },
355
356 UnorderedUnion {
357 inputs: Vec<PhysStream>,
358 },
359
360 Zip {
361 inputs: Vec<PhysStream>,
362 zip_behavior: ZipBehavior,
363 },
364
365 #[allow(unused)]
366 Multiplexer {
367 input: PhysStream,
368 },
369
370 MultiScan {
371 scan_sources: ScanSources,
372
373 file_reader_builder: Arc<dyn FileReaderBuilder>,
374 cloud_options: Option<Arc<CloudOptions>>,
375
376 file_projection_builder: ProjectionBuilder,
378 output_schema: SchemaRef,
380
381 row_index: Option<RowIndex>,
382 pre_slice: Option<Slice>,
383 predicate: Option<ExprIR>,
384 predicate_file_skip_applied: Option<PredicateFileSkip>,
385
386 hive_parts: Option<HivePartitionsDf>,
387 include_file_paths: Option<PlSmallStr>,
388 cast_columns_policy: CastColumnsPolicy,
389 missing_columns_policy: MissingColumnsPolicy,
390 forbid_extra_columns: Option<ForbidExtraColumns>,
391
392 deletion_files: Option<DeletionFilesList>,
393 table_statistics: Option<TableStatistics>,
394
395 file_schema: SchemaRef,
397 disable_morsel_split: bool,
398 },
399
400 #[cfg(feature = "python")]
401 PythonScan {
402 options: polars_plan::plans::python::PythonOptions,
403 },
404
405 GroupBy {
406 inputs: Vec<PhysStream>,
407 key_per_input: Vec<Vec<ExprIR>>,
409 aggs_per_input: Vec<Vec<ExprIR>>,
411 },
412
413 #[cfg(feature = "dynamic_group_by")]
414 DynamicGroupBy {
415 input: PhysStream,
416 options: DynamicGroupOptions,
417 aggs: Vec<ExprIR>,
418 slice: Option<(IdxSize, IdxSize)>,
419 },
420
421 #[cfg(feature = "dynamic_group_by")]
422 RollingGroupBy {
423 input: PhysStream,
424 index_column: PlSmallStr,
425 period: Duration,
426 offset: Duration,
427 closed: ClosedWindow,
428 slice: Option<(IdxSize, IdxSize)>,
429 aggs: Vec<ExprIR>,
430 },
431
432 #[cfg(feature = "is_first_distinct")]
433 IsFirstDistinct {
434 input: PhysStream,
435 out_name: PlSmallStr,
436 columns: Vec<PlSmallStr>,
437 },
438
439 EquiJoin {
440 input_left: PhysStream,
441 input_right: PhysStream,
442 left_on: Vec<ExprIR>,
443 right_on: Vec<ExprIR>,
444 args: JoinArgs,
445 },
446
447 MergeJoin {
448 input_left: PhysStream,
449 input_right: PhysStream,
450 left_on: Vec<PlSmallStr>,
451 right_on: Vec<PlSmallStr>,
452 tmp_left_key_col: Option<PlSmallStr>,
453 tmp_right_key_col: Option<PlSmallStr>,
454 descending: bool,
455 nulls_last: bool,
456 keys_row_encoded: bool,
457 args: JoinArgs,
458 },
459
460 SemiAntiJoin {
461 input_left: PhysStream,
462 input_right: PhysStream,
463 left_on: Vec<ExprIR>,
464 right_on: Vec<ExprIR>,
465 args: JoinArgs,
466 output_bool: bool,
467 },
468
469 CrossJoin {
470 input_left: PhysStream,
471 input_right: PhysStream,
472 args: JoinArgs,
473 },
474
475 AsOfJoin {
476 input_left: PhysStream,
477 input_right: PhysStream,
478 left_on: PlSmallStr,
479 right_on: PlSmallStr,
480 tmp_left_key_col: Option<PlSmallStr>,
481 tmp_right_key_col: Option<PlSmallStr>,
482 by_descending: Option<Vec<bool>>,
483 by_nulls_last: Option<Vec<bool>>,
484 args: JoinArgs,
485 },
486
487 #[cfg(feature = "iejoin")]
488 RangeJoin {
489 input_left: PhysStream,
490 input_right: PhysStream,
491 left_on: Vec<PlSmallStr>,
492 right_on: Vec<PlSmallStr>,
493 tmp_left_key_cols: Vec<Option<PlSmallStr>>,
494 tmp_right_key_cols: Vec<Option<PlSmallStr>>,
495 descending: bool,
496 args: JoinArgs,
497 options: polars_ops::frame::IEJoinOptions,
498 },
499
500 InMemoryJoin {
504 input_left: PhysStream,
505 input_right: PhysStream,
506 left_on: Vec<ExprIR>,
507 right_on: Vec<ExprIR>,
508 args: JoinArgs,
509 options: Option<JoinTypeOptionsIR>,
510 },
511
512 #[cfg(feature = "merge_sorted")]
513 MergeSorted {
514 input_left: PhysStream,
515 input_right: PhysStream,
516 maintain_order: bool,
517 },
518
519 Gather {
520 input: PhysStream,
521 idxs: PhysStream,
522 null_on_oob: bool,
523 },
524
525 #[cfg(feature = "ewma")]
526 EwmMean {
527 input: PhysStream,
528 options: polars_ops::series::EWMOptions,
529 },
530
531 #[cfg(feature = "ewma")]
532 EwmVar {
533 input: PhysStream,
534 options: polars_ops::series::EWMOptions,
535 },
536
537 #[cfg(feature = "ewma")]
538 EwmStd {
539 input: PhysStream,
540 options: polars_ops::series::EWMOptions,
541 },
542}
543
544fn visit_node_inputs_mut(
545 roots: Vec<PhysNodeKey>,
546 phys_sm: &mut SlotMap<PhysNodeKey, PhysNode>,
547 mut visit: impl FnMut(&mut PhysStream),
548) {
549 let mut to_visit = roots;
550 let mut seen: SecondaryMap<PhysNodeKey, ()> =
551 to_visit.iter().copied().map(|n| (n, ())).collect();
552 macro_rules! rec {
553 ($n:expr) => {
554 let n = $n;
555 if seen.insert(n, ()).is_none() {
556 to_visit.push(n)
557 }
558 };
559 }
560 while let Some(node) = to_visit.pop() {
561 match &mut phys_sm[node].kind {
562 PhysNodeKind::InMemorySource { .. }
563 | PhysNodeKind::MultiScan { .. }
564 | PhysNodeKind::InputIndependentSelect { .. } => {},
565 #[cfg(feature = "python")]
566 PhysNodeKind::PythonScan { .. } => {},
567 PhysNodeKind::Select { input, .. }
568 | PhysNodeKind::WithRowIndex { input, .. }
569 | PhysNodeKind::Reduce { input, .. }
570 | PhysNodeKind::StreamingSlice { input, .. }
571 | PhysNodeKind::NegativeSlice { input, .. }
572 | PhysNodeKind::Filter { input, .. }
573 | PhysNodeKind::SimpleProjection { input, .. }
574 | PhysNodeKind::InMemorySink { input }
575 | PhysNodeKind::CallbackSink { input, .. }
576 | PhysNodeKind::FileSink { input, .. }
577 | PhysNodeKind::PartitionedSink { input, .. }
578 | PhysNodeKind::InMemoryMap { input, .. }
579 | PhysNodeKind::SortedGroupBy { input, .. }
580 | PhysNodeKind::Map { input, .. }
581 | PhysNodeKind::Sort { input, .. }
582 | PhysNodeKind::Multiplexer { input }
583 | PhysNodeKind::GatherEvery { input, .. }
584 | PhysNodeKind::ForwardFill { input, .. }
585 | PhysNodeKind::BackwardFill { input, .. }
586 | PhysNodeKind::Rle(input)
587 | PhysNodeKind::RleId(input)
588 | PhysNodeKind::SortedUnique { input, .. }
589 | PhysNodeKind::PeakMinMax { input, .. } => {
590 rec!(input.node);
591 visit(input);
592 },
593
594 #[cfg(feature = "interpolate")]
595 PhysNodeKind::Interpolate { input, .. } => {
596 rec!(input.node);
597 visit(input);
598 },
599
600 #[cfg(feature = "is_first_distinct")]
601 PhysNodeKind::IsFirstDistinct { input, .. } => {
602 rec!(input.node);
603 visit(input);
604 },
605
606 #[cfg(any(
607 feature = "dtype-date",
608 feature = "dtype-datetime",
609 feature = "dtype-time"
610 ))]
611 PhysNodeKind::StrptimeInfer { input, .. } => {
612 rec!(input.node);
613 visit(input);
614 },
615
616 #[cfg(feature = "dynamic_group_by")]
617 PhysNodeKind::DynamicGroupBy { input, .. } => {
618 rec!(input.node);
619 visit(input);
620 },
621 #[cfg(feature = "dynamic_group_by")]
622 PhysNodeKind::RollingGroupBy { input, .. } => {
623 rec!(input.node);
624 visit(input);
625 },
626
627 #[cfg(feature = "cum_agg")]
628 PhysNodeKind::CumAgg { input, .. } => {
629 rec!(input.node);
630 visit(input);
631 },
632
633 PhysNodeKind::InMemoryJoin {
634 input_left,
635 input_right,
636 ..
637 }
638 | PhysNodeKind::EquiJoin {
639 input_left,
640 input_right,
641 ..
642 }
643 | PhysNodeKind::MergeJoin {
644 input_left,
645 input_right,
646 ..
647 }
648 | PhysNodeKind::SemiAntiJoin {
649 input_left,
650 input_right,
651 ..
652 }
653 | PhysNodeKind::CrossJoin {
654 input_left,
655 input_right,
656 ..
657 }
658 | PhysNodeKind::AsOfJoin {
659 input_left,
660 input_right,
661 ..
662 } => {
663 rec!(input_left.node);
664 rec!(input_right.node);
665 visit(input_left);
666 visit(input_right);
667 },
668
669 #[cfg(feature = "iejoin")]
670 PhysNodeKind::RangeJoin {
671 input_left,
672 input_right,
673 ..
674 } => {
675 rec!(input_left.node);
676 rec!(input_right.node);
677 visit(input_left);
678 visit(input_right);
679 },
680
681 #[cfg(feature = "merge_sorted")]
682 PhysNodeKind::MergeSorted {
683 input_left,
684 input_right,
685 ..
686 } => {
687 rec!(input_left.node);
688 rec!(input_right.node);
689 visit(input_left);
690 visit(input_right);
691 },
692
693 PhysNodeKind::Gather { input, idxs, .. } => {
694 rec!(input.node);
695 rec!(idxs.node);
696 visit(input);
697 visit(idxs);
698 },
699
700 PhysNodeKind::TopK { input, k, .. } => {
701 rec!(input.node);
702 rec!(k.node);
703 visit(input);
704 visit(k);
705 },
706
707 PhysNodeKind::DynamicSlice {
708 input,
709 offset,
710 length,
711 } => {
712 rec!(input.node);
713 rec!(offset.node);
714 rec!(length.node);
715 visit(input);
716 visit(offset);
717 visit(length);
718 },
719
720 PhysNodeKind::Shift {
721 input,
722 offset,
723 fill,
724 } => {
725 rec!(input.node);
726 rec!(offset.node);
727 if let Some(fill) = fill {
728 rec!(fill.node);
729 }
730 visit(input);
731 visit(offset);
732 if let Some(fill) = fill {
733 visit(fill);
734 }
735 },
736
737 PhysNodeKind::Repeat { value, repeats } => {
738 rec!(value.node);
739 rec!(repeats.node);
740 visit(value);
741 visit(repeats);
742 },
743
744 PhysNodeKind::GroupBy { inputs, .. }
745 | PhysNodeKind::OrderedUnion { inputs }
746 | PhysNodeKind::UnorderedUnion { inputs }
747 | PhysNodeKind::Zip { inputs, .. }
748 | PhysNodeKind::ColumnarFunction { inputs, .. } => {
749 for input in inputs {
750 rec!(input.node);
751 visit(input);
752 }
753 },
754
755 PhysNodeKind::SinkMultiple { sinks } => {
756 for sink in sinks {
757 rec!(*sink);
758 visit(&mut PhysStream::first(*sink));
759 }
760 },
761
762 #[cfg(feature = "ewma")]
763 PhysNodeKind::EwmMean { input, options: _ }
764 | PhysNodeKind::EwmVar { input, options: _ }
765 | PhysNodeKind::EwmStd { input, options: _ } => {
766 rec!(input.node);
767 visit(input)
768 },
769 }
770 }
771}
772
773fn insert_multiplexers(roots: Vec<PhysNodeKey>, phys_sm: &mut SlotMap<PhysNodeKey, PhysNode>) {
774 let mut refcount: PlIndexMap<_, usize> = PlIndexMap::new();
775 visit_node_inputs_mut(roots.clone(), phys_sm, |i| {
776 *refcount.entry(*i).or_insert(0) += 1;
777 });
778
779 let mut multiplexer_map: PlHashMap<PhysStream, PhysStream> = refcount
780 .into_iter()
781 .filter(|(_stream, refcount)| *refcount > 1)
782 .map(|(stream, refcount)| {
783 let input_schema = Arc::clone(stream.output_schema(phys_sm));
784 let multiplexer_node = phys_sm.insert(PhysNode::new_multi_output(
785 (0..refcount).map(|_| Arc::clone(&input_schema)).collect(),
786 PhysNodeKind::Multiplexer { input: stream },
787 ));
788 (stream, PhysStream::first(multiplexer_node))
789 })
790 .collect();
791
792 visit_node_inputs_mut(roots, phys_sm, |i| {
793 if let Some(m) = multiplexer_map.get_mut(i) {
794 *i = *m;
795 m.port += 1;
796 }
797 });
798}
799
800pub fn build_physical_plan(
801 root: Node,
802 ir_arena: &mut Arena<IR>,
803 expr_arena: &mut Arena<AExpr>,
804 phys_sm: &mut SlotMap<PhysNodeKey, PhysNode>,
805 ctx: StreamingLowerIRContext<'_>,
806) -> PolarsResult<PhysNodeKey> {
807 let mut schema_cache = PlHashMap::with_capacity(ir_arena.len());
808 let mut expr_cache = ExprCache::with_capacity(expr_arena.len());
809 let mut cache_nodes = PlHashMap::new();
810 let phys_root = lower_ir::lower_ir(
811 root,
812 ir_arena,
813 expr_arena,
814 phys_sm,
815 &mut schema_cache,
816 &mut expr_cache,
817 &mut cache_nodes,
818 ctx,
819 None,
820 )?;
821 insert_multiplexers(vec![phys_root.node], phys_sm);
822 Ok(phys_root.node)
823}