|
17 | 17 |
|
18 | 18 | //! This module provides a builder for creating LogicalPlans |
19 | 19 |
|
20 | | -use std::{collections::HashMap, sync::Arc}; |
| 20 | +use std::{ |
| 21 | + collections::{HashMap, HashSet}, |
| 22 | + sync::Arc, |
| 23 | +}; |
21 | 24 |
|
22 | 25 | use arrow::{ |
23 | 26 | datatypes::{Schema, SchemaRef}, |
@@ -220,10 +223,33 @@ impl LogicalPlanBuilder { |
220 | 223 | for e in expr { |
221 | 224 | match e { |
222 | 225 | Expr::Wildcard => { |
223 | | - (0..input_schema.fields().len()).for_each(|i| { |
224 | | - projected_expr |
225 | | - .push(Expr::Column(input_schema.field(i).qualified_column())) |
226 | | - }); |
| 226 | + let columns_to_skip = self |
| 227 | + .plan |
| 228 | + .using_columns()? |
| 229 | + .into_iter() |
| 230 | + // For each USING JOIN condition, only expand to one column in projection |
| 231 | + .map(|cols| { |
| 232 | + let mut cols = cols.into_iter().collect::<Vec<_>>(); |
| 233 | + // sort join columns to make sure we consistently keep the same |
| 234 | + // qualified column |
| 235 | + cols.sort(); |
| 236 | + cols.into_iter().skip(1) |
| 237 | + }) |
| 238 | + .flatten() |
| 239 | + .collect::<HashSet<_>>(); |
| 240 | + |
| 241 | + if columns_to_skip.is_empty() { |
| 242 | + input_schema.fields().iter().for_each(|f| { |
| 243 | + projected_expr.push(Expr::Column(f.qualified_column())) |
| 244 | + }) |
| 245 | + } else { |
| 246 | + input_schema.fields().iter().for_each(|f| { |
| 247 | + let col = f.qualified_column(); |
| 248 | + if !columns_to_skip.contains(&col) { |
| 249 | + projected_expr.push(Expr::Column(col)) |
| 250 | + } |
| 251 | + }) |
| 252 | + } |
227 | 253 | } |
228 | 254 | _ => projected_expr |
229 | 255 | .push(columnize_expr(normalize_col(e, &self.plan)?, input_schema)), |
@@ -587,6 +613,27 @@ mod tests { |
587 | 613 | Ok(()) |
588 | 614 | } |
589 | 615 |
|
| 616 | + #[test] |
| 617 | + fn plan_using_join_wildcard_projection() -> Result<()> { |
| 618 | + let t2 = LogicalPlanBuilder::scan_empty(Some("t2"), &employee_schema(), None)? |
| 619 | + .build()?; |
| 620 | + |
| 621 | + let plan = LogicalPlanBuilder::scan_empty(Some("t1"), &employee_schema(), None)? |
| 622 | + .join_using(&t2, JoinType::Inner, vec!["id"])? |
| 623 | + .project(vec![Expr::Wildcard])? |
| 624 | + .build()?; |
| 625 | + |
| 626 | + // id column should only show up once in projection |
| 627 | + let expected = "Projection: #t1.id, #t1.first_name, #t1.last_name, #t1.state, #t1.salary, #t2.first_name, #t2.last_name, #t2.state, #t2.salary\ |
| 628 | + \n Join: Using #t1.id = #t2.id\ |
| 629 | + \n TableScan: t1 projection=None\ |
| 630 | + \n TableScan: t2 projection=None"; |
| 631 | + |
| 632 | + assert_eq!(expected, format!("{:?}", plan)); |
| 633 | + |
| 634 | + Ok(()) |
| 635 | + } |
| 636 | + |
590 | 637 | #[test] |
591 | 638 | fn plan_builder_union_combined_single_union() -> Result<()> { |
592 | 639 | let plan = LogicalPlanBuilder::scan_empty( |
|
0 commit comments