Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(query): Support select * exclude [column_name | (col_name, col_name,...)] #9009

Merged
merged 7 commits into from Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/query/ast/src/ast/format/ast_format.rs
Expand Up @@ -2093,7 +2093,7 @@ impl<'ast> Visitor<'ast> for AstFormatVisitor {
let node = FormatTreeNode::with_children(format_ctx, vec![child]);
self.children.push(node);
}
SelectTarget::QualifiedName(_) => {
SelectTarget::QualifiedName { .. } => {
let name = format!("Target {}", target);
let format_ctx = AstFormatContext::new(name);
let node = FormatTreeNode::new(format_ctx);
Expand Down
75 changes: 55 additions & 20 deletions src/query/ast/src/ast/format/syntax/query.rs
Expand Up @@ -108,26 +108,61 @@ fn pretty_select_list(select_list: Vec<SelectTarget>) -> RcDoc {
}
.nest(NEST_FACTOR)
.append(
interweave_comma(select_list.into_iter().map(|select_target| {
match select_target {
SelectTarget::AliasedExpr { expr, alias } => {
pretty_expr(*expr).append(if let Some(alias) = alias {
RcDoc::space()
.append(RcDoc::text("AS"))
.append(RcDoc::space())
.append(RcDoc::text(alias.to_string()))
} else {
RcDoc::nil()
})
}
SelectTarget::QualifiedName(object_name) => inline_dot(
object_name
.into_iter()
.map(|indirection| RcDoc::text(indirection.to_string())),
)
.group(),
}
}))
interweave_comma(
select_list
.into_iter()
.map(|select_target| match select_target {
SelectTarget::AliasedExpr { expr, alias } => {
pretty_expr(*expr).append(if let Some(alias) = alias {
RcDoc::space()
.append(RcDoc::text("AS"))
.append(RcDoc::space())
.append(RcDoc::text(alias.to_string()))
} else {
RcDoc::nil()
})
}
SelectTarget::QualifiedName {
qualified: object_name,
exclude,
} => {
let docs = inline_dot(
object_name
.into_iter()
.map(|indirection| RcDoc::text(indirection.to_string())),
)
.group();
docs.append(if let Some(cols) = exclude {
if !cols.is_empty() {
RcDoc::line()
.append(
RcDoc::text("EXCLUDE").append(
if cols.len() > 1 {
RcDoc::line()
} else {
RcDoc::space()
}
.nest(NEST_FACTOR),
),
)
.append(
interweave_comma(cols.into_iter().map(|ident| {
RcDoc::space()
.append(RcDoc::space())
.append(RcDoc::text(ident.to_string()))
}))
.nest(NEST_FACTOR)
.group(),
)
} else {
RcDoc::nil()
}
} else {
RcDoc::nil()
})
}
}),
)
.nest(NEST_FACTOR)
.group(),
)
Expand Down
19 changes: 15 additions & 4 deletions src/query/ast/src/ast/query.rs
Expand Up @@ -119,9 +119,12 @@ pub enum SelectTarget<'a> {
alias: Option<Identifier<'a>>,
},

// Qualified name, e.g. `SELECT t.a, t.* FROM t`.
// Qualified name, e.g. `SELECT t.a, t.* exclude t.a FROM t`.
// For simplicity, wildcard is involved.
QualifiedName(QualifiedName<'a>),
QualifiedName {
qualified: QualifiedName<'a>,
exclude: Option<Vec<Identifier<'a>>>,
},
}

pub type QualifiedName<'a> = Vec<Indirection<'a>>;
Expand Down Expand Up @@ -370,8 +373,16 @@ impl<'a> Display for SelectTarget<'a> {
write!(f, " AS {ident}")?;
}
}
SelectTarget::QualifiedName(indirections) => {
write_period_separated_list(f, indirections)?;
SelectTarget::QualifiedName { qualified, exclude } => {
write_period_separated_list(f, qualified)?;
if let Some(cols) = exclude {
// EXCLUDE
if !cols.is_empty() {
write!(f, " EXCLUDE (")?;
write_comma_separated_list(f, cols)?;
write!(f, ")")?;
}
}
}
}
Ok(())
Expand Down
49 changes: 38 additions & 11 deletions src/query/ast/src/parser/query.rs
Expand Up @@ -83,21 +83,48 @@ pub fn with(i: Input) -> IResult<With> {
)(i)
}

