Skip to content

Commit

Permalink
Add support for UNION DISTINCT BY NAME syntax (apache#997)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Lamb <[email protected]>
  • Loading branch information
2 people authored and serprex committed Nov 6, 2023
1 parent cd02f0c commit f358f84
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 152 deletions.
5 changes: 4 additions & 1 deletion src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ impl fmt::Display for SetExpr {
SetQuantifier::All
| SetQuantifier::Distinct
| SetQuantifier::ByName
| SetQuantifier::AllByName => write!(f, " {set_quantifier}")?,
| SetQuantifier::AllByName
| SetQuantifier::DistinctByName => write!(f, " {set_quantifier}")?,
SetQuantifier::None => write!(f, "{set_quantifier}")?,
}
write!(f, " {right}")?;
Expand Down Expand Up @@ -164,6 +165,7 @@ pub enum SetQuantifier {
Distinct,
ByName,
AllByName,
DistinctByName,
None,
}

Expand All @@ -174,6 +176,7 @@ impl fmt::Display for SetQuantifier {
SetQuantifier::Distinct => write!(f, "DISTINCT"),
SetQuantifier::ByName => write!(f, "BY NAME"),
SetQuantifier::AllByName => write!(f, "ALL BY NAME"),
SetQuantifier::DistinctByName => write!(f, "DISTINCT BY NAME"),
SetQuantifier::None => write!(f, ""),
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5739,7 +5739,9 @@ impl<'a> Parser<'a> {
pub fn parse_set_quantifier(&mut self, op: &Option<SetOperator>) -> SetQuantifier {
match op {
Some(SetOperator::Union) => {
if self.parse_keywords(&[Keyword::BY, Keyword::NAME]) {
if self.parse_keywords(&[Keyword::DISTINCT, Keyword::BY, Keyword::NAME]) {
SetQuantifier::DistinctByName
} else if self.parse_keywords(&[Keyword::BY, Keyword::NAME]) {
SetQuantifier::ByName
} else if self.parse_keyword(Keyword::ALL) {
if self.parse_keywords(&[Keyword::BY, Keyword::NAME]) {
Expand Down
232 changes: 82 additions & 150 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,155 +132,87 @@ fn test_create_table_macro() {

#[test]
fn test_select_union_by_name() {
let ast = duckdb().verified_query("SELECT * FROM capitals UNION BY NAME SELECT * FROM weather");
let expected = Box::<SetExpr>::new(SetExpr::SetOperation {
op: SetOperator::Union,
set_quantifier: SetQuantifier::ByName,
left: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: None,
opt_except: None,
opt_rename: None,
opt_replace: None,
})],
into: None,
from: vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "capitals".to_string(),
quote_style: None,
}]),
alias: None,
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
},
joins: vec![],
}],
lateral_views: vec![],
selection: None,
group_by: GroupByExpr::Expressions(vec![]),
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
named_window: vec![],
qualify: None,
}))),
right: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: None,
opt_except: None,
opt_rename: None,
opt_replace: None,
})],
into: None,
from: vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "weather".to_string(),
quote_style: None,
}]),
alias: None,
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
},
joins: vec![],
}],
lateral_views: vec![],
selection: None,
group_by: GroupByExpr::Expressions(vec![]),
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
named_window: vec![],
qualify: None,
}))),
});

assert_eq!(ast.body, expected);
let q1 = "SELECT * FROM capitals UNION BY NAME SELECT * FROM weather";
let q2 = "SELECT * FROM capitals UNION ALL BY NAME SELECT * FROM weather";
let q3 = "SELECT * FROM capitals UNION DISTINCT BY NAME SELECT * FROM weather";

let ast =
duckdb().verified_query("SELECT * FROM capitals UNION ALL BY NAME SELECT * FROM weather");
let expected = Box::<SetExpr>::new(SetExpr::SetOperation {
op: SetOperator::Union,
set_quantifier: SetQuantifier::AllByName,
left: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: None,
opt_except: None,
opt_rename: None,
opt_replace: None,
})],
into: None,
from: vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "capitals".to_string(),
quote_style: None,
}]),
alias: None,
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
},
joins: vec![],
}],
lateral_views: vec![],
selection: None,
group_by: GroupByExpr::Expressions(vec![]),
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
named_window: vec![],
qualify: None,
}))),
right: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: None,
opt_except: None,
opt_rename: None,
opt_replace: None,
})],
into: None,
from: vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "weather".to_string(),
quote_style: None,
}]),
alias: None,
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
},
joins: vec![],
}],
lateral_views: vec![],
selection: None,
group_by: GroupByExpr::Expressions(vec![]),
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
named_window: vec![],
qualify: None,
}))),
});
assert_eq!(ast.body, expected);
for (ast, expected_quantifier) in &[
(duckdb().verified_query(q1), SetQuantifier::ByName),
(duckdb().verified_query(q2), SetQuantifier::AllByName),
(duckdb().verified_query(q3), SetQuantifier::DistinctByName),
] {
let expected = Box::<SetExpr>::new(SetExpr::SetOperation {
op: SetOperator::Union,
set_quantifier: *expected_quantifier,
left: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: None,
opt_except: None,
opt_rename: None,
opt_replace: None,
})],
into: None,
from: vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "capitals".to_string(),
quote_style: None,
}]),
alias: None,
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
},
joins: vec![],
}],
lateral_views: vec![],
selection: None,
group_by: GroupByExpr::Expressions(vec![]),
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
named_window: vec![],
qualify: None,
}))),
right: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: None,
opt_except: None,
opt_rename: None,
opt_replace: None,
})],
into: None,
from: vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "weather".to_string(),
quote_style: None,
}]),
alias: None,
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
},
joins: vec![],
}],
lateral_views: vec![],
selection: None,
group_by: GroupByExpr::Expressions(vec![]),
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
named_window: vec![],
qualify: None,
}))),
});
assert_eq!(ast.body, expected);
}
}

0 comments on commit f358f84

Please sign in to comment.