diff options
author | vitya-smirnov <[email protected]> | 2025-07-09 14:16:01 +0300 |
---|---|---|
committer | vitya-smirnov <[email protected]> | 2025-07-09 14:38:43 +0300 |
commit | 59c3167c208900bb714ff5cc477476e3ab14f4f0 (patch) | |
tree | 043a4d70e21ede0da9df08806fe5a8c0d656e201 /yql/essentials/sql/v1 | |
parent | 40a6e6e096b3533857b70c79a8ac9820655c7bf3 (diff) |
YQL-17269: Support UNION/INTERSECT/EXCEPT combinations
Introduce `UNION` and `INTERSECT/EXCEPT` grammar rules
for precedence. Rewrote `Build` procedure into `BuildStmt`,
`BuildUnion`, `BuildIntersection`. Added tests, modify format.
It took a lot of time trying to adapt the existing `Build` procedure.
The I noticed that the logic for `union` and `intersection` is
different, since `union` groups arguments into bundles, but
`intersection` is a strictly binary operation.
commit_hash:70008ae3c2603364b6dfbeeb189fdc7f5237433d
Diffstat (limited to 'yql/essentials/sql/v1')
-rw-r--r-- | yql/essentials/sql/v1/SQLv1.g.in | 12 | ||||
-rw-r--r-- | yql/essentials/sql/v1/SQLv1Antlr4.g.in | 12 | ||||
-rw-r--r-- | yql/essentials/sql/v1/complete/analysis/global/column.cpp | 2 | ||||
-rw-r--r-- | yql/essentials/sql/v1/format/sql_format.cpp | 28 | ||||
-rw-r--r-- | yql/essentials/sql/v1/format/sql_format_ut.h | 12 | ||||
-rw-r--r-- | yql/essentials/sql/v1/sql_select.cpp | 278 | ||||
-rw-r--r-- | yql/essentials/sql/v1/sql_select.h | 31 | ||||
-rw-r--r-- | yql/essentials/sql/v1/sql_ut_common.h | 46 |
8 files changed, 295 insertions, 126 deletions
diff --git a/yql/essentials/sql/v1/SQLv1.g.in b/yql/essentials/sql/v1/SQLv1.g.in index 96013b9a7fa..367eafe2b8d 100644 --- a/yql/essentials/sql/v1/SQLv1.g.in +++ b/yql/essentials/sql/v1/SQLv1.g.in @@ -365,13 +365,19 @@ sort_specification: expr (ASC | DESC)?; sort_specification_list: sort_specification (COMMA sort_specification)*; -select_stmt: select_kind_parenthesis (select_op select_kind_parenthesis)*; +select_stmt: select_stmt_intersect (union_op select_stmt_intersect)*; -select_unparenthesized_stmt: select_kind_partial (select_op select_kind_parenthesis)*; +select_stmt_intersect: select_kind_parenthesis (intersect_op select_kind_parenthesis)*; + +select_unparenthesized_stmt: select_unparenthesized_stmt_intersect (union_op select_stmt_intersect)*; + +select_unparenthesized_stmt_intersect: select_kind_partial (intersect_op select_kind_parenthesis)*; select_kind_parenthesis: select_kind_partial | LPAREN select_kind_partial RPAREN; -select_op: (UNION | INTERSECT | EXCEPT) (DISTINCT | ALL)?; +union_op: UNION (DISTINCT | ALL)?; + +intersect_op: (INTERSECT | EXCEPT) (DISTINCT | ALL)?; select_kind_partial: select_kind (LIMIT expr ((OFFSET | COMMA) expr)?)? diff --git a/yql/essentials/sql/v1/SQLv1Antlr4.g.in b/yql/essentials/sql/v1/SQLv1Antlr4.g.in index 5ac9de84998..99b3139dafa 100644 --- a/yql/essentials/sql/v1/SQLv1Antlr4.g.in +++ b/yql/essentials/sql/v1/SQLv1Antlr4.g.in @@ -364,13 +364,19 @@ sort_specification: expr (ASC | DESC)?; sort_specification_list: sort_specification (COMMA sort_specification)*; -select_stmt: select_kind_parenthesis (select_op select_kind_parenthesis)*; +select_stmt: select_stmt_intersect (union_op select_stmt_intersect)*; -select_unparenthesized_stmt: select_kind_partial (select_op select_kind_parenthesis)*; +select_stmt_intersect: select_kind_parenthesis (intersect_op select_kind_parenthesis)*; + +select_unparenthesized_stmt: select_unparenthesized_stmt_intersect (union_op select_stmt_intersect)*; + +select_unparenthesized_stmt_intersect: select_kind_partial (intersect_op select_kind_parenthesis)*; select_kind_parenthesis: select_kind_partial | LPAREN select_kind_partial RPAREN; -select_op: (UNION | INTERSECT | EXCEPT) (DISTINCT | ALL)?; +union_op: UNION (DISTINCT | ALL)?; + +intersect_op: (INTERSECT | EXCEPT) (DISTINCT | ALL)?; select_kind_partial: select_kind (LIMIT expr ((OFFSET | COMMA) expr)?)? diff --git a/yql/essentials/sql/v1/complete/analysis/global/column.cpp b/yql/essentials/sql/v1/complete/analysis/global/column.cpp index 9b6161dc3b8..8fbf90e0c36 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/column.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/column.cpp @@ -134,7 +134,7 @@ namespace NSQLComplete { } std::any visitSelect_stmt(SQLv1::Select_stmtContext* ctx) override { - return AccumulatingVisit(ctx->select_kind_parenthesis()); + return AccumulatingVisit(ctx->select_stmt_intersect()); } std::any visitSelect_core(SQLv1::Select_coreContext* ctx) override { diff --git a/yql/essentials/sql/v1/format/sql_format.cpp b/yql/essentials/sql/v1/format/sql_format.cpp index 4979a75d4e8..e45adc280d8 100644 --- a/yql/essentials/sql/v1/format/sql_format.cpp +++ b/yql/essentials/sql/v1/format/sql_format.cpp @@ -833,10 +833,21 @@ private: void VisitSelect(const TRule_select_stmt& msg) { NewLine(); + Visit(msg.GetRule_select_stmt_intersect1()); + for (const auto& block : msg.GetBlock2()) { + NewLine(); + Visit(block.GetRule_union_op1()); + NewLine(); + Visit(block.GetRule_select_stmt_intersect2()); + } + } + + void VisitSelectIntersect(const TRule_select_stmt_intersect& msg) { + NewLine(); Visit(msg.GetRule_select_kind_parenthesis1()); for (const auto& block : msg.GetBlock2()) { NewLine(); - Visit(block.GetRule_select_op1()); + Visit(block.GetRule_intersect_op1()); NewLine(); Visit(block.GetRule_select_kind_parenthesis2()); } @@ -844,10 +855,21 @@ private: void VisitSelectUnparenthesized(const TRule_select_unparenthesized_stmt& msg) { NewLine(); + Visit(msg.GetRule_select_unparenthesized_stmt_intersect1()); + for (const auto& block : msg.GetBlock2()) { + NewLine(); + Visit(block.GetRule_union_op1()); + NewLine(); + Visit(block.GetRule_select_stmt_intersect2()); + } + } + + void VisitSelectUnparenthesizedIntersect(const TRule_select_unparenthesized_stmt_intersect& msg) { + NewLine(); Visit(msg.GetRule_select_kind_partial1()); for (const auto& block : msg.GetBlock2()) { NewLine(); - Visit(block.GetRule_select_op1()); + Visit(block.GetRule_intersect_op1()); NewLine(); Visit(block.GetRule_select_kind_parenthesis2()); } @@ -3012,7 +3034,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_select_unparenthesized_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSelectUnparenthesized)}, + {TRule_select_unparenthesized_stmt_intersect::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitSelectUnparenthesizedIntersect)}, {TRule_named_nodes_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitNamedNodes)}, {TRule_create_table_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitCreateTable)}, {TRule_drop_table_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitDropTable)}, diff --git a/yql/essentials/sql/v1/format/sql_format_ut.h b/yql/essentials/sql/v1/format/sql_format_ut.h index 35e95634e63..7f77d456a15 100644 --- a/yql/essentials/sql/v1/format/sql_format_ut.h +++ b/yql/essentials/sql/v1/format/sql_format_ut.h @@ -1576,6 +1576,18 @@ Y_UNIT_TEST(Except) { setup.Run(cases); } +Y_UNIT_TEST(UnionIntersectExcept) { + TCases cases = { + {"select 1 union select 2 intersect select 3 except select 4", + "SELECT\n\t1\nUNION\nSELECT\n\t2\nINTERSECT\nSELECT\n\t3\nEXCEPT\nSELECT\n\t4\n;\n"}, + {"select 1 intersect select 2 union select 3 except select 4", + "SELECT\n\t1\nINTERSECT\nSELECT\n\t2\nUNION\nSELECT\n\t3\nEXCEPT\nSELECT\n\t4\n;\n"}, + }; + + TSetup setup; + setup.Run(cases); +} + Y_UNIT_TEST(Comment) { TCases cases = { {"/*\nmulti\nline\ncomment\n*/\npragma foo = \"true\";\npragma bar = \"1\"", diff --git a/yql/essentials/sql/v1/sql_select.cpp b/yql/essentials/sql/v1/sql_select.cpp index 51894a2bda8..fd5aa8d96c2 100644 --- a/yql/essentials/sql/v1/sql_select.cpp +++ b/yql/essentials/sql/v1/sql_select.cpp @@ -1351,94 +1351,46 @@ TSqlSelect::TSelectKindResult TSqlSelect::SelectKind(const TRule_select_kind_par } } -template<typename TRule> -TSourcePtr TSqlSelect::Build(const TRule& node, TPosition pos, TSelectKindResult&& first) { - if (node.GetBlock2().empty()) { - return std::move(first.Source); +template <typename TRule> + requires std::same_as<TRule, TRule_union_op> || + std::same_as<TRule, TRule_intersect_op> +bool TSqlSelect::IsAllQualifiedOp(const TRule& node) { + if (!node.HasBlock2()) { + return false; } - auto blocks = node.GetBlock2(); - - TPosition opPos = pos; // Position of first select - TVector<TSortSpecificationPtr> orderBy; - bool assumeOrderBy = false; - TNodePtr skipTake; - TWriteSettings outermostSettings; - outermostSettings.Discard = first.Settings.Discard; - - TVector<TSourcePtr> sources{ std::move(first.Source)}; - bool currentQuantifier = false; - TString currentSelectOperator; - - for (int i = 0; i < blocks.size(); ++i) { - auto& b = blocks[i]; - const bool first = (i == 0); - const bool last = (i + 1 == blocks.size()); - TSelectKindPlacement placement; - placement.IsLastInSelectOp = last; - - TSelectKindResult next = SelectKind(b.GetRule_select_kind_parenthesis2(), pos, placement); - if (!next) { - return nullptr; - } - - if (last) { - orderBy = next.SelectOpOrderBy; - assumeOrderBy = next.SelectOpAssumeOrderBy; - skipTake = next.SelectOpSkipTake; - outermostSettings.Label = next.Settings.Label; - } - - auto selectOp = b.GetRule_select_op1(); - const TString selectOperator = ToLowerUTF8(Token(selectOp.GetToken1())); - if (selectOperator == "union") { - // nothing - } else if (selectOperator == "intersect" || selectOperator == "except") { - if (!IsBackwardCompatibleFeatureAvailable(Ctx_.Settings.LangVer, MakeLangVersion(2025, 3), Ctx_.Settings.BackportMode)) { - Ctx_.Error() << "INTERSECT and EXCEPT are available starting from 2025.03 version."; - return nullptr; - } - } else { - Y_ABORT("You should change implementation according to grammar changes. Invalid token: %s", selectOperator.c_str()); - } - - bool quantifier = false; - if (selectOp.HasBlock2()) { - const TString token = ToLowerUTF8(Token(selectOp.GetBlock2().GetToken1())); - if (token == "all") { - quantifier = true; - } else if (token == "distinct") { - // nothing - } else { - Y_ABORT("You should change implementation according to grammar changes. Invalid token: %s", token.c_str()); - } - } + const TString token = ToLowerUTF8(Token(node.GetBlock2().GetToken1())); + if (token == "all") { + return true; + } else if (token == "distinct") { + return false; + } else { + Y_ABORT("You should change implementation according to grammar changes. Invalid token: %s", token.c_str()); + } +} - // currentSelectOperator == "" <=> first == true - if (!first) { - if (currentSelectOperator == selectOperator) { - if (currentSelectOperator == "union") { - if (currentQuantifier != quantifier) { - auto source = BuildSelectOp(pos, std::move(sources), currentSelectOperator, currentQuantifier, {}); - sources.clear(); - sources.emplace_back(std::move(source)); - } - } else { - Ctx_.Error() << "Multiple usage of INTERSECT and EXCEPT is not implemented yet."; - return nullptr; - } - } else { - Ctx_.Error() << "Simultaneous usage of UNION, INTERSECT, and EXCEPT is not implemented yet."; - return nullptr; - } - } +template <typename TRule> + requires std::same_as<TRule, TRule_select_stmt> || + std::same_as<TRule, TRule_select_unparenthesized_stmt> +TSourcePtr TSqlSelect::BuildStmt(const TRule& node, TPosition& pos) { + TBuildExtra extra; + auto result = BuildUnion(node, pos, extra); + pos = extra.FirstPos; + if (!result) { + return nullptr; + } - sources.emplace_back(std::move(next.Source)); - currentQuantifier = quantifier; - currentSelectOperator = selectOperator; + if (extra.First.Source == extra.Last.Source) { + return result; } - auto result = BuildSelectOp(pos, std::move(sources), currentSelectOperator, currentQuantifier, outermostSettings); + TVector<TSortSpecificationPtr> orderBy = extra.Last.SelectOpOrderBy; + bool assumeOrderBy = extra.Last.SelectOpAssumeOrderBy; + TNodePtr skipTake = extra.Last.SelectOpSkipTake; + TWriteSettings outermostSettings = { + .Discard = extra.First.Settings.Discard, + .Label = extra.Last.Settings.Label, + }; if (orderBy) { TVector<TNodePtr> groupByExpr; @@ -1454,48 +1406,170 @@ TSourcePtr TSqlSelect::Build(const TRule& node, TPosition pos, TSelectKindResult bool stream = false; TVector<TNodePtr> terms; - terms.push_back(BuildColumn(opPos, "*", "")); + terms.push_back(BuildColumn(pos, "*", "")); - result = BuildSelectCore(Ctx_, opPos, std::move(result), groupByExpr, groupBy, compactGroupBy, groupBySuffix, + result = BuildSelectCore(Ctx_, pos, std::move(result), groupByExpr, groupBy, compactGroupBy, groupBySuffix, assumeOrderBy, orderBy, having, std::move(winSpecs), legacyHoppingWindowSpec, std::move(terms), distinct, std::move(without), forceWithout, stream, outermostSettings, {}, {}); - result = BuildSelect(opPos, std::move(result), skipTake); + result = BuildSelect(pos, std::move(result), skipTake); } else if (skipTake) { - result = BuildSelect(opPos, std::move(result), skipTake); + result = BuildSelect(pos, std::move(result), skipTake); } return result; } -TSourcePtr TSqlSelect::Build(const TRule_select_stmt& node, TPosition& selectPos) { - TMaybe<TSelectKindPlacement> placement; - if (!node.GetBlock2().empty()) { - placement.ConstructInPlace(); - placement->IsFirstInSelectOp = true; +template <typename TRule> + requires std::same_as<TRule, TRule_select_stmt> || + std::same_as<TRule, TRule_select_unparenthesized_stmt> +TSourcePtr TSqlSelect::BuildUnion(const TRule& node, TPosition& pos, TSqlSelect::TBuildExtra& extra) { + const TSelectKindPlacement firstPlacement = { + .IsFirstInSelectOp = true, + .IsLastInSelectOp = node.GetBlock2().empty(), + }; + + TSourcePtr first; + if constexpr (std::is_same_v<TRule, TRule_select_stmt>) { + first = BuildExceptionIntersection(node.GetRule_select_stmt_intersect1(), pos, firstPlacement, extra); + } else if constexpr (std::is_same_v<TRule, TRule_select_unparenthesized_stmt>) { + first = BuildExceptionIntersection(node.GetRule_select_unparenthesized_stmt_intersect1(), pos, firstPlacement, extra); + } else { + static_assert(false, "Change implementation according to grammar changes."); } - auto res = SelectKind(node.GetRule_select_kind_parenthesis1(), selectPos, placement); - if (!res) { + if (first == nullptr) { return nullptr; } - return Build(node, selectPos, std::move(res)); + TVector<TSourcePtr> sources = {std::move(first)}; + bool isLastAllQualified = false; + + const auto& tail = node.GetBlock2(); + for (int i = 0; i < tail.size(); ++i) { + const auto& nextBlock = tail[i]; + + bool isNextAllQualified = IsAllQualifiedOp(nextBlock.GetRule_union_op1()); + + TSelectKindPlacement nextPlacement = { + .IsFirstInSelectOp = false, + .IsLastInSelectOp = (i + 1 == tail.size()), + }; + TSourcePtr next = BuildExceptionIntersection(nextBlock.GetRule_select_stmt_intersect2(), pos, nextPlacement, extra); + if (!next) { + return nullptr; + } + + if (i != 0 && isLastAllQualified != isNextAllQualified) { + auto source = BuildSelectOp(pos, std::move(sources), "union", isLastAllQualified, /* settings = */ {}); + Y_ENSURE(source); + + sources.clear(); + sources.emplace_back(std::move(source)); + } + + sources.emplace_back(std::move(next)); + isLastAllQualified = isNextAllQualified; + } + + if (tail.empty()) { + return sources[0]; + } + + Y_ENSURE(extra.First); + TWriteSettings outermostSettings; + outermostSettings.Discard = extra.First.Settings.Discard; + if (extra.Last) { + outermostSettings.Label = extra.Last.Settings.Label; + } + + return BuildSelectOp(pos, std::move(sources), "union", isLastAllQualified, outermostSettings); } -TSourcePtr TSqlSelect::Build(const TRule_select_unparenthesized_stmt& node, TPosition& selectPos) { - TMaybe<TSelectKindPlacement> placement; - if (!node.GetBlock2().empty()) { - placement.ConstructInPlace(); - placement->IsFirstInSelectOp = true; +template <typename TRule> + requires std::same_as<TRule, TRule_select_stmt_intersect> || + std::same_as<TRule, TRule_select_unparenthesized_stmt_intersect> +TSourcePtr TSqlSelect::BuildExceptionIntersection( + const TRule& node, + TPosition& pos, + TSelectKindPlacement placement, + TSqlSelect::TBuildExtra& extra) +{ + const TSelectKindPlacement firstPlacement = { + .IsFirstInSelectOp = placement.IsFirstInSelectOp, + .IsLastInSelectOp = node.GetBlock2().empty() && placement.IsLastInSelectOp, + }; + + TSelectKindResult first; + if constexpr (std::is_same_v<TRule, TRule_select_stmt_intersect>) { + 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 { + static_assert(false, "Change implementation according to grammar changes."); } - auto res = SelectKind(node.GetRule_select_kind_partial1(), selectPos, placement); - if (!res) { + if (!first) { return nullptr; } - return Build(node, selectPos, std::move(res)); + TSourcePtr result = first.Source; + + const auto& tail = node.GetBlock2(); + for (int i = 0; i < tail.size(); ++i) { + const auto& nextBlock = tail[i]; + + TString nextOp = ToLowerUTF8(Token(nextBlock.GetRule_intersect_op1().GetToken1())); + bool isNextAllQualified = IsAllQualifiedOp(nextBlock.GetRule_intersect_op1()); + + TSelectKindPlacement nextPlacement = { + .IsFirstInSelectOp = false, + .IsLastInSelectOp = (i + 1 == tail.size()) && placement.IsLastInSelectOp, + }; + TSelectKindResult next = BuildAtom(nextBlock.GetRule_select_kind_parenthesis2(), pos, nextPlacement, extra); + if (!next) { + return nullptr; + } + + result = BuildSelectOp(pos, {std::move(result), std::move(next.Source)}, nextOp, isNextAllQualified, /* settings = */ {}); + Y_ENSURE(result); + } + + return result; +} + +template <typename TRule> + requires std::same_as<TRule, TRule_select_kind_parenthesis> || + std::same_as<TRule, TRule_select_kind_partial> +TSqlSelect::TSelectKindResult TSqlSelect::BuildAtom( + const TRule& node, + TPosition& pos, + TSelectKindPlacement placement, + TBuildExtra& extra) +{ + TSqlSelect::TSelectKindResult result; + if (placement.IsFirstInSelectOp && placement.IsLastInSelectOp) { + result = SelectKind(node, pos, /* placement = */ Nothing()); + } else { + result = SelectKind(node, pos, placement); + } + + if (placement.IsFirstInSelectOp) { + extra.First = result; + extra.FirstPos = pos; + } + if (placement.IsLastInSelectOp) { + extra.Last = result; + } + return result; +} + +TSourcePtr TSqlSelect::Build(const TRule_select_stmt& node, TPosition& selectPos) { + return BuildStmt(node, selectPos); +} + +TSourcePtr TSqlSelect::Build(const TRule_select_unparenthesized_stmt& node, TPosition& selectPos) { + return BuildStmt(node, selectPos); } } // namespace NSQLTranslationV1 diff --git a/yql/essentials/sql/v1/sql_select.h b/yql/essentials/sql/v1/sql_select.h index fd6f0bece52..3224a3ca3b3 100644 --- a/yql/essentials/sql/v1/sql_select.h +++ b/yql/essentials/sql/v1/sql_select.h @@ -62,9 +62,36 @@ private: bool ValidateLimitOrderByWithSelectOp(TMaybe<TSelectKindPlacement> placement, TStringBuf what); bool NeedPassLimitOrderByToUnderlyingSelect(TMaybe<TSelectKindPlacement> placement); - template<typename TRule> - TSourcePtr Build(const TRule& node, TPosition pos, TSelectKindResult&& first); + struct TBuildExtra { + TSelectKindResult First; + TPosition FirstPos; + TSelectKindResult Last; + }; + template <typename TRule> + requires std::same_as<TRule, TRule_union_op> || + std::same_as<TRule, TRule_intersect_op> + bool IsAllQualifiedOp(const TRule& node); + + template <typename TRule> + requires std::same_as<TRule, TRule_select_stmt> || + std::same_as<TRule, TRule_select_unparenthesized_stmt> + TSourcePtr 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> + TSourcePtr BuildUnion(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> + TSourcePtr BuildExceptionIntersection(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> + TSelectKindResult BuildAtom(const TRule& node, TPosition& pos, TSelectKindPlacement placement, TBuildExtra& extra); TSelectKindResult SelectKind(const TRule_select_kind& node, TPosition& selectPos, TMaybe<TSelectKindPlacement> placement); TSelectKindResult SelectKind(const TRule_select_kind_partial& node, TPosition& selectPos, TMaybe<TSelectKindPlacement> placement); diff --git a/yql/essentials/sql/v1/sql_ut_common.h b/yql/essentials/sql/v1/sql_ut_common.h index 431fa39d6cb..89a1cf4c0d0 100644 --- a/yql/essentials/sql/v1/sql_ut_common.h +++ b/yql/essentials/sql/v1/sql_ut_common.h @@ -911,7 +911,7 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) { Y_UNIT_TEST(SelectOrderByExpressionAsc) { NYql::TAstParseResult res = SqlToYql("select i.key, i.subkey from plato.Input as i order by cast(key as uint32) % cast(i.subkey as uint32) asc"); - UNIT_ASSERT(res.Root); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { if (word == "Sort") { UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"%MayWarn\"")); @@ -1862,16 +1862,26 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) { settings.LangVer = 202503; NYql::TAstParseResult res = SqlToYqlWithSettings("SELECT key FROM plato.Input INTERSECT SELECT subkey FROM plato.Input INTERSECT SELECT subkey FROM plato.Input;", settings); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:70: Error: Multiple usage of INTERSECT and EXCEPT is not implemented yet.\n"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("Intersect"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["Intersect"]); res = SqlToYqlWithSettings("SELECT key FROM plato.Input INTERSECT SELECT subkey FROM plato.Input INTERSECT ALL SELECT subkey FROM plato.Input;", settings); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:80: Error: Multiple usage of INTERSECT and EXCEPT is not implemented yet.\n"); + UNIT_ASSERT(res.Root); + + elementStat = {{TString("Intersect"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["Intersect"]); res = SqlToYqlWithSettings("SELECT key FROM plato.Input INTERSECT SELECT subkey FROM plato.Input UNION SELECT subkey FROM plato.Input;", settings); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:70: Error: Simultaneous usage of UNION, INTERSECT, and EXCEPT is not implemented yet.\n"); + UNIT_ASSERT(res.Root); + + elementStat = {{TString("Intersect"), 0}, {TString("Union"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Intersect"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Union"]); } // EXCEPT @@ -1936,16 +1946,26 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) { settings.LangVer = 202503; NYql::TAstParseResult res = SqlToYqlWithSettings("SELECT key FROM plato.Input EXCEPT SELECT subkey FROM plato.Input EXCEPT SELECT subkey FROM plato.Input;", settings); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:67: Error: Multiple usage of INTERSECT and EXCEPT is not implemented yet.\n"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("Except"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["Except"]); res = SqlToYqlWithSettings("SELECT key FROM plato.Input EXCEPT SELECT subkey FROM plato.Input EXCEPT ALL SELECT subkey FROM plato.Input;", settings); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:74: Error: Multiple usage of INTERSECT and EXCEPT is not implemented yet.\n"); + UNIT_ASSERT(res.Root); + + elementStat = {{TString("Except"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["Except"]); res = SqlToYqlWithSettings("SELECT key FROM plato.Input EXCEPT SELECT subkey FROM plato.Input UNION SELECT subkey FROM plato.Input;", settings); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:67: Error: Simultaneous usage of UNION, INTERSECT, and EXCEPT is not implemented yet.\n"); + UNIT_ASSERT(res.Root); + + elementStat = {{TString("Except"), 0}, {TString("Union"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Except"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Union"]); } |