Support redshift's columns definition list for system information functions (#769)

* parsing of redshift's column definition list for
pg_get_late_binding_view_cols
pg_get_cols
pg_get_grantee_by_iam_role
pg_get_iam_role_by_user

* Renamed ColsDefinition to TableAliasDefinition

added generic dialect

* Tests fixed

* Visitor for IdentPair

* Parsing redshift table alias based on indentifier and
parentheses instead of function name

* fix clippy

---------

Co-authored-by: Maciej Skrzypkowski <maciej.skrzypkowski@satoricyber.com>
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
Maciej Skrzypkowski 2023-02-17 20:04:59 +01:00 committed by GitHub
parent a2fea10f89
commit c35dcc93a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 194 additions and 0 deletions

View file

@ -4141,6 +4141,35 @@ impl fmt::Display for SearchModifier {
}
}
/// A result table definition i.e. `cols(view_schema name, view_name name, col_name name, col_type varchar, col_num int)`
/// used for redshift functions: pg_get_late_binding_view_cols, pg_get_cols, pg_get_grantee_by_iam_role,pg_get_iam_role_by_user
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct TableAliasDefinition {
pub name: Ident,
pub args: Vec<IdentPair>,
}
impl fmt::Display for TableAliasDefinition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}({})", self.name, display_comma_separated(&self.args))?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct IdentPair(pub Ident, pub Ident);
impl fmt::Display for IdentPair {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.0, self.1)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -580,6 +580,9 @@ pub enum TableFactor {
/// vector of arguments, in the case of a table-valued function call,
/// whereas it's `None` in the case of a regular table name.
args: Option<Vec<FunctionArg>>,
/// A table alias definition i.e. `cols(view_schema name, view_name name, col_name name, col_type varchar, col_num int)`
/// used for redshift functions: pg_get_late_binding_view_cols, pg_get_cols, pg_get_grantee_by_iam_role,pg_get_iam_role_by_user)
columns_definition: Option<TableAliasDefinition>,
/// MSSQL-specific `WITH (...)` hints such as NOLOCK.
with_hints: Vec<Expr>,
},
@ -628,6 +631,7 @@ impl fmt::Display for TableFactor {
name,
alias,
args,
columns_definition,
with_hints,
} => {
write!(f, "{name}")?;
@ -637,6 +641,9 @@ impl fmt::Display for TableFactor {
if let Some(alias) = alias {
write!(f, " AS {alias}")?;
}
if let Some(columns_definition) = columns_definition {
write!(f, " {columns_definition}")?;
}
if !with_hints.is_empty() {
write!(f, " WITH ({})", display_comma_separated(with_hints))?;
}

View file

@ -669,6 +669,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
Keyword::OUTER,
Keyword::SET,
Keyword::QUALIFY,
Keyword::AS,
];
/// Can't be used as a column alias, so that `SELECT <expr> alias`
@ -698,4 +699,5 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
// Reserved only as a column alias in the `SELECT` clause
Keyword::FROM,
Keyword::INTO,
Keyword::AS,
];

View file

