Skip to content

Commit

Permalink
Merge pull request #3713 from benesch/lateral-join
Browse files Browse the repository at this point in the history
sql: support LATERAL joins
  • Loading branch information
benesch committed Jul 28, 2020
2 parents 470649b + d37fe57 commit 210caae
Show file tree
Hide file tree
Showing 16 changed files with 1,204 additions and 352 deletions.
47 changes: 15 additions & 32 deletions src/sql-parser/src/ast/defs/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,12 @@ impl TableWithJoins {
pub enum TableFactor {
Table {
name: ObjectName,
/// Arguments of a table-valued function, as supported by Postgres
/// and MSSQL.
args: Option<FunctionArgs>,
alias: Option<TableAlias>,
/// MSSQL-specific `WITH (...)` hints such as NOLOCK.
with_hints: Vec<Expr>,
},
Function {
name: ObjectName,
args: FunctionArgs,
alias: Option<TableAlias>,
},
Derived {
lateral: bool,
Expand All @@ -323,26 +323,21 @@ pub enum TableFactor {
impl AstDisplay for TableFactor {
fn fmt(&self, f: &mut AstFormatter) {
match self {
TableFactor::Table {
name,
alias,
args,
with_hints,
} => {
TableFactor::Table { name, alias } => {
f.write_node(name);
if let Some(args) = args {
f.write_str("(");
f.write_node(args);
f.write_str(")");
}
if let Some(alias) = alias {
f.write_str(" AS ");
f.write_node(alias);
}
if !with_hints.is_empty() {
f.write_str(" WITH (");
f.write_node(&display::comma_separated(with_hints));
f.write_str(")");
}
TableFactor::Function { name, args, alias } => {
f.write_node(name);
f.write_str("(");
f.write_node(args);
f.write_str(")");
if let Some(alias) = alias {
f.write_str(" AS ");
f.write_node(alias);
}
}
TableFactor::Derived {
Expand Down Expand Up @@ -462,14 +457,6 @@ impl AstDisplay for Join {
f.write_str(" CROSS JOIN ");
f.write_node(&self.relation);
}
JoinOperator::CrossApply => {
f.write_str(" CROSS APPLY ");
f.write_node(&self.relation);
}
JoinOperator::OuterApply => {
f.write_str(" OUTER APPLY ");
f.write_node(&self.relation);
}
}
}
}
Expand All @@ -482,10 +469,6 @@ pub enum JoinOperator {
RightOuter(JoinConstraint),
FullOuter(JoinConstraint),
CrossJoin,
/// CROSS APPLY (non-standard)
CrossApply,
/// OUTER APPLY (non-standard)
OuterApply,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down
53 changes: 22 additions & 31 deletions src/sql-parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2733,15 +2733,18 @@ impl Parser {
/// A table name or a parenthesized subquery, followed by optional `[AS] alias`
fn parse_table_factor(&mut self) -> Result<TableFactor, ParserError> {
if self.parse_keyword("LATERAL") {
// LATERAL must always be followed by a subquery.
if !self.consume_token(&Token::LParen) {
self.expected(
self.peek_range(),
"subquery after LATERAL",
self.peek_token(),
)?;
// LATERAL must always be followed by a subquery or table function.
if self.consume_token(&Token::LParen) {
return self.parse_derived_table_factor(Lateral);
} else {
let name = self.parse_object_name()?;
self.expect_token(&Token::LParen)?;
return Ok(TableFactor::Function {
name,
args: self.parse_optional_args()?,
alias: self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?,
});
}
return self.parse_derived_table_factor(Lateral);
}

if self.consume_token(&Token::LParen) {
Expand Down Expand Up @@ -2793,30 +2796,18 @@ impl Parser {
Ok(TableFactor::NestedJoin(Box::new(table_and_joins)))
} else {
let name = self.parse_object_name()?;
// Postgres, MSSQL: table-valued functions:
let args = if self.consume_token(&Token::LParen) {
Some(self.parse_optional_args()?)
if self.consume_token(&Token::LParen) {
Ok(TableFactor::Function {
name,
args: self.parse_optional_args()?,
alias: self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?,
})
} else {
None
};
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_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_comma_separated(Parser::parse_expr)?;
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,
})
Ok(TableFactor::Table {
name,
alias: self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?,
})
}
}
}

Expand Down
18 changes: 9 additions & 9 deletions src/sql-parser/tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,15 @@ fn test_basic_visitor() -> Result<(), Box<dyn Error>> {
AND EXTRACT(YEAR FROM a37)
AND (SELECT a38)
AND EXISTS (SELECT a39)
FROM a40(a41) AS a42 WITH (a43)
LEFT JOIN a44 ON false
RIGHT JOIN a45 ON false
FULL JOIN a46 ON false
JOIN a47 (a48) USING (a49)
NATURAL JOIN (a50 NATURAL JOIN a51)
FROM a40(a41) AS a42
LEFT JOIN a43 ON false
RIGHT JOIN a44 ON false
FULL JOIN a45 ON false
JOIN a46 (a47) USING (a48)
NATURAL JOIN (a49 NATURAL JOIN a50)
EXCEPT
(SELECT a52(a53) OVER (PARTITION BY a54 ORDER BY a55 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING))
ORDER BY a56
(SELECT a51(a52) OVER (PARTITION BY a53 ORDER BY a54 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING))
ORDER BY a55
LIMIT 1;
UPDATE b01 SET b02 = b03 WHERE b04;
INSERT INTO c01 (c02) VALUES (c03);
Expand Down Expand Up @@ -223,7 +223,7 @@ fn test_basic_visitor() -> Result<(), Box<dyn Error>> {
"a25", "a26", "a27", "a28", "a29", "a30", "a31", "a32", "a33", "a34", "a35", "a36",
"date_part",
"a37", "a38", "a39", "a40", "a41", "a42", "a43", "a44", "a45", "a46", "a47", "a48",
"a49", "a50", "a51", "a52", "a53", "a54", "a55", "a56",
"a49", "a50", "a51", "a52", "a53", "a54", "a55",
"b01", "b02", "b03", "b04",
"c01", "c02", "c03", "c04", "c05",
"d01", "d02",
Expand Down
12 changes: 6 additions & 6 deletions src/sql-parser/tests/testdata/ddl
Original file line number Diff line number Diff line change
Expand Up @@ -200,21 +200,21 @@ CREATE VIEW myschema.myview AS SELECT foo FROM bar
----
CREATE VIEW myschema.myview AS SELECT foo FROM bar
=>
CreateView { name: ObjectName([Ident("myschema"), Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Error, temporary: false, materialized: false }
CreateView { name: ObjectName([Ident("myschema"), Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Error, temporary: false, materialized: false }

parse-statement
CREATE TEMPORARY VIEW myview AS SELECT foo FROM bar
----
CREATE TEMPORARY VIEW myview AS SELECT foo FROM bar
=>
CreateView { name: ObjectName([Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Error, temporary: true, materialized: false }
CreateView { name: ObjectName([Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Error, temporary: true, materialized: false }

parse-statement
CREATE TEMP VIEW myview AS SELECT foo FROM bar
----
CREATE TEMPORARY VIEW myview AS SELECT foo FROM bar
=>
CreateView { name: ObjectName([Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Error, temporary: true, materialized: false }
CreateView { name: ObjectName([Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Error, temporary: true, materialized: false }

parse-statement
CREATE OR REPLACE VIEW v AS SELECT 1
Expand Down Expand Up @@ -258,14 +258,14 @@ CREATE MATERIALIZED VIEW myschema.myview AS SELECT foo FROM bar
----
CREATE MATERIALIZED VIEW myschema.myview AS SELECT foo FROM bar
=>
CreateView { name: ObjectName([Ident("myschema"), Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Error, temporary: false, materialized: true }
CreateView { name: ObjectName([Ident("myschema"), Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Error, temporary: false, materialized: true }

parse-statement
CREATE MATERIALIZED VIEW IF NOT EXISTS myschema.myview AS SELECT foo FROM bar
----
CREATE MATERIALIZED VIEW IF NOT EXISTS myschema.myview AS SELECT foo FROM bar
=>
CreateView { name: ObjectName([Ident("myschema"), Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Skip, temporary: false, materialized: true }
CreateView { name: ObjectName([Ident("myschema"), Ident("myview")]), columns: [], with_options: [], query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, if_exists: Skip, temporary: false, materialized: true }

parse-statement
CREATE SOURCE foo FROM FILE 'bar' FORMAT AVRO USING SCHEMA 'baz'
Expand Down Expand Up @@ -512,7 +512,7 @@ CREATE INDEX fizz ON baz (ascii(x), a IS NOT NULL, (EXISTS (SELECT y FROM boop W
----
CREATE INDEX fizz ON baz (ascii(x), a IS NOT NULL, (EXISTS (SELECT y FROM boop WHERE boop.z = z)), delta)
=>
CreateIndex { name: Some(Ident("fizz")), on_name: ObjectName([Ident("baz")]), key_parts: Some([Function(Function { name: ObjectName([Ident("ascii")]), args: Args([Identifier([Ident("x")])]), filter: None, over: None, distinct: false }), IsNull { expr: Identifier([Ident("a")]), negated: true }, Nested(Exists(Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("y")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("boop")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("boop"), Ident("z")]), op: Eq, right: Identifier([Ident("z")]) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })), Identifier([Ident("delta")])]), if_not_exists: false }
CreateIndex { name: Some(Ident("fizz")), on_name: ObjectName([Ident("baz")]), key_parts: Some([Function(Function { name: ObjectName([Ident("ascii")]), args: Args([Identifier([Ident("x")])]), filter: None, over: None, distinct: false }), IsNull { expr: Identifier([Ident("a")]), negated: true }, Nested(Exists(Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("y")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("boop")]), alias: None }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("boop"), Ident("z")]), op: Eq, right: Identifier([Ident("z")]) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })), Identifier([Ident("delta")])]), if_not_exists: false }

parse-statement
CREATE INDEX ind ON tab ((col + 1))
Expand Down
2 changes: 1 addition & 1 deletion src/sql-parser/tests/testdata/insert
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ INSERT INTO customer WITH foo AS (SELECT 1) SELECT * FROM foo UNION VALUES (1)
----
INSERT INTO customer WITH foo AS (SELECT 1) SELECT * FROM foo UNION VALUES (1)
=>
Insert { table_name: ObjectName([Ident("customer")]), columns: [], source: Query { ctes: [Cte { alias: TableAlias { name: Ident("foo"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("1")), alias: None }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }], body: SetOperation { op: Union, all: false, left: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("foo")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), right: Values(Values([[Value(Number("1"))]])) }, order_by: [], limit: None, offset: None, fetch: None } }
Insert { table_name: ObjectName([Ident("customer")]), columns: [], source: Query { ctes: [Cte { alias: TableAlias { name: Ident("foo"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("1")), alias: None }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }], body: SetOperation { op: Union, all: false, left: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("foo")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), right: Values(Values([[Value(Number("1"))]])) }, order_by: [], limit: None, offset: None, fetch: None } }
Loading

0 comments on commit 210caae

Please sign in to comment.