diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 34099520b..cce26c562 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -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, +} + +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::*; diff --git a/src/ast/query.rs b/src/ast/query.rs index d64babadf..a97013eb9 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -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>, + /// 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, /// MSSQL-specific `WITH (...)` hints such as NOLOCK. with_hints: Vec, }, @@ -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))?; } diff --git a/src/keywords.rs b/src/keywords.rs index d43f8ebf0..18338ccdd 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -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 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, ]; diff --git a/src/parser.rs b/src/parser.rs index 5d659060c..f962f9db1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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, 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 { + 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 { + Ok(IdentPair( + self.parse_identifier()?, + self.parse_identifier()?, + )) + } + pub fn parse_derived_table_factor( &mut self, lateral: IsLateral, diff --git a/src/test_utils.rs b/src/test_utils.rs index d5bafd90f..b2bff0812 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -185,6 +185,7 @@ pub fn table(name: impl Into) -> TableFactor { name: ObjectName(vec![Ident::new(name.into())]), alias: None, args: None, + columns_definition: None, with_hints: vec![], } } diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 24d3fdcc5..4ed80df99 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -44,6 +44,7 @@ fn parse_table_identifiers() { name: ObjectName(expected), alias: None, args: None, + columns_definition: None, with_hints: vec![], }, joins: vec![] diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 3e974d56d..10e0d9ea5 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -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"), diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 76b0dff56..faab5049c 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -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![], diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 064a090f7..7c17ea120 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -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"), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 41b0803e4..874dccfab 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -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"), diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 04c86cb15..b4ae5cd5e 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -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![] diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 08cb0b343..2fbcf977f 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -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"), diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 7597ee981..fdbd66e79 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -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); +} diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index ec43e030c..eeaebf118 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -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"),