Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sql: support LATERAL joins #3713

Merged
merged 8 commits into from
Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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