Skip to main content

polars_lazy/frame/
mod.rs

1//! Lazy variant of a [DataFrame].
2#[cfg(feature = "python")]
3mod python;
4
5mod cached_arenas;
6mod err;
7#[cfg(not(target_arch = "wasm32"))]
8mod exitable;
9
10use std::num::NonZeroUsize;
11use std::sync::mpsc::{Receiver, sync_channel};
12use std::sync::{Arc, Mutex};
13
14pub use anonymous_scan::*;
15#[cfg(feature = "csv")]
16pub use csv::*;
17#[cfg(not(target_arch = "wasm32"))]
18pub use exitable::*;
19pub use file_list_reader::*;
20#[cfg(feature = "json")]
21pub use ndjson::*;
22#[cfg(feature = "parquet")]
23pub use parquet::*;
24use polars_compute::rolling::QuantileMethod;
25use polars_core::error::feature_gated;
26#[cfg(feature = "pivot")]
27use polars_core::frame::PivotColumnNaming;
28use polars_core::prelude::*;
29use polars_core::query_result::QueryResult;
30use polars_io::RowIndex;
31use polars_mem_engine::scan_predicate::functions::apply_scan_predicate_to_scan_ir;
32use polars_mem_engine::{Executor, create_multiple_physical_plans, create_physical_plan};
33use polars_ops::frame::{JoinBuildSide, JoinCoalesce, MaintainOrderJoin};
34#[cfg(feature = "is_between")]
35use polars_ops::prelude::ClosedInterval;
36pub use polars_plan::frame::{AllowedOptimizations, OptFlags};
37use polars_utils::pl_str::PlSmallStr;
38
39use crate::frame::cached_arenas::CachedArena;
40use crate::prelude::*;
41
42pub trait IntoLazy {
43    fn lazy(self) -> LazyFrame;
44}
45
46impl IntoLazy for DataFrame {
47    /// Convert the `DataFrame` into a `LazyFrame`
48    fn lazy(self) -> LazyFrame {
49        let lp = DslBuilder::from_existing_df(self).build();
50        LazyFrame {
51            logical_plan: lp,
52            opt_state: Default::default(),
53            cached_arena: Default::default(),
54        }
55    }
56}
57
58impl IntoLazy for LazyFrame {
59    fn lazy(self) -> LazyFrame {
60        self
61    }
62}
63
64/// Lazy abstraction over an eager `DataFrame`.
65///
66/// It really is an abstraction over a logical plan. The methods of this struct will incrementally
67/// modify a logical plan until output is requested (via [`collect`](crate::frame::LazyFrame::collect)).
68#[derive(Clone, Default)]
69#[must_use]
70pub struct LazyFrame {
71    pub logical_plan: DslPlan,
72    pub(crate) opt_state: OptFlags,
73    pub(crate) cached_arena: Arc<Mutex<Option<CachedArena>>>,
74}
75
76impl From<DslPlan> for LazyFrame {
77    fn from(plan: DslPlan) -> Self {
78        Self {
79            logical_plan: plan,
80            opt_state: OptFlags::default(),
81            cached_arena: Default::default(),
82        }
83    }
84}
85
86impl LazyFrame {
87    pub(crate) fn from_inner(
88        logical_plan: DslPlan,
89        opt_state: OptFlags,
90        cached_arena: Arc<Mutex<Option<CachedArena>>>,
91    ) -> Self {
92        Self {
93            logical_plan,
94            opt_state,
95            cached_arena,
96        }
97    }
98
99    pub(crate) fn get_plan_builder(self) -> DslBuilder {
100        DslBuilder::from(self.logical_plan)
101    }
102
103    fn get_opt_state(&self) -> OptFlags {
104        self.opt_state
105    }
106
107    pub fn from_logical_plan(logical_plan: DslPlan, opt_state: OptFlags) -> Self {
108        LazyFrame {
109            logical_plan,
110            opt_state,
111            cached_arena: Default::default(),
112        }
113    }
114
115    /// Get current optimizations.
116    pub fn get_current_optimizations(&self) -> OptFlags {
117        self.opt_state
118    }
119
120    /// Set allowed optimizations.
121    pub fn with_optimizations(mut self, opt_state: OptFlags) -> Self {
122        self.opt_state = opt_state;
123        self
124    }
125
126    /// Turn off all optimizations.
127    pub fn without_optimizations(self) -> Self {
128        self.with_optimizations(OptFlags::from_bits_truncate(0) | OptFlags::TYPE_COERCION)
129    }
130
131    /// Toggle projection pushdown optimization.
132    pub fn with_projection_pushdown(mut self, toggle: bool) -> Self {
133        self.opt_state.set(OptFlags::PROJECTION_PUSHDOWN, toggle);
134        self
135    }
136
137    /// Toggle cluster with columns optimization.
138    pub fn with_cluster_with_columns(mut self, toggle: bool) -> Self {
139        self.opt_state.set(OptFlags::CLUSTER_WITH_COLUMNS, toggle);
140        self
141    }
142
143    /// Check if operations are order dependent and unset maintaining_order if
144    /// the order would not be observed.
145    pub fn with_check_order(mut self, toggle: bool) -> Self {
146        self.opt_state.set(OptFlags::CHECK_ORDER_OBSERVE, toggle);
147        self
148    }
149
150    /// Toggle predicate pushdown optimization.
151    pub fn with_predicate_pushdown(mut self, toggle: bool) -> Self {
152        self.opt_state.set(OptFlags::PREDICATE_PUSHDOWN, toggle);
153        self
154    }
155
156    /// Toggle type coercion optimization.
157    pub fn with_type_coercion(mut self, toggle: bool) -> Self {
158        self.opt_state.set(OptFlags::TYPE_COERCION, toggle);
159        self
160    }
161
162    /// Toggle type check optimization.
163    pub fn with_type_check(mut self, toggle: bool) -> Self {
164        self.opt_state.set(OptFlags::TYPE_CHECK, toggle);
165        self
166    }
167
168    /// Toggle expression simplification optimization on or off.
169    pub fn with_simplify_expr(mut self, toggle: bool) -> Self {
170        self.opt_state.set(OptFlags::SIMPLIFY_EXPR, toggle);
171        self
172    }
173
174    /// Toggle common subplan elimination optimization on or off
175    #[cfg(feature = "cse")]
176    pub fn with_comm_subplan_elim(mut self, toggle: bool) -> Self {
177        self.opt_state.set(OptFlags::COMM_SUBPLAN_ELIM, toggle);
178        self
179    }
180
181    /// Toggle common subexpression elimination optimization on or off
182    #[cfg(feature = "cse")]
183    pub fn with_comm_subexpr_elim(mut self, toggle: bool) -> Self {
184        self.opt_state.set(OptFlags::COMM_SUBEXPR_ELIM, toggle);
185        self
186    }
187
188    /// Toggle slice pushdown optimization.
189    pub fn with_slice_pushdown(mut self, toggle: bool) -> Self {
190        self.opt_state.set(OptFlags::SLICE_PUSHDOWN, toggle);
191        self
192    }
193
194    #[cfg(feature = "streaming")]
195    pub fn with_streaming(mut self, toggle: bool) -> Self {
196        self.opt_state.set(OptFlags::STREAMING, toggle);
197        self
198    }
199
200    pub fn with_gpu(mut self, toggle: bool) -> Self {
201        self.opt_state.set(OptFlags::GPU, toggle);
202        self
203    }
204
205    /// Try to estimate the number of rows so that joins can determine which side to keep in memory.
206    pub fn with_row_estimate(mut self, toggle: bool) -> Self {
207        self.opt_state.set(OptFlags::ROW_ESTIMATE, toggle);
208        self
209    }
210
211    /// Run every node eagerly. This turns off multi-node optimizations.
212    pub fn _with_eager(mut self, toggle: bool) -> Self {
213        self.opt_state.set(OptFlags::EAGER, toggle);
214        self
215    }
216
217    /// Return a String describing the naive (un-optimized) logical plan.
218    pub fn describe_plan(&self) -> PolarsResult<String> {
219        Ok(self.clone().to_alp()?.describe())
220    }
221
222    /// Return a String describing the naive (un-optimized) logical plan in tree format.
223    pub fn describe_plan_tree(&self) -> PolarsResult<String> {
224        Ok(self.clone().to_alp()?.describe_tree_format())
225    }
226
227    /// Return a String describing the optimized logical plan.
228    ///
229    /// Returns `Err` if optimizing the logical plan fails.
230    pub fn describe_optimized_plan(&self) -> PolarsResult<String> {
231        Ok(self.clone().to_alp_optimized()?.describe())
232    }
233
234    /// Return a String describing the optimized logical plan in tree format.
235    ///
236    /// Returns `Err` if optimizing the logical plan fails.
237    pub fn describe_optimized_plan_tree(&self) -> PolarsResult<String> {
238        Ok(self.clone().to_alp_optimized()?.describe_tree_format())
239    }
240
241    /// Return a String describing the logical plan.
242    ///
243    /// If `optimized` is `true`, explains the optimized plan. If `optimized` is `false`,
244    /// explains the naive, un-optimized plan.
245    pub fn explain(&self, optimized: bool) -> PolarsResult<String> {
246        if optimized {
247            self.describe_optimized_plan()
248        } else {
249            self.describe_plan()
250        }
251    }
252
253    /// Add a sort operation to the logical plan.
254    ///
255    /// Sorts the LazyFrame by the column name specified using the provided options.
256    ///
257    /// # Example
258    ///
259    /// Sort DataFrame by 'sepal_width' column:
260    /// ```rust
261    /// # use polars_core::prelude::*;
262    /// # use polars_lazy::prelude::*;
263    /// fn sort_by_a(df: DataFrame) -> LazyFrame {
264    ///     df.lazy().sort(["sepal_width"], Default::default())
265    /// }
266    /// ```
267    /// Sort by a single column with specific order:
268    /// ```
269    /// # use polars_core::prelude::*;
270    /// # use polars_lazy::prelude::*;
271    /// fn sort_with_specific_order(df: DataFrame, descending: bool) -> LazyFrame {
272    ///     df.lazy().sort(
273    ///         ["sepal_width"],
274    ///         SortMultipleOptions::new()
275    ///             .with_order_descending(descending)
276    ///     )
277    /// }
278    /// ```
279    /// Sort by multiple columns with specifying order for each column:
280    /// ```
281    /// # use polars_core::prelude::*;
282    /// # use polars_lazy::prelude::*;
283    /// fn sort_by_multiple_columns_with_specific_order(df: DataFrame) -> LazyFrame {
284    ///     df.lazy().sort(
285    ///         ["sepal_width", "sepal_length"],
286    ///         SortMultipleOptions::new()
287    ///             .with_order_descending_multi([false, true])
288    ///     )
289    /// }
290    /// ```
291    /// See [`SortMultipleOptions`] for more options.
292    pub fn sort(self, by: impl IntoVec<PlSmallStr>, sort_options: SortMultipleOptions) -> Self {
293        let opt_state = self.get_opt_state();
294        let lp = self
295            .get_plan_builder()
296            .sort(by.into_vec().into_iter().map(col).collect(), sort_options)
297            .build();
298        Self::from_logical_plan(lp, opt_state)
299    }
300
301    /// Add a sort operation to the logical plan.
302    ///
303    /// Sorts the LazyFrame by the provided list of expressions, which will be turned into
304    /// concrete columns before sorting.
305    ///
306    /// See [`SortMultipleOptions`] for more options.
307    ///
308    /// # Example
309    ///
310    /// ```rust
311    /// use polars_core::prelude::*;
312    /// use polars_lazy::prelude::*;
313    ///
314    /// /// Sort DataFrame by 'sepal_width' column
315    /// fn example(df: DataFrame) -> LazyFrame {
316    ///       df.lazy()
317    ///         .sort_by_exprs(vec![col("sepal_width")], Default::default())
318    /// }
319    /// ```
320    pub fn sort_by_exprs<E: AsRef<[Expr]>>(
321        self,
322        by_exprs: E,
323        sort_options: SortMultipleOptions,
324    ) -> Self {
325        let by_exprs = by_exprs.as_ref().to_vec();
326        if by_exprs.is_empty() {
327            self
328        } else {
329            let opt_state = self.get_opt_state();
330            let lp = self.get_plan_builder().sort(by_exprs, sort_options).build();
331            Self::from_logical_plan(lp, opt_state)
332        }
333    }
334
335    pub fn top_k<E: AsRef<[Expr]>>(
336        self,
337        k: IdxSize,
338        by_exprs: E,
339        sort_options: SortMultipleOptions,
340    ) -> Self {
341        // this will optimize to top-k
342        self.sort_by_exprs(
343            by_exprs,
344            sort_options.with_order_reversed().with_nulls_last(true),
345        )
346        .slice(0, k)
347    }
348
349    pub fn bottom_k<E: AsRef<[Expr]>>(
350        self,
351        k: IdxSize,
352        by_exprs: E,
353        sort_options: SortMultipleOptions,
354    ) -> Self {
355        // this will optimize to bottom-k
356        self.sort_by_exprs(by_exprs, sort_options.with_nulls_last(true))
357            .slice(0, k)
358    }
359
360    /// Reverse the `DataFrame` from top to bottom.
361    ///
362    /// Row `i` becomes row `number_of_rows - i - 1`.
363    ///
364    /// # Example
365    ///
366    /// ```rust
367    /// use polars_core::prelude::*;
368    /// use polars_lazy::prelude::*;
369    ///
370    /// fn example(df: DataFrame) -> LazyFrame {
371    ///       df.lazy()
372    ///         .reverse()
373    /// }
374    /// ```
375    pub fn reverse(self) -> Self {
376        self.select(vec![col(PlSmallStr::from_static("*")).reverse()])
377    }
378
379    /// Rename columns in the DataFrame.
380    ///
381    /// `existing` and `new` are iterables of the same length containing the old and
382    /// corresponding new column names. Renaming happens to all `existing` columns
383    /// simultaneously, not iteratively. If `strict` is true, all columns in `existing`
384    /// must be present in the `LazyFrame` when `rename` is called; otherwise, only
385    /// those columns that are actually found will be renamed (others will be ignored).
386    pub fn rename<I, J, T, S>(self, existing: I, new: J, strict: bool) -> Self
387    where
388        I: IntoIterator<Item = T>,
389        J: IntoIterator<Item = S>,
390        T: AsRef<str>,
391        S: AsRef<str>,
392    {
393        let iter = existing.into_iter();
394        let cap = iter.size_hint().0;
395        let mut existing_vec: Vec<PlSmallStr> = Vec::with_capacity(cap);
396        let mut new_vec: Vec<PlSmallStr> = Vec::with_capacity(cap);
397
398        // TODO! should this error if `existing` and `new` have different lengths?
399        // Currently, the longer of the two is truncated.
400        for (existing, new) in iter.zip(new) {
401            let existing = existing.as_ref();
402            let new = new.as_ref();
403            if new != existing {
404                existing_vec.push(existing.into());
405                new_vec.push(new.into());
406            }
407        }
408
409        self.map_private(DslFunction::Rename {
410            existing: existing_vec.into(),
411            new: new_vec.into(),
412            strict,
413        })
414    }
415
416    /// Removes columns from the DataFrame.
417    /// Note that it's better to only select the columns you need
418    /// and let the projection pushdown optimize away the unneeded columns.
419    ///
420    /// Any given columns that are not in the schema will give a [`PolarsError::ColumnNotFound`]
421    /// error while materializing the [`LazyFrame`].
422    pub fn drop(self, columns: Selector) -> Self {
423        let opt_state = self.get_opt_state();
424        let lp = self.get_plan_builder().drop(columns).build();
425        Self::from_logical_plan(lp, opt_state)
426    }
427
428    /// Shift the values by a given period and fill the parts that will be empty due to this operation
429    /// with `Nones`.
430    ///
431    /// See the method on [Series](polars_core::series::SeriesTrait::shift) for more info on the `shift` operation.
432    pub fn shift<E: Into<Expr>>(self, n: E) -> Self {
433        self.select(vec![col(PlSmallStr::from_static("*")).shift(n.into())])
434    }
435
436    /// Shift the values by a given period and fill the parts that will be empty due to this operation
437    /// with the result of the `fill_value` expression.
438    ///
439    /// See the method on [Series](polars_core::series::SeriesTrait::shift) for more info on the `shift` operation.
440    pub fn shift_and_fill<E: Into<Expr>, IE: Into<Expr>>(self, n: E, fill_value: IE) -> Self {
441        self.select(vec![
442            col(PlSmallStr::from_static("*")).shift_and_fill(n.into(), fill_value.into()),
443        ])
444    }
445
446    /// Fill None values in the DataFrame with an expression.
447    pub fn fill_null<E: Into<Expr>>(self, fill_value: E) -> LazyFrame {
448        let opt_state = self.get_opt_state();
449        let lp = self.get_plan_builder().fill_null(fill_value.into()).build();
450        Self::from_logical_plan(lp, opt_state)
451    }
452
453    /// Fill NaN values in the DataFrame with an expression.
454    pub fn fill_nan<E: Into<Expr>>(self, fill_value: E) -> LazyFrame {
455        let opt_state = self.get_opt_state();
456        let lp = self.get_plan_builder().fill_nan(fill_value.into()).build();
457        Self::from_logical_plan(lp, opt_state)
458    }
459
460    /// Caches the result into a new LazyFrame.
461    ///
462    /// This should be used to prevent computations running multiple times.
463    pub fn cache(self) -> Self {
464        let opt_state = self.get_opt_state();
465        let lp = self.get_plan_builder().cache().build();
466        Self::from_logical_plan(lp, opt_state)
467    }
468
469    /// Cast named frame columns, resulting in a new LazyFrame with updated dtypes
470    pub fn cast(self, dtypes: PlHashMap<&str, DataType>, strict: bool) -> Self {
471        let cast_cols: Vec<Expr> = dtypes
472            .into_iter()
473            .map(|(name, dt)| {
474                let name = PlSmallStr::from_str(name);
475
476                if strict {
477                    col(name).strict_cast(dt)
478                } else {
479                    col(name).cast(dt)
480                }
481            })
482            .collect();
483
484        if cast_cols.is_empty() {
485            self
486        } else {
487            self.with_columns(cast_cols)
488        }
489    }
490
491    /// Cast all frame columns to the given dtype, resulting in a new LazyFrame
492    pub fn cast_all(self, dtype: impl Into<DataTypeExpr>, strict: bool) -> Self {
493        self.with_columns(vec![if strict {
494            col(PlSmallStr::from_static("*")).strict_cast(dtype)
495        } else {
496            col(PlSmallStr::from_static("*")).cast(dtype)
497        }])
498    }
499
500    pub fn optimize(
501        self,
502        lp_arena: &mut Arena<IR>,
503        expr_arena: &mut Arena<AExpr>,
504    ) -> PolarsResult<Node> {
505        self.optimize_with_scratch(lp_arena, expr_arena, &mut vec![])
506    }
507
508    pub fn to_alp_optimized(mut self) -> PolarsResult<IRPlan> {
509        let (mut lp_arena, mut expr_arena) = self.get_arenas();
510        let node = self.optimize_with_scratch(&mut lp_arena, &mut expr_arena, &mut vec![])?;
511
512        Ok(IRPlan::new(node, lp_arena, expr_arena))
513    }
514
515    pub fn to_alp(mut self) -> PolarsResult<IRPlan> {
516        let (mut lp_arena, mut expr_arena) = self.get_arenas();
517        let node = to_alp(
518            self.logical_plan,
519            &mut expr_arena,
520            &mut lp_arena,
521            &mut self.opt_state,
522        )?;
523        let plan = IRPlan::new(node, lp_arena, expr_arena);
524        Ok(plan)
525    }
526
527    pub(crate) fn optimize_with_scratch(
528        self,
529        ir_arena: &mut Arena<IR>,
530        expr_arena: &mut Arena<AExpr>,
531        scratch: &mut Vec<Node>,
532    ) -> PolarsResult<Node> {
533        let mut opt_flags = self.opt_state;
534        // Unset CSE
535        // This can be turned on again during ir-conversion.
536        #[allow(clippy::eq_op)]
537        #[cfg(feature = "cse")]
538        if opt_flags.contains(OptFlags::EAGER) {
539            opt_flags &= !(OptFlags::COMM_SUBEXPR_ELIM | OptFlags::COMM_SUBEXPR_ELIM);
540        }
541        let root = to_alp(self.logical_plan, expr_arena, ir_arena, &mut opt_flags)?;
542
543        let lp_top = optimize(
544            root,
545            opt_flags,
546            ir_arena,
547            expr_arena,
548            scratch,
549            apply_scan_predicate_to_scan_ir,
550        )?;
551
552        Ok(lp_top)
553    }
554
555    fn prepare_collect_post_opt<P>(
556        mut self,
557        check_sink: bool,
558        query_start: Option<std::time::Instant>,
559        post_opt: P,
560    ) -> PolarsResult<(ExecutionState, Box<dyn Executor>, bool)>
561    where
562        P: FnOnce(
563            Node,
564            &mut Arena<IR>,
565            &mut Arena<AExpr>,
566            Option<std::time::Duration>,
567        ) -> PolarsResult<()>,
568    {
569        let (mut lp_arena, mut expr_arena) = self.get_arenas();
570
571        let mut scratch = vec![];
572        let lp_top = self.optimize_with_scratch(&mut lp_arena, &mut expr_arena, &mut scratch)?;
573
574        post_opt(
575            lp_top,
576            &mut lp_arena,
577            &mut expr_arena,
578            // Post optimization callback gets the time since the
579            // query was started as its "base" timepoint.
580            query_start.map(|s| s.elapsed()),
581        )?;
582
583        // sink should be replaced
584        let no_file_sink = if check_sink {
585            !matches!(
586                lp_arena.get(lp_top),
587                IR::Sink {
588                    payload: SinkTypeIR::File { .. },
589                    ..
590                }
591            )
592        } else {
593            true
594        };
595        let physical_plan = create_physical_plan(
596            lp_top,
597            &mut lp_arena,
598            &mut expr_arena,
599            BUILD_STREAMING_EXECUTOR,
600        )?;
601
602        let state = ExecutionState::new();
603        Ok((state, physical_plan, no_file_sink))
604    }
605
606    // post_opt: A function that is called after optimization. This can be used to modify the IR jit.
607    pub fn _collect_post_opt<P>(self, post_opt: P) -> PolarsResult<DataFrame>
608    where
609        P: FnOnce(
610            Node,
611            &mut Arena<IR>,
612            &mut Arena<AExpr>,
613            Option<std::time::Duration>,
614        ) -> PolarsResult<()>,
615    {
616        let (mut state, mut physical_plan, _) =
617            self.prepare_collect_post_opt(false, None, post_opt)?;
618        physical_plan.execute(&mut state)
619    }
620
621    #[allow(unused_mut)]
622    fn prepare_collect(
623        self,
624        check_sink: bool,
625        query_start: Option<std::time::Instant>,
626    ) -> PolarsResult<(ExecutionState, Box<dyn Executor>, bool)> {
627        self.prepare_collect_post_opt(check_sink, query_start, |_, _, _, _| Ok(()))
628    }
629
630    /// Execute all the lazy operations and collect them into a [`DataFrame`] using a specified
631    /// `engine`.
632    ///
633    /// The query is optimized prior to execution.
634    pub fn collect_with_engine(mut self, engine: Engine) -> PolarsResult<QueryResult> {
635        let engine = match engine {
636            Engine::Streaming => Engine::Streaming,
637            _ if std::env::var("POLARS_FORCE_STREAMING").as_deref() == Ok("1") => Engine::Streaming,
638            Engine::Auto => Engine::InMemory,
639            v => v,
640        };
641
642        if engine != Engine::Streaming
643            && std::env::var("POLARS_AUTO_STREAMING").as_deref() == Ok("1")
644        {
645            feature_gated!("streaming", {
646                if let Some(r) = self.clone()._collect_with_streaming_suppress_todo_panic() {
647                    return r;
648                }
649            })
650        }
651        match engine {
652            Engine::Streaming => {
653                feature_gated!("streaming", self = self.with_streaming(true))
654            },
655            Engine::Gpu => self = self.with_gpu(true),
656            _ => (),
657        }
658
659        let mut ir_plan = self.to_alp_optimized()?;
660
661        ir_plan.ensure_root_node_is_sink();
662
663        match engine {
664            Engine::Streaming => feature_gated!("streaming", {
665                polars_stream::run_query(
666                    ir_plan.lp_top,
667                    &mut ir_plan.lp_arena,
668                    &mut ir_plan.expr_arena,
669                )
670            }),
671            Engine::InMemory | Engine::Gpu => {
672                if let IR::SinkMultiple { inputs } = ir_plan.root() {
673                    polars_ensure!(
674                        engine != Engine::Gpu,
675                        InvalidOperation:
676                        "collect_all is not supported for the gpu engine"
677                    );
678
679                    return create_multiple_physical_plans(
680                        inputs.clone().as_slice(),
681                        &mut ir_plan.lp_arena,
682                        &mut ir_plan.expr_arena,
683                        BUILD_STREAMING_EXECUTOR,
684                    )?
685                    .execute()
686                    .map(QueryResult::Multiple);
687                }
688
689                let mut physical_plan = create_physical_plan(
690                    ir_plan.lp_top,
691                    &mut ir_plan.lp_arena,
692                    &mut ir_plan.expr_arena,
693                    BUILD_STREAMING_EXECUTOR,
694                )?;
695                let mut state = ExecutionState::new();
696                physical_plan.execute(&mut state).map(QueryResult::Single)
697            },
698            Engine::Auto => unreachable!(),
699        }
700    }
701
702    pub fn explain_all(plans: Vec<DslPlan>, opt_state: OptFlags) -> PolarsResult<String> {
703        let sink_multiple = LazyFrame {
704            logical_plan: DslPlan::SinkMultiple { inputs: plans },
705            opt_state,
706            cached_arena: Default::default(),
707        };
708        sink_multiple.explain(true)
709    }
710
711    pub fn collect_all_with_engine(
712        plans: Vec<DslPlan>,
713        engine: Engine,
714        opt_state: OptFlags,
715    ) -> PolarsResult<Vec<DataFrame>> {
716        if plans.is_empty() {
717            return Ok(Vec::new());
718        }
719
720        LazyFrame {
721            logical_plan: DslPlan::SinkMultiple { inputs: plans },
722            opt_state,
723            cached_arena: Default::default(),
724        }
725        .collect_with_engine(engine)
726        .map(|r| r.unwrap_multiple())
727    }
728
729    /// Execute all the lazy operations and collect them into a [`DataFrame`].
730    ///
731    /// The query is optimized prior to execution.
732    ///
733    /// # Example
734    ///
735    /// ```rust
736    /// use polars_core::prelude::*;
737    /// use polars_lazy::prelude::*;
738    ///
739    /// fn example(df: DataFrame) -> PolarsResult<DataFrame> {
740    ///     df.lazy()
741    ///       .group_by([col("foo")])
742    ///       .agg([col("bar").sum(), col("ham").mean().alias("avg_ham")])
743    ///       .collect()
744    /// }
745    /// ```
746    pub fn collect(self) -> PolarsResult<DataFrame> {
747        self.collect_with_engine(Engine::Auto).map(|r| match r {
748            QueryResult::Single(df) => df,
749            // TODO: Should return query results
750            QueryResult::Multiple(_) => DataFrame::empty(),
751        })
752    }
753
754    /// Collect the query in batches.
755    ///
756    /// If lazy is true the query will not start until the first poll (or until
757    /// start is called on CollectBatches).
758    #[cfg(feature = "async")]
759    pub fn collect_batches(
760        self,
761        engine: Engine,
762        maintain_order: bool,
763        chunk_size: Option<NonZeroUsize>,
764        lazy: bool,
765    ) -> PolarsResult<CollectBatches> {
766        let (send, recv) = sync_channel(1);
767        let runner_send = send.clone();
768        let ldf = self.sink_batches(
769            PlanCallback::new(move |df| {
770                // Stop if receiver has closed.
771                let send_result = send.send(Ok(df));
772                Ok(send_result.is_err())
773            }),
774            maintain_order,
775            chunk_size,
776        )?;
777        let runner = move || {
778            // We use spawn_blocking here as it has a high blocking thread pool limit.
779            polars_core::runtime::ASYNC.spawn_blocking(move || {
780                if let Err(e) = ldf.collect_with_engine(engine) {
781                    runner_send.send(Err(e)).ok();
782                }
783            });
784        };
785
786        let mut collect_batches = CollectBatches {
787            recv,
788            runner: Some(Box::new(runner)),
789        };
790        if !lazy {
791            collect_batches.start();
792        }
793        Ok(collect_batches)
794    }
795
796    // post_opt: A function that is called after optimization. This can be used to modify the IR jit.
797    // This version does profiling of the node execution.
798    pub fn _profile_post_opt<P>(self, post_opt: P) -> PolarsResult<(DataFrame, DataFrame)>
799    where
800        P: FnOnce(
801            Node,
802            &mut Arena<IR>,
803            &mut Arena<AExpr>,
804            Option<std::time::Duration>,
805        ) -> PolarsResult<()>,
806    {
807        let query_start = std::time::Instant::now();
808        let (mut state, mut physical_plan, _) =
809            self.prepare_collect_post_opt(false, Some(query_start), post_opt)?;
810        state.time_nodes(query_start, query_start.elapsed());
811        let out = physical_plan.execute(&mut state)?;
812        let timer_df = state.finish_timer()?;
813        Ok((out, timer_df))
814    }
815
816    /// Profile a LazyFrame.
817    ///
818    /// This will run the query and return a tuple
819    /// containing the materialized DataFrame and a DataFrame that contains profiling information
820    /// of each node that is executed.
821    ///
822    /// The units of the timings are microseconds.
823    pub fn profile(self) -> PolarsResult<(DataFrame, DataFrame)> {
824        self._profile_post_opt(|_, _, _, _| Ok(()))
825    }
826
827    pub fn sink_batches(
828        mut self,
829        function: PlanCallback<DataFrame, bool>,
830        maintain_order: bool,
831        chunk_size: Option<NonZeroUsize>,
832    ) -> PolarsResult<Self> {
833        use polars_plan::prelude::sink::CallbackSinkType;
834
835        polars_ensure!(
836            !matches!(self.logical_plan, DslPlan::Sink { .. }),
837            InvalidOperation: "cannot create a sink on top of another sink"
838        );
839
840        self.logical_plan = DslPlan::Sink {
841            input: Arc::new(self.logical_plan),
842            payload: SinkType::Callback(CallbackSinkType {
843                function,
844                maintain_order,
845                chunk_size,
846            }),
847        };
848
849        Ok(self)
850    }
851
852    /// Collect with the streaming engine. Returns `None` if the streaming engine panics with a todo!.
853    #[cfg(feature = "streaming")]
854    fn _collect_with_streaming_suppress_todo_panic(
855        mut self,
856    ) -> Option<PolarsResult<polars_core::query_result::QueryResult>> {
857        self.opt_state |= OptFlags::STREAMING;
858        let mut ir_plan = match self.to_alp_optimized() {
859            Ok(v) => v,
860            Err(e) => return Some(Err(e)),
861        };
862
863        ir_plan.ensure_root_node_is_sink();
864
865        let f = || {
866            polars_stream::run_query(
867                ir_plan.lp_top,
868                &mut ir_plan.lp_arena,
869                &mut ir_plan.expr_arena,
870            )
871        };
872
873        match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
874            Ok(v) => Some(v),
875            Err(e) => {
876                // Fallback to normal engine if error is due to not being implemented
877                // and auto_streaming is set, otherwise propagate error.
878                if e.downcast_ref::<&str>()
879                    .is_some_and(|s| s.starts_with("not yet implemented"))
880                {
881                    if polars_core::config::verbose() {
882                        eprintln!(
883                            "caught unimplemented error in new streaming engine, falling back to normal engine"
884                        );
885                    }
886                    None
887                } else {
888                    std::panic::resume_unwind(e)
889                }
890            },
891        }
892    }
893
894    pub fn sink(
895        mut self,
896        sink_type: SinkDestination,
897        file_format: FileWriteFormat,
898        unified_sink_args: UnifiedSinkArgs,
899    ) -> PolarsResult<Self> {
900        polars_ensure!(
901            !matches!(self.logical_plan, DslPlan::Sink { .. }),
902            InvalidOperation: "cannot create a sink on top of another sink"
903        );
904
905        self.logical_plan = DslPlan::Sink {
906            input: Arc::new(self.logical_plan),
907            payload: match sink_type {
908                SinkDestination::File { target } => SinkType::File(FileSinkOptions {
909                    target,
910                    file_format,
911                    unified_sink_args,
912                }),
913                SinkDestination::Partitioned {
914                    base_path,
915                    file_path_provider,
916                    partition_strategy,
917                    max_rows_per_file,
918                    approximate_bytes_per_file,
919                } => SinkType::Partitioned(PartitionedSinkOptions {
920                    base_path,
921                    file_path_provider,
922                    partition_strategy,
923                    file_format,
924                    unified_sink_args,
925                    max_rows_per_file,
926                    approximate_bytes_per_file,
927                }),
928            },
929        };
930        Ok(self)
931    }
932
933    /// Filter frame rows that match a predicate expression.
934    ///
935    /// The expression must yield boolean values (note that rows where the
936    /// predicate resolves to `null` are *not* included in the resulting frame).
937    ///
938    /// # Example
939    ///
940    /// ```rust
941    /// use polars_core::prelude::*;
942    /// use polars_lazy::prelude::*;
943    ///
944    /// fn example(df: DataFrame) -> LazyFrame {
945    ///       df.lazy()
946    ///         .filter(col("sepal_width").is_not_null())
947    ///         .select([col("sepal_width"), col("sepal_length")])
948    /// }
949    /// ```
950    pub fn filter(self, predicate: Expr) -> Self {
951        let opt_state = self.get_opt_state();
952        let lp = self.get_plan_builder().filter(predicate).build();
953        Self::from_logical_plan(lp, opt_state)
954    }
955
956    /// Remove frame rows that match a predicate expression.
957    ///
958    /// The expression must yield boolean values (note that rows where the
959    /// predicate resolves to `null` are *not* removed from the resulting frame).
960    ///
961    /// # Example
962    ///
963    /// ```rust
964    /// use polars_core::prelude::*;
965    /// use polars_lazy::prelude::*;
966    ///
967    /// fn example(df: DataFrame) -> LazyFrame {
968    ///       df.lazy()
969    ///         .remove(col("sepal_width").is_null())
970    ///         .select([col("sepal_width"), col("sepal_length")])
971    /// }
972    /// ```
973    pub fn remove(self, predicate: Expr) -> Self {
974        self.filter(predicate.neq_missing(lit(true)))
975    }
976
977    /// Select (and optionally rename, with [`alias`](crate::dsl::Expr::alias)) columns from the query.
978    ///
979    /// Columns can be selected with [`col`];
980    /// If you want to select all columns use `col(PlSmallStr::from_static("*"))`.
981    ///
982    /// # Example
983    ///
984    /// ```rust
985    /// use polars_core::prelude::*;
986    /// use polars_lazy::prelude::*;
987    ///
988    /// /// This function selects column "foo" and column "bar".
989    /// /// Column "bar" is renamed to "ham".
990    /// fn example(df: DataFrame) -> LazyFrame {
991    ///       df.lazy()
992    ///         .select([col("foo"),
993    ///                   col("bar").alias("ham")])
994    /// }
995    ///
996    /// /// This function selects all columns except "foo"
997    /// fn exclude_a_column(df: DataFrame) -> LazyFrame {
998    ///       df.lazy()
999    ///         .select([all().exclude_cols(["foo"]).as_expr()])
1000    /// }
1001    /// ```
1002    pub fn select<E: AsRef<[Expr]>>(self, exprs: E) -> Self {
1003        let exprs = exprs.as_ref().to_vec();
1004        self.select_impl(
1005            exprs,
1006            ProjectionOptions {
1007                run_parallel: true,
1008                duplicate_check: true,
1009                should_broadcast: true,
1010            },
1011        )
1012    }
1013
1014    pub fn select_seq<E: AsRef<[Expr]>>(self, exprs: E) -> Self {
1015        let exprs = exprs.as_ref().to_vec();
1016        self.select_impl(
1017            exprs,
1018            ProjectionOptions {
1019                run_parallel: false,
1020                duplicate_check: true,
1021                should_broadcast: true,
1022            },
1023        )
1024    }
1025
1026    fn select_impl(self, exprs: Vec<Expr>, options: ProjectionOptions) -> Self {
1027        let opt_state = self.get_opt_state();
1028        let lp = self.get_plan_builder().project(exprs, options).build();
1029        Self::from_logical_plan(lp, opt_state)
1030    }
1031
1032    /// Performs a "group-by" on a `LazyFrame`, producing a [`LazyGroupBy`], which can subsequently be aggregated.
1033    ///
1034    /// Takes a list of expressions to group on.
1035    ///
1036    /// # Example
1037    ///
1038    /// ```rust
1039    /// use polars_core::prelude::*;
1040    /// use polars_lazy::prelude::*;
1041    ///
1042    /// fn example(df: DataFrame) -> LazyFrame {
1043    ///       df.lazy()
1044    ///        .group_by([col("date")])
1045    ///        .agg([
1046    ///            col("rain").min().alias("min_rain"),
1047    ///            col("rain").sum().alias("sum_rain"),
1048    ///            col("rain").quantile(lit(0.5), QuantileMethod::Nearest).alias("median_rain"),
1049    ///        ])
1050    /// }
1051    /// ```
1052    pub fn group_by<E: AsRef<[IE]>, IE: Into<Expr> + Clone>(self, by: E) -> LazyGroupBy {
1053        let keys = by
1054            .as_ref()
1055            .iter()
1056            .map(|e| e.clone().into())
1057            .collect::<Vec<_>>();
1058        let opt_state = self.get_opt_state();
1059
1060        #[cfg(feature = "dynamic_group_by")]
1061        {
1062            LazyGroupBy {
1063                logical_plan: self.logical_plan,
1064                opt_state,
1065                keys,
1066                predicates: vec![],
1067                maintain_order: false,
1068                dynamic_options: None,
1069                rolling_options: None,
1070            }
1071        }
1072
1073        #[cfg(not(feature = "dynamic_group_by"))]
1074        {
1075            LazyGroupBy {
1076                logical_plan: self.logical_plan,
1077                opt_state,
1078                keys,
1079                predicates: vec![],
1080                maintain_order: false,
1081            }
1082        }
1083    }
1084
1085    /// Create rolling groups based on a time column.
1086    ///
1087    /// Also works for index values of type UInt32, UInt64, Int32, or Int64.
1088    ///
1089    /// Different from a [`group_by_dynamic`][`Self::group_by_dynamic`], the windows are now determined by the
1090    /// individual values and are not of constant intervals. For constant intervals use
1091    /// *group_by_dynamic*
1092    #[cfg(feature = "dynamic_group_by")]
1093    pub fn rolling<E: AsRef<[Expr]>>(
1094        mut self,
1095        index_column: Expr,
1096        group_by: E,
1097        mut options: RollingGroupOptions,
1098    ) -> LazyGroupBy {
1099        if let Expr::Column(name) = index_column {
1100            options.index_column = name;
1101        } else {
1102            let output_field = index_column
1103                .to_field(&self.collect_schema().unwrap())
1104                .unwrap();
1105            return self.with_column(index_column).rolling(
1106                Expr::Column(output_field.name().clone()),
1107                group_by,
1108                options,
1109            );
1110        }
1111        let opt_state = self.get_opt_state();
1112        LazyGroupBy {
1113            logical_plan: self.logical_plan,
1114            opt_state,
1115            predicates: vec![],
1116            keys: group_by.as_ref().to_vec(),
1117            maintain_order: true,
1118            dynamic_options: None,
1119            rolling_options: Some(options),
1120        }
1121    }
1122
1123    /// Group based on a time value (or index value of type Int32, Int64).
1124    ///
1125    /// Time windows are calculated and rows are assigned to windows. Different from a
1126    /// normal group_by is that a row can be member of multiple groups. The time/index
1127    /// window could be seen as a rolling window, with a window size determined by
1128    /// dates/times/values instead of slots in the DataFrame.
1129    ///
1130    /// A window is defined by:
1131    ///
1132    /// - every: interval of the window
1133    /// - period: length of the window
1134    /// - offset: offset of the window
1135    ///
1136    /// The `group_by` argument should be empty `[]` if you don't want to combine this
1137    /// with a ordinary group_by on these keys.
1138    #[cfg(feature = "dynamic_group_by")]
1139    pub fn group_by_dynamic<E: AsRef<[Expr]>>(
1140        mut self,
1141        index_column: Expr,
1142        group_by: E,
1143        mut options: DynamicGroupOptions,
1144    ) -> LazyGroupBy {
1145        if let Expr::Column(name) = index_column {
1146            options.index_column = name;
1147        } else {
1148            let output_field = index_column
1149                .to_field(&self.collect_schema().unwrap())
1150                .unwrap();
1151            return self.with_column(index_column).group_by_dynamic(
1152                Expr::Column(output_field.name().clone()),
1153                group_by,
1154                options,
1155            );
1156        }
1157        let opt_state = self.get_opt_state();
1158        LazyGroupBy {
1159            logical_plan: self.logical_plan,
1160            opt_state,
1161            predicates: vec![],
1162            keys: group_by.as_ref().to_vec(),
1163            maintain_order: true,
1164            dynamic_options: Some(options),
1165            rolling_options: None,
1166        }
1167    }
1168
1169    /// Similar to [`group_by`][`Self::group_by`], but order of the DataFrame is maintained.
1170    pub fn group_by_stable<E: AsRef<[IE]>, IE: Into<Expr> + Clone>(self, by: E) -> LazyGroupBy {
1171        let keys = by
1172            .as_ref()
1173            .iter()
1174            .map(|e| e.clone().into())
1175            .collect::<Vec<_>>();
1176        let opt_state = self.get_opt_state();
1177
1178        #[cfg(feature = "dynamic_group_by")]
1179        {
1180            LazyGroupBy {
1181                logical_plan: self.logical_plan,
1182                opt_state,
1183                keys,
1184                predicates: vec![],
1185                maintain_order: true,
1186                dynamic_options: None,
1187                rolling_options: None,
1188            }
1189        }
1190
1191        #[cfg(not(feature = "dynamic_group_by"))]
1192        {
1193            LazyGroupBy {
1194                logical_plan: self.logical_plan,
1195                opt_state,
1196                keys,
1197                predicates: vec![],
1198                maintain_order: true,
1199            }
1200        }
1201    }
1202
1203    /// Left anti join this query with another lazy query.
1204    ///
1205    /// Matches on the values of the expressions `left_on` and `right_on`. For more
1206    /// flexible join logic, see [`join`](LazyFrame::join) or
1207    /// [`join_builder`](LazyFrame::join_builder).
1208    ///
1209    /// # Example
1210    ///
1211    /// ```rust
1212    /// use polars_core::prelude::*;
1213    /// use polars_lazy::prelude::*;
1214    /// fn anti_join_dataframes(ldf: LazyFrame, other: LazyFrame) -> LazyFrame {
1215    ///         ldf
1216    ///         .anti_join(other, col("foo"), col("bar").cast(DataType::String))
1217    /// }
1218    /// ```
1219    #[cfg(feature = "semi_anti_join")]
1220    pub fn anti_join<E: Into<Expr>>(self, other: LazyFrame, left_on: E, right_on: E) -> LazyFrame {
1221        self.join(
1222            other,
1223            [left_on.into()],
1224            [right_on.into()],
1225            JoinArgs::new(JoinType::Anti),
1226        )
1227    }
1228
1229    /// Creates the Cartesian product from both frames, preserving the order of the left keys.
1230    #[cfg(feature = "cross_join")]
1231    pub fn cross_join(self, other: LazyFrame, suffix: Option<PlSmallStr>) -> LazyFrame {
1232        self.join(
1233            other,
1234            vec![],
1235            vec![],
1236            JoinArgs::new(JoinType::Cross).with_suffix(suffix),
1237        )
1238    }
1239
1240    /// Left outer join this query with another lazy query.
1241    ///
1242    /// Matches on the values of the expressions `left_on` and `right_on`. For more
1243    /// flexible join logic, see [`join`](LazyFrame::join) or
1244    /// [`join_builder`](LazyFrame::join_builder).
1245    ///
1246    /// # Example
1247    ///
1248    /// ```rust
1249    /// use polars_core::prelude::*;
1250    /// use polars_lazy::prelude::*;
1251    /// fn left_join_dataframes(ldf: LazyFrame, other: LazyFrame) -> LazyFrame {
1252    ///         ldf
1253    ///         .left_join(other, col("foo"), col("bar"))
1254    /// }
1255    /// ```
1256    pub fn left_join<E: Into<Expr>>(self, other: LazyFrame, left_on: E, right_on: E) -> LazyFrame {
1257        self.join(
1258            other,
1259            [left_on.into()],
1260            [right_on.into()],
1261            JoinArgs::new(JoinType::Left),
1262        )
1263    }
1264
1265    /// Inner join this query with another lazy query.
1266    ///
1267    /// Matches on the values of the expressions `left_on` and `right_on`. For more
1268    /// flexible join logic, see [`join`](LazyFrame::join) or
1269    /// [`join_builder`](LazyFrame::join_builder).
1270    ///
1271    /// # Example
1272    ///
1273    /// ```rust
1274    /// use polars_core::prelude::*;
1275    /// use polars_lazy::prelude::*;
1276    /// fn inner_join_dataframes(ldf: LazyFrame, other: LazyFrame) -> LazyFrame {
1277    ///         ldf
1278    ///         .inner_join(other, col("foo"), col("bar").cast(DataType::String))
1279    /// }
1280    /// ```
1281    pub fn inner_join<E: Into<Expr>>(self, other: LazyFrame, left_on: E, right_on: E) -> LazyFrame {
1282        self.join(
1283            other,
1284            [left_on.into()],
1285            [right_on.into()],
1286            JoinArgs::new(JoinType::Inner),
1287        )
1288    }
1289
1290    /// Full outer join this query with another lazy query.
1291    ///
1292    /// Matches on the values of the expressions `left_on` and `right_on`. For more
1293    /// flexible join logic, see [`join`](LazyFrame::join) or
1294    /// [`join_builder`](LazyFrame::join_builder).
1295    ///
1296    /// # Example
1297    ///
1298    /// ```rust
1299    /// use polars_core::prelude::*;
1300    /// use polars_lazy::prelude::*;
1301    /// fn full_join_dataframes(ldf: LazyFrame, other: LazyFrame) -> LazyFrame {
1302    ///         ldf
1303    ///         .full_join(other, col("foo"), col("bar"))
1304    /// }
1305    /// ```
1306    pub fn full_join<E: Into<Expr>>(self, other: LazyFrame, left_on: E, right_on: E) -> LazyFrame {
1307        self.join(
1308            other,
1309            [left_on.into()],
1310            [right_on.into()],
1311            JoinArgs::new(JoinType::Full),
1312        )
1313    }
1314
1315    /// Left semi join this query with another lazy query.
1316    ///
1317    /// Matches on the values of the expressions `left_on` and `right_on`. For more
1318    /// flexible join logic, see [`join`](LazyFrame::join) or
1319    /// [`join_builder`](LazyFrame::join_builder).
1320    ///
1321    /// # Example
1322    ///
1323    /// ```rust
1324    /// use polars_core::prelude::*;
1325    /// use polars_lazy::prelude::*;
1326    /// fn semi_join_dataframes(ldf: LazyFrame, other: LazyFrame) -> LazyFrame {
1327    ///         ldf
1328    ///         .semi_join(other, col("foo"), col("bar").cast(DataType::String))
1329    /// }
1330    /// ```
1331    #[cfg(feature = "semi_anti_join")]
1332    pub fn semi_join<E: Into<Expr>>(self, other: LazyFrame, left_on: E, right_on: E) -> LazyFrame {
1333        self.join(
1334            other,
1335            [left_on.into()],
1336            [right_on.into()],
1337            JoinArgs::new(JoinType::Semi),
1338        )
1339    }
1340
1341    /// Generic function to join two LazyFrames.
1342    ///
1343    /// `join` can join on multiple columns, given as two list of expressions, and with a
1344    /// [`JoinType`] specified by `how`. Non-joined column names in the right DataFrame
1345    /// that already exist in this DataFrame are suffixed with `"_right"`. For control
1346    /// over how columns are renamed and parallelization options, use
1347    /// [`join_builder`](LazyFrame::join_builder).
1348    ///
1349    /// Any provided `args.slice` parameter is not considered, but set by the internal optimizer.
1350    ///
1351    /// # Example
1352    ///
1353    /// ```rust
1354    /// use polars_core::prelude::*;
1355    /// use polars_lazy::prelude::*;
1356    ///
1357    /// fn example(ldf: LazyFrame, other: LazyFrame) -> LazyFrame {
1358    ///         ldf
1359    ///         .join(other, [col("foo"), col("bar")], [col("foo"), col("bar")], JoinArgs::new(JoinType::Inner))
1360    /// }
1361    /// ```
1362    pub fn join<E: AsRef<[Expr]>>(
1363        self,
1364        other: LazyFrame,
1365        left_on: E,
1366        right_on: E,
1367        args: JoinArgs,
1368    ) -> LazyFrame {
1369        let left_on = left_on.as_ref().to_vec();
1370        let right_on = right_on.as_ref().to_vec();
1371
1372        self._join_impl(other, left_on, right_on, args)
1373    }
1374
1375    fn _join_impl(
1376        self,
1377        other: LazyFrame,
1378        left_on: Vec<Expr>,
1379        right_on: Vec<Expr>,
1380        args: JoinArgs,
1381    ) -> LazyFrame {
1382        let JoinArgs {
1383            how,
1384            validation,
1385            suffix,
1386            slice,
1387            nulls_equal,
1388            coalesce,
1389            maintain_order,
1390            build_side,
1391        } = args;
1392
1393        if slice.is_some() {
1394            panic!("impl error: slice is not handled")
1395        }
1396
1397        let mut builder = self
1398            .join_builder()
1399            .with(other)
1400            .left_on(left_on)
1401            .right_on(right_on)
1402            .how(how)
1403            .validate(validation)
1404            .join_nulls(nulls_equal)
1405            .coalesce(coalesce)
1406            .maintain_order(maintain_order)
1407            .build_side(build_side);
1408
1409        if let Some(suffix) = suffix {
1410            builder = builder.suffix(suffix);
1411        }
1412
1413        // Note: args.slice is set by the optimizer
1414        builder.finish()
1415    }
1416
1417    /// Consume `self` and return a [`JoinBuilder`] to customize a join on this LazyFrame.
1418    ///
1419    /// After the `JoinBuilder` has been created and set up, calling
1420    /// [`finish()`](JoinBuilder::finish) on it will give back the `LazyFrame`
1421    /// representing the `join` operation.
1422    pub fn join_builder(self) -> JoinBuilder {
1423        JoinBuilder::new(self)
1424    }
1425
1426    /// Gathers rows from this DataFrame based on the indices in idxs.
1427    ///
1428    /// idxs must only have a single column of indices.
1429    pub fn gather(self, idxs: LazyFrame, null_on_oob: bool) -> LazyFrame {
1430        let opt_state = self.get_opt_state();
1431        let lp = self
1432            .get_plan_builder()
1433            .gather(idxs.logical_plan, null_on_oob)
1434            .build();
1435        Self::from_logical_plan(lp, opt_state)
1436    }
1437
1438    /// Add or replace a column, given as an expression, to a DataFrame.
1439    ///
1440    /// # Example
1441    ///
1442    /// ```rust
1443    /// use polars_core::prelude::*;
1444    /// use polars_lazy::prelude::*;
1445    /// fn add_column(df: DataFrame) -> LazyFrame {
1446    ///     df.lazy()
1447    ///         .with_column(
1448    ///             when(col("sepal_length").lt(lit(5.0)))
1449    ///             .then(lit(10))
1450    ///             .otherwise(lit(1))
1451    ///             .alias("new_column_name"),
1452    ///         )
1453    /// }
1454    /// ```
1455    pub fn with_column(self, expr: Expr) -> LazyFrame {
1456        let opt_state = self.get_opt_state();
1457        let lp = self
1458            .get_plan_builder()
1459            .with_columns(
1460                vec![expr],
1461                ProjectionOptions {
1462                    run_parallel: false,
1463                    duplicate_check: true,
1464                    should_broadcast: true,
1465                },
1466            )
1467            .build();
1468        Self::from_logical_plan(lp, opt_state)
1469    }
1470
1471    /// Add or replace multiple columns, given as expressions, to a DataFrame.
1472    ///
1473    /// # Example
1474    ///
1475    /// ```rust
1476    /// use polars_core::prelude::*;
1477    /// use polars_lazy::prelude::*;
1478    /// fn add_columns(df: DataFrame) -> LazyFrame {
1479    ///     df.lazy()
1480    ///         .with_columns(
1481    ///             vec![lit(10).alias("foo"), lit(100).alias("bar")]
1482    ///          )
1483    /// }
1484    /// ```
1485    pub fn with_columns<E: AsRef<[Expr]>>(self, exprs: E) -> LazyFrame {
1486        let exprs = exprs.as_ref().to_vec();
1487        self.with_columns_impl(
1488            exprs,
1489            ProjectionOptions {
1490                run_parallel: true,
1491                duplicate_check: true,
1492                should_broadcast: true,
1493            },
1494        )
1495    }
1496
1497    /// Add or replace multiple columns to a DataFrame, but evaluate them sequentially.
1498    pub fn with_columns_seq<E: AsRef<[Expr]>>(self, exprs: E) -> LazyFrame {
1499        let exprs = exprs.as_ref().to_vec();
1500        self.with_columns_impl(
1501            exprs,
1502            ProjectionOptions {
1503                run_parallel: false,
1504                duplicate_check: true,
1505                should_broadcast: true,
1506            },
1507        )
1508    }
1509
1510    /// Match or evolve to a certain schema.
1511    pub fn match_to_schema(
1512        self,
1513        schema: SchemaRef,
1514        per_column: Arc<[MatchToSchemaPerColumn]>,
1515        extra_columns: ExtraColumnsPolicy,
1516    ) -> LazyFrame {
1517        let opt_state = self.get_opt_state();
1518        let lp = self
1519            .get_plan_builder()
1520            .match_to_schema(schema, per_column, extra_columns)
1521            .build();
1522        Self::from_logical_plan(lp, opt_state)
1523    }
1524
1525    pub fn pipe_with_schema(
1526        self,
1527        callback: PlanCallback<(Vec<DslPlan>, Vec<SchemaRef>), DslPlan>,
1528    ) -> Self {
1529        let opt_state = self.get_opt_state();
1530        let lp = self
1531            .get_plan_builder()
1532            .pipe_with_schema(vec![], callback)
1533            .build();
1534        Self::from_logical_plan(lp, opt_state)
1535    }
1536
1537    pub fn pipe_with_schemas(
1538        self,
1539        others: Vec<LazyFrame>,
1540        callback: PlanCallback<(Vec<DslPlan>, Vec<SchemaRef>), DslPlan>,
1541    ) -> Self {
1542        let opt_state = self.get_opt_state();
1543        let lp = self
1544            .get_plan_builder()
1545            .pipe_with_schema(
1546                others.into_iter().map(|lf| lf.logical_plan).collect(),
1547                callback,
1548            )
1549            .build();
1550        Self::from_logical_plan(lp, opt_state)
1551    }
1552
1553    fn with_columns_impl(self, exprs: Vec<Expr>, options: ProjectionOptions) -> LazyFrame {
1554        let opt_state = self.get_opt_state();
1555        let lp = self.get_plan_builder().with_columns(exprs, options).build();
1556        Self::from_logical_plan(lp, opt_state)
1557    }
1558
1559    pub fn with_context<C: AsRef<[LazyFrame]>>(self, contexts: C) -> LazyFrame {
1560        let contexts = contexts
1561            .as_ref()
1562            .iter()
1563            .map(|lf| lf.logical_plan.clone())
1564            .collect();
1565        let opt_state = self.get_opt_state();
1566        let lp = self.get_plan_builder().with_context(contexts).build();
1567        Self::from_logical_plan(lp, opt_state)
1568    }
1569
1570    /// Aggregate all the columns as their maximum values.
1571    ///
1572    /// Aggregated columns will have the same names as the original columns.
1573    pub fn max(self) -> Self {
1574        self.map_private(DslFunction::Stats(StatsFunction::Max))
1575    }
1576
1577    /// Aggregate all the columns as their minimum values.
1578    ///
1579    /// Aggregated columns will have the same names as the original columns.
1580    pub fn min(self) -> Self {
1581        self.map_private(DslFunction::Stats(StatsFunction::Min))
1582    }
1583
1584    /// Aggregate all the columns as their sum values.
1585    ///
1586    /// Aggregated columns will have the same names as the original columns.
1587    ///
1588    /// - Boolean columns will sum to a `u32` containing the number of `true`s.
1589    /// - For integer columns, the ordinary checks for overflow are performed:
1590    ///   if running in `debug` mode, overflows will panic, whereas in `release` mode overflows will
1591    ///   silently wrap.
1592    /// - String columns will sum to None.
1593    pub fn sum(self) -> Self {
1594        self.map_private(DslFunction::Stats(StatsFunction::Sum))
1595    }
1596
1597    /// Aggregate all the columns as their mean values.
1598    ///
1599    /// - Boolean and integer columns are converted to `f64` before computing the mean.
1600    /// - String columns will have a mean of None.
1601    pub fn mean(self) -> Self {
1602        self.map_private(DslFunction::Stats(StatsFunction::Mean))
1603    }
1604
1605    /// Aggregate all the columns as their median values.
1606    ///
1607    /// - Boolean and integer results are converted to `f64`. However, they are still
1608    ///   susceptible to overflow before this conversion occurs.
1609    /// - String columns will sum to None.
1610    pub fn median(self) -> Self {
1611        self.map_private(DslFunction::Stats(StatsFunction::Median))
1612    }
1613
1614    /// Aggregate all the columns as their quantile values.
1615    pub fn quantile(self, quantile: Expr, method: QuantileMethod) -> Self {
1616        self.map_private(DslFunction::Stats(StatsFunction::Quantile {
1617            quantile,
1618            method,
1619        }))
1620    }
1621
1622    /// Aggregate all the columns as their standard deviation values.
1623    ///
1624    /// `ddof` is the "Delta Degrees of Freedom"; `N - ddof` will be the denominator when
1625    /// computing the variance, where `N` is the number of rows.
1626    /// > In standard statistical practice, `ddof=1` provides an unbiased estimator of the
1627    /// > variance of a hypothetical infinite population. `ddof=0` provides a maximum
1628    /// > likelihood estimate of the variance for normally distributed variables. The
1629    /// > standard deviation computed in this function is the square root of the estimated
1630    /// > variance, so even with `ddof=1`, it will not be an unbiased estimate of the
1631    /// > standard deviation per se.
1632    ///
1633    /// Source: [Numpy](https://numpy.org/doc/stable/reference/generated/numpy.std.html#)
1634    pub fn std(self, ddof: u8) -> Self {
1635        self.map_private(DslFunction::Stats(StatsFunction::Std { ddof }))
1636    }
1637
1638    /// Aggregate all the columns as their variance values.
1639    ///
1640    /// `ddof` is the "Delta Degrees of Freedom"; `N - ddof` will be the denominator when
1641    /// computing the variance, where `N` is the number of rows.
1642    /// > In standard statistical practice, `ddof=1` provides an unbiased estimator of the
1643    /// > variance of a hypothetical infinite population. `ddof=0` provides a maximum
1644    /// > likelihood estimate of the variance for normally distributed variables.
1645    ///
1646    /// Source: [Numpy](https://numpy.org/doc/stable/reference/generated/numpy.var.html#)
1647    pub fn var(self, ddof: u8) -> Self {
1648        self.map_private(DslFunction::Stats(StatsFunction::Var { ddof }))
1649    }
1650
1651    /// Apply explode operation. [See eager explode](polars_core::frame::DataFrame::explode).
1652    pub fn explode(self, columns: Selector, options: ExplodeOptions) -> LazyFrame {
1653        self.explode_impl(columns, options, false)
1654    }
1655
1656    /// Apply explode operation. [See eager explode](polars_core::frame::DataFrame::explode).
1657    fn explode_impl(
1658        self,
1659        columns: Selector,
1660        options: ExplodeOptions,
1661        allow_empty: bool,
1662    ) -> LazyFrame {
1663        let opt_state = self.get_opt_state();
1664        let lp = self
1665            .get_plan_builder()
1666            .explode(columns, options, allow_empty)
1667            .build();
1668        Self::from_logical_plan(lp, opt_state)
1669    }
1670
1671    /// Aggregate all the columns as the sum of their null value count.
1672    pub fn null_count(self) -> LazyFrame {
1673        self.select(vec![col(PlSmallStr::from_static("*")).null_count()])
1674    }
1675
1676    /// Drop non-unique rows and maintain the order of kept rows.
1677    ///
1678    /// `subset` is an optional `Vec` of column names to consider for uniqueness; if
1679    /// `None`, all columns are considered.
1680    pub fn unique_stable(
1681        self,
1682        subset: Option<Selector>,
1683        keep_strategy: UniqueKeepStrategy,
1684    ) -> LazyFrame {
1685        let subset = subset.map(|s| vec![Expr::Selector(s)]);
1686        self.unique_stable_generic(subset, keep_strategy)
1687    }
1688
1689    pub fn unique_stable_generic(
1690        self,
1691        subset: Option<Vec<Expr>>,
1692        keep_strategy: UniqueKeepStrategy,
1693    ) -> LazyFrame {
1694        let opt_state = self.get_opt_state();
1695        let options = DistinctOptionsDSL {
1696            subset,
1697            maintain_order: true,
1698            keep_strategy,
1699        };
1700        let lp = self.get_plan_builder().distinct(options).build();
1701        Self::from_logical_plan(lp, opt_state)
1702    }
1703
1704    /// Drop non-unique rows without maintaining the order of kept rows.
1705    ///
1706    /// The order of the kept rows may change; to maintain the original row order, use
1707    /// [`unique_stable`](LazyFrame::unique_stable).
1708    ///
1709    /// `subset` is an optional `Vec` of column names to consider for uniqueness; if None,
1710    /// all columns are considered.
1711    pub fn unique(self, subset: Option<Selector>, keep_strategy: UniqueKeepStrategy) -> LazyFrame {
1712        let subset = subset.map(|s| vec![Expr::Selector(s)]);
1713        self.unique_generic(subset, keep_strategy)
1714    }
1715
1716    pub fn unique_generic(
1717        self,
1718        subset: Option<Vec<Expr>>,
1719        keep_strategy: UniqueKeepStrategy,
1720    ) -> LazyFrame {
1721        let opt_state = self.get_opt_state();
1722        let options = DistinctOptionsDSL {
1723            subset,
1724            maintain_order: false,
1725            keep_strategy,
1726        };
1727        let lp = self.get_plan_builder().distinct(options).build();
1728        Self::from_logical_plan(lp, opt_state)
1729    }
1730
1731    /// Drop rows containing one or more NaN values.
1732    ///
1733    /// `subset` is an optional `Vec` of column names to consider for NaNs; if None, all
1734    /// floating point columns are considered.
1735    pub fn drop_nans(self, subset: Option<Selector>) -> LazyFrame {
1736        let opt_state = self.get_opt_state();
1737        let lp = self.get_plan_builder().drop_nans(subset).build();
1738        Self::from_logical_plan(lp, opt_state)
1739    }
1740
1741    /// Drop rows containing one or more None values.
1742    ///
1743    /// `subset` is an optional `Vec` of column names to consider for nulls; if None, all
1744    /// columns are considered.
1745    pub fn drop_nulls(self, subset: Option<Selector>) -> LazyFrame {
1746        let opt_state = self.get_opt_state();
1747        let lp = self.get_plan_builder().drop_nulls(subset).build();
1748        Self::from_logical_plan(lp, opt_state)
1749    }
1750
1751    /// Slice the DataFrame using an offset (starting row) and a length.
1752    ///
1753    /// If `offset` is negative, it is counted from the end of the DataFrame. For
1754    /// instance, `lf.slice(-5, 3)` gets three rows, starting at the row fifth from the
1755    /// end.
1756    ///
1757    /// If `offset` and `len` are such that the slice extends beyond the end of the
1758    /// DataFrame, the portion between `offset` and the end will be returned. In this
1759    /// case, the number of rows in the returned DataFrame will be less than `len`.
1760    pub fn slice(self, offset: i64, len: IdxSize) -> LazyFrame {
1761        let opt_state = self.get_opt_state();
1762        let lp = self.get_plan_builder().slice(offset, len).build();
1763        Self::from_logical_plan(lp, opt_state)
1764    }
1765
1766    /// Remove all the rows of the LazyFrame.
1767    pub fn clear(self) -> LazyFrame {
1768        self.slice(0, 0)
1769    }
1770
1771    /// Get the first row.
1772    ///
1773    /// Equivalent to `self.slice(0, 1)`.
1774    pub fn first(self) -> LazyFrame {
1775        self.slice(0, 1)
1776    }
1777
1778    /// Get the last row.
1779    ///
1780    /// Equivalent to `self.slice(-1, 1)`.
1781    pub fn last(self) -> LazyFrame {
1782        self.slice(-1, 1)
1783    }
1784
1785    /// Get the last `n` rows.
1786    ///
1787    /// Equivalent to `self.slice(-(n as i64), n)`.
1788    pub fn tail(self, n: IdxSize) -> LazyFrame {
1789        let neg_tail = -(n as i64);
1790        self.slice(neg_tail, n)
1791    }
1792
1793    #[cfg(feature = "pivot")]
1794    #[expect(clippy::too_many_arguments)]
1795    pub fn pivot(
1796        self,
1797        on: Selector,
1798        on_columns: Arc<DataFrame>,
1799        index: Selector,
1800        values: Selector,
1801        agg: Expr,
1802        maintain_order: bool,
1803        separator: PlSmallStr,
1804        column_naming: PivotColumnNaming,
1805    ) -> LazyFrame {
1806        let opt_state = self.get_opt_state();
1807        let lp = self
1808            .get_plan_builder()
1809            .pivot(
1810                on,
1811                on_columns,
1812                index,
1813                values,
1814                agg,
1815                maintain_order,
1816                separator,
1817                column_naming,
1818            )
1819            .build();
1820        Self::from_logical_plan(lp, opt_state)
1821    }
1822
1823    /// Unpivot the DataFrame from wide to long format.
1824    ///
1825    /// See [`UnpivotArgsIR`] for information on how to unpivot a DataFrame.
1826    #[cfg(feature = "pivot")]
1827    pub fn unpivot(self, args: UnpivotArgsDSL) -> LazyFrame {
1828        let opt_state = self.get_opt_state();
1829        let lp = self.get_plan_builder().unpivot(args).build();
1830        Self::from_logical_plan(lp, opt_state)
1831    }
1832
1833    /// Limit the DataFrame to the first `n` rows.
1834    pub fn limit(self, n: IdxSize) -> LazyFrame {
1835        self.slice(0, n)
1836    }
1837
1838    /// Apply a function/closure once the logical plan get executed.
1839    ///
1840    /// The function has access to the whole materialized DataFrame at the time it is
1841    /// called.
1842    ///
1843    /// To apply specific functions to specific columns, use [`Expr::map`] in conjunction
1844    /// with `LazyFrame::with_column` or `with_columns`.
1845    ///
1846    /// ## Warning
1847    /// This can blow up in your face if the schema is changed due to the operation. The
1848    /// optimizer relies on a correct schema.
1849    ///
1850    /// You can toggle certain optimizations off.
1851    pub fn map<F>(
1852        self,
1853        function: F,
1854        optimizations: AllowedOptimizations,
1855        schema: Option<Arc<dyn UdfSchema>>,
1856        name: Option<&'static str>,
1857    ) -> LazyFrame
1858    where
1859        F: 'static + Fn(DataFrame) -> PolarsResult<DataFrame> + Send + Sync,
1860    {
1861        let opt_state = self.get_opt_state();
1862        let lp = self
1863            .get_plan_builder()
1864            .map(
1865                function,
1866                optimizations,
1867                schema,
1868                PlSmallStr::from_static(name.unwrap_or("ANONYMOUS UDF")),
1869            )
1870            .build();
1871        Self::from_logical_plan(lp, opt_state)
1872    }
1873
1874    #[cfg(feature = "python")]
1875    pub fn map_python(
1876        self,
1877        function: polars_utils::python_function::PythonFunction,
1878        optimizations: AllowedOptimizations,
1879        schema: Option<SchemaRef>,
1880        validate_output: bool,
1881    ) -> LazyFrame {
1882        let opt_state = self.get_opt_state();
1883        let lp = self
1884            .get_plan_builder()
1885            .map_python(function, optimizations, schema, validate_output)
1886            .build();
1887        Self::from_logical_plan(lp, opt_state)
1888    }
1889
1890    pub(crate) fn map_private(self, function: DslFunction) -> LazyFrame {
1891        let opt_state = self.get_opt_state();
1892        let lp = self.get_plan_builder().map_private(function).build();
1893        Self::from_logical_plan(lp, opt_state)
1894    }
1895
1896    /// Add a new column at index 0 that counts the rows.
1897    ///
1898    /// `name` is the name of the new column. `offset` is where to start counting from; if
1899    /// `None`, it is set to `0`.
1900    ///
1901    /// # Warning
1902    /// This can have a negative effect on query performance. This may for instance block
1903    /// predicate pushdown optimization.
1904    pub fn with_row_index<S>(self, name: S, offset: Option<IdxSize>) -> LazyFrame
1905    where
1906        S: Into<PlSmallStr>,
1907    {
1908        let name = name.into();
1909
1910        match &self.logical_plan {
1911            v @ DslPlan::Scan {
1912                scan_type,
1913                unified_scan_args,
1914                ..
1915            } if unified_scan_args.row_index.is_none()
1916                && !matches!(
1917                    &**scan_type,
1918                    FileScanDsl::Anonymous { .. } | FileScanDsl::ExpandedPaths { .. }
1919                ) =>
1920            {
1921                let DslPlan::Scan {
1922                    sources,
1923                    mut unified_scan_args,
1924                    scan_type,
1925                    cached_ir: _,
1926                } = v.clone()
1927                else {
1928                    unreachable!()
1929                };
1930
1931                unified_scan_args.row_index = Some(RowIndex {
1932                    name,
1933                    offset: offset.unwrap_or(0),
1934                });
1935
1936                DslPlan::Scan {
1937                    sources,
1938                    unified_scan_args,
1939                    scan_type,
1940                    cached_ir: Default::default(),
1941                }
1942                .into()
1943            },
1944            _ => self.map_private(DslFunction::RowIndex { name, offset }),
1945        }
1946    }
1947
1948    /// Return the number of non-null elements for each column.
1949    pub fn count(self) -> LazyFrame {
1950        self.select(vec![col(PlSmallStr::from_static("*")).count()])
1951    }
1952
1953    /// Unnest the given `Struct` columns: the fields of the `Struct` type will be
1954    /// inserted as columns.
1955    #[cfg(feature = "dtype-struct")]
1956    pub fn unnest(self, cols: Selector, separator: Option<PlSmallStr>) -> Self {
1957        self.map_private(DslFunction::Unnest {
1958            columns: cols,
1959            separator,
1960        })
1961    }
1962
1963    #[cfg(feature = "merge_sorted")]
1964    pub fn merge_sorted<I, S>(
1965        self,
1966        other: LazyFrame,
1967        key: I,
1968        maintain_order: bool,
1969    ) -> PolarsResult<LazyFrame>
1970    where
1971        I: IntoIterator<Item = S>,
1972        S: Into<PlSmallStr>,
1973    {
1974        let key: Arc<[PlSmallStr]> = key.into_iter().map(Into::into).collect();
1975
1976        polars_ensure!(
1977            !key.is_empty(),
1978            ComputeError: "merge_sorted requires at least one key column"
1979        );
1980
1981        let lp = DslPlan::MergeSorted {
1982            input_left: Arc::new(self.logical_plan),
1983            input_right: Arc::new(other.logical_plan),
1984            key,
1985            maintain_order,
1986        };
1987        Ok(LazyFrame::from_logical_plan(lp, self.opt_state))
1988    }
1989
1990    pub fn hint(self, hint: HintIR) -> PolarsResult<LazyFrame> {
1991        let lp = DslPlan::MapFunction {
1992            input: Arc::new(self.logical_plan),
1993            function: DslFunction::Hint(hint),
1994        };
1995        Ok(LazyFrame::from_logical_plan(lp, self.opt_state))
1996    }
1997}
1998
1999/// Utility struct for lazy group_by operation.
2000#[derive(Clone)]
2001pub struct LazyGroupBy {
2002    pub logical_plan: DslPlan,
2003    opt_state: OptFlags,
2004    keys: Vec<Expr>,
2005    predicates: Vec<Expr>,
2006    maintain_order: bool,
2007    #[cfg(feature = "dynamic_group_by")]
2008    dynamic_options: Option<DynamicGroupOptions>,
2009    #[cfg(feature = "dynamic_group_by")]
2010    rolling_options: Option<RollingGroupOptions>,
2011}
2012
2013impl From<LazyGroupBy> for LazyFrame {
2014    fn from(lgb: LazyGroupBy) -> Self {
2015        Self {
2016            logical_plan: lgb.logical_plan,
2017            opt_state: lgb.opt_state,
2018            cached_arena: Default::default(),
2019        }
2020    }
2021}
2022
2023impl LazyGroupBy {
2024    /// Filter groups with a predicate after aggregation.
2025    ///
2026    /// Similarly to the [LazyGroupBy::agg] method, the predicate must run an aggregation as it
2027    /// is evaluated on the groups.
2028    /// This method can be chained in which case all predicates must evaluate to `true` for a
2029    /// group to be kept.
2030    ///
2031    /// # Example
2032    ///
2033    /// ```rust
2034    /// use polars_core::prelude::*;
2035    /// use polars_lazy::prelude::*;
2036    ///
2037    /// fn example(df: DataFrame) -> LazyFrame {
2038    ///       df.lazy()
2039    ///        .group_by_stable([col("date")])
2040    ///        .having(col("rain").sum().gt(lit(10)))
2041    ///        .agg([col("rain").min().alias("min_rain")])
2042    /// }
2043    /// ```
2044    pub fn having(mut self, predicate: Expr) -> Self {
2045        self.predicates.push(predicate);
2046        self
2047    }
2048
2049    /// Group by and aggregate.
2050    ///
2051    /// Select a column with [col] and choose an aggregation.
2052    /// If you want to aggregate all columns use `col(PlSmallStr::from_static("*"))`.
2053    ///
2054    /// # Example
2055    ///
2056    /// ```rust
2057    /// use polars_core::prelude::*;
2058    /// use polars_lazy::prelude::*;
2059    ///
2060    /// fn example(df: DataFrame) -> LazyFrame {
2061    ///       df.lazy()
2062    ///        .group_by_stable([col("date")])
2063    ///        .agg([
2064    ///            col("rain").min().alias("min_rain"),
2065    ///            col("rain").sum().alias("sum_rain"),
2066    ///            col("rain").quantile(lit(0.5), QuantileMethod::Nearest).alias("median_rain"),
2067    ///        ])
2068    /// }
2069    /// ```
2070    pub fn agg<E: AsRef<[Expr]>>(self, aggs: E) -> LazyFrame {
2071        #[cfg(feature = "dynamic_group_by")]
2072        let lp = DslBuilder::from(self.logical_plan)
2073            .group_by(
2074                self.keys,
2075                self.predicates,
2076                aggs,
2077                None,
2078                self.maintain_order,
2079                self.dynamic_options,
2080                self.rolling_options,
2081            )
2082            .build();
2083
2084        #[cfg(not(feature = "dynamic_group_by"))]
2085        let lp = DslBuilder::from(self.logical_plan)
2086            .group_by(self.keys, self.predicates, aggs, None, self.maintain_order)
2087            .build();
2088        LazyFrame::from_logical_plan(lp, self.opt_state)
2089    }
2090
2091    /// Return first n rows of each group
2092    pub fn head(self, n: Option<usize>) -> LazyFrame {
2093        let keys = self
2094            .keys
2095            .iter()
2096            .filter_map(|expr| expr_output_name(expr).ok())
2097            .collect::<Vec<_>>();
2098
2099        self.agg([all().as_expr().head(n)]).explode_impl(
2100            all() - by_name(keys.iter().cloned(), false, false),
2101            ExplodeOptions {
2102                empty_as_null: true,
2103                keep_nulls: true,
2104            },
2105            true,
2106        )
2107    }
2108
2109    /// Return last n rows of each group
2110    pub fn tail(self, n: Option<usize>) -> LazyFrame {
2111        let keys = self
2112            .keys
2113            .iter()
2114            .filter_map(|expr| expr_output_name(expr).ok())
2115            .collect::<Vec<_>>();
2116
2117        self.agg([all().as_expr().tail(n)]).explode_impl(
2118            all() - by_name(keys.iter().cloned(), false, false),
2119            ExplodeOptions {
2120                empty_as_null: true,
2121                keep_nulls: true,
2122            },
2123            true,
2124        )
2125    }
2126
2127    /// Apply a function over the groups as a new DataFrame.
2128    ///
2129    /// **It is not recommended that you use this as materializing the DataFrame is very
2130    /// expensive.**
2131    pub fn apply(self, f: PlanCallback<DataFrame, DataFrame>, schema: SchemaRef) -> LazyFrame {
2132        if !self.predicates.is_empty() {
2133            panic!("not yet implemented: `apply` cannot be used with `having` predicates");
2134        }
2135
2136        #[cfg(feature = "dynamic_group_by")]
2137        let options = GroupbyOptions {
2138            dynamic: self.dynamic_options,
2139            rolling: self.rolling_options,
2140            slice: None,
2141        };
2142
2143        #[cfg(not(feature = "dynamic_group_by"))]
2144        let options = GroupbyOptions { slice: None };
2145
2146        let lp = DslPlan::GroupBy {
2147            input: Arc::new(self.logical_plan),
2148            keys: self.keys,
2149            predicates: vec![],
2150            aggs: vec![],
2151            apply: Some((f, schema)),
2152            maintain_order: self.maintain_order,
2153            options: Arc::new(options),
2154        };
2155        LazyFrame::from_logical_plan(lp, self.opt_state)
2156    }
2157}
2158
2159#[must_use]
2160pub struct JoinBuilder {
2161    lf: LazyFrame,
2162    how: JoinType,
2163    other: Option<LazyFrame>,
2164    left_on: Vec<Expr>,
2165    right_on: Vec<Expr>,
2166    allow_parallel: bool,
2167    force_parallel: bool,
2168    suffix: Option<PlSmallStr>,
2169    validation: JoinValidation,
2170    nulls_equal: bool,
2171    coalesce: JoinCoalesce,
2172    maintain_order: MaintainOrderJoin,
2173    build_side: Option<JoinBuildSide>,
2174}
2175impl JoinBuilder {
2176    /// Create the `JoinBuilder` with the provided `LazyFrame` as the left table.
2177    pub fn new(lf: LazyFrame) -> Self {
2178        Self {
2179            lf,
2180            other: None,
2181            how: JoinType::Inner,
2182            left_on: vec![],
2183            right_on: vec![],
2184            allow_parallel: true,
2185            force_parallel: false,
2186            suffix: None,
2187            validation: Default::default(),
2188            nulls_equal: false,
2189            coalesce: Default::default(),
2190            maintain_order: Default::default(),
2191            build_side: None,
2192        }
2193    }
2194
2195    /// The right table in the join.
2196    pub fn with(mut self, other: LazyFrame) -> Self {
2197        self.other = Some(other);
2198        self
2199    }
2200
2201    /// Select the join type.
2202    pub fn how(mut self, how: JoinType) -> Self {
2203        self.how = how;
2204        self
2205    }
2206
2207    pub fn validate(mut self, validation: JoinValidation) -> Self {
2208        self.validation = validation;
2209        self
2210    }
2211
2212    /// The expressions you want to join both tables on.
2213    ///
2214    /// The passed expressions must be valid in both `LazyFrame`s in the join.
2215    pub fn on<E: AsRef<[Expr]>>(mut self, on: E) -> Self {
2216        let on = on.as_ref().to_vec();
2217        self.left_on.clone_from(&on);
2218        self.right_on = on;
2219        self
2220    }
2221
2222    /// The expressions you want to join the left table on.
2223    ///
2224    /// The passed expressions must be valid in the left table.
2225    pub fn left_on<E: AsRef<[Expr]>>(mut self, on: E) -> Self {
2226        self.left_on = on.as_ref().to_vec();
2227        self
2228    }
2229
2230    /// The expressions you want to join the right table on.
2231    ///
2232    /// The passed expressions must be valid in the right table.
2233    pub fn right_on<E: AsRef<[Expr]>>(mut self, on: E) -> Self {
2234        self.right_on = on.as_ref().to_vec();
2235        self
2236    }
2237
2238    /// Allow parallel table evaluation.
2239    pub fn allow_parallel(mut self, allow: bool) -> Self {
2240        self.allow_parallel = allow;
2241        self
2242    }
2243
2244    /// Force parallel table evaluation.
2245    pub fn force_parallel(mut self, force: bool) -> Self {
2246        self.force_parallel = force;
2247        self
2248    }
2249
2250    /// Join on null values. By default null values will never produce matches.
2251    pub fn join_nulls(mut self, nulls_equal: bool) -> Self {
2252        self.nulls_equal = nulls_equal;
2253        self
2254    }
2255
2256    /// Suffix to add duplicate column names in join.
2257    /// Defaults to `"_right"` if this method is never called.
2258    pub fn suffix<S>(mut self, suffix: S) -> Self
2259    where
2260        S: Into<PlSmallStr>,
2261    {
2262        self.suffix = Some(suffix.into());
2263        self
2264    }
2265
2266    /// Whether to coalesce join columns.
2267    pub fn coalesce(mut self, coalesce: JoinCoalesce) -> Self {
2268        self.coalesce = coalesce;
2269        self
2270    }
2271
2272    /// Whether to preserve the row order.
2273    pub fn maintain_order(mut self, maintain_order: MaintainOrderJoin) -> Self {
2274        self.maintain_order = maintain_order;
2275        self
2276    }
2277
2278    /// Whether to prefer a specific build side.
2279    pub fn build_side(mut self, build_side: Option<JoinBuildSide>) -> Self {
2280        self.build_side = build_side;
2281        self
2282    }
2283
2284    /// Finish builder
2285    pub fn finish(self) -> LazyFrame {
2286        let opt_state = self.lf.opt_state;
2287        let other = self.other.expect("'with' not set in join builder");
2288
2289        let args = JoinArgs {
2290            how: self.how,
2291            validation: self.validation,
2292            suffix: self.suffix,
2293            slice: None,
2294            nulls_equal: self.nulls_equal,
2295            coalesce: self.coalesce,
2296            maintain_order: self.maintain_order,
2297            build_side: self.build_side,
2298        };
2299
2300        let lp = self
2301            .lf
2302            .get_plan_builder()
2303            .join(
2304                other.logical_plan,
2305                self.left_on,
2306                self.right_on,
2307                JoinOptions {
2308                    allow_parallel: self.allow_parallel,
2309                    force_parallel: self.force_parallel,
2310                    args,
2311                }
2312                .into(),
2313            )
2314            .build();
2315        LazyFrame::from_logical_plan(lp, opt_state)
2316    }
2317
2318    // Finish with join predicates
2319    pub fn join_where(self, predicates: Vec<Expr>) -> LazyFrame {
2320        let opt_state = self.lf.opt_state;
2321        let other = self.other.expect("with not set");
2322
2323        // Decompose `And` conjunctions into their component expressions
2324        fn decompose_and(predicate: Expr, expanded_predicates: &mut Vec<Expr>) {
2325            if let Expr::BinaryExpr {
2326                op: Operator::And,
2327                left,
2328                right,
2329            } = predicate
2330            {
2331                decompose_and((*left).clone(), expanded_predicates);
2332                decompose_and((*right).clone(), expanded_predicates);
2333            } else {
2334                expanded_predicates.push(predicate);
2335            }
2336        }
2337        let mut expanded_predicates = Vec::with_capacity(predicates.len() * 2);
2338        for predicate in predicates {
2339            decompose_and(predicate, &mut expanded_predicates);
2340        }
2341        let predicates: Vec<Expr> = expanded_predicates;
2342
2343        // Decompose `is_between` predicates to allow for cleaner expression of range joins
2344        #[cfg(feature = "is_between")]
2345        let predicates: Vec<Expr> = {
2346            let mut expanded_predicates = Vec::with_capacity(predicates.len() * 2);
2347            for predicate in predicates {
2348                if let Expr::Function {
2349                    function: FunctionExpr::Boolean(BooleanFunction::IsBetween { closed }),
2350                    input,
2351                    ..
2352                } = &predicate
2353                {
2354                    if let [expr, lower, upper] = input.as_slice() {
2355                        match closed {
2356                            ClosedInterval::Both => {
2357                                expanded_predicates.push(expr.clone().gt_eq(lower.clone()));
2358                                expanded_predicates.push(expr.clone().lt_eq(upper.clone()));
2359                            },
2360                            ClosedInterval::Right => {
2361                                expanded_predicates.push(expr.clone().gt(lower.clone()));
2362                                expanded_predicates.push(expr.clone().lt_eq(upper.clone()));
2363                            },
2364                            ClosedInterval::Left => {
2365                                expanded_predicates.push(expr.clone().gt_eq(lower.clone()));
2366                                expanded_predicates.push(expr.clone().lt(upper.clone()));
2367                            },
2368                            ClosedInterval::None => {
2369                                expanded_predicates.push(expr.clone().gt(lower.clone()));
2370                                expanded_predicates.push(expr.clone().lt(upper.clone()));
2371                            },
2372                        }
2373                        continue;
2374                    }
2375                }
2376                expanded_predicates.push(predicate);
2377            }
2378            expanded_predicates
2379        };
2380
2381        let args = JoinArgs {
2382            how: self.how,
2383            validation: self.validation,
2384            suffix: self.suffix,
2385            slice: None,
2386            nulls_equal: self.nulls_equal,
2387            coalesce: self.coalesce,
2388            maintain_order: self.maintain_order,
2389            build_side: self.build_side,
2390        };
2391        let options = JoinOptions {
2392            allow_parallel: self.allow_parallel,
2393            force_parallel: self.force_parallel,
2394            args,
2395        };
2396
2397        let lp = DslPlan::Join {
2398            input_left: Arc::new(self.lf.logical_plan),
2399            input_right: Arc::new(other.logical_plan),
2400            left_on: Default::default(),
2401            right_on: Default::default(),
2402            predicates,
2403            options: Arc::from(options),
2404        };
2405
2406        LazyFrame::from_logical_plan(lp, opt_state)
2407    }
2408}
2409
2410pub const BUILD_STREAMING_EXECUTOR: Option<polars_mem_engine::StreamingExecutorBuilder> = {
2411    #[cfg(not(feature = "streaming"))]
2412    {
2413        None
2414    }
2415    #[cfg(feature = "streaming")]
2416    {
2417        Some(polars_stream::build_streaming_query_executor)
2418    }
2419};
2420
2421pub struct CollectBatches {
2422    recv: Receiver<PolarsResult<DataFrame>>,
2423    runner: Option<Box<dyn FnOnce() + Send + 'static>>,
2424}
2425
2426impl CollectBatches {
2427    /// Start running the query, if not already.
2428    pub fn start(&mut self) {
2429        if let Some(runner) = self.runner.take() {
2430            runner()
2431        }
2432    }
2433}
2434
2435impl Iterator for CollectBatches {
2436    type Item = PolarsResult<DataFrame>;
2437
2438    fn next(&mut self) -> Option<Self::Item> {
2439        self.start();
2440        self.recv.recv().ok()
2441    }
2442}