mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-01 11:47:20 +00:00
Parse table-valued functions and MSSQL-specific WITH hints
1) Table-valued functions (`FROM possibly_qualified.fn(arg1, ...)`) is not part of ANSI SQL, but is supported in Postgres and MSSQL at least: - "38.5.7. SQL Functions as Table Sources" <https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC-SQL-TABLE-FUNCTIONS> - `user_defined_function` in "FROM (Transact-SQL)" <https://docs.microsoft.com/en-us/sql/t-sql/queries/from-transact-sql?view=sql-server-2017> I've considered renaming TableFactor::Table to something else (Object?), now that it can be a TVF, but couldn't come up with a satisfactory name. 2) "WITH hints" is MSSQL-specific syntax <https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-2017> Note that MSSQL supports the following ways of specifying hints, which are parsed with varying degrees of accuracy: - `FROM tab (NOLOCK)` -- deprecated syntax, parsed as a function with a `NOLOCK` argument - `FROM tab C (NOLOCK)` -- deprecated syntax, rejected ATM - `FROM TAB C WITH (NOLOCK)` -- OK
This commit is contained in:
parent
e5e3d71354
commit
364f62f333
3 changed files with 87 additions and 18 deletions
|
@ -183,6 +183,12 @@ pub enum TableFactor {
|
||||||
Table {
|
Table {
|
||||||
name: SQLObjectName,
|
name: SQLObjectName,
|
||||||
alias: Option<SQLIdent>,
|
alias: Option<SQLIdent>,
|
||||||
|
/// 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`.
|
||||||
|
args: Option<Vec<ASTNode>>,
|
||||||
|
/// MSSQL-specific `WITH (...)` hints such as NOLOCK.
|
||||||
|
with_hints: Vec<ASTNode>,
|
||||||
},
|
},
|
||||||
Derived {
|
Derived {
|
||||||
subquery: Box<SQLQuery>,
|
subquery: Box<SQLQuery>,
|
||||||
|
@ -192,16 +198,32 @@ pub enum TableFactor {
|
||||||
|
|
||||||
impl ToString for TableFactor {
|
impl ToString for TableFactor {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
let (base, alias) = match self {
|
match self {
|
||||||
TableFactor::Table { name, alias } => (name.to_string(), alias),
|
TableFactor::Table {
|
||||||
TableFactor::Derived { subquery, alias } => {
|
name,
|
||||||
(format!("({})", subquery.to_string()), alias)
|
alias,
|
||||||
|
args,
|
||||||
|
with_hints,
|
||||||
|
} => {
|
||||||
|
let mut s = name.to_string();
|
||||||
|
if let Some(args) = args {
|
||||||
|
s += &format!("({})", comma_separated_string(args))
|
||||||
|
};
|
||||||
|
if let Some(alias) = alias {
|
||||||
|
s += &format!(" AS {}", alias);
|
||||||
|
}
|
||||||
|
if !with_hints.is_empty() {
|
||||||
|
s += &format!(" WITH ({})", comma_separated_string(with_hints));
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
TableFactor::Derived { subquery, alias } => {
|
||||||
|
let mut s = format!("({})", subquery.to_string());
|
||||||
|
if let Some(alias) = alias {
|
||||||
|
s += &format!(" AS {}", alias);
|
||||||
|
}
|
||||||
|
s
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if let Some(alias) = alias {
|
|
||||||
format!("{} AS {}", base, alias)
|
|
||||||
} else {
|
|
||||||
base
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,13 +242,7 @@ impl Parser {
|
||||||
|
|
||||||
pub fn parse_function(&mut self, name: SQLObjectName) -> Result<ASTNode, ParserError> {
|
pub fn parse_function(&mut self, name: SQLObjectName) -> Result<ASTNode, ParserError> {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
let args = if self.consume_token(&Token::RParen) {
|
let args = self.parse_optional_args()?;
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
let args = self.parse_expr_list()?;
|
|
||||||
self.expect_token(&Token::RParen)?;
|
|
||||||
args
|
|
||||||
};
|
|
||||||
let over = if self.parse_keyword("OVER") {
|
let over = if self.parse_keyword("OVER") {
|
||||||
// TBD: support window names (`OVER mywin`) in place of inline specification
|
// TBD: support window names (`OVER mywin`) in place of inline specification
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
|
@ -1430,8 +1424,30 @@ impl Parser {
|
||||||
Ok(TableFactor::Derived { subquery, alias })
|
Ok(TableFactor::Derived { subquery, alias })
|
||||||
} else {
|
} else {
|
||||||
let name = self.parse_object_name()?;
|
let name = self.parse_object_name()?;
|
||||||
|
// Postgres, MSSQL: table-valued functions:
|
||||||
|
let args = if self.consume_token(&Token::LParen) {
|
||||||
|
Some(self.parse_optional_args()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
|
let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
|
||||||
Ok(TableFactor::Table { name, alias })
|
// MSSQL-specific table hints:
|
||||||
|
let mut with_hints = vec![];
|
||||||
|
if self.parse_keyword("WITH") {
|
||||||
|
if self.consume_token(&Token::LParen) {
|
||||||
|
with_hints = self.parse_expr_list()?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
} else {
|
||||||
|
// rewind, as WITH may belong to the next statement's CTE
|
||||||
|
self.prev_token();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(TableFactor::Table {
|
||||||
|
name,
|
||||||
|
alias,
|
||||||
|
args,
|
||||||
|
with_hints,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1576,6 +1592,16 @@ impl Parser {
|
||||||
Ok(expr_list)
|
Ok(expr_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_optional_args(&mut self) -> Result<Vec<ASTNode>, ParserError> {
|
||||||
|
if self.consume_token(&Token::RParen) {
|
||||||
|
Ok(vec![])
|
||||||
|
} else {
|
||||||
|
let args = self.parse_expr_list()?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
Ok(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a comma-delimited list of projections after SELECT
|
/// Parse a comma-delimited list of projections after SELECT
|
||||||
pub fn parse_select_list(&mut self) -> Result<Vec<SQLSelectItem>, ParserError> {
|
pub fn parse_select_list(&mut self) -> Result<Vec<SQLSelectItem>, ParserError> {
|
||||||
let mut projections: Vec<SQLSelectItem> = vec![];
|
let mut projections: Vec<SQLSelectItem> = vec![];
|
||||||
|
|
|
@ -641,9 +641,16 @@ fn parse_delimited_identifiers() {
|
||||||
);
|
);
|
||||||
// check FROM
|
// check FROM
|
||||||
match select.relation.unwrap() {
|
match select.relation.unwrap() {
|
||||||
TableFactor::Table { name, alias } => {
|
TableFactor::Table {
|
||||||
|
name,
|
||||||
|
alias,
|
||||||
|
args,
|
||||||
|
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());
|
||||||
|
assert!(args.is_none());
|
||||||
|
assert!(with_hints.is_empty());
|
||||||
}
|
}
|
||||||
_ => panic!("Expecting TableFactor::Table"),
|
_ => panic!("Expecting TableFactor::Table"),
|
||||||
}
|
}
|
||||||
|
@ -751,6 +758,12 @@ fn parse_simple_case_expression() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_from_advanced() {
|
||||||
|
let sql = "SELECT * FROM fn(1, 2) AS foo, schema.bar AS bar WITH (NOLOCK)";
|
||||||
|
let _select = verified_only_select(sql);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_implicit_join() {
|
fn parse_implicit_join() {
|
||||||
let sql = "SELECT * FROM t1, t2";
|
let sql = "SELECT * FROM t1, t2";
|
||||||
|
@ -760,6 +773,8 @@ fn parse_implicit_join() {
|
||||||
relation: TableFactor::Table {
|
relation: TableFactor::Table {
|
||||||
name: SQLObjectName(vec!["t2".to_string()]),
|
name: SQLObjectName(vec!["t2".to_string()]),
|
||||||
alias: None,
|
alias: None,
|
||||||
|
args: None,
|
||||||
|
with_hints: vec![],
|
||||||
},
|
},
|
||||||
join_operator: JoinOperator::Implicit
|
join_operator: JoinOperator::Implicit
|
||||||
},
|
},
|
||||||
|
@ -776,6 +791,8 @@ fn parse_cross_join() {
|
||||||
relation: TableFactor::Table {
|
relation: TableFactor::Table {
|
||||||
name: SQLObjectName(vec!["t2".to_string()]),
|
name: SQLObjectName(vec!["t2".to_string()]),
|
||||||
alias: None,
|
alias: None,
|
||||||
|
args: None,
|
||||||
|
with_hints: vec![],
|
||||||
},
|
},
|
||||||
join_operator: JoinOperator::Cross
|
join_operator: JoinOperator::Cross
|
||||||
},
|
},
|
||||||
|
@ -794,6 +811,8 @@ fn parse_joins_on() {
|
||||||
relation: TableFactor::Table {
|
relation: TableFactor::Table {
|
||||||
name: SQLObjectName(vec![relation.into()]),
|
name: SQLObjectName(vec![relation.into()]),
|
||||||
alias,
|
alias,
|
||||||
|
args: None,
|
||||||
|
with_hints: vec![],
|
||||||
},
|
},
|
||||||
join_operator: f(JoinConstraint::On(ASTNode::SQLBinaryExpr {
|
join_operator: f(JoinConstraint::On(ASTNode::SQLBinaryExpr {
|
||||||
left: Box::new(ASTNode::SQLIdentifier("c1".into())),
|
left: Box::new(ASTNode::SQLIdentifier("c1".into())),
|
||||||
|
@ -845,6 +864,8 @@ fn parse_joins_using() {
|
||||||
relation: TableFactor::Table {
|
relation: TableFactor::Table {
|
||||||
name: SQLObjectName(vec![relation.into()]),
|
name: SQLObjectName(vec![relation.into()]),
|
||||||
alias,
|
alias,
|
||||||
|
args: None,
|
||||||
|
with_hints: vec![],
|
||||||
},
|
},
|
||||||
join_operator: f(JoinConstraint::Using(vec!["c1".into()])),
|
join_operator: f(JoinConstraint::Using(vec!["c1".into()])),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue