From 73ed68587916f2bd51e666c393ad5084330226d3 Mon Sep 17 00:00:00 2001 From: Nikhil Benesch Date: Thu, 30 May 2019 17:06:06 -0400 Subject: [PATCH 1/2] Support views with explicit column names A `CREATE VIEW` statement may provide names for its columns that override the names of the columns derived from the view's query. --- src/sqlast/mod.rs | 12 ++++++++++-- src/sqlparser.rs | 3 ++- tests/sqlparser_common.rs | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/sqlast/mod.rs b/src/sqlast/mod.rs index 70cc9c756..2eaec44f1 100644 --- a/src/sqlast/mod.rs +++ b/src/sqlast/mod.rs @@ -377,6 +377,7 @@ pub enum SQLStatement { SQLCreateView { /// View name name: SQLObjectName, + columns: Vec, query: Box, materialized: bool, with_options: Vec, @@ -474,6 +475,7 @@ impl ToString for SQLStatement { } SQLStatement::SQLCreateView { name, + columns, query, materialized, with_options, @@ -484,12 +486,18 @@ impl ToString for SQLStatement { } else { "".into() }; + let columns = if !columns.is_empty() { + format!(" ({})", comma_separated_string(columns)) + } else { + "".into() + }; format!( - "CREATE{} VIEW {}{} AS {}", + "CREATE{} VIEW {}{}{} AS {}", modifier, name.to_string(), with_options, - query.to_string() + columns, + query.to_string(), ) } SQLStatement::SQLCreateTable { diff --git a/src/sqlparser.rs b/src/sqlparser.rs index c58c8c1a8..ef40c38d1 100644 --- a/src/sqlparser.rs +++ b/src/sqlparser.rs @@ -775,7 +775,7 @@ impl Parser { // Many dialects support `OR REPLACE` | `OR ALTER` right after `CREATE`, but we don't (yet). // ANSI SQL and Postgres support RECURSIVE here, but we don't support it either. let name = self.parse_object_name()?; - // Parenthesized "output" columns list could be handled here. + let columns = self.parse_parenthesized_column_list(Optional)?; let with_options = if self.parse_keyword("WITH") { self.parse_with_options()? } else { @@ -786,6 +786,7 @@ impl Parser { // Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here. Ok(SQLStatement::SQLCreateView { name, + columns, query, materialized, with_options, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index b18d9d41a..a296ee5b6 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1589,11 +1589,13 @@ fn parse_create_view() { match verified_stmt(sql) { SQLStatement::SQLCreateView { name, + columns, query, materialized, with_options, } => { assert_eq!("myschema.myview", name.to_string()); + assert_eq!(Vec::::new(), columns); assert_eq!("SELECT foo FROM bar", query.to_string()); assert!(!materialized); assert_eq!(with_options, vec![]); @@ -1625,17 +1627,40 @@ fn parse_create_view_with_options() { } } +#[test] +fn parse_create_view_with_columns() { + let sql = "CREATE VIEW v (has, cols) AS SELECT 1, 2"; + match verified_stmt(sql) { + SQLStatement::SQLCreateView { + name, + columns, + with_options, + query, + materialized, + } => { + assert_eq!("v", name.to_string()); + assert_eq!(columns, vec!["has".to_string(), "cols".to_string()]); + assert_eq!(with_options, vec![]); + assert_eq!("SELECT 1, 2", query.to_string()); + assert!(!materialized); + } + _ => unreachable!(), + } +} + #[test] fn parse_create_materialized_view() { let sql = "CREATE MATERIALIZED VIEW myschema.myview AS SELECT foo FROM bar"; match verified_stmt(sql) { SQLStatement::SQLCreateView { name, + columns, query, materialized, with_options, } => { assert_eq!("myschema.myview", name.to_string()); + assert_eq!(Vec::::new(), columns); assert_eq!("SELECT foo FROM bar", query.to_string()); assert!(materialized); assert_eq!(with_options, vec![]); From 9abcac350e8caaeced261eeceecf29a74f376e84 Mon Sep 17 00:00:00 2001 From: Nikhil Benesch Date: Thu, 30 May 2019 17:23:05 -0400 Subject: [PATCH 2/2] Support aliasing columns A table alias can specify new names for the columns within the aliased table, in addition to a new name for the table itself. --- src/sqlast/mod.rs | 2 +- src/sqlast/query.rs | 24 ++++++++++++++++++++---- src/sqlparser.rs | 21 +++++++++++++++++++-- tests/sqlparser_common.rs | 27 ++++++++++++++++++++------- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/sqlast/mod.rs b/src/sqlast/mod.rs index 2eaec44f1..780b59384 100644 --- a/src/sqlast/mod.rs +++ b/src/sqlast/mod.rs @@ -25,7 +25,7 @@ use std::ops::Deref; pub use self::ddl::{AlterTableOperation, TableConstraint}; pub use self::query::{ Cte, Fetch, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect, - SQLSelectItem, SQLSetExpr, SQLSetOperator, SQLValues, TableFactor, + SQLSelectItem, SQLSetExpr, SQLSetOperator, SQLValues, TableAlias, TableFactor, }; pub use self::sqltype::SQLType; pub use self::value::Value; diff --git a/src/sqlast/query.rs b/src/sqlast/query.rs index e954289a4..663e0e508 100644 --- a/src/sqlast/query.rs +++ b/src/sqlast/query.rs @@ -202,7 +202,7 @@ impl ToString for SQLSelectItem { pub enum TableFactor { Table { name: SQLObjectName, - alias: Option, + alias: Option, /// Arguments of a table-valued function, as supported by Postgres /// and MSSQL. Note that deprecated MSSQL `FROM foo (NOLOCK)` syntax /// will also be parsed as `args`. @@ -213,7 +213,7 @@ pub enum TableFactor { Derived { lateral: bool, subquery: Box, - alias: Option, + alias: Option, }, } @@ -231,7 +231,7 @@ impl ToString for TableFactor { s += &format!("({})", comma_separated_string(args)) }; if let Some(alias) = alias { - s += &format!(" AS {}", alias); + s += &format!(" AS {}", alias.to_string()); } if !with_hints.is_empty() { s += &format!(" WITH ({})", comma_separated_string(with_hints)); @@ -249,7 +249,7 @@ impl ToString for TableFactor { } s += &format!("({})", subquery.to_string()); if let Some(alias) = alias { - s += &format!(" AS {}", alias); + s += &format!(" AS {}", alias.to_string()); } s } @@ -257,6 +257,22 @@ impl ToString for TableFactor { } } +#[derive(Debug, Clone, PartialEq, Hash)] +pub struct TableAlias { + pub name: SQLIdent, + pub columns: Vec, +} + +impl ToString for TableAlias { + fn to_string(&self) -> String { + let mut s = self.name.clone(); + if !self.columns.is_empty() { + s += &format!(" ({})", comma_separated_string(&self.columns)); + } + s + } +} + #[derive(Debug, Clone, PartialEq, Hash)] pub struct Join { pub relation: TableFactor, diff --git a/src/sqlparser.rs b/src/sqlparser.rs index ef40c38d1..463ab7dff 100644 --- a/src/sqlparser.rs +++ b/src/sqlparser.rs @@ -1208,6 +1208,23 @@ impl Parser { } } + /// Parse `AS identifier` when the AS is describing a table-valued object, + /// like in `... FROM generate_series(1, 10) AS t (col)`. In this case + /// the alias is allowed to optionally name the columns in the table, in + /// addition to the table itself. + pub fn parse_optional_table_alias( + &mut self, + reserved_kwds: &[&str], + ) -> Result, ParserError> { + match self.parse_optional_alias(reserved_kwds)? { + Some(name) => { + let columns = self.parse_parenthesized_column_list(Optional)?; + Ok(Some(TableAlias { name, columns })) + } + None => Ok(None), + } + } + /// Parse one or more identifiers with the specified separator between them pub fn parse_list_of_ids(&mut self, separator: &Token) -> Result, ParserError> { let mut idents = vec![]; @@ -1491,7 +1508,7 @@ impl Parser { if self.consume_token(&Token::LParen) { let subquery = Box::new(self.parse_query()?); self.expect_token(&Token::RParen)?; - let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; + let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; Ok(TableFactor::Derived { lateral, subquery, @@ -1507,7 +1524,7 @@ impl Parser { } else { vec![] }; - let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; + let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; // MSSQL-specific table hints: let mut with_hints = vec![]; if self.parse_keyword("WITH") { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index a296ee5b6..7db3d25bf 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1077,7 +1077,7 @@ fn parse_delimited_identifiers() { with_hints, } => { assert_eq!(vec![r#""a table""#.to_string()], name.0); - assert_eq!(r#""alias""#, alias.unwrap()); + assert_eq!(r#""alias""#, alias.unwrap().name); assert!(args.is_empty()); assert!(with_hints.is_empty()); } @@ -1230,11 +1230,18 @@ fn parse_cross_join() { ); } +fn table_alias(name: impl Into) -> Option { + Some(TableAlias { + name: name.into(), + columns: vec![], + }) +} + #[test] fn parse_joins_on() { fn join_with_constraint( relation: impl Into, - alias: Option, + alias: Option, f: impl Fn(JoinConstraint) -> JoinOperator, ) -> Join { Join { @@ -1256,7 +1263,7 @@ fn parse_joins_on() { verified_only_select("SELECT * FROM t1 JOIN t2 AS foo ON c1 = c2").joins, vec![join_with_constraint( "t2", - Some("foo".to_string()), + table_alias("foo"), JoinOperator::Inner )] ); @@ -1287,7 +1294,7 @@ fn parse_joins_on() { fn parse_joins_using() { fn join_with_constraint( relation: impl Into, - alias: Option, + alias: Option, f: impl Fn(JoinConstraint) -> JoinOperator, ) -> Join { Join { @@ -1305,7 +1312,7 @@ fn parse_joins_using() { verified_only_select("SELECT * FROM t1 JOIN t2 AS foo USING(c1)").joins, vec![join_with_constraint( "t2", - Some("foo".to_string()), + table_alias("foo"), JoinOperator::Inner )] ); @@ -1465,6 +1472,12 @@ fn parse_derived_tables() { let sql = "SELECT a.x, b.y FROM (SELECT x FROM foo) AS a CROSS JOIN (SELECT y FROM bar) AS b"; let _ = verified_only_select(sql); //TODO: add assertions + + let sql = "SELECT a.x, b.y \ + FROM (SELECT x FROM foo) AS a (x) \ + CROSS JOIN (SELECT y FROM bar) AS b (y)"; + let _ = verified_only_select(sql); + //TODO: add assertions } #[test] @@ -1947,11 +1960,11 @@ fn lateral_derived() { if let TableFactor::Derived { lateral, ref subquery, - ref alias, + alias: Some(ref alias), } = select.joins[0].relation { assert_eq!(lateral_in, lateral); - assert_eq!(Some("order".to_string()), *alias); + assert_eq!("order".to_string(), alias.name); assert_eq!( subquery.to_string(), "SELECT * FROM order WHERE order.customer = customer.id LIMIT 3"