Skip to content

Commit 3c401d5

Browse files
authored
Merge pull request apache#120 from nickolay/pr/outer-apply
[mssql/oracle] Support CROSS/OUTER APPLY
2 parents e6b2633 + 4294581 commit 3c401d5

File tree

5 files changed

+45
-11
lines changed

5 files changed

+45
-11
lines changed

src/dialect/keywords.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ define_keywords!(
5858
ALTER,
5959
AND,
6060
ANY,
61+
APPLY,
6162
ARE,
6263
ARRAY,
6364
ARRAY_AGG,
@@ -422,11 +423,8 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[&str] = &[
422423
// Reserved as both a table and a column alias:
423424
WITH, SELECT, WHERE, GROUP, HAVING, ORDER, LIMIT, OFFSET, FETCH, UNION, EXCEPT, INTERSECT,
424425
// Reserved only as a table alias in the `FROM`/`JOIN` clauses:
425-
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING, LIMIT, OFFSET, FETCH,
426-
// Reserved not because of ambiguity, but so that parsing `SELECT * FROM a
427-
// OUTER JOIN b` causes a syntax error, rather than silently parsing to an
428-
// inner join where table `a` is aliased as `OUTER`, which is certainly not
429-
// what the user intended and also not valid according to the SQL standard.
426+
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING,
427+
// for MSSQL-specific OUTER APPLY (seems reserved in most dialects)
430428
OUTER,
431429
];
432430

src/sqlast/query.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,6 @@ impl ToString for Join {
327327
self.relation.to_string(),
328328
suffix(constraint)
329329
),
330-
JoinOperator::Cross => format!(" CROSS JOIN {}", self.relation.to_string()),
331330
JoinOperator::LeftOuter(constraint) => format!(
332331
" {}LEFT JOIN {}{}",
333332
prefix(constraint),
@@ -346,6 +345,9 @@ impl ToString for Join {
346345
self.relation.to_string(),
347346
suffix(constraint)
348347
),
348+
JoinOperator::CrossJoin => format!(" CROSS JOIN {}", self.relation.to_string()),
349+
JoinOperator::CrossApply => format!(" CROSS APPLY {}", self.relation.to_string()),
350+
JoinOperator::OuterApply => format!(" OUTER APPLY {}", self.relation.to_string()),
349351
}
350352
}
351353
}
@@ -356,7 +358,11 @@ pub enum JoinOperator {
356358
LeftOuter(JoinConstraint),
357359
RightOuter(JoinConstraint),
358360
FullOuter(JoinConstraint),
359-
Cross,
361+
CrossJoin,
362+
/// CROSS APPLY (non-standard)
363+
CrossApply,
364+
/// OUTER APPLY (non-standard)
365+
OuterApply,
360366
}
361367

362368
#[derive(Debug, Clone, PartialEq, Hash)]

src/sqlparser.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,10 +1618,24 @@ impl Parser {
16181618
let mut joins = vec![];
16191619
loop {
16201620
let join = if self.parse_keyword("CROSS") {
1621-
self.expect_keyword("JOIN")?;
1621+
let join_operator = if self.parse_keyword("JOIN") {
1622+
JoinOperator::CrossJoin
1623+
} else if self.parse_keyword("APPLY") {
1624+
// MSSQL extension, similar to CROSS JOIN LATERAL
1625+
JoinOperator::CrossApply
1626+
} else {
1627+
return self.expected("JOIN or APPLY after CROSS", self.peek_token());
1628+
};
1629+
Join {
1630+
relation: self.parse_table_factor()?,
1631+
join_operator,
1632+
}
1633+
} else if self.parse_keyword("OUTER") {
1634+
// MSSQL extension, similar to LEFT JOIN LATERAL .. ON 1=1
1635+
self.expect_keyword("APPLY")?;
16221636
Join {
16231637
relation: self.parse_table_factor()?,
1624-
join_operator: JoinOperator::Cross,
1638+
join_operator: JoinOperator::OuterApply,
16251639
}
16261640
} else {
16271641
let natural = self.parse_keyword("NATURAL");

tests/sqlparser_common.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,7 +1560,7 @@ fn parse_cross_join() {
15601560
args: vec![],
15611561
with_hints: vec![],
15621562
},
1563-
join_operator: JoinOperator::Cross
1563+
join_operator: JoinOperator::CrossJoin
15641564
},
15651565
only(only(select.from).joins),
15661566
);
@@ -1804,7 +1804,7 @@ fn parse_join_syntax_variants() {
18041804

18051805
let res = parse_sql_statements("SELECT * FROM a OUTER JOIN b ON 1");
18061806
assert_eq!(
1807-
ParserError::ParserError("Expected LEFT, RIGHT, or FULL, found: OUTER".to_string()),
1807+
ParserError::ParserError("Expected APPLY, found: JOIN".to_string()),
18081808
res.unwrap_err()
18091809
);
18101810
}

tests/sqlparser_mssql.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ fn parse_mssql_delimited_identifiers() {
5252
);
5353
}
5454

55+
#[test]
56+
fn parse_mssql_apply_join() {
57+
let _ = ms_and_generic().verified_only_select(
58+
"SELECT * FROM sys.dm_exec_query_stats AS deqs \
59+
CROSS APPLY sys.dm_exec_query_plan(deqs.plan_handle)",
60+
);
61+
let _ = ms_and_generic().verified_only_select(
62+
"SELECT * FROM sys.dm_exec_query_stats AS deqs \
63+
OUTER APPLY sys.dm_exec_query_plan(deqs.plan_handle)",
64+
);
65+
let _ = ms_and_generic().verified_only_select(
66+
"SELECT * FROM foo \
67+
OUTER APPLY (SELECT foo.x + 1) AS bar",
68+
);
69+
}
70+
5571
fn ms() -> TestedDialects {
5672
TestedDialects {
5773
dialects: vec![Box::new(MsSqlDialect {})],

0 commit comments

Comments
 (0)