@ -5649,6 +5649,7 @@ impl<'a> Parser<'a> {
} else {
None
};
let columns_definition = self.parse_redshift_columns_definition_list()?;
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
// MSSQL-specific table hints:
let mut with_hints = vec![];
@ -5665,11 +5666,56 @@ impl<'a> Parser<'a> {
name,
alias,
args,
columns_definition,
with_hints,
})
}
}
fn parse_redshift_columns_definition_list(
&mut self,
) -> Result<Option<TableAliasDefinition>, ParserError> {
if !dialect_of!(self is RedshiftSqlDialect | GenericDialect) {
return Ok(None);
}
if let Some(col_definition_list_name) = self.parse_optional_columns_definition_list_alias()
{
if self.consume_token(&Token::LParen) {
let names = self.parse_comma_separated(Parser::parse_ident_pair)?;
self.expect_token(&Token::RParen)?;
Ok(Some(TableAliasDefinition {
name: col_definition_list_name,
args: names,
}))
} else {
self.prev_token();
Ok(None)
}
} else {
Ok(None)
}
}
fn parse_optional_columns_definition_list_alias(&mut self) -> Option<Ident> {
match self.next_token().token {
Token::Word(w) if !keywords::RESERVED_FOR_TABLE_ALIAS.contains(&w.keyword) => {
Some(w.to_ident())
}
_ => {
self.prev_token();
None
}
}
}
fn parse_ident_pair(&mut self) -> Result<IdentPair, ParserError> {
Ok(IdentPair(
self.parse_identifier()?,
self.parse_identifier()?,
))
}
pub fn parse_derived_table_factor(
&mut self,
lateral: IsLateral,

View file

@ -185,6 +185,7 @@ pub fn table(name: impl Into<String>) -> TableFactor {
name: ObjectName(vec![Ident::new(name.into())]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
}
}

View file

@ -44,6 +44,7 @@ fn parse_table_identifiers() {
name: ObjectName(expected),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![]

View file

@ -60,6 +60,7 @@ fn parse_map_access_expr() {
name: ObjectName(vec![Ident::new("foos")]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![]
@ -164,11 +165,13 @@ fn parse_delimited_identifiers() {
name,
alias,
args,
columns_definition,
with_hints,
} => {
assert_eq!(vec![Ident::with_quote('"', "a table")], name.0);
assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name);
assert!(args.is_none());
assert!(columns_definition.is_none());
assert!(with_hints.is_empty());
}
_ => panic!("Expecting TableFactor::Table"),

View file

@ -210,6 +210,7 @@ fn parse_update_set_from() {
name: ObjectName(vec![Ident::new("t1")]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![],
@ -236,6 +237,7 @@ fn parse_update_set_from() {
name: ObjectName(vec![Ident::new("t1")]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![],
@ -298,6 +300,7 @@ fn parse_update_with_table_alias() {
columns: vec![],
}),
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![],
@ -353,6 +356,7 @@ fn parse_delete_statement() {
name: ObjectName(vec![Ident::with_quote('"', "table")]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
table_name
@ -379,6 +383,7 @@ fn parse_where_delete_statement() {
name: ObjectName(vec![Ident::new("foo")]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
table_name,
@ -419,6 +424,7 @@ fn parse_where_delete_with_alias_statement() {
columns: vec![],
}),
args: None,
columns_definition: None,
with_hints: vec![],
},
table_name,
@ -432,6 +438,7 @@ fn parse_where_delete_with_alias_statement() {
columns: vec![],
}),
args: None,
columns_definition: None,
with_hints: vec![],
}),
using
@ -3447,6 +3454,7 @@ fn parse_interval_and_or_xor() {
}]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![],
@ -3893,6 +3901,7 @@ fn parse_implicit_join() {
name: ObjectName(vec!["t1".into()]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![],
@ -3902,6 +3911,7 @@ fn parse_implicit_join() {
name: ObjectName(vec!["t2".into()]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![],
@ -3919,6 +3929,7 @@ fn parse_implicit_join() {
name: ObjectName(vec!["t1a".into()]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![Join {
@ -3926,6 +3937,7 @@ fn parse_implicit_join() {
name: ObjectName(vec!["t1b".into()]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
join_operator: JoinOperator::Inner(JoinConstraint::Natural),
@ -3936,6 +3948,7 @@ fn parse_implicit_join() {
name: ObjectName(vec!["t2a".into()]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![Join {
@ -3943,6 +3956,7 @@ fn parse_implicit_join() {
name: ObjectName(vec!["t2b".into()]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
join_operator: JoinOperator::Inner(JoinConstraint::Natural),
@ -3963,6 +3977,7 @@ fn parse_cross_join() {
name: ObjectName(vec![Ident::new("t2")]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
join_operator: JoinOperator::CrossJoin,
@ -3983,6 +3998,7 @@ fn parse_joins_on() {
name: ObjectName(vec![Ident::new(relation.into())]),
alias,
args: None,
columns_definition: None,
with_hints: vec![],
},
join_operator: f(JoinConstraint::On(Expr::BinaryOp {
@ -4052,6 +4068,7 @@ fn parse_joins_using() {
name: ObjectName(vec![Ident::new(relation.into())]),
alias,
args: None,
columns_definition: None,
with_hints: vec![],
},
join_operator: f(JoinConstraint::Using(vec!["c1".into()])),
@ -4113,6 +4130,7 @@ fn parse_natural_join() {
name: ObjectName(vec![Ident::new("t2")]),
alias,
args: None,
columns_definition: None,
with_hints: vec![],
},
join_operator: f(JoinConstraint::Natural),
@ -4377,6 +4395,7 @@ fn parse_derived_tables() {
name: ObjectName(vec!["t2".into()]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
join_operator: JoinOperator::Inner(JoinConstraint::Natural),
@ -5668,6 +5687,7 @@ fn parse_merge() {
columns: vec![],
}),
args: None,
columns_definition: None,
with_hints: vec![],
}
);
@ -5691,6 +5711,7 @@ fn parse_merge() {
name: ObjectName(vec![Ident::new("s"), Ident::new("foo")]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![],

View file

@ -320,11 +320,13 @@ fn parse_delimited_identifiers() {
name,
alias,
args,
columns_definition,
with_hints,
} => {
assert_eq!(vec![Ident::with_quote('"', "a table")], name.0);
assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name);
assert!(args.is_none());
assert!(columns_definition.is_none());
assert!(with_hints.is_empty());
}
_ => panic!("Expecting TableFactor::Table"),

View file

@ -152,11 +152,13 @@ fn parse_delimited_identifiers() {
name,
alias,
args,
columns_definition,
with_hints,
} => {
assert_eq!(vec![Ident::with_quote('"', "a table")], name.0);
assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name);
assert!(args.is_none());
assert!(columns_definition.is_none());
assert!(with_hints.is_empty());
}
_ => panic!("Expecting TableFactor::Table"),

View file

@ -870,6 +870,7 @@ fn parse_update_with_joins() {
columns: vec![]
}),
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![Join {
@ -880,6 +881,7 @@ fn parse_update_with_joins() {
columns: vec![]
}),
args: None,
columns_definition: None,
with_hints: vec![],
},
join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp {
@ -1001,6 +1003,7 @@ fn parse_substring_in_select() {
}]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![]
},
joins: vec![]

View file

@ -2188,11 +2188,13 @@ fn parse_delimited_identifiers() {
name,
alias,
args,
columns_definition,
with_hints,
} => {
assert_eq!(vec![Ident::with_quote('"', "a table")], name.0);
assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name);
assert!(args.is_none());
assert!(columns_definition.is_none());
assert!(with_hints.is_empty());
}
_ => panic!("Expecting TableFactor::Table"),

View file

@ -44,6 +44,7 @@ fn test_square_brackets_over_db_schema_table_name() {
]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![],
@ -88,6 +89,7 @@ fn test_double_quotes_over_db_schema_table_name() {
]),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![],
@ -107,11 +109,13 @@ fn parse_delimited_identifiers() {
name,
alias,
args,
columns_definition,
with_hints,
} => {
assert_eq!(vec![Ident::with_quote('"', "a table")], name.0);
assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name);
assert!(args.is_none());
assert!(columns_definition.is_none());
assert!(with_hints.is_empty());
}
_ => panic!("Expecting TableFactor::Table"),
@ -272,3 +276,72 @@ fn test_sharp() {
select.projection[0]
);
}
#[test]
fn test_parse_pg_get_late_binding_view_cols() {
let sql = "select * from pg_get_late_binding_view_cols() some_name_cols(view_schema name, view_name name, col_name name)";
let expected = "SELECT * FROM pg_get_late_binding_view_cols() some_name_cols(view_schema name, view_name name, col_name name)";
redshift().one_statement_parses_to(sql, expected);
let select = redshift().verified_only_select(expected);
assert_eq!(
TableFactor::Table {
name: ObjectName(vec![Ident::new("pg_get_late_binding_view_cols")],),
args: Some(vec![]),
alias: None,
columns_definition: Some(TableAliasDefinition {
name: Ident::new("some_name_cols"),
args: vec![
IdentPair(Ident::new("view_schema"), Ident::new("name")),
IdentPair(Ident::new("view_name"), Ident::new("name")),
IdentPair(Ident::new("col_name"), Ident::new("name"))
]
}),
with_hints: vec![]
},
select.from[0].relation
);
}
#[test]
fn test_parse_pg_get_cols() {
let sql =
"SELECT * FROM pg_get_cols() some_name(view_schema name, view_name name, col_name name)";
redshift().verified_stmt(sql);
}
#[test]
fn test_parse_pg_get_grantee_by_iam_role() {
let sql = "SELECT grantee, grantee_type, cmd_type FROM pg_get_grantee_by_iam_role('arn:aws:iam::123456789012:role/Redshift-S3-Write') res_grantee(grantee text, grantee_type text, cmd_type text)";
redshift().verified_stmt(sql);
}
#[test]
fn test_parse_pg_get_iam_role_by_user() {
let sql = "SELECT username, iam_role, cmd FROM pg_get_iam_role_by_user('reg_user1') res_iam_role(username text, iam_role text, cmd text)";
redshift().verified_stmt(sql);
}
#[test]
fn test_parse_pg_get_late_binding_view_cols_in_select() {
let sql = "SELECT pg_get_late_binding_view_cols()";
redshift().verified_stmt(sql);
}
#[test]
fn test_parse_pg_get_cols_in_select() {
let sql = "SELECT pg_get_cols()";
redshift().verified_stmt(sql);
}
#[test]
fn test_parse_pg_get_grantee_by_iam_role_in_select() {
let sql = "SELECT pg_get_grantee_by_iam_role()";
redshift().verified_stmt(sql);
}
#[test]
fn test_parse_pg_get_iam_role_by_user_in_select() {
let sql = "SELECT pg_get_iam_role_by_user()";
redshift().verified_stmt(sql);
}

View file

@ -220,11 +220,13 @@ fn parse_delimited_identifiers() {
name,
alias,
args,
columns_definition,
with_hints,
} => {
assert_eq!(vec![Ident::with_quote('"', "a table")], name.0);
assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name);
assert!(args.is_none());
assert!(columns_definition.is_none());
assert!(with_hints.is_empty());
}
_ => panic!("Expecting TableFactor::Table"),