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.
This commit is contained in:
Nikhil Benesch 2019-05-30 17:23:05 -04:00
parent 73ed685879
commit 9abcac350e
No known key found for this signature in database
GPG key ID: F7386C5DEADABA7F
4 changed files with 60 additions and 14 deletions

View file

@ -25,7 +25,7 @@ use std::ops::Deref;
pub use self::ddl::{AlterTableOperation, TableConstraint}; pub use self::ddl::{AlterTableOperation, TableConstraint};
pub use self::query::{ pub use self::query::{
Cte, Fetch, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect, 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::sqltype::SQLType;
pub use self::value::Value; pub use self::value::Value;

View file

@ -202,7 +202,7 @@ impl ToString for SQLSelectItem {
pub enum TableFactor { pub enum TableFactor {
Table { Table {
name: SQLObjectName, name: SQLObjectName,
alias: Option<SQLIdent>, alias: Option<TableAlias>,
/// Arguments of a table-valued function, as supported by Postgres /// Arguments of a table-valued function, as supported by Postgres
/// and MSSQL. Note that deprecated MSSQL `FROM foo (NOLOCK)` syntax /// and MSSQL. Note that deprecated MSSQL `FROM foo (NOLOCK)` syntax
/// will also be parsed as `args`. /// will also be parsed as `args`.
@ -213,7 +213,7 @@ pub enum TableFactor {
Derived { Derived {
lateral: bool, lateral: bool,
subquery: Box<SQLQuery>, subquery: Box<SQLQuery>,
alias: Option<SQLIdent>, alias: Option<TableAlias>,
}, },
} }
@ -231,7 +231,7 @@ impl ToString for TableFactor {
s += &format!("({})", comma_separated_string(args)) s += &format!("({})", comma_separated_string(args))
}; };
if let Some(alias) = alias { if let Some(alias) = alias {
s += &format!(" AS {}", alias); s += &format!(" AS {}", alias.to_string());
} }
if !with_hints.is_empty() { if !with_hints.is_empty() {
s += &format!(" WITH ({})", comma_separated_string(with_hints)); s += &format!(" WITH ({})", comma_separated_string(with_hints));
@ -249,7 +249,7 @@ impl ToString for TableFactor {
} }
s += &format!("({})", subquery.to_string()); s += &format!("({})", subquery.to_string());
if let Some(alias) = alias { if let Some(alias) = alias {
s += &format!(" AS {}", alias); s += &format!(" AS {}", alias.to_string());
} }
s s
} }
@ -257,6 +257,22 @@ impl ToString for TableFactor {
} }
} }
#[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)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct Join { pub struct Join {
pub relation: TableFactor, pub relation: TableFactor,

View file

@ -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<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 /// 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> { pub fn parse_list_of_ids(&mut self, separator: &Token) -> Result<Vec<SQLIdent>, ParserError> {
let mut idents = vec![]; let mut idents = vec![];
@ -1491,7 +1508,7 @@ impl Parser {
if self.consume_token(&Token::LParen) { if self.consume_token(&Token::LParen) {
let subquery = Box::new(self.parse_query()?); let subquery = Box::new(self.parse_query()?);
self.expect_token(&Token::RParen)?; 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 { Ok(TableFactor::Derived {
lateral, lateral,
subquery, subquery,
@ -1507,7 +1524,7 @@ impl Parser {
} else { } else {
vec![] 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: // MSSQL-specific table hints:
let mut with_hints = vec![]; let mut with_hints = vec![];
if self.parse_keyword("WITH") { if self.parse_keyword("WITH") {

View file

@ -1077,7 +1077,7 @@ fn parse_delimited_identifiers() {
with_hints, with_hints,
} => { } => {
assert_eq!(vec![r#""a table""#.to_string()], name.0); 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!(args.is_empty());
assert!(with_hints.is_empty()); assert!(with_hints.is_empty());
} }
@ -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] #[test]
fn parse_joins_on() { fn parse_joins_on() {
fn join_with_constraint( fn join_with_constraint(
relation: impl Into<String>, relation: impl Into<String>,
alias: Option<SQLIdent>, alias: Option<TableAlias>,
f: impl Fn(JoinConstraint) -> JoinOperator, f: impl Fn(JoinConstraint) -> JoinOperator,
) -> Join { ) -> Join {
Join { Join {
@ -1256,7 +1263,7 @@ fn parse_joins_on() {
verified_only_select("SELECT * FROM t1 JOIN t2 AS foo ON c1 = c2").joins, verified_only_select("SELECT * FROM t1 JOIN t2 AS foo ON c1 = c2").joins,
vec![join_with_constraint( vec![join_with_constraint(
"t2", "t2",
Some("foo".to_string()), table_alias("foo"),
JoinOperator::Inner JoinOperator::Inner
)] )]
); );
@ -1287,7 +1294,7 @@ fn parse_joins_on() {
fn parse_joins_using() { fn parse_joins_using() {
fn join_with_constraint( fn join_with_constraint(
relation: impl Into<String>, relation: impl Into<String>,
alias: Option<SQLIdent>, alias: Option<TableAlias>,
f: impl Fn(JoinConstraint) -> JoinOperator, f: impl Fn(JoinConstraint) -> JoinOperator,
) -> Join { ) -> Join {
Join { Join {
@ -1305,7 +1312,7 @@ fn parse_joins_using() {
verified_only_select("SELECT * FROM t1 JOIN t2 AS foo USING(c1)").joins, verified_only_select("SELECT * FROM t1 JOIN t2 AS foo USING(c1)").joins,
vec![join_with_constraint( vec![join_with_constraint(
"t2", "t2",
Some("foo".to_string()), table_alias("foo"),
JoinOperator::Inner 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 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); let _ = verified_only_select(sql);
//TODO: add assertions //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] #[test]
@ -1947,11 +1960,11 @@ fn lateral_derived() {
if let TableFactor::Derived { if let TableFactor::Derived {
lateral, lateral,
ref subquery, ref subquery,
ref alias, alias: Some(ref alias),
} = select.joins[0].relation } = select.joins[0].relation
{ {
assert_eq!(lateral_in, lateral); assert_eq!(lateral_in, lateral);
assert_eq!(Some("order".to_string()), *alias); assert_eq!("order".to_string(), alias.name);
assert_eq!( assert_eq!(
subquery.to_string(), subquery.to_string(),
"SELECT * FROM order WHERE order.customer = customer.id LIMIT 3" "SELECT * FROM order WHERE order.customer = customer.id LIMIT 3"