diff --git a/src/sql-parser/src/ast/defs/query.rs b/src/sql-parser/src/ast/defs/query.rs index 3eff6765743b..330e05cd8ffd 100644 --- a/src/sql-parser/src/ast/defs/query.rs +++ b/src/sql-parser/src/ast/defs/query.rs @@ -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, alias: Option, - /// MSSQL-specific `WITH (...)` hints such as NOLOCK. - with_hints: Vec, + }, + Function { + name: ObjectName, + args: FunctionArgs, + alias: Option, }, Derived { lateral: bool, @@ -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 { @@ -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); - } } } } @@ -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)] diff --git a/src/sql-parser/src/parser.rs b/src/sql-parser/src/parser.rs index ebc27715ee9f..3a61c0a56839 100644 --- a/src/sql-parser/src/parser.rs +++ b/src/sql-parser/src/parser.rs @@ -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 { 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) { @@ -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)?, + }) + } } } diff --git a/src/sql-parser/tests/sqlparser_common.rs b/src/sql-parser/tests/sqlparser_common.rs index 466dff05ef51..5201d624dfa2 100644 --- a/src/sql-parser/tests/sqlparser_common.rs +++ b/src/sql-parser/tests/sqlparser_common.rs @@ -185,15 +185,15 @@ fn test_basic_visitor() -> Result<(), Box> { 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); @@ -223,7 +223,7 @@ fn test_basic_visitor() -> Result<(), Box> { "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", diff --git a/src/sql-parser/tests/testdata/ddl b/src/sql-parser/tests/testdata/ddl index b06d2157b1fb..bb39c124e2df 100644 --- a/src/sql-parser/tests/testdata/ddl +++ b/src/sql-parser/tests/testdata/ddl @@ -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 @@ -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' @@ -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)) diff --git a/src/sql-parser/tests/testdata/insert b/src/sql-parser/tests/testdata/insert index 67bbd175a088..781a5923e6ad 100644 --- a/src/sql-parser/tests/testdata/insert +++ b/src/sql-parser/tests/testdata/insert @@ -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 } } diff --git a/src/sql-parser/tests/testdata/select b/src/sql-parser/tests/testdata/select index 5fd703db70c8..d7fd9b4154be 100644 --- a/src/sql-parser/tests/testdata/select +++ b/src/sql-parser/tests/testdata/select @@ -73,7 +73,7 @@ SELECT id, fname, lname FROM customer WHERE id = 1 LIMIT 5 ---- SELECT id, fname, lname FROM customer WHERE id = 1 LIMIT 5 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("id")]), op: Eq, right: Value(Number("1")) }), group_by: [], having: None }), order_by: [], limit: Some(Value(Number("5"))), offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("id")]), op: Eq, right: Value(Number("1")) }), group_by: [], having: None }), order_by: [], limit: Some(Value(Number("5"))), offset: None, fetch: None }, as_of: None } # LIMIT should not be parsed as an alias. @@ -82,7 +82,7 @@ SELECT id FROM customer LIMIT 1 ---- SELECT id FROM customer LIMIT 1 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: Some(Value(Number("1"))), offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: Some(Value(Number("1"))), offset: None, fetch: None }, as_of: None } parse-statement SELECT 1 LIMIT 5 @@ -96,7 +96,7 @@ SELECT DISTINCT name FROM customer ---- SELECT DISTINCT name FROM customer => -Select { query: Query { ctes: [], body: Select(Select { distinct: true, projection: [Expr { expr: Identifier([Ident("name")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: true, projection: [Expr { expr: Identifier([Ident("name")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement roundtrip SELECT ALL name FROM customer @@ -108,14 +108,14 @@ SELECT * FROM foo ---- SELECT * FROM foo => -Select { query: Query { ctes: [], body: 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 }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("foo")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT foo.* FROM foo ---- SELECT foo.* FROM foo => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: QualifiedWildcard([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("foo")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: QualifiedWildcard([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("foo")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT (x).a, (x).a.b.c @@ -152,7 +152,7 @@ SELECT a.col + 1 AS newname FROM foo AS a ---- SELECT a.col + 1 AS newname FROM foo AS a => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: BinaryOp { left: Identifier([Ident("a"), Ident("col")]), op: Plus, right: Value(Number("1")) }, alias: Some(Ident("newname")) }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("foo")]), args: None, alias: Some(TableAlias { name: Ident("a"), columns: [], strict: false }), with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: BinaryOp { left: Identifier([Ident("a"), Ident("col")]), op: Plus, right: Value(Number("1")) }, alias: Some(Ident("newname")) }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("foo")]), alias: Some(TableAlias { name: Ident("a"), columns: [], strict: false }) }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement roundtrip SELECT a.col + 1 AS newname FROM foo AS a @@ -194,14 +194,14 @@ SELECT count(*) FILTER (WHERE foo) FROM customer ---- SELECT count(*) FILTER (WHERE foo) FROM customer => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Function(Function { name: ObjectName([Ident("count")]), args: Star, filter: Some(Identifier([Ident("foo")])), over: None, distinct: false }), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Function(Function { name: ObjectName([Ident("count")]), args: Star, filter: Some(Identifier([Ident("foo")])), over: None, distinct: false }), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT count(DISTINCT + x) FROM customer ---- SELECT count(DISTINCT + x) FROM customer => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Function(Function { name: ObjectName([Ident("count")]), args: Args([UnaryOp { op: Plus, expr: Identifier([Ident("x")]) }]), filter: None, over: None, distinct: true }), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Function(Function { name: ObjectName([Ident("count")]), args: Args([UnaryOp { op: Plus, expr: Identifier([Ident("x")]) }]), filter: None, over: None, distinct: true }), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement roundtrip SELECT count(ALL + x) FROM customer @@ -275,70 +275,70 @@ SELECT * FROM customers WHERE segment IN (SELECT segm FROM bar) ---- SELECT * FROM customers WHERE segment IN (SELECT segm FROM bar) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customers")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(InSubquery { expr: Identifier([Ident("segment")]), subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("segm")]), 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 }, negated: false }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customers")]), alias: None }, joins: [] }], selection: Some(InSubquery { expr: Identifier([Ident("segment")]), subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("segm")]), 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 }, negated: false }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t WHERE x IN (VALUES (1)) ---- SELECT * FROM t WHERE x IN (VALUES (1)) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(InSubquery { expr: Identifier([Ident("x")]), subquery: Query { ctes: [], body: Values(Values([[Value(Number("1"))]])), order_by: [], limit: None, offset: None, fetch: None }, negated: false }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t")]), alias: None }, joins: [] }], selection: Some(InSubquery { expr: Identifier([Ident("x")]), subquery: Query { ctes: [], body: Values(Values([[Value(Number("1"))]])), order_by: [], limit: None, offset: None, fetch: None }, negated: false }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM customers WHERE age BETWEEN 25 AND 32 ---- SELECT * FROM customers WHERE age BETWEEN 25 AND 32 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customers")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(Between { expr: Identifier([Ident("age")]), negated: false, low: Value(Number("25")), high: Value(Number("32")) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customers")]), alias: None }, joins: [] }], selection: Some(Between { expr: Identifier([Ident("age")]), negated: false, low: Value(Number("25")), high: Value(Number("32")) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM customers WHERE age NOT BETWEEN 25 AND 32 ---- SELECT * FROM customers WHERE age NOT BETWEEN 25 AND 32 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customers")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(Between { expr: Identifier([Ident("age")]), negated: true, low: Value(Number("25")), high: Value(Number("32")) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customers")]), alias: None }, joins: [] }], selection: Some(Between { expr: Identifier([Ident("age")]), negated: true, low: Value(Number("25")), high: Value(Number("32")) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t WHERE 1 BETWEEN 1 + 2 AND 3 + 4 IS NULL ---- SELECT * FROM t WHERE 1 BETWEEN 1 + 2 AND 3 + 4 IS NULL => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(IsNull { expr: Between { expr: Value(Number("1")), negated: false, low: BinaryOp { left: Value(Number("1")), op: Plus, right: Value(Number("2")) }, high: BinaryOp { left: Value(Number("3")), op: Plus, right: Value(Number("4")) } }, negated: false }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t")]), alias: None }, joins: [] }], selection: Some(IsNull { expr: Between { expr: Value(Number("1")), negated: false, low: BinaryOp { left: Value(Number("1")), op: Plus, right: Value(Number("2")) }, high: BinaryOp { left: Value(Number("3")), op: Plus, right: Value(Number("4")) } }, negated: false }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t WHERE 1 = 1 AND 1 + x BETWEEN 1 AND 2 ---- SELECT * FROM t WHERE 1 = 1 AND 1 + x BETWEEN 1 AND 2 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: BinaryOp { left: Value(Number("1")), op: Eq, right: Value(Number("1")) }, op: And, right: Between { expr: BinaryOp { left: Value(Number("1")), op: Plus, right: Identifier([Ident("x")]) }, negated: false, low: Value(Number("1")), high: Value(Number("2")) } }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t")]), alias: None }, joins: [] }], selection: Some(BinaryOp { left: BinaryOp { left: Value(Number("1")), op: Eq, right: Value(Number("1")) }, op: And, right: Between { expr: BinaryOp { left: Value(Number("1")), op: Plus, right: Identifier([Ident("x")]) }, negated: false, low: Value(Number("1")), high: Value(Number("2")) } }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t WHERE 1 = 1 AND 1 + x BETWEEN 1 AND 2 ---- SELECT * FROM t WHERE 1 = 1 AND 1 + x BETWEEN 1 AND 2 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: BinaryOp { left: Value(Number("1")), op: Eq, right: Value(Number("1")) }, op: And, right: Between { expr: BinaryOp { left: Value(Number("1")), op: Plus, right: Identifier([Ident("x")]) }, negated: false, low: Value(Number("1")), high: Value(Number("2")) } }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t")]), alias: None }, joins: [] }], selection: Some(BinaryOp { left: BinaryOp { left: Value(Number("1")), op: Eq, right: Value(Number("1")) }, op: And, right: Between { expr: BinaryOp { left: Value(Number("1")), op: Plus, right: Identifier([Ident("x")]) }, negated: false, low: Value(Number("1")), high: Value(Number("2")) } }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY lname ASC, fname DESC, id ---- SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY lname ASC, fname DESC, id => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("id")]), op: Lt, right: Value(Number("5")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("lname")]), asc: Some(true) }, OrderByExpr { expr: Identifier([Ident("fname")]), asc: Some(false) }, OrderByExpr { expr: Identifier([Ident("id")]), asc: None }], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("id")]), op: Lt, right: Value(Number("5")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("lname")]), asc: Some(true) }, OrderByExpr { expr: Identifier([Ident("fname")]), asc: Some(false) }, OrderByExpr { expr: Identifier([Ident("id")]), asc: None }], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT id, fname, lname FROM customer ORDER BY lname ASC, fname DESC, id ---- SELECT id, fname, lname FROM customer ORDER BY lname ASC, fname DESC, id => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("lname")]), asc: Some(true) }, OrderByExpr { expr: Identifier([Ident("fname")]), asc: Some(false) }, OrderByExpr { expr: Identifier([Ident("id")]), asc: None }], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("lname")]), asc: Some(true) }, OrderByExpr { expr: Identifier([Ident("fname")]), asc: Some(false) }, OrderByExpr { expr: Identifier([Ident("id")]), asc: None }], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT id, fname, lname FROM customer ORDER BY lname ASC, fname DESC, id ---- SELECT id, fname, lname FROM customer ORDER BY lname ASC, fname DESC, id => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("lname")]), asc: Some(true) }, OrderByExpr { expr: Identifier([Ident("fname")]), asc: Some(false) }, OrderByExpr { expr: Identifier([Ident("id")]), asc: None }], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("lname")]), asc: Some(true) }, OrderByExpr { expr: Identifier([Ident("fname")]), asc: Some(false) }, OrderByExpr { expr: Identifier([Ident("id")]), asc: None }], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT id, fname, lname FROM customer WHERE id < 5 @@ -346,35 +346,35 @@ ORDER BY lname ASC, fname DESC LIMIT 2 ---- SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY lname ASC, fname DESC LIMIT 2 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("id")]), op: Lt, right: Value(Number("5")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("lname")]), asc: Some(true) }, OrderByExpr { expr: Identifier([Ident("fname")]), asc: Some(false) }], limit: Some(Value(Number("2"))), offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("id")]), op: Lt, right: Value(Number("5")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("lname")]), asc: Some(true) }, OrderByExpr { expr: Identifier([Ident("fname")]), asc: Some(false) }], limit: Some(Value(Number("2"))), offset: None, fetch: None }, as_of: None } parse-statement SELECT id, fname, lname FROM customer GROUP BY lname, fname ---- SELECT id, fname, lname FROM customer GROUP BY lname, fname => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [Identifier([Ident("lname")]), Identifier([Ident("fname")])], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("id")]), alias: None }, Expr { expr: Identifier([Ident("fname")]), alias: None }, Expr { expr: Identifier([Ident("lname")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [] }], selection: None, group_by: [Identifier([Ident("lname")]), Identifier([Ident("fname")])], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT foo FROM bar GROUP BY foo HAVING count(*) > 1 ---- SELECT foo FROM bar GROUP BY foo HAVING count(*) > 1 => -Select { 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: [Identifier([Ident("foo")])], having: Some(BinaryOp { left: Function(Function { name: ObjectName([Ident("count")]), args: Star, filter: None, over: None, distinct: false }), op: Gt, right: Value(Number("1")) }) }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { 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: [Identifier([Ident("foo")])], having: Some(BinaryOp { left: Function(Function { name: ObjectName([Ident("count")]), args: Star, filter: None, over: None, distinct: false }), op: Gt, right: Value(Number("1")) }) }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT foo FROM bar GROUP BY foo HAVING count(*) > 1 ---- SELECT foo FROM bar GROUP BY foo HAVING count(*) > 1 => -Select { 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: [Identifier([Ident("foo")])], having: Some(BinaryOp { left: Function(Function { name: ObjectName([Ident("count")]), args: Star, filter: None, over: None, distinct: false }), op: Gt, right: Value(Number("1")) }) }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { 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: [Identifier([Ident("foo")])], having: Some(BinaryOp { left: Function(Function { name: ObjectName([Ident("count")]), args: Star, filter: None, over: None, distinct: false }), op: Gt, right: Value(Number("1")) }) }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT foo FROM bar GROUP BY foo HAVING 1 = 1 ---- SELECT foo FROM bar GROUP BY foo HAVING 1 = 1 => -Select { 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: [Identifier([Ident("foo")])], having: Some(BinaryOp { left: Value(Number("1")), op: Eq, right: Value(Number("1")) }) }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { 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: [Identifier([Ident("foo")])], having: Some(BinaryOp { left: Value(Number("1")), op: Eq, right: Value(Number("1")) }) }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement roundtrip SELECT id, fname, lname FROM customer WHERE id = 1 LIMIT ALL @@ -421,70 +421,74 @@ CREATE TABLE foo (bar int) parse-statement roundtrip SELECT * FROM fn(1, 2) AS foo, schema.bar AS bar WITH (nolock) ---- +error: +Parse error: SELECT * FROM fn(1, 2) AS foo, schema.bar AS bar WITH (nolock) + ^^^^ +Expected end of statement, found: WITH parse-statement SELECT * FROM t1, t2 ---- SELECT * FROM t1, t2 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [] }, TableWithJoins { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [] }, TableWithJoins { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t1a NATURAL JOIN t1b, t2a NATURAL JOIN t2b ---- SELECT * FROM t1a NATURAL JOIN t1b, t2a NATURAL JOIN t2b => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1a")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t1b")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }] }, TableWithJoins { relation: Table { name: ObjectName([Ident("t2a")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2b")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1a")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t1b")]), alias: None }, join_operator: Inner(Natural) }] }, TableWithJoins { relation: Table { name: ObjectName([Ident("t2a")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2b")]), alias: None }, join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t1 CROSS JOIN t2 ---- SELECT * FROM t1 CROSS JOIN t2 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: CrossJoin }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: CrossJoin }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t1 JOIN t2 AS foo USING(c1) ---- SELECT * FROM t1 JOIN t2 AS foo USING(c1) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: Some(TableAlias { name: Ident("foo"), columns: [], strict: false }), with_hints: [] }, join_operator: Inner(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: Some(TableAlias { name: Ident("foo"), columns: [], strict: false }) }, join_operator: Inner(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t1 JOIN t2 foo USING(c1) ---- SELECT * FROM t1 JOIN t2 AS foo USING(c1) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: Some(TableAlias { name: Ident("foo"), columns: [], strict: false }), with_hints: [] }, join_operator: Inner(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: Some(TableAlias { name: Ident("foo"), columns: [], strict: false }) }, join_operator: Inner(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t1 NATURAL JOIN t2 ---- SELECT * FROM t1 NATURAL JOIN t2 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t1 NATURAL LEFT JOIN t2 ---- SELECT * FROM t1 NATURAL LEFT JOIN t2 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: LeftOuter(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: LeftOuter(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t1 NATURAL RIGHT JOIN t2 ---- SELECT * FROM t1 NATURAL RIGHT JOIN t2 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: RightOuter(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: RightOuter(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t1 NATURAL FULL JOIN t2 ---- SELECT * FROM t1 NATURAL FULL JOIN t2 => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: FullOuter(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: FullOuter(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM t1 natural @@ -500,35 +504,35 @@ SELECT c1, c2 FROM t1, t4 JOIN t2 ON t2.c = t1.c LEFT JOIN t3 USING(q, c) WHERE ---- SELECT c1, c2 FROM t1, t4 JOIN t2 ON t2.c = t1.c LEFT JOIN t3 USING(q, c) WHERE t4.c = t1.c => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }, Expr { expr: Identifier([Ident("c2")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [] }, TableWithJoins { relation: Table { name: ObjectName([Ident("t4")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(On(BinaryOp { left: Identifier([Ident("t2"), Ident("c")]), op: Eq, right: Identifier([Ident("t1"), Ident("c")]) })) }, Join { relation: Table { name: ObjectName([Ident("t3")]), args: None, alias: None, with_hints: [] }, join_operator: LeftOuter(Using([Ident("q"), Ident("c")])) }] }], selection: Some(BinaryOp { left: Identifier([Ident("t4"), Ident("c")]), op: Eq, right: Identifier([Ident("t1"), Ident("c")]) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }, Expr { expr: Identifier([Ident("c2")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [] }, TableWithJoins { relation: Table { name: ObjectName([Ident("t4")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: Inner(On(BinaryOp { left: Identifier([Ident("t2"), Ident("c")]), op: Eq, right: Identifier([Ident("t1"), Ident("c")]) })) }, Join { relation: Table { name: ObjectName([Ident("t3")]), alias: None }, join_operator: LeftOuter(Using([Ident("q"), Ident("c")])) }] }], selection: Some(BinaryOp { left: Identifier([Ident("t4"), Ident("c")]), op: Eq, right: Identifier([Ident("t1"), Ident("c")]) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM a NATURAL JOIN (b NATURAL JOIN (c NATURAL JOIN d NATURAL JOIN e)) NATURAL JOIN (f NATURAL JOIN (g NATURAL JOIN h)) ---- SELECT * FROM a NATURAL JOIN (b NATURAL JOIN (c NATURAL JOIN d NATURAL JOIN e)) NATURAL JOIN (f NATURAL JOIN (g NATURAL JOIN h)) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("b")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("c")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("d")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }, Join { relation: Table { name: ObjectName([Ident("e")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }] }), join_operator: Inner(Natural) }] }), join_operator: Inner(Natural) }, Join { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("f")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("g")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("h")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }] }), join_operator: Inner(Natural) }] }), join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), alias: None }, joins: [Join { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("b")]), alias: None }, joins: [Join { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("c")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("d")]), alias: None }, join_operator: Inner(Natural) }, Join { relation: Table { name: ObjectName([Ident("e")]), alias: None }, join_operator: Inner(Natural) }] }), join_operator: Inner(Natural) }] }), join_operator: Inner(Natural) }, Join { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("f")]), alias: None }, joins: [Join { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("g")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("h")]), alias: None }, join_operator: Inner(Natural) }] }), join_operator: Inner(Natural) }] }), join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM (a NATURAL JOIN b) NATURAL JOIN c ---- SELECT * FROM (a NATURAL JOIN b) NATURAL JOIN c => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("b")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }] }), joins: [Join { relation: Table { name: ObjectName([Ident("c")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("b")]), alias: None }, join_operator: Inner(Natural) }] }), joins: [Join { relation: Table { name: ObjectName([Ident("c")]), alias: None }, join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM (((a NATURAL JOIN b))) ---- SELECT * FROM (((a NATURAL JOIN b))) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: NestedJoin(TableWithJoins { relation: NestedJoin(TableWithJoins { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("b")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }] }), joins: [] }), joins: [] }), joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: NestedJoin(TableWithJoins { relation: NestedJoin(TableWithJoins { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("b")]), alias: None }, join_operator: Inner(Natural) }] }), joins: [] }), joins: [] }), joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM a NATURAL JOIN (((b NATURAL JOIN c))) ---- SELECT * FROM a NATURAL JOIN (((b NATURAL JOIN c))) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: NestedJoin(TableWithJoins { relation: NestedJoin(TableWithJoins { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("b")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("c")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Natural) }] }), joins: [] }), joins: [] }), join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), alias: None }, joins: [Join { relation: NestedJoin(TableWithJoins { relation: NestedJoin(TableWithJoins { relation: NestedJoin(TableWithJoins { relation: Table { name: ObjectName([Ident("b")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("c")]), alias: None }, join_operator: Inner(Natural) }] }), joins: [] }), joins: [] }), join_operator: Inner(Natural) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM (a NATURAL JOIN (b)) @@ -544,28 +548,28 @@ SELECT c1 FROM t1 INNER JOIN t2 USING(c1) ---- SELECT c1 FROM t1 JOIN t2 USING(c1) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: Inner(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: Inner(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT c1 FROM t1 LEFT OUTER JOIN t2 USING(c1) ---- SELECT c1 FROM t1 LEFT JOIN t2 USING(c1) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: LeftOuter(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: LeftOuter(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT c1 FROM t1 RIGHT OUTER JOIN t2 USING(c1) ---- SELECT c1 FROM t1 RIGHT JOIN t2 USING(c1) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: RightOuter(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: RightOuter(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT c1 FROM t1 FULL OUTER JOIN t2 USING(c1) ---- SELECT c1 FROM t1 FULL JOIN t2 USING(c1) => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), args: None, alias: None, with_hints: [] }, join_operator: FullOuter(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("c1")]), alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("t1")]), alias: None }, joins: [Join { relation: Table { name: ObjectName([Ident("t2")]), alias: None }, join_operator: FullOuter(Using([Ident("c1")])) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM a OUTER JOIN b ON 1 @@ -584,7 +588,7 @@ SELECT foo + bar FROM a, b ---- WITH a AS (SELECT 1 AS foo), b AS (SELECT 2 AS bar) SELECT foo + bar FROM a, b => -Select { query: Query { ctes: [Cte { alias: TableAlias { name: Ident("a"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("1")), alias: Some(Ident("foo")) }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }, Cte { alias: TableAlias { name: Ident("b"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("2")), alias: Some(Ident("bar")) }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }], body: Select(Select { distinct: false, projection: [Expr { expr: BinaryOp { left: Identifier([Ident("foo")]), op: Plus, right: Identifier([Ident("bar")]) }, alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), args: None, alias: None, with_hints: [] }, joins: [] }, TableWithJoins { relation: Table { name: ObjectName([Ident("b")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [Cte { alias: TableAlias { name: Ident("a"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("1")), alias: Some(Ident("foo")) }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }, Cte { alias: TableAlias { name: Ident("b"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("2")), alias: Some(Ident("bar")) }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }], body: Select(Select { distinct: false, projection: [Expr { expr: BinaryOp { left: Identifier([Ident("foo")]), op: Plus, right: Identifier([Ident("bar")]) }, alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), alias: None }, joins: [] }, TableWithJoins { relation: Table { name: ObjectName([Ident("b")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement CREATE VIEW v AS @@ -595,7 +599,7 @@ CREATE VIEW v AS ---- CREATE VIEW v AS WITH a AS (SELECT 1 AS foo), b AS (SELECT 2 AS bar) SELECT foo + bar FROM a, b => -CreateView { name: ObjectName([Ident("v")]), columns: [], with_options: [], query: Query { ctes: [Cte { alias: TableAlias { name: Ident("a"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("1")), alias: Some(Ident("foo")) }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }, Cte { alias: TableAlias { name: Ident("b"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("2")), alias: Some(Ident("bar")) }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }], body: Select(Select { distinct: false, projection: [Expr { expr: BinaryOp { left: Identifier([Ident("foo")]), op: Plus, right: Identifier([Ident("bar")]) }, alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), args: None, alias: None, with_hints: [] }, joins: [] }, TableWithJoins { relation: Table { name: ObjectName([Ident("b")]), 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("v")]), columns: [], with_options: [], query: Query { ctes: [Cte { alias: TableAlias { name: Ident("a"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("1")), alias: Some(Ident("foo")) }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }, Cte { alias: TableAlias { name: Ident("b"), columns: [], strict: false }, query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Value(Number("2")), alias: Some(Ident("bar")) }], from: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None } }], body: Select(Select { distinct: false, projection: [Expr { expr: BinaryOp { left: Identifier([Ident("foo")]), op: Plus, right: Identifier([Ident("bar")]) }, alias: None }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("a")]), alias: None }, joins: [] }, TableWithJoins { relation: Table { name: ObjectName([Ident("b")]), 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 roundtrip WITH cte (col1, col2) AS (SELECT foo, bar FROM baz) SELECT * FROM cte @@ -676,35 +680,51 @@ SELECT foo FROM bar OFFSET 2 ROWS ---- SELECT foo FROM bar OFFSET 2 ROWS => -Select { 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: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { 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: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS ---- SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS => -Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS ---- SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS => -Select { 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: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { 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: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS ---- SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS => -Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS ---- SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], 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: Some(Value(Number("2"))), fetch: None }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } + +parse-statement +SELECT foo FROM LATERAL bar(1) +---- +SELECT foo FROM bar(1) +=> +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Function { name: ObjectName([Ident("bar")]), args: Args([Value(Number("1"))]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } + +parse-statement +SELECT foo FROM LATERAL bar +---- +error: +Parse error: +SELECT foo FROM LATERAL bar + ^ +Expected (, found: EOF parse-statement SELECT 'foo' OFFSET 0 ROWS @@ -718,49 +738,49 @@ SELECT foo FROM bar OFFSET 2 ---- SELECT foo FROM bar OFFSET 2 ROWS => -Select { 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: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { 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: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ---- SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS => -Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM bar ORDER BY baz OFFSET 2 ---- SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS => -Select { 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: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { 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: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ---- SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS => -Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM (SELECT * FROM bar OFFSET 2) OFFSET 2 ---- SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], 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: Some(Value(Number("2"))), fetch: None }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ---- SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], 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: Some(Value(Number("2"))), fetch: None }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT foo FROM (SELECT * FROM bar OFFSET 2) OFFSET 2 ROWS ---- SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], 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: Some(Value(Number("2"))), fetch: None }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: None }, as_of: None } parse-statement SELECT 'foo' OFFSET 0 @@ -779,7 +799,7 @@ SELECT foo FROM bar FETCH FIRST 2 ROWS ONLY ---- SELECT foo FROM bar FETCH FIRST 2 ROWS ONLY => -Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } +Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } parse-statement SELECT 'foo' FETCH FIRST 2 ROWS ONLY @@ -793,114 +813,112 @@ SELECT foo FROM bar FETCH FIRST ROWS ONLY ---- SELECT foo FROM bar FETCH FIRST ROWS ONLY => -Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: None }) }, as_of: None } +Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: None }) }, as_of: None } parse-statement SELECT foo FROM bar WHERE foo = 4 FETCH FIRST 2 ROWS ONLY ---- SELECT foo FROM bar WHERE foo = 4 FETCH FIRST 2 ROWS ONLY => -Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } +Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } parse-statement SELECT foo FROM bar ORDER BY baz FETCH FIRST 2 ROWS ONLY ---- SELECT foo FROM bar ORDER BY baz FETCH FIRST 2 ROWS ONLY => -Select { 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: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: None, fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } +Select { 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: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: None, fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } parse-statement SELECT foo FROM bar WHERE foo = 4 ORDER BY baz FETCH FIRST 2 ROWS WITH TIES ---- SELECT foo FROM bar WHERE foo = 4 ORDER BY baz FETCH FIRST 2 ROWS WITH TIES => -Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: None, fetch: Some(Fetch { with_ties: true, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } +Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: None, fetch: Some(Fetch { with_ties: true, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } parse-statement SELECT foo FROM bar FETCH FIRST 50 PERCENT ROWS ONLY ---- SELECT foo FROM bar FETCH FIRST 50 PERCENT ROWS ONLY => -Select { 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: Some(Fetch { with_ties: false, percent: true, quantity: Some(Value(Number("50"))) }) }, as_of: None } +Select { 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: Some(Fetch { with_ties: false, percent: true, quantity: Some(Value(Number("50"))) }) }, as_of: None } parse-statement SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY ---- SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY => -Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } +Select { 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: Some(BinaryOp { left: Identifier([Ident("foo")]), op: Eq, right: Value(Number("4")) }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier([Ident("baz")]), asc: None }], limit: None, offset: Some(Value(Number("2"))), fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } parse-statement SELECT foo FROM (SELECT * FROM bar FETCH FIRST 2 ROWS ONLY) FETCH FIRST 2 ROWS ONLY ---- SELECT foo FROM (SELECT * FROM bar FETCH FIRST 2 ROWS ONLY) FETCH FIRST 2 ROWS ONLY => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], 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: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } parse-statement SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY) OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY ---- SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY) OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], 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: Some(Value(Number("2"))), fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Expr { expr: Identifier([Ident("foo")]), alias: None }], from: [TableWithJoins { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("bar")]), alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, alias: None }, joins: [] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: Some(Value(Number("2"))), fetch: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("2"))) }) }, as_of: None } parse-statement SELECT foo FROM bar FETCH FIRST 10 ROW ONLY ---- SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY => -Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("10"))) }) }, as_of: None } +Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("10"))) }) }, as_of: None } parse-statement SELECT foo FROM bar FETCH NEXT 10 ROW ONLY ---- SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY => -Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("10"))) }) }, as_of: None } +Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: Some(Value(Number("10"))) }) }, as_of: None } parse-statement SELECT foo FROM bar FETCH NEXT 10 ROWS WITH TIES ---- SELECT foo FROM bar FETCH FIRST 10 ROWS WITH TIES => -Select { 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: Some(Fetch { with_ties: true, percent: false, quantity: Some(Value(Number("10"))) }) }, as_of: None } +Select { 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: Some(Fetch { with_ties: true, percent: false, quantity: Some(Value(Number("10"))) }) }, as_of: None } parse-statement SELECT foo FROM bar FETCH NEXT ROWS WITH TIES ---- SELECT foo FROM bar FETCH FIRST ROWS WITH TIES => -Select { 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: Some(Fetch { with_ties: true, percent: false, quantity: None }) }, as_of: None } +Select { 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: Some(Fetch { with_ties: true, percent: false, quantity: None }) }, as_of: None } parse-statement SELECT foo FROM bar FETCH FIRST ROWS ONLY ---- SELECT foo FROM bar FETCH FIRST ROWS ONLY => -Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: None }) }, as_of: None } +Select { 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: Some(Fetch { with_ties: false, percent: false, quantity: None }) }, as_of: None } parse-statement SELECT * FROM customer LEFT JOIN (SELECT * FROM "order" WHERE "order".customer = customer.id LIMIT 3) AS "order" ON true ---- SELECT * FROM customer LEFT JOIN (SELECT * FROM "order" WHERE "order".customer = customer.id LIMIT 3) AS "order" ON true => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("order")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("order"), Ident("customer")]), op: Eq, right: Identifier([Ident("customer"), Ident("id")]) }), group_by: [], having: None }), order_by: [], limit: Some(Value(Number("3"))), offset: None, fetch: None }, alias: Some(TableAlias { name: Ident("order"), columns: [], strict: false }) }, join_operator: LeftOuter(On(Value(Boolean(true)))) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [Join { relation: Derived { lateral: false, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("order")]), alias: None }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("order"), Ident("customer")]), op: Eq, right: Identifier([Ident("customer"), Ident("id")]) }), group_by: [], having: None }), order_by: [], limit: Some(Value(Number("3"))), offset: None, fetch: None }, alias: Some(TableAlias { name: Ident("order"), columns: [], strict: false }) }, join_operator: LeftOuter(On(Value(Boolean(true)))) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM customer LEFT JOIN LATERAL (SELECT * FROM "order" WHERE "order".customer = customer.id LIMIT 3) AS "order" ON true ---- SELECT * FROM customer LEFT JOIN LATERAL (SELECT * FROM "order" WHERE "order".customer = customer.id LIMIT 3) AS "order" ON true => -Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), args: None, alias: None, with_hints: [] }, joins: [Join { relation: Derived { lateral: true, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("order")]), args: None, alias: None, with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("order"), Ident("customer")]), op: Eq, right: Identifier([Ident("customer"), Ident("id")]) }), group_by: [], having: None }), order_by: [], limit: Some(Value(Number("3"))), offset: None, fetch: None }, alias: Some(TableAlias { name: Ident("order"), columns: [], strict: false }) }, join_operator: LeftOuter(On(Value(Boolean(true)))) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [Join { relation: Derived { lateral: true, subquery: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("order")]), alias: None }, joins: [] }], selection: Some(BinaryOp { left: Identifier([Ident("order"), Ident("customer")]), op: Eq, right: Identifier([Ident("customer"), Ident("id")]) }), group_by: [], having: None }), order_by: [], limit: Some(Value(Number("3"))), offset: None, fetch: None }, alias: Some(TableAlias { name: Ident("order"), columns: [], strict: false }) }, join_operator: LeftOuter(On(Value(Boolean(true)))) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement -SELECT * FROM customer LEFT JOIN LATERAL generate_series(1, customer.id) +SELECT * FROM customer LEFT JOIN LATERAL generate_series(1, customer.id) ON true ---- -error: -Parse error: -SELECT * FROM customer LEFT JOIN LATERAL generate_series(1, customer.id) - ^^^^^^^^^^^^^^^ -Expected subquery after LATERAL, found: generate_series +SELECT * FROM customer LEFT JOIN generate_series(1, customer.id) ON true +=> +Select { query: Query { ctes: [], body: Select(Select { distinct: false, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: ObjectName([Ident("customer")]), alias: None }, joins: [Join { relation: Function { name: ObjectName([Ident("generate_series")]), args: Args([Value(Number("1")), Identifier([Ident("customer"), Ident("id")])]), alias: None }, join_operator: LeftOuter(On(Value(Boolean(true)))) }] }], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None }, as_of: None } parse-statement SELECT * FROM a LEFT JOIN LATERAL (b CROSS JOIN c) diff --git a/src/sql/src/normalize.rs b/src/sql/src/normalize.rs index fbbbbff4a23c..5328e3c3a06e 100644 --- a/src/sql/src/normalize.rs +++ b/src/sql/src/normalize.rs @@ -117,20 +117,20 @@ pub fn create_statement(scx: &StatementContext, mut stmt: Statement) -> Result { + self.visit_object_name_mut(name); + if let Some(alias) = alias { + self.visit_table_alias_mut(alias); + } + } + TableFactor::Function { + name: _, args, alias, - with_hints, } => { - // Only attempt to resolve the name if it is not a table - // function (i.e., there are no arguments). - if args.is_none() { - self.visit_object_name_mut(name) - } match args { - None | Some(FunctionArgs::Star) => (), - Some(FunctionArgs::Args(args)) => { + FunctionArgs::Star => (), + FunctionArgs::Args(args) => { for expr in args { self.visit_expr_mut(expr); } @@ -139,12 +139,10 @@ pub fn create_statement(scx: &StatementContext, mut stmt: Statement) -> Result visit_mut::visit_table_factor_mut(self, table_factor), } } diff --git a/src/sql/src/plan/decorrelate.rs b/src/sql/src/plan/decorrelate.rs index 3de3938744ad..fc4af55df650 100644 --- a/src/sql/src/plan/decorrelate.rs +++ b/src/sql/src/plan/decorrelate.rs @@ -37,6 +37,7 @@ use std::collections::{BTreeSet, HashMap}; use anyhow::bail; +use itertools::Itertools; use ore::collections::CollectionExt; use repr::RelationType; @@ -80,6 +81,32 @@ impl ColumnMap { fn len(&self) -> usize { self.inner.len() } + + /// Updates references in the `ColumnMap` for use in a nested scope. The + /// provided `arity` must specify the arity of the current scope. + fn enter_scope(&self, arity: usize) -> ColumnMap { + // From the perspective of the nested scope, all existing column + // references will be one level greater. + let existing = self + .inner + .clone() + .into_iter() + .update(|(col, _i)| col.level += 1); + + // All columns in the current scope become explicit entries in the + // immediate parent scope. + let new = (0..arity).map(|i| { + ( + ColumnRef { + level: 1, + column: i, + }, + self.len() + i, + ) + }); + + ColumnMap::new(existing.chain(new).collect()) + } } impl RelationExpr { @@ -165,13 +192,13 @@ impl RelationExpr { } input } - FlatMap { input, func, exprs } => { + CallTable { func, exprs } => { // FlatMap expressions may contain correlated subqueries. Unlike Map they are not // allowed to refer to the results of previous expressions, and we have a simpler // implementation that appends all relevant columns first, then applies the flatmap // operator to the result, then strips off any columns introduce by subqueries. - let mut input = input.applied_to(id_gen, get_outer, col_map); + let mut input = get_outer; let old_arity = input.arity(); let exprs = exprs @@ -209,6 +236,60 @@ impl RelationExpr { } input } + Join { + left, + right, + on, + kind, + } if kind.is_lateral() => { + // A LATERAL join is a join in which the right expression has + // access to the columns in the left expression. It turns out + // this is *exactly* our branch operator, plus some additional + // null handling in the case of left joins. (Right and full + // lateral joins are not permitted.) + // + // As with normal joins, the `on` predicate may be correlated, + // and we treat it as a filter that follows the branch. + + let left = left.applied_to(id_gen, get_outer, col_map); + left.let_in(id_gen, |id_gen, get_left| { + let mut join = branch( + id_gen, + get_left.clone(), + col_map, + *right, + |id_gen, right, get_left, col_map| { + right.applied_to(id_gen, get_left, col_map) + }, + ); + + // Plan the `on` predicate. + let old_arity = join.arity(); + let on = on.applied_to(id_gen, col_map, &mut join); + join = join.filter(vec![on]); + let new_arity = join.arity(); + if old_arity != new_arity { + // This means we added some columns to handle + // subqueries, and now we need to get rid of them. + join = join.project((0..old_arity).collect()); + } + + // If a left join, reintroduce any rows from the left that + // are missing, with nulls filled in for the right columns. + if let JoinKind::LeftOuter { .. } = kind { + let default = join + .typ() + .column_types + .into_iter() + .skip(get_left.arity()) + .map(|typ| (Datum::Null, typ.nullable(true))) + .collect(); + get_left.lookup(id_gen, join, default) + } else { + join + } + }) + } Join { left, right, @@ -257,7 +338,7 @@ impl RelationExpr { } join.let_in(id_gen, |id_gen, get_join| { let mut result = get_join.clone(); - if let JoinKind::LeftOuter | JoinKind::FullOuter = kind { + if let JoinKind::LeftOuter { .. } | JoinKind::FullOuter { .. } = kind { let left_outer = get_left.clone().anti_lookup( id_gen, get_join.clone(), @@ -549,6 +630,65 @@ where // at the least for purposes of understanding. It was difficult for one reader // to understand the required properties of `outer` and `col_map`. + // If the inner expression is sufficiently simple, it is safe to apply it + // *directly* to outer, rather than applying it to the distinctified key + // (see below). + // + // As an example, consider the following two queries: + // + // CREATE TABLE t (a int, b int); + // SELECT a, series FROM t, generate_series(1, t.b) series; + // + // The "simple" path for the `SELECT` yields + // + // %0 = + // | Get t + // | FlatMap generate_series(1, #1) + // + // while the non-simple path yields: + // + // %0 = + // | Get t + // + // %1 = + // | Get t + // | Distinct group=(#1) + // | FlatMap generate_series(1, #0) + // + // %2 = + // | LeftJoin %1 %2 (= #1 #2) + // + // There is a tradeoff here: the simple plan is stateless, but the non- + // simple plan may do (much) less computation if there are only a few + // distinct values of `t.b`. + // + // We apply a very simple heuristic here and take the simple path if `inner` + // contains only maps, filters, projections, and calls to table functions. + // The intuition is that straightforward usage of table functions should + // take the simple path, while everything else should not. (In theory we + // think this transformation is valid as long as `inner` does not contain a + // Reduce, Distinct, or TopK node, but it is not always an optimization in + // the general case.) + // + // TODO(benesch): this should all be handled by a proper optimizer, but + // detecting the moment of decorrelation in the optimizer right now is too + // hard. + let mut is_simple = true; + inner.visit(&mut |expr| match expr { + RelationExpr::Constant { .. } + | RelationExpr::Project { .. } + | RelationExpr::Map { .. } + | RelationExpr::Filter { .. } + | RelationExpr::CallTable { .. } => (), + _ => is_simple = false, + }); + if is_simple { + let new_col_map = col_map.enter_scope(outer.arity() - col_map.len()); + return outer.let_in(id_gen, |id_gen, get_outer| { + apply(id_gen, inner, get_outer, &new_col_map) + }); + } + // The key consists of the columns from the outer expression upon which the // inner relation depends. We discover these dependencies by walking the // inner relation expression and looking for column references whose level diff --git a/src/sql/src/plan/explain.rs b/src/sql/src/plan/explain.rs index 3ff835e286f2..07bcf1a9b266 100644 --- a/src/sql/src/plan/explain.rs +++ b/src/sql/src/plan/explain.rs @@ -133,7 +133,7 @@ impl RelationExpr { Some(parent_expr) => match parent_expr { Project { .. } | Map { .. } - | FlatMap { .. } + | CallTable { .. } | Filter { .. } | Reduce { .. } | TopK { .. } @@ -163,7 +163,7 @@ impl RelationExpr { | TopK { .. } => (), Map { scalars, .. } => scalar_exprs.extend(scalars), Filter { predicates, .. } => scalar_exprs.extend(predicates), - FlatMap { exprs, .. } => scalar_exprs.extend(exprs), + CallTable { exprs, .. } => scalar_exprs.extend(exprs), Join { on, .. } => scalar_exprs.push(on), Reduce { aggregates, .. } => { scalar_exprs.extend(aggregates.iter().map(|a| &*a.expr)) @@ -241,10 +241,10 @@ impl RelationExpr { ) .unwrap(); } - FlatMap { func, exprs, .. } => { + CallTable { func, exprs } => { write!( pretty, - "FlatMap {}({})", + "CallTable {}({})", func, Separated( ", ", @@ -445,8 +445,10 @@ impl std::fmt::Display for JoinKind { f, "{}", match self { - JoinKind::Inner => "Inner", - JoinKind::LeftOuter => "LeftOuter", + JoinKind::Inner { lateral: false } => "Inner", + JoinKind::Inner { lateral: true } => "InnerLateral", + JoinKind::LeftOuter { lateral: false } => "LeftOuter", + JoinKind::LeftOuter { lateral: true } => "LeftOuterLateral", JoinKind::RightOuter => "RightOuter", JoinKind::FullOuter => "FullOuter", } diff --git a/src/sql/src/plan/expr.rs b/src/sql/src/plan/expr.rs index 1eef463da60d..a389bd785915 100644 --- a/src/sql/src/plan/expr.rs +++ b/src/sql/src/plan/expr.rs @@ -12,6 +12,7 @@ //! similar to that file, with some differences which are noted below. It gets turned into that //! representation via a call to decorrelate(). +use std::borrow::Cow; use std::collections::BTreeMap; use std::mem; @@ -54,8 +55,7 @@ pub enum RelationExpr { input: Box, scalars: Vec, }, - FlatMap { - input: Box, + CallTable { func: TableFunc, exprs: Vec, }, @@ -269,12 +269,21 @@ pub struct ColumnRef { #[derive(Debug, Clone, PartialEq, Eq)] pub enum JoinKind { - Inner, - LeftOuter, + Inner { lateral: bool }, + LeftOuter { lateral: bool }, RightOuter, FullOuter, } +impl JoinKind { + pub fn is_lateral(&self) -> bool { + match self { + JoinKind::Inner { lateral } | JoinKind::LeftOuter { lateral } => *lateral, + JoinKind::RightOuter | JoinKind::FullOuter => false, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct AggregateExpr { pub func: AggregateFunc, @@ -307,32 +316,35 @@ impl RelationExpr { } typ } - RelationExpr::FlatMap { - input, - func, - exprs: _, - } => { - let mut typ = input.typ(outers, params); - typ.column_types.extend(func.output_type().column_types); - // FlatMap can add duplicate rows, so input keys are no longer valid - RelationType::new(typ.column_types) - } + RelationExpr::CallTable { func, exprs: _ } => func.output_type(), RelationExpr::Filter { input, .. } | RelationExpr::TopK { input, .. } => { input.typ(outers, params) } RelationExpr::Join { left, right, kind, .. } => { - let left_nullable = *kind == JoinKind::RightOuter || *kind == JoinKind::FullOuter; - let right_nullable = *kind == JoinKind::LeftOuter || *kind == JoinKind::FullOuter; + let left_nullable = matches!(kind, JoinKind::RightOuter | JoinKind::FullOuter); + let right_nullable = + matches!(kind, JoinKind::LeftOuter { .. } | JoinKind::FullOuter); let lt = left.typ(outers, params).column_types.into_iter().map(|t| { let nullable = t.nullable || left_nullable; t.nullable(nullable) }); - let rt = right.typ(outers, params).column_types.into_iter().map(|t| { - let nullable = t.nullable || right_nullable; - t.nullable(nullable) - }); + let outers = if kind.is_lateral() { + let mut outers = outers.to_vec(); + outers.push(RelationType::new(lt.clone().collect())); + Cow::Owned(outers) + } else { + Cow::Borrowed(outers) + }; + let rt = right + .typ(&outers, params) + .column_types + .into_iter() + .map(|t| { + let nullable = t.nullable || right_nullable; + t.nullable(nullable) + }); RelationType::new(lt.chain(rt).collect()) } RelationExpr::Reduce { @@ -378,7 +390,7 @@ impl RelationExpr { RelationExpr::Get { typ, .. } => typ.column_types.len(), RelationExpr::Project { outputs, .. } => outputs.len(), RelationExpr::Map { input, scalars } => input.arity() + scalars.len(), - RelationExpr::FlatMap { input, func, .. } => input.arity() + func.output_arity(), + RelationExpr::CallTable { func, .. } => func.output_arity(), RelationExpr::Filter { input, .. } | RelationExpr::TopK { input, .. } | RelationExpr::Distinct { input } @@ -440,21 +452,6 @@ impl RelationExpr { } } - pub fn product(self, right: Self) -> Self { - if self.is_join_identity() { - right - } else if right.is_join_identity() { - self - } else { - RelationExpr::Join { - left: Box::new(self), - right: Box::new(right), - on: ScalarExpr::literal_true(), - kind: JoinKind::Inner, - } - } - } - pub fn reduce(self, group_key: Vec, aggregates: Vec) -> Self { RelationExpr::Reduce { input: Box::new(self), @@ -536,16 +533,15 @@ impl RelationExpr { F: FnMut(&'a Self), { match self { - RelationExpr::Constant { .. } | RelationExpr::Get { .. } => (), + RelationExpr::Constant { .. } + | RelationExpr::Get { .. } + | RelationExpr::CallTable { .. } => (), RelationExpr::Project { input, .. } => { f(input); } RelationExpr::Map { input, .. } => { f(input); } - RelationExpr::FlatMap { input, .. } => { - f(input); - } RelationExpr::Filter { input, .. } => { f(input); } @@ -588,16 +584,15 @@ impl RelationExpr { F: FnMut(&'a mut Self), { match self { - RelationExpr::Constant { .. } | RelationExpr::Get { .. } => (), + RelationExpr::Constant { .. } + | RelationExpr::Get { .. } + | RelationExpr::CallTable { .. } => (), RelationExpr::Project { input, .. } => { f(input); } RelationExpr::Map { input, .. } => { f(input); } - RelationExpr::FlatMap { input, .. } => { - f(input); - } RelationExpr::Filter { input, .. } => { f(input); } @@ -636,37 +631,56 @@ impl RelationExpr { where F: FnMut(usize, &mut ColumnRef), { - self.visit_mut(&mut |e| match e { - RelationExpr::Join { on, .. } => on.visit_columns(depth, f), - RelationExpr::Map { scalars, .. } => { + match self { + RelationExpr::Join { + kind, + on, + left, + right, + } => { + left.visit_columns(depth, f); + let depth = if kind.is_lateral() { depth + 1 } else { depth }; + right.visit_columns(depth, f); + on.visit_columns(depth, f); + } + RelationExpr::Map { scalars, input } => { for scalar in scalars { scalar.visit_columns(depth, f); } + input.visit_columns(depth, f); } - RelationExpr::FlatMap { exprs, .. } => { + RelationExpr::CallTable { exprs, .. } => { for expr in exprs { expr.visit_columns(depth, f); } } - RelationExpr::Filter { predicates, .. } => { + RelationExpr::Filter { predicates, input } => { for predicate in predicates { predicate.visit_columns(depth, f); } + input.visit_columns(depth, f); } - RelationExpr::Reduce { aggregates, .. } => { + RelationExpr::Reduce { + aggregates, input, .. + } => { for aggregate in aggregates { aggregate.visit_columns(depth, f); } + input.visit_columns(depth, f); } - RelationExpr::Constant { .. } - | RelationExpr::Get { .. } - | RelationExpr::Project { .. } - | RelationExpr::Distinct { .. } - | RelationExpr::TopK { .. } - | RelationExpr::Negate { .. } - | RelationExpr::Threshold { .. } - | RelationExpr::Union { .. } => (), - }) + RelationExpr::Union { left, right } => { + left.visit_columns(depth, f); + right.visit_columns(depth, f); + } + RelationExpr::Project { input, .. } + | RelationExpr::Distinct { input } + | RelationExpr::TopK { input, .. } + | RelationExpr::Negate { input } + | RelationExpr::Threshold { input } => { + input.visit_columns(depth, f); + } + RelationExpr::Constant { .. } | RelationExpr::Get { .. } => (), + } } /// Replaces any parameter references in the expression with the @@ -679,7 +693,7 @@ impl RelationExpr { scalar.bind_parameters(parameters); } } - RelationExpr::FlatMap { exprs, .. } => { + RelationExpr::CallTable { exprs, .. } => { for expr in exprs { expr.bind_parameters(parameters); } diff --git a/src/sql/src/plan/query.rs b/src/sql/src/plan/query.rs index 200435a3ef00..895a64750601 100644 --- a/src/sql/src/plan/query.rs +++ b/src/sql/src/plan/query.rs @@ -915,76 +915,78 @@ fn plan_table_with_joins<'a>( Ok((left, left_scope)) } -fn plan_table_factor<'a>( +fn plan_table_factor( qcx: &QueryContext, left: RelationExpr, left_scope: Scope, join_operator: &JoinOperator, - table_factor: &'a TableFactor, + table_factor: &TableFactor, ) -> Result<(RelationExpr, Scope), anyhow::Error> { - match table_factor { - TableFactor::Table { - name, - alias, - args, - with_hints, - } => { - if !with_hints.is_empty() { - unsupported!("WITH hints"); - } - if let Some(args) = args { - let ecx = &ExprContext { - qcx, - name: "FROM table function", - scope: &left_scope, - relation_type: &qcx.relation_type(&left), - allow_aggregates: false, - allow_subqueries: true, - }; - plan_table_function(ecx, left, &name, alias.as_ref(), args) - } else { - let name = qcx.scx.resolve_item(name.clone())?; - let item = qcx.scx.catalog.get_item(&name); - let expr = RelationExpr::Get { - id: Id::Global(item.id()), - typ: item.desc()?.typ().clone(), - }; - let column_names = item.desc()?.iter_names().map(|n| n.cloned()).collect(); - let scope = plan_table_alias(qcx, alias.as_ref(), Some(name.into()), column_names)?; - plan_join_operator(qcx, &join_operator, left, left_scope, expr, scope) - } + let lateral = matches!( + table_factor, + TableFactor::Function { .. } | TableFactor::Derived { lateral: true, .. } + ); + + let qcx = if lateral { + Cow::Owned(qcx.derived_context(left_scope.clone(), &qcx.relation_type(&left))) + } else { + Cow::Borrowed(qcx) + }; + + let (expr, scope) = match table_factor { + TableFactor::Table { name, alias } => { + let name = qcx.scx.resolve_item(name.clone())?; + let item = qcx.scx.catalog.get_item(&name); + let expr = RelationExpr::Get { + id: Id::Global(item.id()), + typ: item.desc()?.typ().clone(), + }; + let column_names = item.desc()?.iter_names().map(|n| n.cloned()).collect(); + let scope = plan_table_alias(&qcx, alias.as_ref(), Some(name.into()), column_names)?; + (expr, scope) } + + TableFactor::Function { name, args, alias } => { + let ecx = &ExprContext { + qcx: &qcx, + name: "FROM table function", + scope: &Scope::empty(Some(qcx.outer_scope.clone())), + relation_type: &RelationType::empty(), + allow_aggregates: false, + allow_subqueries: true, + }; + plan_table_function(ecx, &name, alias.as_ref(), args)? + } + TableFactor::Derived { - lateral, + lateral: _, subquery, alias, } => { - if *lateral { - unsupported!(3111, "LATERAL derived tables"); - } let (expr, scope) = plan_subquery(&qcx, &subquery)?; let table_name = None; let column_names = scope.column_names().map(|n| n.cloned()).collect(); - let scope = plan_table_alias(qcx, alias.as_ref(), table_name, column_names)?; - plan_join_operator(qcx, &join_operator, left, left_scope, expr, scope) + let scope = plan_table_alias(&qcx, alias.as_ref(), table_name, column_names)?; + (expr, scope) } + TableFactor::NestedJoin(table_with_joins) => { - let (identity, identity_scope) = plan_join_identity(qcx); - let (expr, scope) = plan_table_with_joins( - qcx, + let (identity, identity_scope) = plan_join_identity(&qcx); + plan_table_with_joins( + &qcx, identity, identity_scope, &JoinOperator::CrossJoin, table_with_joins, - )?; - plan_join_operator(qcx, &join_operator, left, left_scope, expr, scope) + )? } - } + }; + + plan_join_operator(&qcx, &join_operator, left, left_scope, expr, scope, lateral) } fn plan_table_function( ecx: &ExprContext, - left: RelationExpr, name: &ObjectName, alias: Option<&TableAlias>, args: &FunctionArgs, @@ -995,8 +997,7 @@ fn plan_table_function( FunctionArgs::Args(args) => args, }; let tf = func::select_table_func(ecx, &*ident, args)?; - let call = RelationExpr::FlatMap { - input: Box::new(left), + let call = RelationExpr::CallTable { func: tf.func, exprs: tf.exprs, }; @@ -1006,7 +1007,7 @@ fn plan_table_function( item: ident, }; let scope = plan_table_alias(ecx.qcx, alias, Some(name), tf.column_names)?; - Ok((call, ecx.scope.clone().product(scope))) + Ok((call, scope)) } fn plan_table_alias( @@ -1169,6 +1170,7 @@ fn plan_join_operator( left_scope: Scope, right: RelationExpr, right_scope: Scope, + lateral: bool, ) -> Result<(RelationExpr, Scope), anyhow::Error> { match operator { JoinOperator::Inner(constraint) => plan_join_constraint( @@ -1178,7 +1180,7 @@ fn plan_join_operator( left_scope, right, right_scope, - JoinKind::Inner, + JoinKind::Inner { lateral }, ), JoinOperator::LeftOuter(constraint) => plan_join_constraint( qcx, @@ -1187,32 +1189,51 @@ fn plan_join_operator( left_scope, right, right_scope, - JoinKind::LeftOuter, + JoinKind::LeftOuter { lateral }, ), - JoinOperator::RightOuter(constraint) => plan_join_constraint( - qcx, - &constraint, - left, - left_scope, - right, - right_scope, - JoinKind::RightOuter, - ), - JoinOperator::FullOuter(constraint) => plan_join_constraint( - qcx, - &constraint, - left, - left_scope, - right, - right_scope, - JoinKind::FullOuter, - ), - JoinOperator::CrossJoin => Ok((left.product(right), left_scope.product(right_scope))), - // The remaining join types are MSSQL-specific. We are unlikely to - // ever support them. The standard SQL equivalent is LATERAL, which - // we are not capable of even parsing at the moment. - JoinOperator::CrossApply => unsupported!("CROSS APPLY"), - JoinOperator::OuterApply => unsupported!("OUTER APPLY"), + JoinOperator::RightOuter(constraint) => { + if lateral { + bail!("the combining JOIN type must be INNER or LEFT for a LATERAL reference"); + } + plan_join_constraint( + qcx, + &constraint, + left, + left_scope, + right, + right_scope, + JoinKind::RightOuter, + ) + } + JoinOperator::FullOuter(constraint) => { + if lateral { + bail!("the combining JOIN type must be INNER or LEFT for a LATERAL reference"); + } + plan_join_constraint( + qcx, + &constraint, + left, + left_scope, + right, + right_scope, + JoinKind::FullOuter, + ) + } + JoinOperator::CrossJoin => { + let join = if left.is_join_identity() { + right + } else if right.is_join_identity() { + left + } else { + RelationExpr::Join { + left: Box::new(left), + right: Box::new(right), + on: ScalarExpr::literal_true(), + kind: JoinKind::Inner { lateral }, + } + }; + Ok((join, left_scope.product(right_scope))) + } } } @@ -1244,7 +1265,7 @@ fn plan_join_constraint<'a>( allow_subqueries: true, }; let on = plan_expr(ecx, expr)?.type_as(ecx, ScalarType::Bool)?; - if kind == JoinKind::Inner { + if let JoinKind::Inner { .. } = kind { for (l, r) in find_trivial_column_equivalences(&on) { // When we can statically prove that two columns are // equivalent after a join, the right column becomes @@ -2105,7 +2126,7 @@ pub enum QueryLifetime { } /// The state required when planning a `Query`. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct QueryContext<'a> { /// The context for the containing `Statement`. pub scx: &'a StatementContext<'a>, @@ -2138,6 +2159,21 @@ impl<'a> QueryContext<'a> { fn relation_type(&self, expr: &RelationExpr) -> RelationType { expr.typ(&self.outer_relation_types, &self.param_types.borrow()) } + + fn derived_context(&self, scope: Scope, relation_type: &RelationType) -> QueryContext<'a> { + QueryContext { + scx: self.scx, + lifetime: self.lifetime, + outer_scope: scope, + outer_relation_types: self + .outer_relation_types + .iter() + .chain(std::iter::once(relation_type)) + .cloned() + .collect(), + param_types: self.param_types.clone(), + } + } } /// A bundle of unrelated things that we need for planning `Expr`s. @@ -2181,18 +2217,7 @@ impl<'a> ExprContext<'a> { } fn derived_query_context(&self) -> QueryContext { - QueryContext { - scx: self.qcx.scx, - lifetime: self.qcx.lifetime, - outer_scope: self.scope.clone(), - outer_relation_types: self - .qcx - .outer_relation_types - .iter() - .chain(std::iter::once(self.relation_type)) - .cloned() - .collect(), - param_types: self.qcx.param_types.clone(), - } + self.qcx + .derived_context(self.scope.clone(), self.relation_type) } } diff --git a/src/sql/src/plan/scope.rs b/src/sql/src/plan/scope.rs index aefa2b80848e..62324894db9f 100644 --- a/src/sql/src/plan/scope.rs +++ b/src/sql/src/plan/scope.rs @@ -266,7 +266,6 @@ impl Scope { } pub fn product(self, right: Self) -> Self { - assert!(self.outer_scope == right.outer_scope); Scope { items: self .items diff --git a/src/sql/src/plan/transform_expr.rs b/src/sql/src/plan/transform_expr.rs index 408e2b4de892..04b7eb234b7f 100644 --- a/src/sql/src/plan/transform_expr.rs +++ b/src/sql/src/plan/transform_expr.rs @@ -58,7 +58,7 @@ pub fn split_subquery_predicates(expr: &mut RelationExpr) { walk_scalar(scalar); } } - RelationExpr::FlatMap { exprs, .. } => { + RelationExpr::CallTable { exprs, .. } => { for expr in exprs { walk_scalar(expr); } @@ -155,12 +155,7 @@ pub fn split_subquery_predicates(expr: &mut RelationExpr) { pub fn try_simplify_quantified_comparisons(expr: &mut RelationExpr) { fn walk_relation(expr: &mut RelationExpr, outers: &[RelationType]) { expr.visit_mut(&mut |expr| match expr { - RelationExpr::Map { scalars, input } - | RelationExpr::FlatMap { - exprs: scalars, - input, - .. - } => { + RelationExpr::Map { scalars, input } => { let mut outers = outers.to_vec(); outers.push(input.typ(&outers, &NO_PARAMS)); for scalar in scalars { @@ -174,6 +169,11 @@ pub fn try_simplify_quantified_comparisons(expr: &mut RelationExpr) { walk_scalar(pred, &outers, true); } } + RelationExpr::CallTable { exprs, .. } => { + for scalar in exprs { + walk_scalar(scalar, &outers, false); + } + } _ => (), }) } diff --git a/test/sqllogictest/cockroach/subquery_correlated.slt b/test/sqllogictest/cockroach/subquery_correlated.slt index 087564966faf..dc37d0595aac 100644 --- a/test/sqllogictest/cockroach/subquery_correlated.slt +++ b/test/sqllogictest/cockroach/subquery_correlated.slt @@ -878,23 +878,38 @@ SELECT (SELECT string_agg(DISTINCT ship, ', ') WHERE o_c_id=c.c_id) FROM c ORDER BY c_id -# #3111 -statement error supported +query ITI SELECT * FROM - (SELECT c_id AS c_c_id, bill FROM c), - LATERAL (SELECT row_number() OVER () AS rownum FROM o WHERE c_id = c_c_id) -ORDER BY c_c_id, bill, rownum + (SELECT c_id AS c_c_id, bill FROM c) s1, + LATERAL (SELECT o_id FROM o WHERE c_id = c_c_id) s2 +ORDER BY c_c_id, bill, o_id +---- +1 CA 10 +1 CA 20 +1 CA 30 +2 TX 40 +2 TX 50 +2 TX 60 +4 TX 70 +4 TX 80 +6 FL 90 -# #3111 -statement error supported +query TI SELECT * FROM - (SELECT bill FROM c), - LATERAL (SELECT row_number() OVER (PARTITION BY bill) AS rownum FROM o WHERE ship = bill) -ORDER BY bill, rownum + (SELECT bill FROM c) s1, + LATERAL (SELECT o_id FROM o WHERE ship = bill) s2 +ORDER BY bill, o_id +---- +CA 10 +CA 20 +CA 30 +CA 40 +TX 50 +TX 50 # ------------------------------------------------------------------------------ # Subqueries in other interesting locations. diff --git a/test/sqllogictest/postgres/join-lateral.slt b/test/sqllogictest/postgres/join-lateral.slt new file mode 100644 index 000000000000..2d13e2947158 --- /dev/null +++ b/test/sqllogictest/postgres/join-lateral.slt @@ -0,0 +1,648 @@ +# Copyright 1994, Regents of the University of California. +# Copyright 1996-2019 PostgreSQL Global Development Group. +# Copyright Materialize, Inc. All rights reserved. +# +# Use of this software is governed by the Business Source License +# included in the LICENSE file at the root of this repository. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0. +# +# This file is derived from the regression test suite in PostgreSQL. +# The original file was retrieved on July 23, 2020 from: +# +# https://github.com/postgres/postgres/blob/5940ffb221316ab73e6fdc780dfe9a07d4221ebb/src/test/regress/expected/join.out +# +# The original source code is subject to the terms of the PostgreSQL +# license, a copy of which can be found in the LICENSE file at the +# root of this repository. + +mode cockroach + +statement ok +CREATE TABLE int2_tbl (f1 smallint) + +statement ok +INSERT INTO int2_tbl (f1) VALUES (0), (1234), (-1234), (32767), (-32767); + +statement ok +CREATE TABLE int4_tbl (f1 int) + +statement ok +INSERT INTO int4_tbl (f1) VALUES (0), (123456), (-123456), (2147483647), (-2147483647) + +statement ok +CREATE TABLE int8_tbl (q1 bigint, q2 bigint) + +statement ok +INSERT INTO int8_tbl VALUES + (123, 456), + (123, 4567890123456789), + (4567890123456789, 123), + (4567890123456789, 4567890123456789), + (4567890123456789, -4567890123456789) + +statement ok +CREATE TABLE tenk1 ( + unique1 int, + unique2 int, + two int, +) + +statement ok +INSERT INTO tenk1 VALUES + (2, 0, 0), + (1, 3, 1), + (6, 9, 0), + (5, 8, 0), + (0, 4, 0), + (7, 7, 1), + (9, 2, 1), + (4, 6, 1), + (8, 1, 0), + (3, 5, 1) + +# NOTE(benesch): The tenk1 table is named as such because it is meant to contain +# 10k rows. We include only 10 rows here because 10k rows causes Materialize to +# absolutely fall over since the plans for a lot of these lateral joins are +# absolutely horrible. + +# statement ok +# CREATE TABLE onerow () + +# statement ok +# INSERT INTO onerow DEFAULT VALUES + +query II colnames +select unique2, x.* +from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x; +---- +unique2 f1 +4 0 + +query II colnames +select unique2, x.* +from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss; +---- +unique2 f1 +4 0 + +query II colnames,rowsort +select unique2, x.* +from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on true; +---- +unique2 f1 +4 0 +NULL -123456 +NULL -2147483647 +NULL 123456 +NULL 2147483647 + +# check scoping of lateral versus parent references +# the first of these should return int8_tbl.q2, the second int8_tbl.q1 +# TODO(benesch): col name should be "r", not "?column?" +query III colnames,rowsort +select *, (select r from (select q1 as q2) x, (select q2 as r) y) from int8_tbl +---- +q1 q2 ?column? +123 456 456 +123 4567890123456789 4567890123456789 +4567890123456789 123 123 +4567890123456789 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 -4567890123456789 + +# TODO(benesch): col name should be "r", not "?column?" +query III colnames,rowsort +select *, (select r from (select q1 as q2) x, lateral (select q2 as r) y) from int8_tbl; +---- +q1 q2 ?column? +123 456 123 +123 4567890123456789 123 +4567890123456789 123 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 4567890123456789 + +# lateral with function in FROM +query I colnames +select count(*) from tenk1 a, lateral generate_series(1,two) g; +---- +count +5 + +query III colnames,rowsort +select * from generate_series(100,200) g (g), + lateral (select * from int8_tbl a where g = q1 union all + select * from int8_tbl b where g = q2) ss +---- +g q1 q2 +123 123 456 +123 123 4567890123456789 +123 4567890123456789 123 + +query I colnames +select count(*) from tenk1 a, + tenk1 b join lateral (values(a.unique1)) ss(x) on b.unique2 = ss.x; +---- +count +10 + +query I colnames +select count(*) from tenk1 a, + tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x; +---- +count +10 + +# lateral injecting a strange outer join condition +query IIIII colnames +select * from int8_tbl a, + int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z) + on x.q2 = ss.z + order by a.q1, a.q2, x.q1, x.q2, ss.z; +---- +q1 q2 q1 q2 z +123 456 123 456 NULL +123 456 123 4567890123456789 NULL +123 456 4567890123456789 -4567890123456789 NULL +123 456 4567890123456789 123 123 +123 456 4567890123456789 123 123 +123 456 4567890123456789 123 123 +123 456 4567890123456789 123 123 +123 456 4567890123456789 123 123 +123 456 4567890123456789 4567890123456789 NULL +123 4567890123456789 123 456 NULL +123 4567890123456789 123 4567890123456789 NULL +123 4567890123456789 4567890123456789 -4567890123456789 NULL +123 4567890123456789 4567890123456789 123 123 +123 4567890123456789 4567890123456789 123 123 +123 4567890123456789 4567890123456789 123 123 +123 4567890123456789 4567890123456789 123 123 +123 4567890123456789 4567890123456789 123 123 +123 4567890123456789 4567890123456789 4567890123456789 NULL +4567890123456789 -4567890123456789 123 456 NULL +4567890123456789 -4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 4567890123456789 -4567890123456789 NULL +4567890123456789 -4567890123456789 4567890123456789 123 NULL +4567890123456789 -4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 123 123 456 NULL +4567890123456789 123 123 4567890123456789 4567890123456789 +4567890123456789 123 123 4567890123456789 4567890123456789 +4567890123456789 123 123 4567890123456789 4567890123456789 +4567890123456789 123 123 4567890123456789 4567890123456789 +4567890123456789 123 123 4567890123456789 4567890123456789 +4567890123456789 123 4567890123456789 -4567890123456789 NULL +4567890123456789 123 4567890123456789 123 NULL +4567890123456789 123 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 123 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 123 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 123 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 123 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 123 456 NULL +4567890123456789 4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 123 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 -4567890123456789 NULL +4567890123456789 4567890123456789 4567890123456789 123 NULL +4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 + +# lateral reference to a join alias variable +query III colnames +select * from (select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1, + lateral (select x) ss2(y); +---- +x f1 y +0 0 0 + +query III colnames,rowsort +select * from (select f1 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1, + lateral (values(x)) ss2(y); +---- +x f1 y + 0 0 0 + 123456 123456 123456 + -123456 -123456 -123456 + 2147483647 2147483647 2147483647 +-2147483647 -2147483647 -2147483647 + +# TODO(benesch): parse failure. +# +# query III colnames +# select * from ((select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1) j, +# lateral (select x) ss2(y); +# ---- +# x f1 y +# 0 0 0 + +# lateral references requiring pullup +query II rowsort +select * from (values(1)) x(lb), + lateral generate_series(lb,4) x4; +---- +1 1 +1 2 +1 3 +1 4 + +query II rowsort +select * from (select f1/1000000000 from int4_tbl) x(lb), + lateral generate_series(lb,4) x4; +---- +-2 0 +-2 1 +-2 2 +-2 3 +-2 4 +-2 -1 +-2 -2 +0 0 +0 0 +0 0 +0 1 +0 1 +0 1 +0 2 +0 2 +0 2 +0 3 +0 3 +0 3 +0 4 +0 4 +0 4 +2 2 +2 3 +2 4 + +query II colnames +select * from (values(1)) x(lb), + lateral (values(lb)) y(lbcopy) +---- +lb lbcopy +1 1 + +query II colnames +select * from (values(1)) x(lb), + lateral (select lb from int4_tbl) y(lbcopy); +---- +lb lbcopy +1 1 +1 1 +1 1 +1 1 +1 1 + +query IIIIIII colnames,rowsort +select * from + int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, + lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2); +---- +q1 q2 q1 q2 xq1 yq1 yq2 +4567890123456789 -4567890123456789 NULL NULL 4567890123456789 NULL NULL +4567890123456789 123 123 456 4567890123456789 123 456 +4567890123456789 123 123 4567890123456789 4567890123456789 123 4567890123456789 +123 456 NULL NULL 123 NULL NULL +123 4567890123456789 4567890123456789 123 123 4567890123456789 123 +123 4567890123456789 4567890123456789 4567890123456789 123 4567890123456789 4567890123456789 +123 4567890123456789 4567890123456789 -4567890123456789 123 4567890123456789 -4567890123456789 +4567890123456789 4567890123456789 4567890123456789 123 4567890123456789 4567890123456789 123 +4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 -4567890123456789 4567890123456789 4567890123456789 -4567890123456789 + +query IIIIIII colnames,rowsort +select * from + int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, + lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2); +---- +q1 q2 q1 q2 xq1 yq1 yq2 +4567890123456789 -4567890123456789 NULL NULL 4567890123456789 NULL NULL +4567890123456789 123 123 456 4567890123456789 123 456 +4567890123456789 123 123 4567890123456789 4567890123456789 123 4567890123456789 +123 456 NULL NULL 123 NULL NULL +123 4567890123456789 4567890123456789 123 123 4567890123456789 123 +123 4567890123456789 4567890123456789 4567890123456789 123 4567890123456789 4567890123456789 +123 4567890123456789 4567890123456789 -4567890123456789 123 4567890123456789 -4567890123456789 +4567890123456789 4567890123456789 4567890123456789 123 4567890123456789 4567890123456789 123 +4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 -4567890123456789 4567890123456789 4567890123456789 -4567890123456789 + +query II colnames,rowsort +select x.* from + int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, + lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2); +---- +q1 q2 +123 456 +123 4567890123456789 +123 4567890123456789 +123 4567890123456789 +4567890123456789 123 +4567890123456789 123 +4567890123456789 4567890123456789 +4567890123456789 4567890123456789 +4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 + +query II colnames,rowsort +select v.* from + (int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1) + left join int4_tbl z on z.f1 = x.q2, + lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy); +---- +vx vy +123 NULL +456 NULL +123 4567890123456789 +4567890123456789 -4567890123456789 +123 4567890123456789 +4567890123456789 4567890123456789 +123 4567890123456789 +4567890123456789 123 +4567890123456789 123 +123 4567890123456789 +4567890123456789 123 +123 456 +4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 +4567890123456789 4567890123456789 +4567890123456789 4567890123456789 +4567890123456789 4567890123456789 +4567890123456789 123 +4567890123456789 NULL +-4567890123456789 NULL + +query II colnames,rowsort +select v.* from + (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) + left join int4_tbl z on z.f1 = x.q2, + lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy); +---- +vx vy +4567890123456789 NULL +-4567890123456789 NULL +4567890123456789 123 +123 456 +4567890123456789 123 +123 4567890123456789 +123 NULL +456 NULL +123 4567890123456789 +4567890123456789 123 +123 4567890123456789 +4567890123456789 4567890123456789 +123 4567890123456789 +4567890123456789 -4567890123456789 +4567890123456789 4567890123456789 +4567890123456789 123 +4567890123456789 4567890123456789 +4567890123456789 4567890123456789 +4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 + +# TODO(benesch): cannot construct onerow table. +# +# query II +# select v.* from +# (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) +# left join int4_tbl z on z.f1 = x.q2, +# lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy); +# ---- +# vx vy +# 4567890123456789 123 +# 123 456 +# 4567890123456789 123 +# 123 4567890123456789 +# 4567890123456789 4567890123456789 +# 4567890123456789 123 +# 123 4567890123456789 +# 4567890123456789 123 +# 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 +# 123 4567890123456789 +# 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 +# 4567890123456789 -4567890123456789 +# 123 4567890123456789 +# 4567890123456789 -4567890123456789 +# 123 NULL +# 456 NULL +# 4567890123456789 NULL +# -4567890123456789 NULL + +query IIIII rowsort +select * from + int8_tbl a left join + lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1; +---- +123 456 NULL NULL NULL +123 4567890123456789 4567890123456789 -4567890123456789 4567890123456789 +123 4567890123456789 4567890123456789 123 4567890123456789 +123 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 NULL NULL NULL +4567890123456789 123 123 456 123 +4567890123456789 123 123 4567890123456789 123 +4567890123456789 4567890123456789 4567890123456789 -4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 123 4567890123456789 + +query IIIII colnames,rowsort +select * from + int8_tbl a left join + lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1; +---- +q1 q2 q1 q2 x +123 456 NULL NULL NULL +123 4567890123456789 4567890123456789 123 4567890123456789 +123 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +123 4567890123456789 4567890123456789 -4567890123456789 4567890123456789 +4567890123456789 123 123 456 123 +4567890123456789 123 123 4567890123456789 123 +4567890123456789 4567890123456789 4567890123456789 123 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +4567890123456789 4567890123456789 4567890123456789 -4567890123456789 4567890123456789 +4567890123456789 -4567890123456789 NULL NULL NULL + +# lateral can result in join conditions appearing below their +# real semantic level +query II colnames,rowsort +select * from int4_tbl i left join + lateral (select * from int2_tbl j where i.f1 = j.f1) k on true; +---- +f1 f1 +0 0 +123456 NULL +-123456 NULL +2147483647 NULL +-2147483647 NULL + +# TODO(benesch): correlated table reference. +# +# query II colnames,rowsort +# select * from int4_tbl i left join +# lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true; +# ---- +# f1 coalesce +# 0 (0) +# 123456 NULL +# -123456 NULL +# 2147483647 NULL +# -2147483647 NULL + +query IIII colnames,rowsort +select * from int4_tbl a, + lateral ( + select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2) + ) ss; +---- +f1 f1 q1 q2 +0 0 NULL NULL +0 123456 NULL NULL +0 -123456 NULL NULL +0 2147483647 NULL NULL +0 -2147483647 NULL NULL +123456 0 NULL NULL +123456 123456 NULL NULL +123456 -123456 NULL NULL +123456 2147483647 NULL NULL +123456 -2147483647 NULL NULL +-123456 0 NULL NULL +-123456 123456 NULL NULL +-123456 -123456 NULL NULL +-123456 2147483647 NULL NULL +-123456 -2147483647 NULL NULL +2147483647 0 NULL NULL +2147483647 123456 NULL NULL +2147483647 -123456 NULL NULL +2147483647 2147483647 NULL NULL +2147483647 -2147483647 NULL NULL +-2147483647 0 NULL NULL +-2147483647 123456 NULL NULL +-2147483647 -123456 NULL NULL +-2147483647 2147483647 NULL NULL +-2147483647 -2147483647 NULL NULL + +# TODO(benesch): least function is unsupported. +# +# lateral reference in a PlaceHolderVar evaluated at join level +# query IIIII colnames,rowsort +# select * from +# int8_tbl a left join lateral +# (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from +# int8_tbl b cross join int8_tbl c) ss +# on a.q2 = ss.bq1; +# ---- +# q1 q2 bq1 cq1 least +# 123 456 NULL NULL NULL +# 123 4567890123456789 4567890123456789 123 123 +# 123 4567890123456789 4567890123456789 123 123 +# 123 4567890123456789 4567890123456789 123 123 +# 123 4567890123456789 4567890123456789 123 123 +# 123 4567890123456789 4567890123456789 123 123 +# 123 4567890123456789 4567890123456789 123 123 +# 123 4567890123456789 4567890123456789 4567890123456789 123 +# 123 4567890123456789 4567890123456789 4567890123456789 123 +# 123 4567890123456789 4567890123456789 4567890123456789 123 +# 123 4567890123456789 4567890123456789 4567890123456789 123 +# 123 4567890123456789 4567890123456789 4567890123456789 123 +# 123 4567890123456789 4567890123456789 4567890123456789 123 +# 123 4567890123456789 4567890123456789 4567890123456789 123 +# 123 4567890123456789 4567890123456789 4567890123456789 123 +# 123 4567890123456789 4567890123456789 4567890123456789 123 +# 4567890123456789 123 123 123 123 +# 4567890123456789 123 123 123 123 +# 4567890123456789 123 123 123 123 +# 4567890123456789 123 123 123 123 +# 4567890123456789 123 123 4567890123456789 123 +# 4567890123456789 123 123 4567890123456789 123 +# 4567890123456789 123 123 4567890123456789 123 +# 4567890123456789 123 123 4567890123456789 123 +# 4567890123456789 123 123 4567890123456789 123 +# 4567890123456789 123 123 4567890123456789 123 +# 4567890123456789 4567890123456789 4567890123456789 123 123 +# 4567890123456789 4567890123456789 4567890123456789 123 123 +# 4567890123456789 4567890123456789 4567890123456789 123 123 +# 4567890123456789 4567890123456789 4567890123456789 123 123 +# 4567890123456789 4567890123456789 4567890123456789 123 123 +# 4567890123456789 4567890123456789 4567890123456789 123 123 +# 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +# 4567890123456789 4567890123456789 4567890123456789 4567890123456789 4567890123456789 +# 4567890123456789 -4567890123456789 NULL NULL NULL + +# check handling of nested appendrels inside LATERAL +query II colnames,rowsort +select * from + ((select 2 as v) union all (select 3 as v)) as q1 + cross join lateral + ((select * from + ((select 4 as v) union all (select 5 as v)) as q3) + union all + (select q1.v) + ) as q2; +---- +v v +2 4 +2 5 +2 2 +3 4 +3 5 +3 3 + +# check we don't try to do a unique-ified semijoin with LATERAL +query III colnames +select * from + (values (0,4), (1,1000)) v(id,x), + lateral (select f1 from int4_tbl + where f1 = any (select unique1 from tenk1 + where unique2 = v.x offset 0)) ss; +---- +id x f1 +0 4 0 + +query error column "f1" does not exist +select f1,g from int4_tbl a, (select f1 as g) ss; + +query error column "a.f1" does not exist +select f1,g from int4_tbl a, (select a.f1 as g) ss; + +query error column "f1" does not exist +select f1,g from int4_tbl a cross join (select f1 as g) ss; + +query error column "a.f1" does not exist +select f1,g from int4_tbl a cross join (select a.f1 as g) ss; + +# SQL:2008 says the left table is in scope but illegal to access here +query error the combining JOIN type must be INNER or LEFT for a LATERAL reference +select f1,g from int4_tbl a right join lateral generate_series(0, a.f1) g on true; + +query error the combining JOIN type must be INNER or LEFT for a LATERAL reference +select f1,g from int4_tbl a full join lateral generate_series(0, a.f1) g on true; + +# check we complain about ambiguous table references +# query error table reference "x" is ambiguous +# select * from +# int8_tbl x cross join (int4_tbl x cross join lateral (select x.f1) ss); + +# LATERAL can be used to put an aggregate into the FROM clause of its query +# query error aggregate functions are not allowed in FROM clause of their own query level +# select 1 from tenk1 a, lateral (select max(a.unique1) from int4_tbl b) ss; diff --git a/test/sqllogictest/table_func.slt b/test/sqllogictest/table_func.slt index 7b1b5a5a59c0..e61916db315a 100644 --- a/test/sqllogictest/table_func.slt +++ b/test/sqllogictest/table_func.slt @@ -81,8 +81,7 @@ query T multiline EXPLAIN RAW PLAN FOR SELECT generate_series FROM generate_series(-2, 2) ---- %0 = -| Constant () -| FlatMap generate_series(-(2), 2) +| CallTable generate_series(-(2), 2) EOF @@ -167,7 +166,12 @@ EXPLAIN RAW PLAN FOR SELECT * FROM x, generate_series(1, a) ---- %0 = | Get materialize.public.x (u5) -| FlatMap generate_series(1, #0) + +%1 = +| CallTable generate_series(1, #^0) + +%2 = +| InnerLateralJoin %0 %1 on true EOF @@ -176,7 +180,22 @@ EXPLAIN RAW PLAN FOR SELECT * FROM x, generate_series(100::bigint, a) ---- %0 = | Get materialize.public.x (u5) -| FlatMap generate_series(i32toi64(100), i32toi64(#0)) + +%1 = +| CallTable generate_series(i32toi64(100), i32toi64(#^0)) + +%2 = +| InnerLateralJoin %0 %1 on true + +EOF + +query T multiline +EXPLAIN PLAN FOR SELECT * FROM x, generate_series(1, 10) +---- +%0 = +| Get materialize.public.x (u5) +| FlatMap generate_series(1, 10) +| | demand = (#0..#2) EOF