Move TableFactor to be a separate enum

ASTNode can now be renamed SQLExpression, as it represents a node in
the "expression" part of the AST -- other nodes have their own types.
This commit is contained in:
Nickolay Ponomarev 2019-02-06 06:02:54 +03:00
parent e0ceacd1ad
commit 9967031cba
4 changed files with 82 additions and 67 deletions

View file

@ -20,7 +20,7 @@ mod sqltype;
mod table_key; mod table_key;
mod value; mod value;
pub use self::query::{Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLSelect}; pub use self::query::{Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLSelect, TableFactor};
pub use self::sqltype::SQLType; pub use self::sqltype::SQLType;
pub use self::table_key::{AlterOperation, Key, TableKey}; pub use self::table_key::{AlterOperation, Key, TableKey};
pub use self::value::Value; pub use self::value::Value;
@ -30,7 +30,8 @@ pub use self::sql_operator::SQLOperator;
/// Identifier name, in the originally quoted form (e.g. `"id"`) /// Identifier name, in the originally quoted form (e.g. `"id"`)
pub type SQLIdent = String; pub type SQLIdent = String;
/// SQL Abstract Syntax Tree (AST) /// Represents a parsed SQL expression, which is a common building
/// block of SQL statements (the part after SELECT, WHERE, etc.)
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum ASTNode { pub enum ASTNode {
/// Identifier e.g. table name or column name /// Identifier e.g. table name or column name
@ -73,11 +74,6 @@ pub enum ASTNode {
results: Vec<ASTNode>, results: Vec<ASTNode>,
else_result: Option<Box<ASTNode>>, else_result: Option<Box<ASTNode>>,
}, },
/// A table name or a parenthesized subquery with an optional alias
TableFactor {
relation: Box<ASTNode>, // SQLNested or SQLCompoundIdentifier
alias: Option<SQLIdent>,
},
/// A parenthesized subquery `(SELECT ...)`, used in expression like /// A parenthesized subquery `(SELECT ...)`, used in expression like
/// `SELECT (subquery) AS x` or `WHERE (subquery) = x` /// `SELECT (subquery) AS x` or `WHERE (subquery) = x`
SQLSubquery(SQLSelect), SQLSubquery(SQLSelect),
@ -134,13 +130,6 @@ impl ToString for ASTNode {
} }
s + " END" s + " END"
} }
ASTNode::TableFactor { relation, alias } => {
if let Some(alias) = alias {
format!("{} AS {}", relation.to_string(), alias)
} else {
relation.to_string()
}
}
ASTNode::SQLSubquery(s) => format!("({})", s.to_string()), ASTNode::SQLSubquery(s) => format!("({})", s.to_string()),
} }
} }

View file

@ -5,7 +5,7 @@ pub struct SQLSelect {
/// projection expressions /// projection expressions
pub projection: Vec<ASTNode>, pub projection: Vec<ASTNode>,
/// FROM /// FROM
pub relation: Option<Box<ASTNode>>, // TableFactor pub relation: Option<TableFactor>,
// JOIN // JOIN
pub joins: Vec<Join>, pub joins: Vec<Join>,
/// WHERE /// WHERE
@ -31,7 +31,7 @@ impl ToString for SQLSelect {
.join(", ") .join(", ")
); );
if let Some(ref relation) = self.relation { if let Some(ref relation) = self.relation {
s += &format!(" FROM {}", relation.as_ref().to_string()); s += &format!(" FROM {}", relation.to_string());
} }
for join in &self.joins { for join in &self.joins {
s += &join.to_string(); s += &join.to_string();
@ -69,9 +69,38 @@ impl ToString for SQLSelect {
} }
} }
/// A table name or a parenthesized subquery with an optional alias
#[derive(Debug, Clone, PartialEq)]
pub enum TableFactor {
Table {
name: SQLObjectName,
alias: Option<SQLIdent>,
},
Derived {
subquery: Box<SQLSelect>,
alias: Option<SQLIdent>,
},
}
impl ToString for TableFactor {
fn to_string(&self) -> String {
let (base, alias) = match self {
TableFactor::Table { name, alias } => (name.to_string(), alias),
TableFactor::Derived { subquery, alias } => {
(format!("({})", subquery.to_string()), alias)
}
};
if let Some(alias) = alias {
format!("{} AS {}", base, alias)
} else {
base
}
}
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Join { pub struct Join {
pub relation: ASTNode, // TableFactor pub relation: TableFactor,
pub join_operator: JoinOperator, pub join_operator: JoinOperator,
} }

View file

@ -1119,8 +1119,8 @@ impl Parser {
pub fn parse_select(&mut self) -> Result<SQLSelect, ParserError> { pub fn parse_select(&mut self) -> Result<SQLSelect, ParserError> {
let projection = self.parse_expr_list()?; let projection = self.parse_expr_list()?;
let (relation, joins): (Option<Box<ASTNode>>, Vec<Join>) = if self.parse_keyword("FROM") { let (relation, joins) = if self.parse_keyword("FROM") {
let relation = Some(Box::new(self.parse_table_factor()?)); let relation = Some(self.parse_table_factor()?);
let joins = self.parse_joins()?; let joins = self.parse_joins()?;
(relation, joins) (relation, joins)
} else { } else {
@ -1171,20 +1171,21 @@ impl Parser {
} }
/// A table name or a parenthesized subquery, followed by optional `[AS] alias` /// A table name or a parenthesized subquery, followed by optional `[AS] alias`
pub fn parse_table_factor(&mut self) -> Result<ASTNode, ParserError> { pub fn parse_table_factor(&mut self) -> Result<TableFactor, ParserError> {
let relation = if self.consume_token(&Token::LParen) { if self.consume_token(&Token::LParen) {
self.expect_keyword("SELECT")?; self.expect_keyword("SELECT")?;
let subquery = self.parse_select()?; let subquery = self.parse_select()?;
self.expect_token(&Token::RParen)?; self.expect_token(&Token::RParen)?;
ASTNode::SQLSubquery(subquery) Ok(TableFactor::Derived {
subquery: Box::new(subquery),
alias: self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?,
})
} else { } else {
ASTNode::SQLCompoundIdentifier(self.parse_object_name()?.0) Ok(TableFactor::Table {
}; name: self.parse_object_name()?,
let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; alias: self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?,
Ok(ASTNode::TableFactor { })
relation: Box::new(relation), }
alias,
})
} }
fn parse_join_constraint(&mut self, natural: bool) -> Result<JoinConstraint, ParserError> { fn parse_join_constraint(&mut self, natural: bool) -> Result<JoinConstraint, ParserError> {

View file

@ -432,6 +432,14 @@ fn parse_delimited_identifiers() {
// check that quoted identifiers in any position remain quoted after serialization // check that quoted identifiers in any position remain quoted after serialization
let sql = r#"SELECT "alias"."bar baz", "myfun"(), "simple id" FROM "a table" AS "alias""#; let sql = r#"SELECT "alias"."bar baz", "myfun"(), "simple id" FROM "a table" AS "alias""#;
let select = verified_only_select(sql); let select = verified_only_select(sql);
// check FROM
match select.relation.unwrap() {
TableFactor::Table { name, alias } => {
assert_eq!(vec![r#""a table""#.to_string()], name.0);
assert_eq!(r#""alias""#, alias.unwrap());
}
_ => panic!("Expecting TableFactor::Table"),
}
// check SELECT // check SELECT
assert_eq!(3, select.projection.len()); assert_eq!(3, select.projection.len());
assert_eq!( assert_eq!(
@ -515,45 +523,33 @@ fn parse_case_expression() {
#[test] #[test]
fn parse_implicit_join() { fn parse_implicit_join() {
let sql = "SELECT * FROM t1, t2"; let sql = "SELECT * FROM t1, t2";
let select = verified_only_select(sql);
match verified_stmt(sql) { assert_eq!(
SQLStatement::SQLSelect(SQLSelect { joins, .. }) => { &Join {
assert_eq!(joins.len(), 1); relation: TableFactor::Table {
assert_eq!( name: SQLObjectName(vec!["t2".to_string()]),
joins[0], alias: None,
Join { },
relation: ASTNode::TableFactor { join_operator: JoinOperator::Implicit
relation: Box::new(ASTNode::SQLCompoundIdentifier(vec!["t2".to_string()])), },
alias: None, only(&select.joins),
}, );
join_operator: JoinOperator::Implicit
}
)
}
_ => assert!(false),
}
} }
#[test] #[test]
fn parse_cross_join() { fn parse_cross_join() {
let sql = "SELECT * FROM t1 CROSS JOIN t2"; let sql = "SELECT * FROM t1 CROSS JOIN t2";
let select = verified_only_select(sql);
match verified_stmt(sql) { assert_eq!(
SQLStatement::SQLSelect(SQLSelect { joins, .. }) => { &Join {
assert_eq!(joins.len(), 1); relation: TableFactor::Table {
assert_eq!( name: SQLObjectName(vec!["t2".to_string()]),
joins[0], alias: None,
Join { },
relation: ASTNode::TableFactor { join_operator: JoinOperator::Cross
relation: Box::new(ASTNode::SQLCompoundIdentifier(vec!["t2".to_string()])), },
alias: None, only(&select.joins),
}, );
join_operator: JoinOperator::Cross
}
)
}
_ => assert!(false),
}
} }
#[test] #[test]
@ -564,8 +560,8 @@ fn parse_joins_on() {
f: impl Fn(JoinConstraint) -> JoinOperator, f: impl Fn(JoinConstraint) -> JoinOperator,
) -> Join { ) -> Join {
Join { Join {
relation: ASTNode::TableFactor { relation: TableFactor::Table {
relation: Box::new(ASTNode::SQLCompoundIdentifier(vec![relation.into()])), name: SQLObjectName(vec![relation.into()]),
alias, alias,
}, },
join_operator: f(JoinConstraint::On(ASTNode::SQLBinaryExpr { join_operator: f(JoinConstraint::On(ASTNode::SQLBinaryExpr {
@ -615,8 +611,8 @@ fn parse_joins_using() {
f: impl Fn(JoinConstraint) -> JoinOperator, f: impl Fn(JoinConstraint) -> JoinOperator,
) -> Join { ) -> Join {
Join { Join {
relation: ASTNode::TableFactor { relation: TableFactor::Table {
relation: Box::new(ASTNode::SQLCompoundIdentifier(vec![relation.into()])), name: SQLObjectName(vec![relation.into()]),
alias, alias,
}, },
join_operator: f(JoinConstraint::Using(vec!["c1".into()])), join_operator: f(JoinConstraint::Using(vec!["c1".into()])),