summaryrefslogtreecommitdiffstats
path: root/yql/essentials/sql/v1
diff options
context:
space:
mode:
authorvitya-smirnov <[email protected]>2025-07-09 14:16:01 +0300
committervitya-smirnov <[email protected]>2025-07-09 14:38:43 +0300
commit59c3167c208900bb714ff5cc477476e3ab14f4f0 (patch)
tree043a4d70e21ede0da9df08806fe5a8c0d656e201 /yql/essentials/sql/v1
parent40a6e6e096b3533857b70c79a8ac9820655c7bf3 (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.in12
-rw-r--r--yql/essentials/sql/v1/SQLv1Antlr4.g.in12
-rw-r--r--yql/essentials/sql/v1/complete/analysis/global/column.cpp2
-rw-r--r--yql/essentials/sql/v1/format/sql_format.cpp28
-rw-r--r--yql/essentials/sql/v1/format/sql_format_ut.h12
-rw-r--r--yql/essentials/sql/v1/sql_select.cpp278
-rw-r--r--yql/essentials/sql/v1/sql_select.h31
-rw-r--r--yql/essentials/sql/v1/sql_ut_common.h46
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"]);
}