mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 15:39:12 +00:00
Support AS
and qualified wildcards in SELECT
This commit is contained in:
parent
bf0c07bb1b
commit
bed03abe44
5 changed files with 160 additions and 31 deletions
|
@ -715,8 +715,19 @@ pub const ALL_KEYWORDS: &'static [&'static str] = &[
|
|||
/// These keywords can't be used as a table alias, so that `FROM table_name alias`
|
||||
/// can be parsed unambiguously without looking ahead.
|
||||
pub const RESERVED_FOR_TABLE_ALIAS: &'static [&'static str] = &[
|
||||
WHERE, GROUP, ON, // keyword is 'reserved' in most dialects
|
||||
JOIN, INNER, CROSS, FULL, LEFT, RIGHT, // not reserved in Oracle
|
||||
NATURAL, USING, // not reserved in Oracle & MSSQL
|
||||
ORDER, // UNION, EXCEPT, INTERSECT, // TODO add these with tests.
|
||||
// Reserved as both a table and a column alias:
|
||||
WITH, SELECT, WHERE, GROUP, ORDER,
|
||||
// TODO add these with tests: UNION, EXCEPT, INTERSECT,
|
||||
// Reserved only as a table alias in the `FROM`/`JOIN` clauses:
|
||||
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING,
|
||||
];
|
||||
|
||||
/// Can't be used as a column alias, so that `SELECT <expr> alias`
|
||||
/// can be parsed unambiguously without looking ahead.
|
||||
pub const RESERVED_FOR_COLUMN_ALIAS: &'static [&'static str] = &[
|
||||
// Reserved as both a table and a column alias:
|
||||
WITH, SELECT, WHERE, GROUP, ORDER,
|
||||
// TODO add these with tests: UNION, EXCEPT, INTERSECT,
|
||||
// Reserved only as a column alias in the `SELECT` clause:
|
||||
FROM,
|
||||
];
|
||||
|
|
|
@ -21,7 +21,8 @@ mod table_key;
|
|||
mod value;
|
||||
|
||||
pub use self::query::{
|
||||
Cte, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect, TableFactor,
|
||||
Cte, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect, SQLSelectItem,
|
||||
TableFactor,
|
||||
};
|
||||
pub use self::sqltype::SQLType;
|
||||
pub use self::table_key::{AlterOperation, Key, TableKey};
|
||||
|
@ -38,8 +39,13 @@ pub type SQLIdent = String;
|
|||
pub enum ASTNode {
|
||||
/// Identifier e.g. table name or column name
|
||||
SQLIdentifier(SQLIdent),
|
||||
/// Wildcard e.g. `*`
|
||||
/// Unqualified wildcard (`*`). SQL allows this in limited contexts (such as right
|
||||
/// after `SELECT` or as part of an aggregate function, e.g. `COUNT(*)`, but we
|
||||
/// currently accept it in contexts where it doesn't make sense, such as `* + *`
|
||||
SQLWildcard,
|
||||
/// Qualified wildcard, e.g. `alias.*` or `schema.table.*`.
|
||||
/// (Same caveats apply to SQLQualifiedWildcard as to SQLWildcard.)
|
||||
SQLQualifiedWildcard(Vec<SQLIdent>),
|
||||
/// Multi part identifier e.g. `myschema.dbo.mytable`
|
||||
SQLCompoundIdentifier(Vec<SQLIdent>),
|
||||
/// `IS NULL` expression
|
||||
|
@ -86,6 +92,7 @@ impl ToString for ASTNode {
|
|||
match self {
|
||||
ASTNode::SQLIdentifier(s) => s.to_string(),
|
||||
ASTNode::SQLWildcard => "*".to_string(),
|
||||
ASTNode::SQLQualifiedWildcard(q) => q.join(".") + "*",
|
||||
ASTNode::SQLCompoundIdentifier(s) => s.join("."),
|
||||
ASTNode::SQLIsNull(ast) => format!("{} IS NULL", ast.as_ref().to_string()),
|
||||
ASTNode::SQLIsNotNull(ast) => format!("{} IS NOT NULL", ast.as_ref().to_string()),
|
||||
|
|
|
@ -51,7 +51,7 @@ impl ToString for SQLQuery {
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SQLSelect {
|
||||
/// projection expressions
|
||||
pub projection: Vec<ASTNode>,
|
||||
pub projection: Vec<SQLSelectItem>,
|
||||
/// FROM
|
||||
pub relation: Option<TableFactor>,
|
||||
/// JOIN
|
||||
|
@ -107,6 +107,32 @@ pub struct Cte {
|
|||
pub query: SQLQuery,
|
||||
}
|
||||
|
||||
/// One item of the comma-separated list following `SELECT`
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SQLSelectItem {
|
||||
/// Any expression, not followed by `[ AS ] alias`
|
||||
UnnamedExpression(ASTNode),
|
||||
/// An expression, followed by `[ AS ] alias`
|
||||
ExpressionWithAlias(ASTNode, SQLIdent),
|
||||
/// `alias.*` or even `schema.table.*`
|
||||
QualifiedWildcard(SQLObjectName),
|
||||
/// An unqualified `*`
|
||||
Wildcard,
|
||||
}
|
||||
|
||||
impl ToString for SQLSelectItem {
|
||||
fn to_string(&self) -> String {
|
||||
match &self {
|
||||
SQLSelectItem::UnnamedExpression(expr) => expr.to_string(),
|
||||
SQLSelectItem::ExpressionWithAlias(expr, alias) => {
|
||||
format!("{} AS {}", expr.to_string(), alias)
|
||||
}
|
||||
SQLSelectItem::QualifiedWildcard(prefix) => format!("{}.*", prefix.to_string()),
|
||||
SQLSelectItem::Wildcard => "*".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A table name or a parenthesized subquery with an optional alias
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TableFactor {
|
||||
|
|
|
@ -178,9 +178,14 @@ impl Parser {
|
|||
Some(Token::LParen) => self.parse_function(w.as_sql_ident()),
|
||||
Some(Token::Period) => {
|
||||
let mut id_parts: Vec<SQLIdent> = vec![w.as_sql_ident()];
|
||||
let mut ends_with_wildcard = false;
|
||||
while self.consume_token(&Token::Period) {
|
||||
match self.next_token() {
|
||||
Some(Token::SQLWord(w)) => id_parts.push(w.as_sql_ident()),
|
||||
Some(Token::Mult) => {
|
||||
ends_with_wildcard = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
return parser_err!(format!(
|
||||
"Error parsing compound identifier"
|
||||
|
@ -188,7 +193,11 @@ impl Parser {
|
|||
}
|
||||
}
|
||||
}
|
||||
Ok(ASTNode::SQLCompoundIdentifier(id_parts))
|
||||
if ends_with_wildcard {
|
||||
Ok(ASTNode::SQLQualifiedWildcard(id_parts))
|
||||
} else {
|
||||
Ok(ASTNode::SQLCompoundIdentifier(id_parts))
|
||||
}
|
||||
}
|
||||
_ => Ok(ASTNode::SQLIdentifier(w.as_sql_ident())),
|
||||
},
|
||||
|
@ -1193,7 +1202,7 @@ impl Parser {
|
|||
/// Parse a restricted `SELECT` statement (no CTEs / `UNION` / `ORDER BY`),
|
||||
/// assuming the initial `SELECT` was already consumed
|
||||
pub fn parse_select(&mut self) -> Result<SQLSelect, ParserError> {
|
||||
let projection = self.parse_expr_list()?;
|
||||
let projection = self.parse_select_list()?;
|
||||
|
||||
let (relation, joins) = if self.parse_keyword("FROM") {
|
||||
let relation = Some(self.parse_table_factor()?);
|
||||
|
@ -1381,20 +1390,42 @@ impl Parser {
|
|||
let mut expr_list: Vec<ASTNode> = vec![];
|
||||
loop {
|
||||
expr_list.push(self.parse_expr()?);
|
||||
if let Some(t) = self.peek_token() {
|
||||
if t == Token::Comma {
|
||||
self.next_token();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
//EOF
|
||||
break;
|
||||
}
|
||||
match self.peek_token() {
|
||||
Some(Token::Comma) => self.next_token(),
|
||||
_ => break,
|
||||
};
|
||||
}
|
||||
Ok(expr_list)
|
||||
}
|
||||
|
||||
/// Parse a comma-delimited list of projections after SELECT
|
||||
pub fn parse_select_list(&mut self) -> Result<Vec<SQLSelectItem>, ParserError> {
|
||||
let mut projections: Vec<SQLSelectItem> = vec![];
|
||||
loop {
|
||||
let expr = self.parse_expr()?;
|
||||
if let ASTNode::SQLWildcard = expr {
|
||||
projections.push(SQLSelectItem::Wildcard);
|
||||
} else if let ASTNode::SQLQualifiedWildcard(prefix) = expr {
|
||||
projections.push(SQLSelectItem::QualifiedWildcard(SQLObjectName(prefix)));
|
||||
} else {
|
||||
// `expr` is a regular SQL expression and can be followed by an alias
|
||||
if let Some(alias) =
|
||||
self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS)?
|
||||
{
|
||||
projections.push(SQLSelectItem::ExpressionWithAlias(expr, alias));
|
||||
} else {
|
||||
projections.push(SQLSelectItem::UnnamedExpression(expr));
|
||||
}
|
||||
}
|
||||
|
||||
match self.peek_token() {
|
||||
Some(Token::Comma) => self.next_token(),
|
||||
_ => break,
|
||||
};
|
||||
}
|
||||
Ok(projections)
|
||||
}
|
||||
|
||||
/// Parse a comma-delimited list of SQL ORDER BY expressions
|
||||
pub fn parse_order_by_expr_list(&mut self) -> Result<Vec<SQLOrderByExpr>, ParserError> {
|
||||
let mut expr_list: Vec<SQLOrderByExpr> = vec![];
|
||||
|
|
|
@ -59,7 +59,46 @@ fn parse_simple_select() {
|
|||
fn parse_select_wildcard() {
|
||||
let sql = "SELECT * FROM foo";
|
||||
let select = verified_only_select(sql);
|
||||
assert_eq!(&ASTNode::SQLWildcard, only(&select.projection));
|
||||
assert_eq!(&SQLSelectItem::Wildcard, only(&select.projection));
|
||||
|
||||
let sql = "SELECT foo.* FROM foo";
|
||||
let select = verified_only_select(sql);
|
||||
assert_eq!(
|
||||
&SQLSelectItem::QualifiedWildcard(SQLObjectName(vec!["foo".to_string()])),
|
||||
only(&select.projection)
|
||||
);
|
||||
|
||||
let sql = "SELECT myschema.mytable.* FROM myschema.mytable";
|
||||
let select = verified_only_select(sql);
|
||||
assert_eq!(
|
||||
&SQLSelectItem::QualifiedWildcard(SQLObjectName(vec![
|
||||
"myschema".to_string(),
|
||||
"mytable".to_string(),
|
||||
])),
|
||||
only(&select.projection)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_column_aliases() {
|
||||
let sql = "SELECT a.col + 1 AS newname FROM foo AS a";
|
||||
let select = verified_only_select(sql);
|
||||
if let SQLSelectItem::ExpressionWithAlias(
|
||||
ASTNode::SQLBinaryExpr {
|
||||
ref op, ref right, ..
|
||||
},
|
||||
ref alias,
|
||||
) = only(&select.projection)
|
||||
{
|
||||
assert_eq!(&SQLOperator::Plus, op);
|
||||
assert_eq!(&ASTNode::SQLValue(Value::Long(1)), right.as_ref());
|
||||
assert_eq!("newname", alias);
|
||||
} else {
|
||||
panic!("Expected ExpressionWithAlias")
|
||||
}
|
||||
|
||||
// alias without AS is parsed correctly:
|
||||
one_statement_parses_to("SELECT a.col + 1 newname FROM foo AS a", &sql);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -236,6 +275,7 @@ fn parse_select_order_by() {
|
|||
chk("SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY lname ASC, fname DESC, id");
|
||||
// make sure ORDER is not treated as an alias
|
||||
chk("SELECT id, fname, lname FROM customer ORDER BY lname ASC, fname DESC, id");
|
||||
chk("SELECT 1 AS lname, 2 AS fname, 3 AS id, 4 ORDER BY lname ASC, fname DESC, id");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -396,8 +436,9 @@ fn parse_select_version() {
|
|||
#[test]
|
||||
fn parse_delimited_identifiers() {
|
||||
// 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 select = verified_only_select(sql);
|
||||
let select = verified_only_select(
|
||||
r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#
|
||||
);
|
||||
// check FROM
|
||||
match select.relation.unwrap() {
|
||||
TableFactor::Table { name, alias } => {
|
||||
|
@ -419,10 +460,13 @@ fn parse_delimited_identifiers() {
|
|||
},
|
||||
expr_from_projection(&select.projection[1]),
|
||||
);
|
||||
assert_eq!(
|
||||
&ASTNode::SQLIdentifier(r#""simple id""#.to_string()),
|
||||
expr_from_projection(&select.projection[2]),
|
||||
);
|
||||
match &select.projection[2] {
|
||||
&SQLSelectItem::ExpressionWithAlias(ref expr, ref alias) => {
|
||||
assert_eq!(&ASTNode::SQLIdentifier(r#""simple id""#.to_string()), expr);
|
||||
assert_eq!(r#""column alias""#, alias);
|
||||
}
|
||||
_ => panic!("Expected ExpressionWithAlias"),
|
||||
}
|
||||
|
||||
verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#);
|
||||
verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#);
|
||||
|
@ -644,9 +688,7 @@ fn parse_join_syntax_variants() {
|
|||
|
||||
#[test]
|
||||
fn parse_ctes() {
|
||||
// To be valid SQL this needs aliases for the derived columns, but
|
||||
// we don't support them yet in the context of a SELECT's projection.
|
||||
let cte_sqls = vec!["SELECT 1", "SELECT 2"];
|
||||
let cte_sqls = vec!["SELECT 1 AS foo", "SELECT 2 AS bar"];
|
||||
let with = &format!(
|
||||
"WITH a AS ({}), b AS ({}) SELECT foo + bar FROM a, b",
|
||||
cte_sqls[0], cte_sqls[1]
|
||||
|
@ -725,6 +767,15 @@ fn parse_multiple_statements() {
|
|||
);
|
||||
}
|
||||
test_with("SELECT foo", "SELECT", " bar");
|
||||
// ensure that SELECT/WITH is not parsed as a table or column alias if ';'
|
||||
// separating the statements is omitted:
|
||||
test_with("SELECT foo FROM baz", "SELECT", " bar");
|
||||
test_with("SELECT foo", "WITH", " cte AS (SELECT 1 AS s) SELECT bar");
|
||||
test_with(
|
||||
"SELECT foo FROM baz",
|
||||
"WITH",
|
||||
" cte AS (SELECT 1 AS s) SELECT bar",
|
||||
);
|
||||
test_with("DELETE FROM foo", "SELECT", " bar");
|
||||
test_with("INSERT INTO foo VALUES(1)", "SELECT", " bar");
|
||||
test_with("CREATE TABLE foo (baz int)", "SELECT", " bar");
|
||||
|
@ -780,8 +831,11 @@ fn verified_query(query: &str) -> SQLQuery {
|
|||
}
|
||||
}
|
||||
|
||||
fn expr_from_projection(item: &ASTNode) -> &ASTNode {
|
||||
item // Will be changed later to extract expression from `expr AS alias` struct
|
||||
fn expr_from_projection(item: &SQLSelectItem) -> &ASTNode {
|
||||
match item {
|
||||
SQLSelectItem::UnnamedExpression(expr) => expr,
|
||||
_ => panic!("Expected UnnamedExpression"),
|
||||
}
|
||||
}
|
||||
|
||||
fn verified_only_select(query: &str) -> SQLSelect {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue