Merge pull request #120 from nickolay/pr/outer-apply

[mssql/oracle] Support CROSS/OUTER APPLY
This commit is contained in:
Nickolay Ponomarev 2019-06-19 13:26:35 +03:00 committed by GitHub
commit 3c401d5e4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 45 additions and 11 deletions

View file

@ -58,6 +58,7 @@ define_keywords!(
ALTER,
AND,
ANY,
APPLY,
ARE,
ARRAY,
ARRAY_AGG,
@ -422,11 +423,8 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[&str] = &[
// Reserved as both a table and a column alias:
WITH, SELECT, WHERE, GROUP, HAVING, ORDER, LIMIT, OFFSET, FETCH, UNION, EXCEPT, INTERSECT,
// Reserved only as a table alias in the `FROM`/`JOIN` clauses:
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING, LIMIT, OFFSET, FETCH,
// Reserved not because of ambiguity, but so that parsing `SELECT * FROM a
// OUTER JOIN b` causes a syntax error, rather than silently parsing to an
// inner join where table `a` is aliased as `OUTER`, which is certainly not
// what the user intended and also not valid according to the SQL standard.
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING,
// for MSSQL-specific OUTER APPLY (seems reserved in most dialects)
OUTER,
];

View file

@ -327,7 +327,6 @@ impl ToString for Join {
self.relation.to_string(),
suffix(constraint)
),
JoinOperator::Cross => format!(" CROSS JOIN {}", self.relation.to_string()),
JoinOperator::LeftOuter(constraint) => format!(
" {}LEFT JOIN {}{}",
prefix(constraint),
@ -346,6 +345,9 @@ impl ToString for Join {
self.relation.to_string(),
suffix(constraint)
),
JoinOperator::CrossJoin => format!(" CROSS JOIN {}", self.relation.to_string()),
JoinOperator::CrossApply => format!(" CROSS APPLY {}", self.relation.to_string()),
JoinOperator::OuterApply => format!(" OUTER APPLY {}", self.relation.to_string()),
}
}
}
@ -356,7 +358,11 @@ pub enum JoinOperator {
LeftOuter(JoinConstraint),
RightOuter(JoinConstraint),
FullOuter(JoinConstraint),
Cross,
CrossJoin,
/// CROSS APPLY (non-standard)
CrossApply,
/// OUTER APPLY (non-standard)
OuterApply,
}
#[derive(Debug, Clone, PartialEq, Hash)]

View file

@ -1618,10 +1618,24 @@ impl Parser {
let mut joins = vec![];
loop {
let join = if self.parse_keyword("CROSS") {
self.expect_keyword("JOIN")?;
let join_operator = if self.parse_keyword("JOIN") {
JoinOperator::CrossJoin
} else if self.parse_keyword("APPLY") {
// MSSQL extension, similar to CROSS JOIN LATERAL
JoinOperator::CrossApply
} else {
return self.expected("JOIN or APPLY after CROSS", self.peek_token());
};
Join {
relation: self.parse_table_factor()?,
join_operator: JoinOperator::Cross,
join_operator,
}
} else if self.parse_keyword("OUTER") {
// MSSQL extension, similar to LEFT JOIN LATERAL .. ON 1=1
self.expect_keyword("APPLY")?;
Join {
relation: self.parse_table_factor()?,
join_operator: JoinOperator::OuterApply,
}
} else {
let natural = self.parse_keyword("NATURAL");

View file

@ -1560,7 +1560,7 @@ fn parse_cross_join() {
args: vec![],
with_hints: vec![],
},
join_operator: JoinOperator::Cross
join_operator: JoinOperator::CrossJoin
},
only(only(select.from).joins),
);
@ -1804,7 +1804,7 @@ fn parse_join_syntax_variants() {
let res = parse_sql_statements("SELECT * FROM a OUTER JOIN b ON 1");
assert_eq!(
ParserError::ParserError("Expected LEFT, RIGHT, or FULL, found: OUTER".to_string()),
ParserError::ParserError("Expected APPLY, found: JOIN".to_string()),
res.unwrap_err()
);
}

View file

@ -52,6 +52,22 @@ fn parse_mssql_delimited_identifiers() {
);
}
#[test]
fn parse_mssql_apply_join() {
let _ = ms_and_generic().verified_only_select(
"SELECT * FROM sys.dm_exec_query_stats AS deqs \
CROSS APPLY sys.dm_exec_query_plan(deqs.plan_handle)",
);
let _ = ms_and_generic().verified_only_select(
"SELECT * FROM sys.dm_exec_query_stats AS deqs \
OUTER APPLY sys.dm_exec_query_plan(deqs.plan_handle)",
);
let _ = ms_and_generic().verified_only_select(
"SELECT * FROM foo \
OUTER APPLY (SELECT foo.x + 1) AS bar",
);
}
fn ms() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(MsSqlDialect {})],