diff options
author | vitya-smirnov <[email protected]> | 2025-10-01 20:51:41 +0300 |
---|---|---|
committer | vitya-smirnov <[email protected]> | 2025-10-01 21:25:50 +0300 |
commit | f3f7b33a285e94cb3e27aaa4d9b23b543ece4106 (patch) | |
tree | 89740eeddb803123f1ed3d6b6f7be2bcd865f68c /yql/essentials/sql | |
parent | 5cfb01d33937d2b2e038abf3b50e04136dcf7496 (diff) |
YQL-20307: Support inline subqueries
- Alter grammar to support inline subqueries.
- Support inline subqueries in `sql/v1` (translator).
- Introduce `sql/v1/proto_ast/parse_tree.h` for reusable parse tree predicates.
- Support inline subqueries in `sql/v1/format`.
- Support inline subqueries in `sql/v1/complete`.
- Add some SQL-tests.
- Pass all tests.
commit_hash:075b2240778d071e1c7542f912d3cc83019ef849
Diffstat (limited to 'yql/essentials/sql')
22 files changed, 952 insertions, 203 deletions
diff --git a/yql/essentials/sql/v1/SQLv1.g.in b/yql/essentials/sql/v1/SQLv1.g.in index 9035701614f..8675d3cfef8 100644 --- a/yql/essentials/sql/v1/SQLv1.g.in +++ b/yql/essentials/sql/v1/SQLv1.g.in @@ -174,7 +174,6 @@ in_atom_expr: | cast_expr | case_expr | an_id_or_type NAMESPACE (id_or_type | STRING_VALUE) - | LPAREN select_stmt RPAREN // TODO: resolve ANTLR error: rule in_atom_expr has non-LL(*) decision due to recursive rule invocations reachable from alts 3,8 // | LPAREN values_stmt RPAREN | value_constructor @@ -234,8 +233,19 @@ json_query: JSON_QUERY LPAREN (json_query_handler ON ERROR)? RPAREN; -// struct, tuple or named list -smart_parenthesis: LPAREN named_expr_list? COMMA? RPAREN; +select_subexpr: + select_subexpr_intersect (union_op select_subexpr_intersect)* +; + +select_subexpr_intersect: + select_or_expr (intersect_op select_or_expr)* +; + +select_or_expr: select_kind_partial | tuple_or_expr; + +tuple_or_expr: expr (AS an_id_or_type)? (COMMA named_expr)* COMMA?; + +smart_parenthesis: LPAREN (select_subexpr | COMMA?) RPAREN; expr_list: expr (COMMA expr)*; @@ -601,8 +611,6 @@ values_source: values_stmt | select_stmt; values_source_row_list: values_source_row (COMMA values_source_row)*; values_source_row: LPAREN expr_list RPAREN; -simple_values_source: expr_list | select_stmt; - create_external_data_source_stmt: CREATE (OR REPLACE)? EXTERNAL DATA SOURCE (IF NOT EXISTS)? object_ref with_table_settings ; @@ -816,7 +824,7 @@ column_option: ; family_relation: FAMILY an_id; -nullability: NOT? NULL; +nullability: NOT? NULL; default_value: DEFAULT expr; column_order_by_specification: an_id (ASC | DESC)?; @@ -1041,7 +1049,7 @@ set_clause_choice: set_clause_list | multiple_column_assignment; set_clause_list: set_clause (COMMA set_clause)*; set_clause: set_target EQUALS expr; set_target: column_name; -multiple_column_assignment: set_target_list EQUALS LPAREN simple_values_source RPAREN; +multiple_column_assignment: set_target_list EQUALS smart_parenthesis; set_target_list: LPAREN set_target (COMMA set_target)* RPAREN; // topics @@ -1145,11 +1153,9 @@ window_frame_exclusion: EXCLUDE CURRENT ROW | EXCLUDE GROUP | EXCLUDE TIES | EXC // EXTRAS use_stmt: USE cluster_expr; -subselect_stmt: (LPAREN select_stmt RPAREN | select_unparenthesized_stmt); - // TODO: [fatal] rule named_nodes_stmt has non-LL(*) decision due to recursive rule invocations reachable from alts 1,3 // named_nodes_stmt: bind_parameter_list EQUALS (expr | subselect_stmt | values_stmt | LPAREN values_stmt RPAREN); -named_nodes_stmt: bind_parameter_list EQUALS (expr | subselect_stmt); +named_nodes_stmt: bind_parameter_list EQUALS (expr | select_unparenthesized_stmt); commit_stmt: COMMIT; diff --git a/yql/essentials/sql/v1/SQLv1Antlr4.g.in b/yql/essentials/sql/v1/SQLv1Antlr4.g.in index b52309c484f..919da25318d 100644 --- a/yql/essentials/sql/v1/SQLv1Antlr4.g.in +++ b/yql/essentials/sql/v1/SQLv1Antlr4.g.in @@ -173,7 +173,6 @@ in_atom_expr: | cast_expr | case_expr | an_id_or_type NAMESPACE (id_or_type | STRING_VALUE) - | LPAREN select_stmt RPAREN // TODO: resolve ANTLR error: rule in_atom_expr has non-LL(*) decision due to recursive rule invocations reachable from alts 3,8 // | LPAREN values_stmt RPAREN | value_constructor @@ -233,8 +232,19 @@ json_query: JSON_QUERY LPAREN (json_query_handler ON ERROR)? RPAREN; -// struct, tuple or named list -smart_parenthesis: LPAREN named_expr_list? COMMA? RPAREN; +select_subexpr: + select_subexpr_intersect (union_op select_subexpr_intersect)* +; + +select_subexpr_intersect: + select_or_expr (intersect_op select_or_expr)* +; + +select_or_expr: select_kind_partial | tuple_or_expr; + +tuple_or_expr: expr (AS an_id_or_type)? (COMMA named_expr)* COMMA?; + +smart_parenthesis: LPAREN (select_subexpr | COMMA?) RPAREN; expr_list: expr (COMMA expr)*; @@ -600,8 +610,6 @@ values_source: values_stmt | select_stmt; values_source_row_list: values_source_row (COMMA values_source_row)*; values_source_row: LPAREN expr_list RPAREN; -simple_values_source: expr_list | select_stmt; - create_external_data_source_stmt: CREATE (OR REPLACE)? EXTERNAL DATA SOURCE (IF NOT EXISTS)? object_ref with_table_settings ; @@ -815,7 +823,7 @@ column_option: ; family_relation: FAMILY an_id; -nullability: NOT? NULL; +nullability: NOT? NULL; default_value: DEFAULT expr; column_order_by_specification: an_id (ASC | DESC)?; @@ -1041,7 +1049,7 @@ set_clause_choice: set_clause_list | multiple_column_assignment; set_clause_list: set_clause (COMMA set_clause)*; set_clause: set_target EQUALS expr; set_target: column_name; -multiple_column_assignment: set_target_list EQUALS LPAREN simple_values_source RPAREN; +multiple_column_assignment: set_target_list EQUALS smart_parenthesis; set_target_list: LPAREN set_target (COMMA set_target)* RPAREN; // topics @@ -1145,11 +1153,9 @@ window_frame_exclusion: EXCLUDE CURRENT ROW | EXCLUDE GROUP | EXCLUDE TIES | EXC // EXTRAS use_stmt: USE cluster_expr; -subselect_stmt: (LPAREN select_stmt RPAREN | select_unparenthesized_stmt); - // TODO: [fatal] rule named_nodes_stmt has non-LL(*) decision due to recursive rule invocations reachable from alts 1,3 // named_nodes_stmt: bind_parameter_list EQUALS (expr | subselect_stmt | values_stmt | LPAREN values_stmt RPAREN); -named_nodes_stmt: bind_parameter_list EQUALS (expr | subselect_stmt); +named_nodes_stmt: bind_parameter_list EQUALS (expr | select_unparenthesized_stmt); commit_stmt: COMMIT; diff --git a/yql/essentials/sql/v1/complete/analysis/global/column.cpp b/yql/essentials/sql/v1/complete/analysis/global/column.cpp index 3f71ae6763d..d8c73e00b7f 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/column.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/column.cpp @@ -295,6 +295,30 @@ namespace NSQLComplete { const TNamedNodes* Nodes_; }; + class TEnclosingSelectVisitor: public TSQLv1NarrowingVisitor { + public: + explicit TEnclosingSelectVisitor(const TParsedInput& input) + : TSQLv1NarrowingVisitor(input) + { + } + + std::any visitSelect_core(SQLv1::Select_coreContext* ctx) override { + if (!IsEnclosing(ctx)) { + return {}; + } + + Enclosing_ = ctx; + return visitChildren(ctx); + } + + SQLv1::Select_coreContext* GetEnclosing() && { + return Enclosing_; + } + + private: + SQLv1::Select_coreContext* Enclosing_ = nullptr; + }; + class TVisitor: public TSQLv1NarrowingVisitor { public: TVisitor(const TParsedInput& input, const TNamedNodes* nodes) @@ -343,11 +367,23 @@ namespace NSQLComplete { const TNamedNodes* Nodes_; }; + antlr4::ParserRuleContext* Enclosing(const TParsedInput& input) { + TEnclosingSelectVisitor visitor(input); + visitor.visit(input.SqlQuery); + + antlr4::ParserRuleContext* ctx = std::move(visitor).GetEnclosing(); + if (!ctx) { + ctx = input.SqlQuery; + } + + return ctx; + } + } // namespace TMaybe<TColumnContext> InferColumnContext(TParsedInput input, const TNamedNodes& nodes) { // TODO: add utility `auto ToMaybe<T>(std::any any) -> TMaybe<T>` - std::any result = TVisitor(input, &nodes).visit(input.SqlQuery); + std::any result = TVisitor(input, &nodes).visit(Enclosing(input)); if (!result.has_value()) { return Nothing(); } diff --git a/yql/essentials/sql/v1/complete/analysis/global/named_node.cpp b/yql/essentials/sql/v1/complete/analysis/global/named_node.cpp index af8c17d3da2..4199da625c2 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/named_node.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/named_node.cpp @@ -99,8 +99,6 @@ namespace NSQLComplete { if (auto* expr = ctx->expr()) { (*Names_)[std::move(*id)] = expr; - } else if (auto* subselect = ctx->subselect_stmt()) { - (*Names_)[std::move(*id)] = subselect; } else { (*Names_)[std::move(*id)] = std::monostate(); } diff --git a/yql/essentials/sql/v1/complete/analysis/global/named_node.h b/yql/essentials/sql/v1/complete/analysis/global/named_node.h index 07e1e6f91b9..0050915f523 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/named_node.h +++ b/yql/essentials/sql/v1/complete/analysis/global/named_node.h @@ -13,7 +13,6 @@ namespace NSQLComplete { using TNamedNode = std::variant< SQLv1::ExprContext*, - SQLv1::Subselect_stmtContext*, NYT::TNode, std::monostate>; diff --git a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp index ecb3084dcba..679230c7a24 100644 --- a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp +++ b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp @@ -1,6 +1,6 @@ #include "sql_complete.h" - #include <yql/essentials/sql/v1/complete/syntax/grammar.h> + #include <yql/essentials/sql/v1/complete/name/cache/local/cache.h> #include <yql/essentials/sql/v1/complete/name/cluster/static/discovery.h> #include <yql/essentials/sql/v1/complete/name/object/simple/schema.h> @@ -857,6 +857,8 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "$x = sel#"), expected); UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "$x = (sel#)"), expected); + UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "SELECT (sel#)"), expected); + UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "SELECT * FROM t WHERE (sel#)"), expected); } Y_UNIT_TEST(Upsert) { @@ -1813,6 +1815,26 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); } + Y_UNIT_TEST(ColumnAtSubqueryExpresson) { + auto engine = MakeSqlCompletionEngineUT(); + + TVector<TString> input = { + R"sql(SELECT (SELECT # FROM (SELECT 1 AS a));)sql", + R"sql(SELECT 1 + (SELECT # FROM (SELECT 1 AS a));)sql", + R"sql(SELECT * FROM t WHERE (SELECT # FROM (SELECT 1 AS a));)sql", + R"sql(SELECT * FROM t WHERE 1 < (SELECT # FROM (SELECT 1 AS a));)sql", + }; + + TVector<TCandidate> expected = { + {ColumnName, "a"}, + }; + + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, input[0]), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, input[1]), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, input[2]), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, input[3]), expected); + } + Y_UNIT_TEST(NoBindingAtQuoted) { auto engine = MakeSqlCompletionEngineUT(); diff --git a/yql/essentials/sql/v1/format/sql_format.cpp b/yql/essentials/sql/v1/format/sql_format.cpp index 4308e2b2baf..d76b64df3b7 100644 --- a/yql/essentials/sql/v1/format/sql_format.cpp +++ b/yql/essentials/sql/v1/format/sql_format.cpp @@ -1,5 +1,7 @@ #include "sql_format.h" +#include <yql/essentials/sql/v1/proto_parser/parse_tree.h> + #include <yql/essentials/parser/lexer_common/lexer.h> #include <yql/essentials/core/sql_types/simple_types.h> @@ -20,10 +22,10 @@ namespace NSQLFormat { namespace { using namespace NSQLv1Generated; +using namespace NSQLTranslationV1; using NSQLTranslation::TParsedToken; using NSQLTranslation::TParsedTokenList; -using NSQLTranslationV1::IsProbablyKeyword; using TTokenIterator = TParsedTokenList::const_iterator; TTokenIterator SkipWS(TTokenIterator curr, TTokenIterator end) { @@ -568,6 +570,14 @@ private: ForceExpandedColumn_ = paren.GetColumn(); ForceExpandedLine_ = paren.GetLine(); suppressExpr = true; + } else if (descr == TRule_smart_parenthesis::GetDescriptor()) { + const auto& value = dynamic_cast<const TRule_smart_parenthesis&>(msg); + if (IsSelect(value)) { + auto& paren = value.GetToken1(); + ForceExpandedColumn_ = paren.GetColumn(); + ForceExpandedLine_ = paren.GetLine(); + suppressExpr = true; + } } else if (descr == TRule_case_expr::GetDescriptor()) { const auto& value = dynamic_cast<const TRule_case_expr&>(msg); auto& token = value.GetToken1(); @@ -703,7 +713,13 @@ private: return true; case TRule_sql_stmt_core::kAltSqlStmtCore3: { // named nodes const auto& stmt = msg.GetAlt_sql_stmt_core3().GetRule_named_nodes_stmt1(); - if (stmt.GetBlock3().HasAlt1()) { + + const bool isSelect = ( + (stmt.GetBlock3().HasAlt1() && + IsSelect(stmt.GetBlock3().GetAlt1().GetRule_expr1())) || + (stmt.GetBlock3().HasAlt2())); + + if (!isSelect) { return true; } break; @@ -856,6 +872,42 @@ private: } } + void VisitSmartParenthesis(const TRule_smart_parenthesis& msg) { + if (!IsSelect(msg)) { + return VisitAllFields(msg.GetDescriptor(), msg); + } + + Y_ENSURE(msg.GetBlock2().HasAlt1()); + + Visit(msg.GetToken1()); + PushCurrentIndent(); + NewLine(); + Visit(msg.GetBlock2().GetAlt1().GetRule_select_subexpr1()); + NewLine(); + PopCurrentIndent(); + Visit(msg.GetToken3()); + } + + void VisitSelectSubExpr(const TRule_select_subexpr& msg) { + Visit(msg.GetRule_select_subexpr_intersect1()); + for (const auto& block : msg.GetBlock2()) { + NewLine(); + Visit(block.GetRule_union_op1()); + NewLine(); + Visit(block.GetRule_select_subexpr_intersect2()); + } + } + + void VisitSelectSubExprIntersect(const TRule_select_subexpr_intersect& msg) { + Visit(msg.GetRule_select_or_expr1()); + for (const auto& block : msg.GetBlock2()) { + NewLine(); + Visit(block.GetRule_intersect_op1()); + NewLine(); + Visit(block.GetRule_select_or_expr2()); + } + } + void VisitSelectUnparenthesized(const TRule_select_unparenthesized_stmt& msg) { NewLine(); Visit(msg.GetRule_select_unparenthesized_stmt_intersect1()); @@ -893,36 +945,13 @@ private: case TRule_named_nodes_stmt::TBlock3::kAlt2: { const auto& alt = msg.GetBlock3().GetAlt2(); - const auto& subselect = alt.GetRule_subselect_stmt1(); - switch (subselect.GetBlock1().Alt_case()) { - case TRule_subselect_stmt::TBlock1::kAlt1: { - const auto& alt = subselect.GetBlock1().GetAlt1(); - Visit(alt.GetToken1()); - NewLine(); - PushCurrentIndent(); - Visit(alt.GetRule_select_stmt2()); - PopCurrentIndent(); - NewLine(); - Visit(alt.GetToken3()); - break; - } - - case TRule_subselect_stmt::TBlock1::kAlt2: { - const auto& alt = subselect.GetBlock1().GetAlt2(); - Out(" ("); - NewLine(); - PushCurrentIndent(); - Visit(alt); - PopCurrentIndent(); - NewLine(); - Out(')'); - break; - } - - default: - ythrow yexception() << "Alt is not supported"; - } - + Out(" ("); + NewLine(); + PushCurrentIndent(); + Visit(alt); + PopCurrentIndent(); + NewLine(); + Out(')'); break; } @@ -1092,39 +1121,43 @@ private: PopCurrentIndent(); Visit(targets.GetToken4()); Visit(multiColumn.GetToken2()); - Visit(multiColumn.GetToken3()); - NewLine(); - const auto& simpleValues = multiColumn.GetRule_simple_values_source4(); - switch (simpleValues.Alt_case()) { - case TRule_simple_values_source::kAltSimpleValuesSource1: { - const auto& exprs = simpleValues.GetAlt_simple_values_source1().GetRule_expr_list1(); - NewLine(); - PushCurrentIndent(); - Visit(exprs.GetRule_expr1()); - for (const auto& block : exprs.GetBlock2()) { - Visit(block.GetToken1()); - NewLine(); - Visit(block.GetRule_expr2()); - } - PopCurrentIndent(); - NewLine(); + const auto& parenthesis = multiColumn.GetRule_smart_parenthesis3(); + + const auto* tuple_or_expr = GetTupleOrExpr(parenthesis); + if (!tuple_or_expr) { + Visit(parenthesis); break; } - case TRule_simple_values_source::kAltSimpleValuesSource2: { - NewLine(); - PushCurrentIndent(); - Visit(simpleValues.GetAlt_simple_values_source2()); - PopCurrentIndent(); - NewLine(); + + const bool isHeadNamed = tuple_or_expr->HasBlock2(); + const bool isTailNamed = AnyOf(tuple_or_expr->GetBlock3(), [](const auto& block) { + return block.GetRule_named_expr2().HasBlock2(); + }); + if (isHeadNamed || isTailNamed) { + Visit(parenthesis); break; } - default: - ythrow yexception() << "Alt is not supported"; + + + Visit(parenthesis.GetToken1()); + PushCurrentIndent(); + NewLine(); + + Visit(tuple_or_expr->GetRule_expr1()); + for (auto& block : tuple_or_expr->GetBlock3()) { + Visit(block.GetToken1()); + NewLine(); + Visit(block.GetRule_named_expr2().GetRule_expr1()); + } + if (tuple_or_expr->HasBlock4()) { + Visit(tuple_or_expr->GetBlock4().GetToken1()); } NewLine(); - Visit(multiColumn.GetToken5()); + PopCurrentIndent(); + Visit(parenthesis.GetToken3()); + break; } default: @@ -2576,21 +2609,6 @@ private: NewLine(); } - void VisitInAtomExpr(const TRule_in_atom_expr& msg) { - if (msg.Alt_case() == TRule_in_atom_expr::kAltInAtomExpr7) { - const auto& alt = msg.GetAlt_in_atom_expr7(); - Visit(alt.GetToken1()); - NewLine(); - PushCurrentIndent(); - Visit(alt.GetRule_select_stmt2()); - NewLine(); - PopCurrentIndent(); - Visit(alt.GetToken3()); - } else { - VisitAllFields(TRule_in_atom_expr::GetDescriptor(), msg); - } - } - void VisitSelectKindParenthesis(const TRule_select_kind_parenthesis& msg) { if (msg.Alt_case() == TRule_select_kind_parenthesis::kAltSelectKindParenthesis2) { const auto& alt = msg.GetAlt_select_kind_parenthesis2(); @@ -3063,7 +3081,6 @@ TStaticData::TStaticData() {TRule_window_specification::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitWindowSpecification)}, {TRule_window_partition_clause::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitWindowParitionClause)}, {TRule_lambda_body::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitLambdaBody)}, - {TRule_in_atom_expr::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitInAtomExpr)}, {TRule_select_kind_parenthesis::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSelectKindParenthesis)}, {TRule_cast_expr::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitCastExpr)}, {TRule_bitcast_expr::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitBitCastExpr)}, @@ -3092,6 +3109,9 @@ TStaticData::TStaticData() {TRule_pragma_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitPragma)}, {TRule_select_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSelect)}, {TRule_select_stmt_intersect::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSelectIntersect)}, + {TRule_smart_parenthesis::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSmartParenthesis)}, + {TRule_select_subexpr::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSelectSubExpr)}, + {TRule_select_subexpr_intersect::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSelectSubExprIntersect)}, {TRule_select_unparenthesized_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSelectUnparenthesized)}, {TRule_select_unparenthesized_stmt_intersect::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSelectUnparenthesizedIntersect)}, {TRule_named_nodes_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitNamedNodes)}, diff --git a/yql/essentials/sql/v1/format/sql_format_ut.h b/yql/essentials/sql/v1/format/sql_format_ut.h index e33858d0b62..d487b956e37 100644 --- a/yql/essentials/sql/v1/format/sql_format_ut.h +++ b/yql/essentials/sql/v1/format/sql_format_ut.h @@ -2001,3 +2001,90 @@ Y_UNIT_TEST(DropStreamingQuery) { TSetup setup; setup.Run(cases); } + +Y_UNIT_TEST(NamedNodeNewLine) { + TString input = R"sql( +DEFINE SUBQUERY $x() AS + $a = SELECT 1; + $b = SELECT $a; + SELECT $b; +END DEFINE; +)sql"; + + TString expected = R"sql( +DEFINE SUBQUERY $x() AS + $a = ( + SELECT + 1 + ); + + $b = ( + SELECT + $a + ); + + SELECT + $b + ; +END DEFINE; +)sql"; + + input.erase(0, 1); + expected.erase(0, 1); + + TCases cases = { + {input, expected}, + }; + + TSetup setup; + setup.Run(cases); +} + +Y_UNIT_TEST(InlineSubquery) { + TString input = R"sql( +SELECT (SELECT 1); +SELECT (SELECT * FROM t WHERE p); +SELECT * FROM t WHERE x > (SELECT 1); +)sql"; + + TString expected = R"sql( +SELECT + ( + SELECT + 1 + ) +; + +SELECT + ( + SELECT + * + FROM + t + WHERE + p + ) +; + +SELECT + * +FROM + t +WHERE + x > ( + SELECT + 1 + ) +; +)sql"; + + input.erase(0, 1); + expected.erase(0, 1); + + TCases cases = { + {input, expected}, + }; + + TSetup setup; + setup.Run(cases); +} diff --git a/yql/essentials/sql/v1/proto_parser/parse_tree.cpp b/yql/essentials/sql/v1/proto_parser/parse_tree.cpp new file mode 100644 index 00000000000..a9318368436 --- /dev/null +++ b/yql/essentials/sql/v1/proto_parser/parse_tree.cpp @@ -0,0 +1,110 @@ +#include "parse_tree.h" + +namespace NSQLTranslationV1 { + + const TRule_select_or_expr* GetSelectOrExpr(const TRule_smart_parenthesis& msg) { + if (!msg.GetBlock2().HasAlt1()) { + return nullptr; + } + + return &msg.GetBlock2() + .GetAlt1() + .GetRule_select_subexpr1() + .GetRule_select_subexpr_intersect1() + .GetRule_select_or_expr1(); + } + + const TRule_tuple_or_expr* GetTupleOrExpr(const TRule_smart_parenthesis& msg) { + const auto* select_or_expr = GetSelectOrExpr(msg); + if (!select_or_expr) { + return nullptr; + } + + if (!select_or_expr->HasAlt_select_or_expr2()) { + return nullptr; + } + + return &select_or_expr + ->GetAlt_select_or_expr2() + .GetRule_tuple_or_expr1(); + } + + const TRule_smart_parenthesis* GetParenthesis(const TRule_expr& msg) { + if (!msg.HasAlt_expr1()) { + return nullptr; + } + + const auto& con = msg.GetAlt_expr1() + .GetRule_or_subexpr1() + .GetRule_and_subexpr1() + .GetRule_xor_subexpr1() + .GetRule_eq_subexpr1() + .GetRule_neq_subexpr1() + .GetRule_bit_subexpr1() + .GetRule_add_subexpr1() + .GetRule_mul_subexpr1() + .GetRule_con_subexpr1(); + + if (!con.HasAlt_con_subexpr1()) { + return nullptr; + } + + const auto& unary_subexpr = con.GetAlt_con_subexpr1() + .GetRule_unary_subexpr1(); + + if (!unary_subexpr.HasAlt_unary_subexpr1()) { + return nullptr; + } + + const auto& block = unary_subexpr.GetAlt_unary_subexpr1() + .GetRule_unary_casual_subexpr1() + .GetBlock1(); + + if (!block.HasAlt2()) { + return nullptr; + } + + const auto& atom = block.GetAlt2() + .GetRule_atom_expr1(); + + if (!atom.HasAlt_atom_expr3()) { + return nullptr; + } + + return &atom.GetAlt_atom_expr3() + .GetRule_lambda1() + .GetRule_smart_parenthesis1(); + } + + bool IsSelect(const TRule_smart_parenthesis& msg) { + const auto* select_or_expr = GetSelectOrExpr(msg); + if (!select_or_expr) { + return false; + } + + if (select_or_expr->HasAlt_select_or_expr1()) { + return true; + } + + return IsSelect( + select_or_expr + ->GetAlt_select_or_expr2() + .GetRule_tuple_or_expr1() + .GetRule_expr1()); + } + + bool IsSelect(const TRule_expr& msg) { + const auto* parenthesis = GetParenthesis(msg); + if (!parenthesis) { + return false; + } + + return IsSelect(*parenthesis); + } + + bool IsOnlySubExpr(const TRule_select_subexpr& node) { + return node.GetBlock2().size() == 0 && + node.GetRule_select_subexpr_intersect1().GetBlock2().size() == 0; + } + +} // namespace NSQLTranslationV1 diff --git a/yql/essentials/sql/v1/proto_parser/parse_tree.h b/yql/essentials/sql/v1/proto_parser/parse_tree.h new file mode 100644 index 00000000000..74936d90466 --- /dev/null +++ b/yql/essentials/sql/v1/proto_parser/parse_tree.h @@ -0,0 +1,17 @@ +#pragma once + +#include <yql/essentials/parser/proto_ast/gen/v1_proto_split/SQLv1Parser.pb.main.h> + +namespace NSQLTranslationV1 { + + using namespace NSQLv1Generated; + + const TRule_tuple_or_expr* GetTupleOrExpr(const TRule_smart_parenthesis& msg); + + bool IsSelect(const TRule_smart_parenthesis& msg); + + bool IsSelect(const TRule_expr& msg); + + bool IsOnlySubExpr(const TRule_select_subexpr& msg); + +} // namespace NSQLTranslationV1 diff --git a/yql/essentials/sql/v1/proto_parser/ya.make b/yql/essentials/sql/v1/proto_parser/ya.make index d87741f8a91..0fbe9f45469 100644 --- a/yql/essentials/sql/v1/proto_parser/ya.make +++ b/yql/essentials/sql/v1/proto_parser/ya.make @@ -3,9 +3,11 @@ LIBRARY() PEERDIR( yql/essentials/utils yql/essentials/parser/proto_ast/collect_issues + yql/essentials/parser/proto_ast/gen/v1_proto_split ) SRCS( + parse_tree.cpp proto_parser.cpp ) @@ -17,4 +19,3 @@ RECURSE( antlr4 antlr4_ansi ) - diff --git a/yql/essentials/sql/v1/select.cpp b/yql/essentials/sql/v1/select.cpp index c6ba387e7bd..41689db592c 100644 --- a/yql/essentials/sql/v1/select.cpp +++ b/yql/essentials/sql/v1/select.cpp @@ -662,6 +662,10 @@ TNodePtr BuildSubqueryRef(TNodePtr subquery, const TString& alias, int tupleInde return new TSubqueryRefNode(std::move(subquery), alias, tupleIndex); } +bool IsSubqueryRef(const TSourcePtr& source) { + return dynamic_cast<const TSubqueryRefNode*>(source.Get()) != nullptr; +} + class TInvalidSubqueryRefNode: public ISource { public: TInvalidSubqueryRefNode(TPosition pos) diff --git a/yql/essentials/sql/v1/source.cpp b/yql/essentials/sql/v1/source.cpp index 613c521da6e..1a5f9d1cd09 100644 --- a/yql/essentials/sql/v1/source.cpp +++ b/yql/essentials/sql/v1/source.cpp @@ -939,7 +939,7 @@ bool ISource::InitFilters(TContext& ctx) { } TAstNode* ISource::Translate(TContext& ctx) const { - Y_DEBUG_ABORT_UNLESS(false); + Y_DEBUG_ABORT_UNLESS(false, "Can't tranlsate ISource, maybe it is used in a scalar context"); Y_UNUSED(ctx); return nullptr; } @@ -991,6 +991,16 @@ TNodePtr ISource::BuildMatchRecognize(TContext& ctx, TString&& inputTable){ return MatchRecognizeBuilder_->Build(ctx, std::move(inputTable), this); }; +TSourcePtr MoveOutIfSource(TNodePtr& node) { + ISource* source = dynamic_cast<ISource*>(node.Get()); + if (!source) { + return nullptr; + } + + YQL_ENSURE(source == node.Release()); + return source; +} + IJoin::IJoin(TPosition pos) : ISource(pos) { diff --git a/yql/essentials/sql/v1/source.h b/yql/essentials/sql/v1/source.h index 20f81135cd4..bd3a0327cb2 100644 --- a/yql/essentials/sql/v1/source.h +++ b/yql/essentials/sql/v1/source.h @@ -164,6 +164,8 @@ namespace NSQLTranslationV1 { return cloneArgs; } + TSourcePtr MoveOutIfSource(TNodePtr& node); + struct TJoinLinkSettings { enum class EStrategy { Default, @@ -243,6 +245,8 @@ namespace NSQLTranslationV1 { // Implemented in select.cpp TNodePtr BuildSubquery(TSourcePtr source, const TString& alias, bool inSubquery, int ensureTupleSize, TScopedStatePtr scoped); TNodePtr BuildSubqueryRef(TNodePtr subquery, const TString& alias, int tupleIndex = -1); + bool IsSubqueryRef(const TSourcePtr& source); + TNodePtr BuildInvalidSubqueryRef(TPosition subqueryPos); TNodePtr BuildSourceNode(TPosition pos, TSourcePtr source, bool checkExist = false, bool withTables = false); TSourcePtr BuildMuxSource(TPosition pos, TVector<TSourcePtr>&& sources); diff --git a/yql/essentials/sql/v1/sql_expression.cpp b/yql/essentials/sql/v1/sql_expression.cpp index 4451e58a9b6..79d7c7c66b7 100644 --- a/yql/essentials/sql/v1/sql_expression.cpp +++ b/yql/essentials/sql/v1/sql_expression.cpp @@ -2,10 +2,12 @@ #include "sql_call_expr.h" #include "sql_select.h" #include "sql_values.h" +#include <yql/essentials/sql/v1/proto_parser/parse_tree.h> #include <yql/essentials/utils/utf8.h> #include <util/charset/wide.h> #include <util/string/ascii.h> #include <util/string/hex.h> +#include <util/generic/scope.h> #include "antlr_token.h" namespace NSQLTranslationV1 { @@ -15,7 +17,7 @@ using NALPDefaultAntlr4::SQLv1Antlr4Lexer; using namespace NSQLv1Generated; -TNodePtr TSqlExpression::Build(const TRule_expr& node) { +TNodePtr TSqlExpression::BuildSourceOrNode(const TRule_expr& node) { // expr: // or_subexpr (OR or_subexpr)* // | type_name_composite @@ -33,6 +35,16 @@ TNodePtr TSqlExpression::Build(const TRule_expr& node) { } } +TNodePtr TSqlExpression::Build(const TRule_expr& node) { + const bool prevIsSourceAllowed = IsSourceAllowed_; + Y_DEFER { + IsSourceAllowed_ = prevIsSourceAllowed; + }; + + IsSourceAllowed_ = false; + return BuildSourceOrNode(node); +} + TNodePtr TSqlExpression::Build(const TRule_lambda_or_parameter& node) { // lambda_or_parameter: // lambda @@ -58,6 +70,24 @@ TNodePtr TSqlExpression::Build(const TRule_lambda_or_parameter& node) { } } +TSourcePtr TSqlExpression::BuildSource(const TRule_select_or_expr& node) { + TNodePtr result = SelectOrExpr(node); + if (!result) { + return nullptr; + } + + if (TSourcePtr source = MoveOutIfSource(result)) { + return source; + } + + Ctx_.Error(result->GetPos()) << "Expected SELECT/PROCESS/REDUCE statement"; + return nullptr; +} + +TNodePtr TSqlExpression::BuildSourceOrNode(const TRule_smart_parenthesis& node) { + return SmartParenthesis(node); +} + TNodePtr TSqlExpression::SubExpr(const TRule_mul_subexpr& node, const TTrailingQuestions& tail) { // mul_subexpr: con_subexpr (DOUBLE_PIPE con_subexpr)*; auto getNode = [](const TRule_mul_subexpr::TBlock2& b) -> const TRule_con_subexpr& { return b.GetRule_con_subexpr2(); }; @@ -1520,40 +1550,20 @@ TMaybe<TExprOrIdent> TSqlExpression::InAtomExpr(const TRule_in_atom_expr& node, break; } case TRule_in_atom_expr::kAltInAtomExpr7: { - Token(node.GetAlt_in_atom_expr7().GetToken1()); - // reset column reference scope (select will reenable it where needed) - TColumnRefScope scope(Ctx_, EColumnRefState::Deny); - TSqlSelect select(Ctx_, Mode_); - TPosition pos; - auto source = select.Build(node.GetAlt_in_atom_expr7().GetRule_select_stmt2(), pos); - if (!source) { - Ctx_.IncrementMonCounter("sql_errors", "BadSource"); - return {}; - } - Ctx_.IncrementMonCounter("sql_features", "InSubquery"); - const auto alias = Ctx_.MakeName("subquerynode"); - const auto ref = Ctx_.MakeName("subquery"); - auto& blocks = Ctx_.GetCurrentBlocks(); - blocks.push_back(BuildSubquery(std::move(source), alias, Mode_ == NSQLTranslation::ESqlMode::SUBQUERY, -1, Ctx_.Scoped)); - blocks.back()->SetLabel(ref); - result.Expr = BuildSubqueryRef(blocks.back(), ref, -1); + result.Expr = ValueConstructor(node.GetAlt_in_atom_expr7().GetRule_value_constructor1()); break; } - case TRule_in_atom_expr::kAltInAtomExpr8: { - result.Expr = ValueConstructor(node.GetAlt_in_atom_expr8().GetRule_value_constructor1()); + case TRule_in_atom_expr::kAltInAtomExpr8: + result.Expr = BitCastRule(node.GetAlt_in_atom_expr8().GetRule_bitcast_expr1()); break; - } case TRule_in_atom_expr::kAltInAtomExpr9: - result.Expr = BitCastRule(node.GetAlt_in_atom_expr9().GetRule_bitcast_expr1()); + result.Expr = ListLiteral(node.GetAlt_in_atom_expr9().GetRule_list_literal1()); break; case TRule_in_atom_expr::kAltInAtomExpr10: - result.Expr = ListLiteral(node.GetAlt_in_atom_expr10().GetRule_list_literal1()); + result.Expr = DictLiteral(node.GetAlt_in_atom_expr10().GetRule_dict_literal1()); break; case TRule_in_atom_expr::kAltInAtomExpr11: - result.Expr = DictLiteral(node.GetAlt_in_atom_expr11().GetRule_dict_literal1()); - break; - case TRule_in_atom_expr::kAltInAtomExpr12: - result.Expr = StructLiteral(node.GetAlt_in_atom_expr12().GetRule_struct_literal1()); + result.Expr = StructLiteral(node.GetAlt_in_atom_expr11().GetRule_struct_literal1()); break; case TRule_in_atom_expr::ALT_NOT_SET: AltNotImplemented("in_atom_expr", node); @@ -2310,25 +2320,119 @@ TNodePtr TSqlExpression::SqlInExpr(const TRule_in_expr& node, const TTrailingQue TSqlExpression expr(Ctx_, Mode_); expr.SetSmartParenthesisMode(TSqlExpression::ESmartParenthesis::InStatement); auto result = expr.UnaryExpr(node.GetRule_in_unary_subexpr1(), tail); + + if (TSourcePtr source = MoveOutIfSource(result)) { + if (IsSubqueryRef(source)) { // Prevent redundant ref to ref + return source; + } + + Ctx_.IncrementMonCounter("sql_features", "InSubquery"); + + const auto alias = Ctx_.MakeName("subquerynode"); + const auto ref = Ctx_.MakeName("subquery"); + + auto& blocks = Ctx_.GetCurrentBlocks(); + blocks.emplace_back(BuildSubquery( + std::move(source), + alias, + /* inSubquery = */ Mode_ == NSQLTranslation::ESqlMode::SUBQUERY, + /* ensureTupleSize = */ -1, + Ctx_.Scoped)); + blocks.back()->SetLabel(ref); + + return BuildSubqueryRef(blocks.back(), ref, /* tupleIndex = */ -1); + } + return result; } -TNodePtr TSqlExpression::SmartParenthesis(const TRule_smart_parenthesis& node) { +bool TSqlExpression::IsTopLevelGroupBy() const { + return MaybeUnnamedSmartParenOnTop_ && + SmartParenthesisMode_ == ESmartParenthesis::GroupBy; +} + +TSourcePtr TSqlExpression::LangVersionedSubSelect(TSourcePtr source) { + if (!source) { + return nullptr; + } + + if (!IsSourceAllowed_ && !IsBackwardCompatibleFeatureAvailable(MakeLangVersion(2025, 04))) { + Ctx_.Error(source->GetPos()) << "Inline subquery is not available before 2025.04"; + return nullptr; + } + + return source; +} + +TNodePtr TSqlExpression::SelectSubExpr(const TRule_select_subexpr& node) { + TNodePtr result; + if (IsOnlySubExpr(node)) { + result = SelectOrExpr(node.GetRule_select_subexpr_intersect1() + .GetRule_select_or_expr1()); + } else { + result = LangVersionedSubSelect(TSqlSelect(Ctx_, Mode_).BuildSubSelect(node)); + } + + if (TSourcePtr source = MoveOutIfSource(result)) { + if (IsSourceAllowed_ || IsSubqueryRef(source)) { + return source; + } + + source->UseAsInner(); + result = BuildSourceNode(source->GetPos(), std::move(source)); + } + + return result; +} + +TNodePtr TSqlExpression::SelectOrExpr(const TRule_select_or_expr& node) { + switch (node.Alt_case()) { + case NSQLv1Generated::TRule_select_or_expr::kAltSelectOrExpr1: { + const auto& select_kind = node.GetAlt_select_or_expr1().GetRule_select_kind_partial1(); + TSourcePtr source = TSqlSelect(Ctx_, Mode_).BuildSubSelect(select_kind); + return LangVersionedSubSelect(std::move(source)); + } + case NSQLv1Generated::TRule_select_or_expr::kAltSelectOrExpr2: + return TupleOrExpr(node.GetAlt_select_or_expr2().GetRule_tuple_or_expr1()); + case NSQLv1Generated::TRule_select_or_expr::ALT_NOT_SET: + Y_ABORT("You should change implementation according to grammar changes"); + } +} + +TNodePtr TSqlExpression::TupleOrExpr(const TRule_tuple_or_expr& node) { TVector<TNodePtr> exprs; - Token(node.GetToken1()); const TPosition pos(Ctx_.Pos()); - const bool isTuple = node.HasBlock3(); + + const bool isTuple = node.HasBlock4(); + bool expectTuple = SmartParenthesisMode_ == ESmartParenthesis::InStatement; EExpr mode = EExpr::Regular; if (SmartParenthesisMode_ == ESmartParenthesis::SqlLambdaParams) { mode = EExpr::SqlLambdaParams; expectTuple = true; } - if (node.HasBlock2() && !NamedExprList(node.GetBlock2().GetRule_named_expr_list1(), exprs, mode)) { - return {}; - } - bool topLevelGroupBy = MaybeUnnamedSmartParenOnTop_ && SmartParenthesisMode_ == ESmartParenthesis::GroupBy; + { + const auto& head = node.GetRule_expr1(); + const auto* headName = node.HasBlock2() ? &node.GetBlock2().GetRule_an_id_or_type2() : nullptr; + + bool isDefinitelyTuple = isTuple || expectTuple || !node.GetBlock3().empty(); + if ((!headName && !isDefinitelyTuple) || IsSelect(head)) { + return BuildSourceOrNode(head); + } + + exprs.emplace_back(NamedExpr(head, headName, mode)); + if (!exprs.back()) { + return nullptr; + } + + for (const auto& item : node.GetBlock3()) { + exprs.emplace_back(NamedExpr(item.GetRule_named_expr2(), mode)); + if (!exprs.back()) { + return nullptr; + } + } + } bool hasAliases = false; bool hasUnnamed = false; @@ -2338,19 +2442,16 @@ TNodePtr TSqlExpression::SmartParenthesis(const TRule_smart_parenthesis& node) { } else { hasUnnamed = true; } - if (hasAliases && hasUnnamed && !topLevelGroupBy) { + if (hasAliases && hasUnnamed && !IsTopLevelGroupBy()) { Ctx_.IncrementMonCounter("sql_errors", "AnonymousStructMembers"); Ctx_.Error(pos) << "Structure does not allow anonymous members"; return nullptr; } } - if (exprs.size() == 1 && hasUnnamed && !isTuple && !expectTuple) { - return exprs.back(); - } - if (topLevelGroupBy) { + if (IsTopLevelGroupBy()) { if (isTuple) { Ctx_.IncrementMonCounter("sql_errors", "SimpleTupleInGroupBy"); - Token(node.GetBlock3().GetToken1()); + Token(node.GetBlock4().GetToken1()); Ctx_.Error() << "Unexpected trailing comma in grouping elements list"; return nullptr; } @@ -2361,4 +2462,24 @@ TNodePtr TSqlExpression::SmartParenthesis(const TRule_smart_parenthesis& node) { return (hasUnnamed || expectTuple || exprs.size() == 0) ? BuildTuple(pos, exprs) : BuildStructure(pos, exprs); } +TNodePtr TSqlExpression::EmptyTuple() { + if (IsTopLevelGroupBy()) { + return BuildListOfNamedNodes(Ctx_.Pos(), TVector<TNodePtr>{}); + } + + return BuildTuple(Ctx_.Pos(), TVector<TNodePtr>{}); +} + +TNodePtr TSqlExpression::SmartParenthesis(const TRule_smart_parenthesis& node) { + Token(node.GetToken1()); + switch (node.GetBlock2().GetAltCase()) { + case NSQLv1Generated::TRule_smart_parenthesis_TBlock2::kAlt1: + return SelectSubExpr(node.GetBlock2().GetAlt1().GetRule_select_subexpr1()); + case NSQLv1Generated::TRule_smart_parenthesis_TBlock2::kAlt2: + return EmptyTuple(); + case NSQLv1Generated::TRule_smart_parenthesis_TBlock2::ALT_NOT_SET: + Y_ABORT("You should change implementation according to grammar changes"); + } +} + } // namespace NSQLTranslationV1 diff --git a/yql/essentials/sql/v1/sql_expression.h b/yql/essentials/sql/v1/sql_expression.h index 794f5c5db3f..3751d80aefd 100644 --- a/yql/essentials/sql/v1/sql_expression.h +++ b/yql/essentials/sql/v1/sql_expression.h @@ -21,8 +21,11 @@ public: { } + TNodePtr BuildSourceOrNode(const TRule_expr& node); TNodePtr Build(const TRule_expr& node); TNodePtr Build(const TRule_lambda_or_parameter& node); + TSourcePtr BuildSource(const TRule_select_or_expr& node); + TNodePtr BuildSourceOrNode(const TRule_smart_parenthesis& node); void SetSmartParenthesisMode(ESmartParenthesis mode) { SmartParenthesisMode_ = mode; @@ -127,10 +130,17 @@ private: Ctx_.Error(tail.Pos) << "Unexpected token '?' at the end of expression"; } + bool IsTopLevelGroupBy() const; + TSourcePtr LangVersionedSubSelect(TSourcePtr source); + TNodePtr SelectSubExpr(const TRule_select_subexpr& node); + TNodePtr SelectOrExpr(const TRule_select_or_expr& node); + TNodePtr TupleOrExpr(const TRule_tuple_or_expr& node); + TNodePtr EmptyTuple(); TNodePtr SmartParenthesis(const TRule_smart_parenthesis& node); ESmartParenthesis SmartParenthesisMode_ = ESmartParenthesis::Default; bool MaybeUnnamedSmartParenOnTop_ = true; + bool IsSourceAllowed_ = true; THashMap<TString, TNodePtr> ExprShortcuts_; }; diff --git a/yql/essentials/sql/v1/sql_query.cpp b/yql/essentials/sql/v1/sql_query.cpp index 5de1a6de2ef..3f31663f8b1 100644 --- a/yql/essentials/sql/v1/sql_query.cpp +++ b/yql/essentials/sql/v1/sql_query.cpp @@ -3943,30 +3943,18 @@ TSourcePtr TSqlQuery::Build(const TRule_set_clause_list& stmt) { TSourcePtr TSqlQuery::Build(const TRule_multiple_column_assignment& stmt) { TVector<TString> targetList; FillTargetList(*this, stmt.GetRule_set_target_list1(), targetList); - auto simpleValuesNode = stmt.GetRule_simple_values_source4(); + const TPosition pos(Ctx_.Pos()); - switch (simpleValuesNode.Alt_case()) { - case TRule_simple_values_source::kAltSimpleValuesSource1: { - TVector<TNodePtr> values; - TSqlExpression sqlExpr(Ctx_, Mode_); - if (!ExprList(sqlExpr, values, simpleValuesNode.GetAlt_simple_values_source1().GetRule_expr_list1())) { - return nullptr; - } - return BuildUpdateValues(pos, targetList, values); - } - case TRule_simple_values_source::kAltSimpleValuesSource2: { - TSqlSelect select(Ctx_, Mode_); - TPosition selectPos; - auto source = select.Build(simpleValuesNode.GetAlt_simple_values_source2().GetRule_select_stmt1(), selectPos); - if (!source) { - return nullptr; - } - return BuildWriteValues(pos, "UPDATE", targetList, std::move(source)); - } - case TRule_simple_values_source::ALT_NOT_SET: - Ctx_.IncrementMonCounter("sql_errors", "UnknownSimpleValuesSourceAlt"); - AltNotImplemented("simple_values_source", simpleValuesNode); - return nullptr; + auto parenthesis = stmt.GetRule_smart_parenthesis3(); + + TNodePtr node = TSqlExpression(Ctx_, Mode_).BuildSourceOrNode(parenthesis); + if (TSourcePtr source = MoveOutIfSource(node)) { + return BuildWriteValues(pos, "UPDATE", targetList, std::move(source)); + } else if (TTupleNode* tuple = dynamic_cast<TTupleNode*>(node.Get())) { + return BuildUpdateValues(pos, targetList, tuple->Elements()); + } else { + Error() << "Expected source or tuple, but got something else"; + return nullptr; } } diff --git a/yql/essentials/sql/v1/sql_select.cpp b/yql/essentials/sql/v1/sql_select.cpp index ea769eb5385..3d84bc9f532 100644 --- a/yql/essentials/sql/v1/sql_select.cpp +++ b/yql/essentials/sql/v1/sql_select.cpp @@ -1383,15 +1383,36 @@ bool TSqlSelect::IsAllQualifiedOp(const TRule& node) { template <typename TRule> requires std::same_as<TRule, TRule_select_stmt> || - std::same_as<TRule, TRule_select_unparenthesized_stmt> + std::same_as<TRule, TRule_select_unparenthesized_stmt> || + std::same_as<TRule, TRule_select_subexpr> TSourcePtr TSqlSelect::BuildStmt(const TRule& node, TPosition& pos) { TBuildExtra extra; - auto result = BuildUnionException(node, pos, extra); - pos = extra.FirstPos; + TSourcePtr result = BuildUnionException(node, pos, extra); + return BuildStmt(std::move(result), std::move(extra)); +} + +TSourcePtr TSqlSelect::BuildSubSelect(const TRule_select_kind_partial& node) { + TColumnRefScope scope(Ctx_, EColumnRefState::Deny); + + TPosition position; + TSelectKindResult result = SelectKind(node, position, /* placement = */ Nothing()); + + TBuildExtra extra = { + .First = result, + .FirstPos = position, + .Last = result, + }; + + return BuildStmt(std::move(result.Source), std::move(extra)); +} + +TSourcePtr TSqlSelect::BuildStmt(TSourcePtr result, TBuildExtra extra) { if (!result) { return nullptr; } + TPosition pos = extra.FirstPos; + if (extra.First.Source == extra.Last.Source) { return result; } @@ -1447,7 +1468,8 @@ TSourcePtr TSqlSelect::BuildStmt(const TRule& node, TPosition& pos) { template <typename TRule> requires std::same_as<TRule, TRule_select_stmt> || - std::same_as<TRule, TRule_select_unparenthesized_stmt> + std::same_as<TRule, TRule_select_unparenthesized_stmt> || + std::same_as<TRule, TRule_select_subexpr> TSourcePtr TSqlSelect::BuildUnionException(const TRule& node, TPosition& pos, TSqlSelect::TBuildExtra& extra) { const TSelectKindPlacement firstPlacement = { .IsFirstInSelectOp = true, @@ -1459,6 +1481,8 @@ TSourcePtr TSqlSelect::BuildUnionException(const TRule& node, TPosition& pos, TS first = BuildIntersection(node.GetRule_select_stmt_intersect1(), pos, firstPlacement, extra); } else if constexpr (std::is_same_v<TRule, TRule_select_unparenthesized_stmt>) { first = BuildIntersection(node.GetRule_select_unparenthesized_stmt_intersect1(), pos, firstPlacement, extra); + } else if constexpr (std::is_same_v<TRule, TRule_select_subexpr>) { + first = BuildIntersection(node.GetRule_select_subexpr_intersect1(), pos, firstPlacement, extra); } else { static_assert(false, "Change implementation according to grammar changes."); } @@ -1503,7 +1527,14 @@ TSourcePtr TSqlSelect::BuildUnionException(const TRule& node, TPosition& pos, TS .IsFirstInSelectOp = false, .IsLastInSelectOp = (i + 1 == tail.size()), }; - TSourcePtr next = BuildIntersection(nextBlock.GetRule_select_stmt_intersect2(), pos, nextPlacement, extra); + + TSourcePtr next; + if constexpr (std::is_same_v<TRule, TRule_select_subexpr>) { + next = BuildIntersection(nextBlock.GetRule_select_subexpr_intersect2(), pos, nextPlacement, extra); + } else { + next = BuildIntersection(nextBlock.GetRule_select_stmt_intersect2(), pos, nextPlacement, extra); + } + if (!next) { return nullptr; } @@ -1541,7 +1572,8 @@ TSourcePtr TSqlSelect::BuildUnionException(const TRule& node, TPosition& pos, TS template <typename TRule> requires std::same_as<TRule, TRule_select_stmt_intersect> || - std::same_as<TRule, TRule_select_unparenthesized_stmt_intersect> + std::same_as<TRule, TRule_select_unparenthesized_stmt_intersect> || + std::same_as<TRule, TRule_select_subexpr_intersect> TSourcePtr TSqlSelect::BuildIntersection( const TRule& node, TPosition& pos, @@ -1558,6 +1590,8 @@ TSourcePtr TSqlSelect::BuildIntersection( first = BuildAtom(node.GetRule_select_kind_parenthesis1(), pos, firstPlacement, extra); } else if constexpr (std::is_same_v<TRule, TRule_select_unparenthesized_stmt_intersect>) { first = BuildAtom(node.GetRule_select_kind_partial1(), pos, firstPlacement, extra); + } else if constexpr (std::is_same_v<TRule, TRule_select_subexpr_intersect>) { + first = BuildAtom(node.GetRule_select_or_expr1(), pos, firstPlacement, extra); } else { static_assert(false, "Change implementation according to grammar changes."); } @@ -1587,7 +1621,14 @@ TSourcePtr TSqlSelect::BuildIntersection( .IsFirstInSelectOp = false, .IsLastInSelectOp = (i + 1 == tail.size()) && placement.IsLastInSelectOp, }; - TSelectKindResult next = BuildAtom(nextBlock.GetRule_select_kind_parenthesis2(), pos, nextPlacement, extra); + + TSelectKindResult next; + if constexpr (std::is_same_v<TRule, TRule_select_subexpr_intersect>) { + next = BuildAtom(nextBlock.GetRule_select_or_expr2(), pos, nextPlacement, extra); + } else { + next = BuildAtom(nextBlock.GetRule_select_kind_parenthesis2(), pos, nextPlacement, extra); + } + if (!next) { return nullptr; } @@ -1601,7 +1642,8 @@ TSourcePtr TSqlSelect::BuildIntersection( template <typename TRule> requires std::same_as<TRule, TRule_select_kind_parenthesis> || - std::same_as<TRule, TRule_select_kind_partial> + std::same_as<TRule, TRule_select_kind_partial> || + std::same_as<TRule, TRule_select_or_expr> TSqlSelect::TSelectKindResult TSqlSelect::BuildAtom( const TRule& node, TPosition& pos, @@ -1609,7 +1651,21 @@ TSqlSelect::TSelectKindResult TSqlSelect::BuildAtom( TBuildExtra& extra) { TSqlSelect::TSelectKindResult result; - if (placement.IsFirstInSelectOp && placement.IsLastInSelectOp) { + if constexpr (std::is_same_v<TRule, TRule_select_or_expr>) { + switch (node.Alt_case()) { + case NSQLv1Generated::TRule_select_or_expr::kAltSelectOrExpr1: { + const auto& select_kind = node.GetAlt_select_or_expr1().GetRule_select_kind_partial1(); + result = SelectKind(select_kind, pos, placement); + break; + } + case NSQLv1Generated::TRule_select_or_expr::kAltSelectOrExpr2: { + result.Source = TSqlExpression(Ctx_, Mode_).BuildSource(node); + break; + } + case NSQLv1Generated::TRule_select_or_expr::ALT_NOT_SET: + Y_ABORT("You should change implementation according to grammar changes"); + } + } else if (placement.IsFirstInSelectOp && placement.IsLastInSelectOp) { result = SelectKind(node, pos, /* placement = */ Nothing()); } else { result = SelectKind(node, pos, placement); @@ -1633,4 +1689,10 @@ TSourcePtr TSqlSelect::Build(const TRule_select_unparenthesized_stmt& node, TPos return BuildStmt(node, selectPos); } +TSourcePtr TSqlSelect::BuildSubSelect(const TRule_select_subexpr& node) { + TColumnRefScope scope(Ctx_, EColumnRefState::Deny); + TPosition pos; + return BuildStmt(node, pos); +} + } // namespace NSQLTranslationV1 diff --git a/yql/essentials/sql/v1/sql_select.h b/yql/essentials/sql/v1/sql_select.h index f4097396745..9bd553d49ab 100644 --- a/yql/essentials/sql/v1/sql_select.h +++ b/yql/essentials/sql/v1/sql_select.h @@ -16,6 +16,8 @@ public: TSourcePtr Build(const TRule_select_stmt& node, TPosition& selectPos); TSourcePtr Build(const TRule_select_unparenthesized_stmt& node, TPosition& selectPos); + TSourcePtr BuildSubSelect(const TRule_select_kind_partial& node); + TSourcePtr BuildSubSelect(const TRule_select_subexpr& node); private: bool SelectTerm(TVector<TNodePtr>& terms, const TRule_result_column& node); @@ -75,22 +77,30 @@ private: template <typename TRule> requires std::same_as<TRule, TRule_select_stmt> || - std::same_as<TRule, TRule_select_unparenthesized_stmt> + std::same_as<TRule, TRule_select_unparenthesized_stmt> || + std::same_as<TRule, TRule_select_subexpr> TSourcePtr BuildStmt(const TRule& node, TPosition& pos); + TSourcePtr BuildStmt(const TRule_select_kind_partial& node); + + TSourcePtr BuildStmt(TSourcePtr result, TBuildExtra extra); + template <typename TRule> requires std::same_as<TRule, TRule_select_stmt> || - std::same_as<TRule, TRule_select_unparenthesized_stmt> + std::same_as<TRule, TRule_select_unparenthesized_stmt> || + std::same_as<TRule, TRule_select_subexpr> TSourcePtr BuildUnionException(const TRule& node, TPosition& pos, TBuildExtra& extra); template <typename TRule> requires std::same_as<TRule, TRule_select_stmt_intersect> || - std::same_as<TRule, TRule_select_unparenthesized_stmt_intersect> + std::same_as<TRule, TRule_select_unparenthesized_stmt_intersect> || + std::same_as<TRule, TRule_select_subexpr_intersect> TSourcePtr BuildIntersection(const TRule& node, TPosition& pos, TSelectKindPlacement placement, TBuildExtra& extra); template <typename TRule> requires std::same_as<TRule, TRule_select_kind_parenthesis> || - std::same_as<TRule, TRule_select_kind_partial> + std::same_as<TRule, TRule_select_kind_partial> || + std::same_as<TRule, TRule_select_or_expr> TSelectKindResult BuildAtom(const TRule& node, TPosition& pos, TSelectKindPlacement placement, TBuildExtra& extra); TSelectKindResult SelectKind(const TRule_select_kind& node, TPosition& selectPos, TMaybe<TSelectKindPlacement> placement); diff --git a/yql/essentials/sql/v1/sql_translation.cpp b/yql/essentials/sql/v1/sql_translation.cpp index 40d7a88776b..81d177cb51b 100644 --- a/yql/essentials/sql/v1/sql_translation.cpp +++ b/yql/essentials/sql/v1/sql_translation.cpp @@ -944,29 +944,39 @@ TTableHints GetTableFuncHints(TStringBuf funcName) { return res; } - -TNodePtr TSqlTranslation::NamedExpr(const TRule_named_expr& node, EExpr exprMode) { +TNodePtr TSqlTranslation::NamedExpr( + const TRule_expr& exprTree, + const TRule_an_id_or_type* nameTree, + EExpr exprMode) +{ TSqlExpression expr(Ctx_, Mode_); if (exprMode == EExpr::GroupBy) { expr.SetSmartParenthesisMode(TSqlExpression::ESmartParenthesis::GroupBy); } else if (exprMode == EExpr::SqlLambdaParams) { expr.SetSmartParenthesisMode(TSqlExpression::ESmartParenthesis::SqlLambdaParams); } - if (node.HasBlock2()) { + if (nameTree) { expr.MarkAsNamed(); } - TNodePtr exprNode(expr.Build(node.GetRule_expr1())); + TNodePtr exprNode = expr.Build(exprTree); if (!exprNode) { Ctx_.IncrementMonCounter("sql_errors", "NamedExprInvalid"); return nullptr; } - if (node.HasBlock2()) { + if (nameTree) { exprNode = SafeClone(exprNode); - exprNode->SetLabel(Id(node.GetBlock2().GetRule_an_id_or_type2(), *this)); + exprNode->SetLabel(Id(*nameTree, *this)); } return exprNode; } +TNodePtr TSqlTranslation::NamedExpr(const TRule_named_expr& node, EExpr exprMode) { + return NamedExpr( + node.GetRule_expr1(), + (node.HasBlock2() ? &node.GetBlock2().GetRule_an_id_or_type2() : nullptr), + exprMode); +} + bool TSqlTranslation::NamedExprList(const TRule_named_expr_list& node, TVector<TNodePtr>& exprs, EExpr exprMode) { exprs.emplace_back(NamedExpr(node.GetRule_named_expr1(), exprMode)); if (!exprs.back()) { @@ -3803,8 +3813,7 @@ bool TSqlTranslation::TopicRefImpl(const TRule_topic_ref& node, TTopicRef& resul } TNodePtr TSqlTranslation::NamedNode(const TRule_named_nodes_stmt& rule, TVector<TSymbolNameWithPos>& names) { - // named_nodes_stmt: bind_parameter_list EQUALS (expr | subselect_stmt); - // subselect_stmt: (LPAREN select_stmt RPAREN | select_unparenthesized_stmt); + // named_nodes_stmt: bind_parameter_list EQUALS (expr | select_unparenthesized_stmt); if (!BindList(rule.GetRule_bind_parameter_list1(), names)) { return {}; } @@ -3813,30 +3822,18 @@ TNodePtr TSqlTranslation::NamedNode(const TRule_named_nodes_stmt& rule, TVector< switch (rule.GetBlock3().Alt_case()) { case TRule_named_nodes_stmt::TBlock3::kAlt1: { TSqlExpression expr(Ctx_, Mode_); - auto result = expr.Build(rule.GetBlock3().GetAlt1().GetRule_expr1()); + auto result = expr.BuildSourceOrNode(rule.GetBlock3().GetAlt1().GetRule_expr1()); + if (TSourcePtr source = MoveOutIfSource(result)) { + result = BuildSourceNode(Ctx_.Pos(), std::move(source)); + } return result; } case TRule_named_nodes_stmt::TBlock3::kAlt2:{ - const auto& subselect_rule = rule.GetBlock3().GetAlt2().GetRule_subselect_stmt1(); + const auto& subselect_rule = rule.GetBlock3().GetAlt2().GetRule_select_unparenthesized_stmt1(); - TSqlSelect expr(Ctx_, Mode_); TPosition pos; - TSourcePtr source = nullptr; - switch (subselect_rule.GetBlock1().Alt_case()) { - case TRule_subselect_stmt::TBlock1::kAlt1: - source = expr.Build(subselect_rule.GetBlock1().GetAlt1().GetRule_select_stmt2(), pos); - break; - - case TRule_subselect_stmt::TBlock1::kAlt2: - source = expr.Build(subselect_rule.GetBlock1().GetAlt2().GetRule_select_unparenthesized_stmt1(), pos); - break; - - case TRule_subselect_stmt::TBlock1::ALT_NOT_SET: - AltNotImplemented("subselect_stmt", subselect_rule.GetBlock1()); - Ctx_.IncrementMonCounter("sql_errors", "UnknownNamedNode"); - return nullptr; - } + TSourcePtr source = TSqlSelect(Ctx_, Mode_).Build(subselect_rule, pos); if (!source) { return {}; diff --git a/yql/essentials/sql/v1/sql_translation.h b/yql/essentials/sql/v1/sql_translation.h index 63b961b0dae..6858ee3a588 100644 --- a/yql/essentials/sql/v1/sql_translation.h +++ b/yql/essentials/sql/v1/sql_translation.h @@ -135,6 +135,12 @@ protected: GroupBy, SqlLambdaParams, }; + + TNodePtr NamedExpr( + const TRule_expr& exprTree, + const TRule_an_id_or_type* nameTree, + EExpr exprMode = EExpr::Regular); + TNodePtr NamedExpr(const TRule_named_expr& node, EExpr exprMode = EExpr::Regular); bool NamedExprList(const TRule_named_expr_list& node, TVector<TNodePtr>& exprs, EExpr exprMode = EExpr::Regular); bool BindList(const TRule_bind_parameter_list& node, TVector<TSymbolNameWithPos>& bindNames); diff --git a/yql/essentials/sql/v1/sql_ut_common.h b/yql/essentials/sql/v1/sql_ut_common.h index 3837b892464..d6d86e9954b 100644 --- a/yql/essentials/sql/v1/sql_ut_common.h +++ b/yql/essentials/sql/v1/sql_ut_common.h @@ -2516,7 +2516,7 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) { SELECT * FROM $squ2; SELECT * FROM $squ3; )"); - UNIT_ASSERT(res.Root); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); } Y_UNIT_TEST(SubqueriesJoin) { @@ -10525,3 +10525,238 @@ return /*Комментарий*/ $x; UNIT_ASSERT_VALUES_EQUAL(0, NSQLTranslationV1::GetQueryPosition(query, tokenProto, antlr4)); } } + +Y_UNIT_TEST_SUITE(InlineUncorrelatedSubquery) { + Y_UNIT_TEST(EmptyTuple) { + NYql::TAstParseResult res = SqlToYql(R"sql( + SELECT (); + SELECT (()); + SELECT (,); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(ParenthesisedExpression) { + NYql::TAstParseResult res = SqlToYql(R"sql( + SELECT 1; + SELECT (1); + SELECT ((1)); + SELECT (((1))); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(Tuple) { + NYql::TAstParseResult res = SqlToYql(R"sql( + SELECT (1,); + SELECT (1, 2); + SELECT (1, 2, 3); + SELECT (1, 2, 3, 4); + SELECT ((1, 2)); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(Struct) { + NYql::TAstParseResult res = SqlToYql(R"sql( + SELECT (1 AS a); + SELECT (1 AS a, 2 AS b); + SELECT (1 AS a, 2 AS b, 3 AS c); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(Lambda) { + NYql::TAstParseResult res = SqlToYql(R"sql( + SELECT (($a) -> { RETURN $a; })(1); + SELECT (($a, $b) -> { RETURN $a + $b; })(1, 2); + SELECT (($a, $b, $c) -> { RETURN $a + $b + $c; })(1, 2, 3); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(AtProjection) { + NSQLTranslation::TTranslationSettings settings; + settings.LangVer = NYql::MakeLangVersion(2025, 4); + + NYql::TAstParseResult res = SqlToYqlWithSettings(R"sql( + SELECT (SELECT 1); + SELECT (SELECT (SELECT 1)); + )sql", settings); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(AtExpression) { + NSQLTranslation::TTranslationSettings settings; + settings.LangVer = NYql::MakeLangVersion(2025, 4); + + NYql::TAstParseResult res = SqlToYqlWithSettings(R"sql( + SELECT 1 + (SELECT 1); + SELECT (SELECT 1) + 1; + )sql", settings); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(UnionParenthesis) { + NSQLTranslation::TTranslationSettings settings; + settings.LangVer = NYql::MakeLangVersion(2025, 4); + + NYql::TAstParseResult res = SqlToYqlWithSettings(R"sql( + SELECT ( SELECT 1 UNION SELECT 1); + SELECT ( SELECT 1 UNION (SELECT 1)); + SELECT ((SELECT 1) UNION SELECT 1); + SELECT ((SELECT 1) UNION (SELECT 1)); + )sql", settings); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(IntersectParenthesis) { + NSQLTranslation::TTranslationSettings settings; + settings.LangVer = NYql::MakeLangVersion(2025, 4); + + NYql::TAstParseResult res = SqlToYqlWithSettings(R"sql( + SELECT ( SELECT 1 INTERSECT SELECT 1); + SELECT ( SELECT 1 INTERSECT (SELECT 1)); + SELECT ((SELECT 1) INTERSECT SELECT 1); + SELECT ((SELECT 1) INTERSECT (SELECT 1)); + )sql", settings); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(UnionIntersect) { + NSQLTranslation::TTranslationSettings settings; + settings.LangVer = NYql::MakeLangVersion(2025, 4); + + NYql::TAstParseResult res = SqlToYqlWithSettings(R"sql( + SELECT (SELECT 1 UNION SELECT 1 UNION SELECT 1); + SELECT (SELECT 1 UNION SELECT 1 INTERSECT SELECT 1); + SELECT (SELECT 1 INTERSECT SELECT 1 UNION SELECT 1); + SELECT (SELECT 1 INTERSECT SELECT 1 INTERSECT SELECT 1); + )sql", settings); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(ScalarExpressionUnion) { + NSQLTranslation::TTranslationSettings settings; + settings.LangVer = NYql::MakeLangVersion(2025, 4); + + NYql::TAstParseResult res = SqlToYqlWithSettings(R"sql( + SELECT ((2 + 2) UNION (2 * 2)); + )sql", settings); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_STRING_CONTAINS( + res.Issues.ToOneLineString(), + "2:24: Error: Expected SELECT/PROCESS/REDUCE statement"); + } + + Y_UNIT_TEST(OrderByIgnorance1) { + NSQLTranslation::TTranslationSettings settings; + settings.LangVer = NYql::MakeLangVersion(2025, 4); + + NYql::TAstParseResult res = SqlToYqlWithSettings(R"sql( + SELECT (SELECT * FROM (SELECT * FROM (SELECT 1 AS x UNION SELECT 2 AS x) ORDER BY x)); + + )sql", settings); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS( + res.Issues.ToOneLineString(), + "ORDER BY without LIMIT in subquery will be ignored"); + + TWordCountHive stat = {{TString("Sort"), 0}}; + VerifyProgram(res, stat); + UNIT_ASSERT_VALUES_EQUAL(stat["Sort"], 0); + } + + Y_UNIT_TEST(OrderByIgnorance2) { + NSQLTranslation::TTranslationSettings settings; + settings.LangVer = NYql::MakeLangVersion(2025, 4); + + NYql::TAstParseResult res = SqlToYqlWithSettings(R"sql( + SELECT (SELECT * FROM (SELECT 1 AS x UNION SELECT 2 AS x) ORDER BY x); + )sql", settings); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS( + res.Issues.ToOneLineString(), + "ORDER BY without LIMIT in subquery will be ignored"); + + TWordCountHive stat = {{TString("Sort"), 0}}; + VerifyProgram(res, stat); + UNIT_ASSERT_VALUES_EQUAL(stat["Sort"], 0); + } + + Y_UNIT_TEST(InSubquery) { + NYql::TAstParseResult res; + + res = SqlToYql(R"sql( + SELECT 1 IN (SELECT 1); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + + res = SqlToYql(R"sql( + SELECT * FROM (SELECT 1 AS x) WHERE x IN (SELECT 1); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + + res = SqlToYql(R"sql( + SELECT * FROM (SELECT 1 AS x) WHERE x IN ((SELECT 1)); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(GroupByUnit) { + NYql::TAstParseResult res = SqlToYql(R"sql( + SELECT * FROM (SELECT 1) GROUP BY (); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(NamedNodeUnion) { + NYql::TAstParseResult res = SqlToYql(R"sql( + $a = (SELECT 1); + $b = (SELECT 1); + $x = ($a UNION $b); + SELECT * FROM $x; + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(NamedNodeExpr) { + NYql::TAstParseResult res = SqlToYql(R"sql( + $a = 1; SELECT $a; + $b = SELECT 1; SELECT $b; + $c = (SELECT 1); SELECT $c; + $d = ((SELECT 1)); SELECT $d; + SELECT $b + 1; + SELECT ($b); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); + } + + Y_UNIT_TEST(NamedNodeProcess) { + NYql::TAstParseResult res = SqlToYql(R"sql( + $a = SELECT 1, 2; + $a = PROCESS $a; + SELECT $a; + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); + } + + Y_UNIT_TEST(SubqueryDeduplication) { + NYql::TAstParseResult res = SqlToYql(R"sql( + DEFINE SUBQUERY $sub() AS + SELECT * FROM (SELECT 1); + END DEFINE; + SELECT * FROM $sub(); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } + + Y_UNIT_TEST(NamedNode) { + NYql::TAstParseResult res = SqlToYql(R"sql( + $x = (SELECT 1 AS x); + SELECT 1 < $x; + SELECT 1 < ($x); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToOneLineString()); + } +} |