Skip to content

Support column names in more places #98

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

Merged
merged 2 commits into from
Jun 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/sqlast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -377,6 +377,7 @@ pub enum SQLStatement {
SQLCreateView {
/// View name
name: SQLObjectName,
columns: Vec<SQLIdent>,
query: Box<SQLQuery>,
materialized: bool,
with_options: Vec<SQLOption>,
Expand Down Expand Up @@ -474,6 +475,7 @@ impl ToString for SQLStatement {
}
SQLStatement::SQLCreateView {
name,
columns,
query,
materialized,
with_options,
Expand All @@ -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 {
Expand Down
24 changes: 20 additions & 4 deletions src/sqlast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ impl ToString for SQLSelectItem {
pub enum TableFactor {
Table {
name: SQLObjectName,
alias: Option<SQLIdent>,
alias: Option<TableAlias>,
/// 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`.
Expand All @@ -213,7 +213,7 @@ pub enum TableFactor {
Derived {
lateral: bool,
subquery: Box<SQLQuery>,
alias: Option<SQLIdent>,
alias: Option<TableAlias>,
},
}

Expand All @@ -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));
Expand All @@ -249,14 +249,30 @@ 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
}
}
}
}

#[derive(Debug, Clone, PartialEq, Hash)]
pub struct TableAlias {
pub name: SQLIdent,
pub columns: Vec<SQLIdent>,
}

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,
Expand Down
24 changes: 21 additions & 3 deletions src/sqlparser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand Down Expand Up @@ -1207,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<Option<TableAlias>, 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<Vec<SQLIdent>, ParserError> {
let mut idents = vec![];
Expand Down Expand Up @@ -1490,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,
Expand All @@ -1506,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") {
Expand Down
52 changes: 45 additions & 7 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down Expand Up @@ -1230,11 +1230,18 @@ fn parse_cross_join() {
);
}

fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
Some(TableAlias {
name: name.into(),
columns: vec![],
})
}

#[test]
fn parse_joins_on() {
fn join_with_constraint(
relation: impl Into<String>,
alias: Option<SQLIdent>,
alias: Option<TableAlias>,
f: impl Fn(JoinConstraint) -> JoinOperator,
) -> Join {
Join {
Expand All @@ -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
)]
);
Expand Down Expand Up @@ -1287,7 +1294,7 @@ fn parse_joins_on() {
fn parse_joins_using() {
fn join_with_constraint(
relation: impl Into<String>,
alias: Option<SQLIdent>,
alias: Option<TableAlias>,
f: impl Fn(JoinConstraint) -> JoinOperator,
) -> Join {
Join {
Expand All @@ -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
)]
);
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -1589,11 +1602,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::<SQLIdent>::new(), columns);
assert_eq!("SELECT foo FROM bar", query.to_string());
assert!(!materialized);
assert_eq!(with_options, vec![]);
Expand Down Expand Up @@ -1625,17 +1640,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::<SQLIdent>::new(), columns);
assert_eq!("SELECT foo FROM bar", query.to_string());
assert!(materialized);
assert_eq!(with_options, vec![]);
Expand Down Expand Up @@ -1922,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"
Expand Down