pub fn exclude_col(i: Input) -> IResult<Vec<Identifier>> {
let var = map(
rule! {
#ident
},
|col| vec![col],
);
let vars = map(
rule! {
"(" ~ ^#comma_separated_list1(ident) ~ ")"
TCeason marked this conversation as resolved.
Show resolved Hide resolved
},
|(_, cols, _)| cols,
);

rule!(
#var
| #vars
)(i)
}

pub fn select_target(i: Input) -> IResult<SelectTarget> {
let qualified_wildcard = map(
rule! {
( #ident ~ "." ~ ( #ident ~ "." )? )? ~ "*"
( #ident ~ "." ~ ( #ident ~ "." )? )? ~ "*" ~ ( EXCLUDE ~ #exclude_col )?
},
|(res, _)| match res {
Some((fst, _, Some((snd, _)))) => SelectTarget::QualifiedName(vec![
Indirection::Identifier(fst),
Indirection::Identifier(snd),
Indirection::Star,
]),
Some((fst, _, None)) => {
SelectTarget::QualifiedName(vec![Indirection::Identifier(fst), Indirection::Star])
}
None => SelectTarget::QualifiedName(vec![Indirection::Star]),
|(res, _, opt_exclude)| match res {
Some((fst, _, Some((snd, _)))) => SelectTarget::QualifiedName {
qualified: vec![
Indirection::Identifier(fst),
Indirection::Identifier(snd),
Indirection::Star,
],
exclude: opt_exclude.map(|(_, exclude)| exclude),
},
Some((fst, _, None)) => SelectTarget::QualifiedName {
qualified: vec![Indirection::Identifier(fst), Indirection::Star],
exclude: opt_exclude.map(|(_, exclude)| exclude),
},
None => SelectTarget::QualifiedName {
qualified: vec![Indirection::Star],
exclude: opt_exclude.map(|(_, exclude)| exclude),
},
TCeason marked this conversation as resolved.
Show resolved Hide resolved
},
);
let projection = map(
Expand Down
2 changes: 2 additions & 0 deletions src/query/ast/src/parser/token.rs
Expand Up @@ -367,6 +367,8 @@ pub enum TokenKind {
DROP,
#[token("EXCEPT", ignore(ascii_case))]
EXCEPT,
#[token("EXCLUDE", ignore(ascii_case))]
EXCLUDE,
#[token("ELSE", ignore(ascii_case))]
ELSE,
#[token("END", ignore(ascii_case))]
Expand Down
10 changes: 9 additions & 1 deletion src/query/ast/src/visitors/walk.rs
Expand Up @@ -185,7 +185,10 @@ pub fn walk_select_target<'a, V: Visitor<'a>>(visitor: &mut V, target: &'a Selec
visitor.visit_identifier(alias);
}
}
SelectTarget::QualifiedName(names) => {
SelectTarget::QualifiedName {
qualified: names,
exclude,
} => {
for indirection in names {
match indirection {
Indirection::Identifier(ident) => {
Expand All @@ -194,6 +197,11 @@ pub fn walk_select_target<'a, V: Visitor<'a>>(visitor: &mut V, target: &'a Selec
Indirection::Star => {}
}
}
if let Some(cols) = exclude {
for ident in cols.iter() {
visitor.visit_identifier(ident);
}
}
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/query/ast/src/visitors/walk_mut.rs
Expand Up @@ -185,7 +185,10 @@ pub fn walk_select_target_mut<'a, V: VisitorMut>(visitor: &mut V, target: &mut S
visitor.visit_identifier(alias);
}
}
SelectTarget::QualifiedName(names) => {
SelectTarget::QualifiedName {
qualified: names,
exclude,
} => {
for indirection in names {
match indirection {
Indirection::Identifier(ident) => {
Expand All @@ -194,6 +197,11 @@ pub fn walk_select_target_mut<'a, V: VisitorMut>(visitor: &mut V, target: &mut S
Indirection::Star => {}
}
}
if let Some(cols) = exclude {
for ident in cols {
visitor.visit_identifier(ident);
}
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/query/ast/tests/it/parser.rs
Expand Up @@ -418,6 +418,7 @@ fn test_query() {
let mut mint = Mint::new("tests/it/testdata");
let mut file = mint.new_goldenfile("query.txt").unwrap();
let cases = &[
r#"select * exclude c1, b.* exclude (c2, c3, c4) from customer inner join orders on a = b limit 1"#,
r#"select * from customer inner join orders"#,
r#"select * from customer cross join orders"#,
r#"select * from customer inner join orders on a = b limit 1"#,
Expand Down