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