1use std::path::Path;
2
3use crate::lazy::{LogicalPlan, Optimizer, ProjectionKind};
4use crate::ops::{FillNull, JoinKeys, JoinType, SortOptions};
5use crate::{DataFrame, Expr, Result};
6
7#[derive(Debug, Clone)]
9pub struct LazyFrame {
10 plan: LogicalPlan,
11}
12
13impl LazyFrame {
14 pub fn from_dataframe(df: DataFrame) -> Self {
16 Self {
17 plan: LogicalPlan::DataFrameScan { df },
18 }
19 }
20
21 pub fn scan_csv(path: impl AsRef<Path>) -> Result<Self> {
23 Ok(Self {
24 plan: LogicalPlan::CsvScan {
25 path: path.as_ref().to_path_buf(),
26 predicate: None,
27 projection: None,
28 },
29 })
30 }
31
32 pub fn scan_parquet(path: impl AsRef<Path>) -> Result<Self> {
34 Ok(Self {
35 plan: LogicalPlan::ParquetScan {
36 path: path.as_ref().to_path_buf(),
37 predicate: None,
38 projection: None,
39 },
40 })
41 }
42
43 pub fn select(self, exprs: Vec<Expr>) -> Self {
45 Self {
46 plan: LogicalPlan::Projection {
47 input: Box::new(self.plan),
48 exprs,
49 kind: ProjectionKind::Select,
50 },
51 }
52 }
53
54 pub fn filter(self, predicate: Expr) -> Self {
56 Self {
57 plan: LogicalPlan::Filter {
58 input: Box::new(self.plan),
59 predicate,
60 },
61 }
62 }
63
64 pub fn with_columns(self, exprs: Vec<Expr>) -> Self {
66 Self {
67 plan: LogicalPlan::Projection {
68 input: Box::new(self.plan),
69 exprs,
70 kind: ProjectionKind::WithColumns,
71 },
72 }
73 }
74
75 pub fn group_by(self, by: Vec<Expr>) -> LazyGroupBy {
77 LazyGroupBy {
78 plan: self.plan,
79 by,
80 }
81 }
82
83 pub fn join<K: Into<JoinKeys>>(self, other: LazyFrame, keys: K, how: JoinType) -> Self {
85 let keys = keys.into();
86 Self {
87 plan: LogicalPlan::Join {
88 left: Box::new(self.plan),
89 right: Box::new(other.plan),
90 keys,
91 how,
92 },
93 }
94 }
95
96 pub fn sort(self, mut options: SortOptions) -> Self {
98 options.nulls_last = true;
99 options.stable = true;
100 Self {
101 plan: LogicalPlan::Sort {
102 input: Box::new(self.plan),
103 options,
104 },
105 }
106 }
107
108 pub fn head(self, n: usize) -> Self {
110 Self {
111 plan: LogicalPlan::Slice {
112 input: Box::new(self.plan),
113 offset: 0,
114 len: n,
115 from_end: false,
116 },
117 }
118 }
119
120 pub fn tail(self, n: usize) -> Self {
122 Self {
123 plan: LogicalPlan::Slice {
124 input: Box::new(self.plan),
125 offset: 0,
126 len: n,
127 from_end: true,
128 },
129 }
130 }
131
132 pub fn unique(self, subset: Option<Vec<String>>) -> Self {
134 Self {
135 plan: LogicalPlan::Unique {
136 input: Box::new(self.plan),
137 subset,
138 },
139 }
140 }
141
142 pub fn fill_null<T: Into<FillNull>>(self, fill: T) -> Self {
144 Self {
145 plan: LogicalPlan::FillNull {
146 input: Box::new(self.plan),
147 fill: fill.into(),
148 },
149 }
150 }
151
152 pub fn drop_nulls(self, subset: Option<Vec<String>>) -> Self {
154 Self {
155 plan: LogicalPlan::DropNulls {
156 input: Box::new(self.plan),
157 subset,
158 },
159 }
160 }
161
162 pub fn null_count(self) -> Self {
164 Self {
165 plan: LogicalPlan::NullCount {
166 input: Box::new(self.plan),
167 },
168 }
169 }
170
171 pub fn explode(self, column: impl Into<String>) -> Self {
173 Self {
174 plan: LogicalPlan::Explode {
175 input: Box::new(self.plan),
176 column: column.into(),
177 },
178 }
179 }
180
181 pub fn implode(self) -> Self {
183 Self {
184 plan: LogicalPlan::Implode {
185 input: Box::new(self.plan),
186 },
187 }
188 }
189
190 pub fn collect(self) -> Result<DataFrame> {
192 let optimized = Optimizer::optimize(&self.plan);
193 let physical = crate::physical::compile(&optimized)?;
194 let batches = crate::physical::Executor::execute(physical)?;
195 DataFrame::from_batches(batches)
196 }
197
198 pub fn explain(self, optimized: bool) -> String {
202 if optimized {
203 Optimizer::optimize(&self.plan).display()
204 } else {
205 self.plan.display()
206 }
207 }
208}
209
210#[derive(Debug, Clone)]
212pub struct LazyGroupBy {
213 by: Vec<Expr>,
214 plan: LogicalPlan,
215}
216
217impl LazyGroupBy {
218 pub fn agg(self, aggs: Vec<Expr>) -> LazyFrame {
220 LazyFrame {
221 plan: LogicalPlan::Aggregate {
222 input: Box::new(self.plan),
223 group_by: self.by,
224 aggs,
225 },
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use std::sync::Arc;
233
234 use arrow::array::{ArrayRef, Int64Array};
235
236 use super::LazyFrame;
237 use crate::expr::{col, lit};
238 use crate::{DataFrame, Series};
239
240 fn df() -> DataFrame {
241 let a: ArrayRef = Arc::new(Int64Array::from(vec![1, 2, 3]));
242 let b: ArrayRef = Arc::new(Int64Array::from(vec![10, 20, 30]));
243 DataFrame::new(vec![
244 Series::from_arrow("a", vec![a]).unwrap(),
245 Series::from_arrow("b", vec![b]).unwrap(),
246 ])
247 .unwrap()
248 }
249
250 #[test]
251 fn explain_builds_plan_without_io() {
252 let lf = LazyFrame::scan_csv("test.csv").unwrap();
253 let s = lf.explain(false);
254 assert!(s.contains("scan[csv"));
255 }
256
257 #[test]
258 fn collect_executes_filter_and_select_on_dataframe_scan() {
259 let lf = LazyFrame::from_dataframe(df())
260 .filter(col("a").gt(lit(1_i64)))
261 .select(vec![col("b").alias("bb")]);
262 let out = lf.collect().unwrap();
263 assert_eq!(out.height(), 2);
264 let bb = out.column("bb").unwrap();
265 assert_eq!(bb.len(), 2);
266 }
267
268 #[test]
269 fn group_by_agg_executes_sum_and_count() {
270 let lf = LazyFrame::from_dataframe(df())
271 .group_by(vec![col("a")])
272 .agg(vec![
273 col("b").sum().alias("sum_b"),
274 col("b").count().alias("cnt_b"),
275 ]);
276 let out = lf.collect().unwrap();
277 assert_eq!(out.width(), 3);
278 assert_eq!(out.column("a").unwrap().len(), 3);
279 assert_eq!(out.column("sum_b").unwrap().len(), 3);
280 assert_eq!(out.column("cnt_b").unwrap().len(), 3);
281 }
282}