From 7fff89e9c06b799c1f9f651b613b8d4306d4e584 Mon Sep 17 00:00:00 2001 From: Chen22 Date: Thu, 13 May 2021 11:57:54 -0700 Subject: [PATCH 1/3] feat: support last join 1. change LAST as reserved keyword 2. change keywords_test ut 3. add LAST JOIN test cases --- zetasql/parser/bison_parser.y | 5 +- zetasql/parser/keywords.cc | 2 +- zetasql/parser/keywords_test.cc | 2 +- zetasql/parser/parse_tree.cc | 2 + zetasql/parser/parse_tree_manual.h | 2 +- zetasql/parser/testdata/from.test | 83 +++++++++++++++++++++++++++- zetasql/parser/testdata/orderby.test | 18 +++--- 7 files changed, 99 insertions(+), 15 deletions(-) diff --git a/zetasql/parser/bison_parser.y b/zetasql/parser/bison_parser.y index 20c206567..3c0b112f5 100644 --- a/zetasql/parser/bison_parser.y +++ b/zetasql/parser/bison_parser.y @@ -703,6 +703,7 @@ using zetasql::ASTDropStatement; %token KW_INTO "INTO" %token KW_IS "IS" %token KW_JOIN "JOIN" +%token KW_LAST "LAST" %token KW_LEFT "LEFT" %token KW_LIKE "LIKE" %token KW_LIMIT "LIMIT" @@ -842,7 +843,6 @@ using zetasql::ASTDropStatement; %token KW_JSON "JSON" %token KW_KEY "KEY" %token KW_LANGUAGE "LANGUAGE" -%token KW_LAST "LAST" %token KW_LEAVE "LEAVE" %token KW_LEVEL "LEVEL" %token KW_LOOP "LOOP" @@ -4631,6 +4631,7 @@ join_type: | "INNER" { $$ = zetasql::ASTJoin::INNER; } | "LEFT" opt_outer { $$ = zetasql::ASTJoin::LEFT; } | "RIGHT" opt_outer { $$ = zetasql::ASTJoin::RIGHT; } + | "LAST" { $$ = zetasql::ASTJoin::LAST; } | /* Nothing */ { $$ = zetasql::ASTJoin::DEFAULT_JOIN_TYPE; } ; @@ -7036,6 +7037,7 @@ reserved_keyword_rule: | "INTO" | "IS" | "JOIN" + | "LAST" | "LEFT" | "LIKE" | "LIMIT" @@ -7171,7 +7173,6 @@ keyword_as_identifier: | "JSON" | "KEY" | "LANGUAGE" - | "LAST" | "LEAVE" | "LEVEL" | "LOOP" diff --git a/zetasql/parser/keywords.cc b/zetasql/parser/keywords.cc index b9a3f9ea1..6eb2b7834 100644 --- a/zetasql/parser/keywords.cc +++ b/zetasql/parser/keywords.cc @@ -173,7 +173,7 @@ constexpr KeywordInfoPOD kAllKeywords[] = { {"json", KW_JSON}, {"key", KW_KEY}, {"language", KW_LANGUAGE}, - {"last", KW_LAST}, + {"last", KW_LAST, KeywordInfo::kReserved}, {"lateral", KW_LATERAL, KeywordInfo::kReserved}, {"leave", KW_LEAVE}, {"left", KW_LEFT, KeywordInfo::kReserved}, diff --git a/zetasql/parser/keywords_test.cc b/zetasql/parser/keywords_test.cc index f71129ea6..16e9a5247 100644 --- a/zetasql/parser/keywords_test.cc +++ b/zetasql/parser/keywords_test.cc @@ -237,7 +237,7 @@ TEST(ParserTest, DontAddNewReservedKeywords) { // allows new queries to work that will not work on older code. // Before changing this, co-ordinate with all engines to make sure the change // is done safely. - EXPECT_EQ(97 /* CAUTION */, num_reserved); + EXPECT_EQ(98 /* CAUTION */, num_reserved); } } // namespace diff --git a/zetasql/parser/parse_tree.cc b/zetasql/parser/parse_tree.cc index dd48240cc..192a048f9 100644 --- a/zetasql/parser/parse_tree.cc +++ b/zetasql/parser/parse_tree.cc @@ -649,6 +649,8 @@ std::string ASTJoin::GetSQLForJoinType() const { return "LEFT"; case RIGHT: return "RIGHT"; + case LAST: + return "LAST"; } } diff --git a/zetasql/parser/parse_tree_manual.h b/zetasql/parser/parse_tree_manual.h index e656c623d..177644486 100644 --- a/zetasql/parser/parse_tree_manual.h +++ b/zetasql/parser/parse_tree_manual.h @@ -1286,7 +1286,7 @@ class ASTJoin final : public ASTTableExpression { std::string GetSQLForJoinType() const; std::string GetSQLForJoinHint() const; - enum JoinType { DEFAULT_JOIN_TYPE, COMMA, CROSS, FULL, INNER, LEFT, RIGHT }; + enum JoinType { DEFAULT_JOIN_TYPE, COMMA, CROSS, FULL, INNER, LEFT, RIGHT, LAST}; JoinType join_type() const { return join_type_; } void set_join_type(JoinType join_type) { join_type_ = join_type; } diff --git a/zetasql/parser/testdata/from.test b/zetasql/parser/testdata/from.test index 82b2b0dca..6038f20ec 100644 --- a/zetasql/parser/testdata/from.test +++ b/zetasql/parser/testdata/from.test @@ -90,6 +90,50 @@ FROM USING(c, d) == +select T1.a as a, T2.b as b from Table1 as T1 last join Table2 T2 using (c, d); +-- +QueryStatement [0-78] + Query [0-78] + Select [0-78] + SelectList [7-27] + SelectColumn [7-16] + PathExpression [7-11] + Identifier(T1) [7-9] + Identifier(a) [10-11] + Alias [12-16] + Identifier(a) [15-16] + SelectColumn [18-27] + PathExpression [18-22] + Identifier(T2) [18-20] + Identifier(b) [21-22] + Alias [23-27] + Identifier(b) [26-27] + FromClause [28-78] + Join(LAST) [46-78] + TablePathExpression [33-45] + PathExpression [33-39] + Identifier(Table1) [33-39] + Alias [40-45] + Identifier(T1) [43-45] + TablePathExpression [56-65] + PathExpression [56-62] + Identifier(Table2) [56-62] + Alias [63-65] + Identifier(T2) [63-65] + UsingClause [66-78] + Identifier(c) [73-74] + Identifier(d) [76-77] +-- +SELECT + T1.a AS a, + T2.b AS b +FROM + Table1 AS T1 + LAST JOIN + Table2 AS T2 + USING(c, d) +== + select a as x, b as y from Table1 as T1 inner join Table2 T2 on T2.x = T3.y join Table3 as T3 using(c); -- @@ -774,7 +818,7 @@ select * from A, B JOIN (C, D) == # RIGHT JOIN and FULL JOIN require parentheses when following a comma join. -select * FROM A, B {{INNER|CROSS|LEFT|RIGHT|FULL}} JOIN C +select * FROM A, B {{INNER|CROSS|LEFT|LAST|RIGHT|FULL}} JOIN C -- ALTERNATION GROUP: INNER -- @@ -863,6 +907,35 @@ FROM LEFT JOIN C -- +ALTERNATION GROUP: LAST +-- +QueryStatement [0-30] + Query [0-30] + Select [0-30] + SelectList [7-8] + SelectColumn [7-8] + Star(*) [7-8] + FromClause [9-30] + Join(LAST) [19-30] + Join(COMMA) [15-18] + TablePathExpression [14-15] + PathExpression [14-15] + Identifier(A) [14-15] + TablePathExpression [17-18] + PathExpression [17-18] + Identifier(B) [17-18] + TablePathExpression [29-30] + PathExpression [29-30] + Identifier(C) [29-30] +-- +SELECT + * +FROM + A, + B + LAST JOIN + C +-- ALTERNATION GROUP: RIGHT -- ERROR: Syntax error: RIGHT JOIN must be parenthesized when following a comma join. Also, if the preceding comma join is a correlated CROSS JOIN that unnests an array, then CROSS JOIN syntax must be used in place of the comma join [at 1:20] @@ -949,6 +1022,14 @@ select * FROM A, B, C, D JOIN E LEFT JOIN F RIGHT JOIN G ^ == +# Comma join error happens even after chains of other joins. +select * FROM A, B, C, D JOIN E LAST JOIN F RIGHT JOIN G +-- +ERROR: Syntax error: RIGHT JOIN must be parenthesized when following a comma join. Also, if the preceding comma join is a correlated CROSS JOIN that unnests an array, then CROSS JOIN syntax must be used in place of the comma join [at 1:45] +select * FROM A, B, C, D JOIN E LAST JOIN F RIGHT JOIN G + ^ +== + # Commas to the right of FULL/RIGHT join are okay. select * FROM A RIGHT JOIN B FULL JOIN C, D LEFT JOIN E -- diff --git a/zetasql/parser/testdata/orderby.test b/zetasql/parser/testdata/orderby.test index 261d68fa8..a34225e81 100644 --- a/zetasql/parser/testdata/orderby.test +++ b/zetasql/parser/testdata/orderby.test @@ -591,10 +591,10 @@ FROM ORDER BY a DESC NULLS LAST == -select a from T order by 1 nulls last, first desc nulls first, last asc nulls last; +select a from T order by 1 nulls last, first desc nulls first, `last` asc nulls last; -- -QueryStatement [0-82] - Query [0-82] +QueryStatement [0-84] + Query [0-84] Select [0-15] SelectList [7-8] SelectColumn [7-8] @@ -604,7 +604,7 @@ QueryStatement [0-82] TablePathExpression [14-15] PathExpression [14-15] Identifier(T) [14-15] - OrderBy [16-82] + OrderBy [16-84] OrderingExpression(ASC) [25-37] IntLiteral(1) [25-26] NullOrder(NULLS LAST) [27-37] @@ -612,13 +612,13 @@ QueryStatement [0-82] PathExpression [39-44] Identifier(first) [39-44] NullOrder(NULLS FIRST) [50-61] - OrderingExpression(ASC EXPLICITLY) [63-82] - PathExpression [63-67] - Identifier(last) [63-67] - NullOrder(NULLS LAST) [72-82] + OrderingExpression(ASC EXPLICITLY) [63-84] + PathExpression [63-69] + Identifier(`last`) [63-69] + NullOrder(NULLS LAST) [74-84] -- SELECT a FROM T -ORDER BY 1 NULLS LAST, first DESC NULLS FIRST, last ASC NULLS LAST +ORDER BY 1 NULLS LAST, first DESC NULLS FIRST, `last` ASC NULLS LAST From 761419f862d559227d8b150a55ee72bcd5a48732 Mon Sep 17 00:00:00 2001 From: Chen22 Date: Thu, 13 May 2021 18:24:42 -0700 Subject: [PATCH 2/3] feat: support last join order by 1. join and from clause support last join order by 2. add test case --- zetasql/parser/bison_parser.y | 38 +++++- zetasql/parser/join_proccessor.cc | 7 +- zetasql/parser/join_proccessor.h | 1 + zetasql/parser/parse_tree_manual.h | 12 +- zetasql/parser/testdata/from.test | 191 +++++++++++++++++++++++------ 5 files changed, 202 insertions(+), 47 deletions(-) diff --git a/zetasql/parser/bison_parser.y b/zetasql/parser/bison_parser.y index 3c0b112f5..84203b1e6 100644 --- a/zetasql/parser/bison_parser.y +++ b/zetasql/parser/bison_parser.y @@ -4631,7 +4631,6 @@ join_type: | "INNER" { $$ = zetasql::ASTJoin::INNER; } | "LEFT" opt_outer { $$ = zetasql::ASTJoin::LEFT; } | "RIGHT" opt_outer { $$ = zetasql::ASTJoin::RIGHT; } - | "LAST" { $$ = zetasql::ASTJoin::LAST; } | /* Nothing */ { $$ = zetasql::ASTJoin::DEFAULT_JOIN_TYPE; } ; @@ -4658,7 +4657,20 @@ join: zetasql::parser::ErrorInfo error_info; auto node = zetasql::parser::JoinRuleAction( FirstNonEmptyLocation({@2, @3, @4, @5}), @$, - $1, $2, $3, $4, $6, $7, $8, parser, &error_info); + $1, $2, $3, $4, $6, $7, nullptr, $8, parser, &error_info); + if (node == nullptr) { + YYERROR_AND_ABORT_AT(error_info.location, error_info.message); + } + + $$ = node; + } + | join_input opt_natural "LAST" join_hint "JOIN" opt_hint table_primary + opt_order_by_clause opt_on_or_using_clause_list + { + zetasql::parser::ErrorInfo error_info; + auto node = zetasql::parser::JoinRuleAction( + FirstNonEmptyLocation({@2, @3, @4, @5}), @$, + $1, $2, zetasql::ASTJoin::LAST, $4, $6, $7, $8, $9, parser, &error_info); if (node == nullptr) { YYERROR_AND_ABORT_AT(error_info.location, error_info.message); } @@ -4716,7 +4728,7 @@ from_clause_contents: zetasql::parser::ErrorInfo error_info; auto node = zetasql::parser::JoinRuleAction( FirstNonEmptyLocation({@2, @3, @4, @5}), @$, - $1, $2, $3, $4, $6, $7, $8, + $1, $2, $3, $4, $6, $7, nullptr, $8, parser, &error_info); if (node == nullptr) { YYERROR_AND_ABORT_AT(error_info.location, error_info.message); @@ -4724,7 +4736,25 @@ from_clause_contents: $$ = node; } - | "@" + | from_clause_contents opt_natural "LAST" join_hint "JOIN" opt_hint + table_primary opt_order_by_clause on_or_using_clause_list + { + // Give an error if we have a RIGHT or FULL JOIN following a comma + // join since our left-to-right binding would violate the standard. + // See (broken link). + zetasql::parser::ErrorInfo error_info; + auto node = zetasql::parser::JoinRuleAction( + FirstNonEmptyLocation({@2, @3, @4, @5}), @$, + $1, $2, zetasql::ASTJoin::LAST, $4, $6, $7, $8, $9, + parser, &error_info); + if (node == nullptr) { + YYERROR_AND_ABORT_AT(error_info.location, error_info.message); + } + + $$ = node; + } + | + "@" { YYERROR_AND_ABORT_AT( @1, "Query parameters cannot be used in place of table names"); diff --git a/zetasql/parser/join_proccessor.cc b/zetasql/parser/join_proccessor.cc index 647372027..5c9b82d38 100644 --- a/zetasql/parser/join_proccessor.cc +++ b/zetasql/parser/join_proccessor.cc @@ -400,7 +400,8 @@ ASTNode* JoinRuleAction( const zetasql_bison_parser::location& start_location, const zetasql_bison_parser::location& end_location, ASTNode* lhs, bool natural, ASTJoin::JoinType join_type, ASTJoin::JoinHint join_hint, - ASTNode* hint, ASTNode* table_primary, ASTNode* on_or_using_clause_list, + ASTNode* hint, ASTNode* table_primary, ASTNode* order_by, + ASTNode* on_or_using_clause_list, BisonParser* parser, ErrorInfo* error_info) { auto clause_list = @@ -440,11 +441,11 @@ ASTNode* JoinRuleAction( } join = parser->CreateASTNode( start_location, end_location, - {lhs, hint, table_primary, on_or_using_clause}); + {lhs, hint, table_primary, order_by, on_or_using_clause}); join->set_transformation_needed(IsTransformationNeeded(lhs)); } else { join = parser->CreateASTNode( - start_location, end_location, {lhs, hint, table_primary, clause_list}); + start_location, end_location, {lhs, hint, table_primary, order_by, clause_list}); join->set_transformation_needed(true); } diff --git a/zetasql/parser/join_proccessor.h b/zetasql/parser/join_proccessor.h index 43f407870..d7ec17327 100644 --- a/zetasql/parser/join_proccessor.h +++ b/zetasql/parser/join_proccessor.h @@ -394,6 +394,7 @@ ASTNode* JoinRuleAction( const zetasql_bison_parser::location& end_location, ASTNode* lhs, bool opt_natural, ASTJoin::JoinType join_type, ASTJoin::JoinHint join_hint, ASTNode* opt_hint, ASTNode* table_primary, + ASTNode* opt_order_by, ASTNode* opt_on_or_using_clause_list, BisonParser* parser, ErrorInfo* error_info); diff --git a/zetasql/parser/parse_tree_manual.h b/zetasql/parser/parse_tree_manual.h index 177644486..4664aa4b1 100644 --- a/zetasql/parser/parse_tree_manual.h +++ b/zetasql/parser/parse_tree_manual.h @@ -1302,6 +1302,8 @@ class ASTJoin final : public ASTTableExpression { const ASTHint* hint() const { return hint_; } const ASTTableExpression* lhs() const { return lhs_; } const ASTTableExpression* rhs() const { return rhs_; } + // If present, order rhs before apply using clause + const ASTOrderBy* order_by() const { return order_by_; } const ASTOnClause* on_clause() const { return on_clause_; } const ASTUsingClause* using_clause() const { return using_clause_; } @@ -1331,6 +1333,7 @@ class ASTJoin final : public ASTTableExpression { fl.AddRequired(&lhs_); fl.AddOptional(&hint_, AST_HINT); fl.AddRequired(&rhs_); + fl.AddOptional(&order_by_, AST_ORDER_BY); fl.AddOptional(&on_clause_, AST_ON_CLAUSE); fl.AddOptional(&using_clause_, AST_USING_CLAUSE); @@ -1345,6 +1348,7 @@ class ASTJoin final : public ASTTableExpression { const ASTHint* hint_ = nullptr; const ASTTableExpression* lhs_ = nullptr; const ASTTableExpression* rhs_ = nullptr; + const ASTOrderBy* order_by_ = nullptr; const ASTOnClause* on_clause_ = nullptr; const ASTUsingClause* using_clause_ = nullptr; const ASTOnOrUsingClauseList* clause_list_ = nullptr; @@ -1405,10 +1409,10 @@ class ASTOnClause final : public ASTNode { FieldLoader fl(this); fl.AddRequired(&expression_); } - const ASTExpression* expression_ = nullptr; }; + class ASTUsingClause final : public ASTNode { public: static constexpr ASTNodeKind kConcreteNodeKind = AST_USING_CLAUSE; @@ -1418,6 +1422,9 @@ class ASTUsingClause final : public ASTNode { zetasql_base::StatusOr Accept( NonRecursiveParseTreeVisitor* visitor) const override; + // If present, order dataset before apply using clause + const ASTOrderBy* order_by() const { return order_by_; } + const absl::Span& keys() const { return keys_; } @@ -1425,9 +1432,10 @@ class ASTUsingClause final : public ASTNode { private: void InitFields() final { FieldLoader fl(this); + fl.AddRequired(&order_by_); fl.AddRestAsRepeated(&keys_); } - + const ASTOrderBy* order_by_ = nullptr; absl::Span keys_; }; diff --git a/zetasql/parser/testdata/from.test b/zetasql/parser/testdata/from.test index 6038f20ec..f673a20a1 100644 --- a/zetasql/parser/testdata/from.test +++ b/zetasql/parser/testdata/from.test @@ -134,6 +134,158 @@ FROM USING(c, d) == +select T1.a as a, T2.b as b from Table1 as T1 last join Table2 T2 order by e using (c, d); +-- +QueryStatement [0-89] + Query [0-89] + Select [0-89] + SelectList [7-27] + SelectColumn [7-16] + PathExpression [7-11] + Identifier(T1) [7-9] + Identifier(a) [10-11] + Alias [12-16] + Identifier(a) [15-16] + SelectColumn [18-27] + PathExpression [18-22] + Identifier(T2) [18-20] + Identifier(b) [21-22] + Alias [23-27] + Identifier(b) [26-27] + FromClause [28-89] + Join(LAST) [46-89] + TablePathExpression [33-45] + PathExpression [33-39] + Identifier(Table1) [33-39] + Alias [40-45] + Identifier(T1) [43-45] + TablePathExpression [56-65] + PathExpression [56-62] + Identifier(Table2) [56-62] + Alias [63-65] + Identifier(T2) [63-65] + OrderBy [66-76] + OrderingExpression(ASC) [75-76] + PathExpression [75-76] + Identifier(e) [75-76] + UsingClause [77-89] + Identifier(c) [84-85] + Identifier(d) [87-88] +-- +SELECT + T1.a AS a, + T2.b AS b +FROM + Table1 AS T1 + LAST JOIN + Table2 AS T2 + ORDER BY e + USING(c, d) +== + +select T1.a as a, T2.b as b from Table1 as T1 last join Table2 T2 on T1.c = T2.c; +-- +QueryStatement [0-80] + Query [0-80] + Select [0-80] + SelectList [7-27] + SelectColumn [7-16] + PathExpression [7-11] + Identifier(T1) [7-9] + Identifier(a) [10-11] + Alias [12-16] + Identifier(a) [15-16] + SelectColumn [18-27] + PathExpression [18-22] + Identifier(T2) [18-20] + Identifier(b) [21-22] + Alias [23-27] + Identifier(b) [26-27] + FromClause [28-80] + Join(LAST) [46-80] + TablePathExpression [33-45] + PathExpression [33-39] + Identifier(Table1) [33-39] + Alias [40-45] + Identifier(T1) [43-45] + TablePathExpression [56-65] + PathExpression [56-62] + Identifier(Table2) [56-62] + Alias [63-65] + Identifier(T2) [63-65] + OnClause [66-80] + BinaryExpression(=) [69-80] + PathExpression [69-73] + Identifier(T1) [69-71] + Identifier(c) [72-73] + PathExpression [76-80] + Identifier(T2) [76-78] + Identifier(c) [79-80] +-- +SELECT + T1.a AS a, + T2.b AS b +FROM + Table1 AS T1 + LAST JOIN + Table2 AS T2 + ON T1.c = T2.c +== + +select T1.a as a, T2.b as b from Table1 as T1 last join Table2 T2 order by e on T1.c = T2.c; +-- +QueryStatement [0-91] + Query [0-91] + Select [0-91] + SelectList [7-27] + SelectColumn [7-16] + PathExpression [7-11] + Identifier(T1) [7-9] + Identifier(a) [10-11] + Alias [12-16] + Identifier(a) [15-16] + SelectColumn [18-27] + PathExpression [18-22] + Identifier(T2) [18-20] + Identifier(b) [21-22] + Alias [23-27] + Identifier(b) [26-27] + FromClause [28-91] + Join(LAST) [46-91] + TablePathExpression [33-45] + PathExpression [33-39] + Identifier(Table1) [33-39] + Alias [40-45] + Identifier(T1) [43-45] + TablePathExpression [56-65] + PathExpression [56-62] + Identifier(Table2) [56-62] + Alias [63-65] + Identifier(T2) [63-65] + OrderBy [66-76] + OrderingExpression(ASC) [75-76] + PathExpression [75-76] + Identifier(e) [75-76] + OnClause [77-91] + BinaryExpression(=) [80-91] + PathExpression [80-84] + Identifier(T1) [80-82] + Identifier(c) [83-84] + PathExpression [87-91] + Identifier(T2) [87-89] + Identifier(c) [90-91] +-- +SELECT + T1.a AS a, + T2.b AS b +FROM + Table1 AS T1 + LAST JOIN + Table2 AS T2 + ORDER BY e + ON T1.c = T2.c +== + select a as x, b as y from Table1 as T1 inner join Table2 T2 on T2.x = T3.y join Table3 as T3 using(c); -- @@ -818,7 +970,7 @@ select * from A, B JOIN (C, D) == # RIGHT JOIN and FULL JOIN require parentheses when following a comma join. -select * FROM A, B {{INNER|CROSS|LEFT|LAST|RIGHT|FULL}} JOIN C +select * FROM A, B {{INNER|CROSS|LEFT|RIGHT|FULL}} JOIN C -- ALTERNATION GROUP: INNER -- @@ -907,35 +1059,6 @@ FROM LEFT JOIN C -- -ALTERNATION GROUP: LAST --- -QueryStatement [0-30] - Query [0-30] - Select [0-30] - SelectList [7-8] - SelectColumn [7-8] - Star(*) [7-8] - FromClause [9-30] - Join(LAST) [19-30] - Join(COMMA) [15-18] - TablePathExpression [14-15] - PathExpression [14-15] - Identifier(A) [14-15] - TablePathExpression [17-18] - PathExpression [17-18] - Identifier(B) [17-18] - TablePathExpression [29-30] - PathExpression [29-30] - Identifier(C) [29-30] --- -SELECT - * -FROM - A, - B - LAST JOIN - C --- ALTERNATION GROUP: RIGHT -- ERROR: Syntax error: RIGHT JOIN must be parenthesized when following a comma join. Also, if the preceding comma join is a correlated CROSS JOIN that unnests an array, then CROSS JOIN syntax must be used in place of the comma join [at 1:20] @@ -1022,14 +1145,6 @@ select * FROM A, B, C, D JOIN E LEFT JOIN F RIGHT JOIN G ^ == -# Comma join error happens even after chains of other joins. -select * FROM A, B, C, D JOIN E LAST JOIN F RIGHT JOIN G --- -ERROR: Syntax error: RIGHT JOIN must be parenthesized when following a comma join. Also, if the preceding comma join is a correlated CROSS JOIN that unnests an array, then CROSS JOIN syntax must be used in place of the comma join [at 1:45] -select * FROM A, B, C, D JOIN E LAST JOIN F RIGHT JOIN G - ^ -== - # Commas to the right of FULL/RIGHT join are okay. select * FROM A RIGHT JOIN B FULL JOIN C, D LEFT JOIN E -- From e94b6db3abad9de8ff1f7928ddfdd02e0b0a3d4c Mon Sep 17 00:00:00 2001 From: Chen22 Date: Thu, 13 May 2021 19:46:45 -0700 Subject: [PATCH 3/3] feat: add error syntax test case for last join without on/using clause --- zetasql/parser/testdata/from.test | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/zetasql/parser/testdata/from.test b/zetasql/parser/testdata/from.test index f673a20a1..beb9185f3 100644 --- a/zetasql/parser/testdata/from.test +++ b/zetasql/parser/testdata/from.test @@ -970,7 +970,8 @@ select * from A, B JOIN (C, D) == # RIGHT JOIN and FULL JOIN require parentheses when following a comma join. -select * FROM A, B {{INNER|CROSS|LEFT|RIGHT|FULL}} JOIN C +# LAST JOIN require to be followed by ON/USING clause +select * FROM A, B {{INNER|CROSS|LEFT|RIGHT|FULL|LAST}} JOIN C -- ALTERNATION GROUP: INNER -- @@ -1070,6 +1071,13 @@ ALTERNATION GROUP: FULL ERROR: Syntax error: FULL JOIN must be parenthesized when following a comma join. Also, if the preceding comma join is a correlated CROSS JOIN that unnests an array, then CROSS JOIN syntax must be used in place of the comma join [at 1:20] select * FROM A, B FULL JOIN C ^ + +-- +ALTERNATION GROUP: LAST +-- +ERROR: Syntax error: Expected keyword ON or keyword USING but got end of statement [at 1:31] +select * FROM A, B LAST JOIN C + ^ == # Comma join error happens even after chains of other joins.