Skip to content

Commit 70eed72

Browse files
authored
Support AnsiImplicitCrossJoin mode (#6950)
1 parent 298b189 commit 70eed72

File tree

5 files changed

+134
-67
lines changed

5 files changed

+134
-67
lines changed

ydb/library/yql/sql/v1/context.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ THashMap<TStringBuf, TPragmaField> CTX_PRAGMA_FIELDS = {
6363
{"UnorderedResult", &TContext::UnorderedResult},
6464
{"CompactNamedExprs", &TContext::CompactNamedExprs},
6565
{"ValidateUnusedExprs", &TContext::ValidateUnusedExprs},
66+
{"AnsiImplicitCrossJoin", &TContext::AnsiImplicitCrossJoin},
6667
};
6768

6869
typedef TMaybe<bool> TContext::*TPragmaMaybeField;

ydb/library/yql/sql/v1/context.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ namespace NSQLTranslationV1 {
318318
ui64 ParallelModeCount = 0;
319319
bool CompactNamedExprs = false;
320320
bool ValidateUnusedExprs = false;
321+
bool AnsiImplicitCrossJoin = false; // select * from A,B
321322
};
322323

323324
class TColumnRefScope {

ydb/library/yql/sql/v1/sql_query.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2611,6 +2611,12 @@ TNodePtr TSqlQuery::PragmaStatement(const TRule_pragma_stmt& stmt, bool& success
26112611
} else if (normalizedPragma == "disablevalidateunusedexprs") {
26122612
Ctx.ValidateUnusedExprs = false;
26132613
Ctx.IncrementMonCounter("sql_pragma", "DisableValidateUnusedExprs");
2614+
} else if (normalizedPragma == "ansiimplicitcrossjoin") {
2615+
Ctx.AnsiImplicitCrossJoin = true;
2616+
Ctx.IncrementMonCounter("sql_pragma", "AnsiImplicitCrossJoin");
2617+
} else if (normalizedPragma == "disableansiimplicitcrossjoin") {
2618+
Ctx.AnsiImplicitCrossJoin = false;
2619+
Ctx.IncrementMonCounter("sql_pragma", "DisableAnsiImplicitCrossJoin");
26142620
} else {
26152621
Error() << "Unknown pragma: " << pragma;
26162622
Ctx.IncrementMonCounter("sql_errors", "UnknownPragma");

ydb/library/yql/sql/v1/sql_select.cpp

Lines changed: 89 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace NSQLTranslationV1 {
99

1010
using namespace NSQLv1Generated;
1111

12+
namespace {
13+
1214
bool IsColumnsOnly(const TVector<TSortSpecificationPtr>& container) {
1315
for (const auto& elem: container) {
1416
if (!elem->OrderExpr->GetColumnName()) {
@@ -18,52 +20,72 @@ bool IsColumnsOnly(const TVector<TSortSpecificationPtr>& container) {
1820
return true;
1921
}
2022

23+
bool CollectJoinLinkSettings(TPosition pos, TJoinLinkSettings& linkSettings, TContext& ctx) {
24+
linkSettings = {};
25+
auto hints = ctx.PullHintForToken(pos);
26+
for (const auto& hint: hints) {
27+
const auto canonizedName = to_lower(hint.Name);
28+
auto newStrategy = TJoinLinkSettings::EStrategy::Default;
29+
if (canonizedName == "merge") {
30+
newStrategy = TJoinLinkSettings::EStrategy::SortedMerge;
31+
} else if (canonizedName == "streamlookup") {
32+
newStrategy = TJoinLinkSettings::EStrategy::StreamLookup;
33+
} else if (canonizedName == "map") {
34+
newStrategy = TJoinLinkSettings::EStrategy::ForceMap;
35+
} else if (canonizedName == "grace") {
36+
newStrategy = TJoinLinkSettings::EStrategy::ForceGrace;
37+
} else {
38+
ctx.Warning(hint.Pos, TIssuesIds::YQL_UNUSED_HINT) << "Unsupported join strategy: " << hint.Name;
39+
}
40+
41+
if (TJoinLinkSettings::EStrategy::Default == linkSettings.Strategy) {
42+
linkSettings.Strategy = newStrategy;
43+
} else if (newStrategy == linkSettings.Strategy) {
44+
ctx.Error() << "Duplicate join strategy hint";
45+
return false;
46+
} else {
47+
ctx.Error() << "Conflicting join strategy hints";
48+
return false;
49+
}
50+
}
51+
return true;
52+
}
53+
54+
} // namespace
55+
2156
bool TSqlSelect::JoinOp(ISource* join, const TRule_join_source::TBlock3& block, TMaybe<TPosition> anyPos) {
2257
// block: (join_op (ANY)? flatten_source join_constraint?)
2358
// join_op:
2459
// COMMA
2560
// | (NATURAL)? ((LEFT (ONLY | SEMI)? | RIGHT (ONLY | SEMI)? | EXCLUSION | FULL)? (OUTER)? | INNER | CROSS) JOIN
2661
//;
2762
const auto& node = block.GetRule_join_op1();
63+
TString joinOp("Inner");
64+
TJoinLinkSettings linkSettings;
2865
switch (node.Alt_case()) {
29-
case TRule_join_op::kAltJoinOp1:
66+
case TRule_join_op::kAltJoinOp1: {
67+
joinOp = "Cross";
68+
if (!Ctx.AnsiImplicitCrossJoin) {
69+
Error() << "Cartesian product of tables is disabled. Please use "
70+
"explicit CROSS JOIN or enable it via PRAGMA AnsiImplicitCrossJoin";
71+
return false;
72+
}
73+
auto alt = node.GetAlt_join_op1();
74+
if (!CollectJoinLinkSettings(Ctx.TokenPosition(alt.GetToken1()), linkSettings, Ctx)) {
75+
return false;
76+
}
3077
Ctx.IncrementMonCounter("sql_join_operations", "CartesianProduct");
31-
Error() << "Cartesian product of tables is not supported. Please use explicit CROSS JOIN";
32-
return false;
78+
break;
79+
}
3380
case TRule_join_op::kAltJoinOp2: {
3481
auto alt = node.GetAlt_join_op2();
3582
if (alt.HasBlock1()) {
3683
Ctx.IncrementMonCounter("sql_join_operations", "Natural");
3784
Error() << "Natural join is not implemented yet";
3885
return false;
3986
}
40-
TString joinOp("Inner");
41-
auto hints = Ctx.PullHintForToken(Ctx.TokenPosition(alt.GetToken3()));
42-
TJoinLinkSettings linkSettings;
43-
for (const auto& hint: hints) {
44-
const auto canonizedName = to_lower(hint.Name);
45-
auto newStrategy = TJoinLinkSettings::EStrategy::Default;
46-
if (canonizedName == "merge") {
47-
newStrategy = TJoinLinkSettings::EStrategy::SortedMerge;
48-
} else if (canonizedName == "streamlookup") {
49-
newStrategy = TJoinLinkSettings::EStrategy::StreamLookup;
50-
} else if (canonizedName == "map") {
51-
newStrategy = TJoinLinkSettings::EStrategy::ForceMap;
52-
} else if (canonizedName == "grace") {
53-
newStrategy = TJoinLinkSettings::EStrategy::ForceGrace;
54-
} else {
55-
Ctx.Warning(hint.Pos, TIssuesIds::YQL_UNUSED_HINT) << "Unsupported join strategy: " << hint.Name;
56-
}
57-
58-
if (TJoinLinkSettings::EStrategy::Default == linkSettings.Strategy) {
59-
linkSettings.Strategy = newStrategy;
60-
} else if (newStrategy == linkSettings.Strategy) {
61-
Error() << "Duplicate join strategy hint";
62-
return false;
63-
} else {
64-
Error() << "Conflicting join strategy hints";
65-
return false;
66-
}
87+
if (!CollectJoinLinkSettings(Ctx.TokenPosition(alt.GetToken3()), linkSettings, Ctx)) {
88+
return false;
6789
}
6890
switch (alt.GetBlock2().Alt_case()) {
6991
case TRule_join_op::TAlt2::TBlock2::kAlt1:
@@ -122,52 +144,52 @@ bool TSqlSelect::JoinOp(ISource* join, const TRule_join_source::TBlock3& block,
122144
AltNotImplemented("join_op", node);
123145
return false;
124146
}
125-
126-
joinOp = NormalizeJoinOp(joinOp);
127147
Ctx.IncrementMonCounter("sql_features", "Join");
128148
Ctx.IncrementMonCounter("sql_join_operations", joinOp);
129-
if (linkSettings.Strategy != TJoinLinkSettings::EStrategy::Default && joinOp == "Cross") {
130-
Ctx.Warning(Ctx.Pos(), TIssuesIds::YQL_UNUSED_HINT) << "Non-default join strategy will not be used for CROSS JOIN";
131-
linkSettings.Strategy = TJoinLinkSettings::EStrategy::Default;
132-
}
133-
134-
TNodePtr joinKeyExpr;
135-
if (block.HasBlock4()) {
136-
if (joinOp == "Cross") {
137-
Error() << "Cross join should not have ON or USING expression";
138-
Ctx.IncrementMonCounter("sql_errors", "BadJoinExpr");
139-
return false;
140-
}
141-
142-
joinKeyExpr = JoinExpr(join, block.GetBlock4().GetRule_join_constraint1());
143-
if (!joinKeyExpr) {
144-
Ctx.IncrementMonCounter("sql_errors", "BadJoinExpr");
145-
return false;
146-
}
147-
}
148-
else {
149-
if (joinOp != "Cross") {
150-
Error() << "Expected ON or USING expression";
151-
Ctx.IncrementMonCounter("sql_errors", "BadJoinExpr");
152-
return false;
153-
}
154-
}
155-
156-
if (joinOp == "Cross" && anyPos) {
157-
Ctx.Error(*anyPos) << "ANY should not be used with Cross JOIN";
158-
Ctx.IncrementMonCounter("sql_errors", "BadJoinAny");
159-
return false;
160-
}
161-
162-
Y_DEBUG_ABORT_UNLESS(join->GetJoin());
163-
join->GetJoin()->SetupJoin(joinOp, joinKeyExpr, linkSettings);
164149
break;
165150
}
166151
case TRule_join_op::ALT_NOT_SET:
167152
Ctx.IncrementMonCounter("sql_errors", "UnknownJoinOperation2");
168153
AltNotImplemented("join_op", node);
169154
return false;
170155
}
156+
joinOp = NormalizeJoinOp(joinOp);
157+
if (linkSettings.Strategy != TJoinLinkSettings::EStrategy::Default && joinOp == "Cross") {
158+
Ctx.Warning(Ctx.Pos(), TIssuesIds::YQL_UNUSED_HINT) << "Non-default join strategy will not be used for CROSS JOIN";
159+
linkSettings.Strategy = TJoinLinkSettings::EStrategy::Default;
160+
}
161+
162+
TNodePtr joinKeyExpr;
163+
if (block.HasBlock4()) {
164+
if (joinOp == "Cross") {
165+
Error() << "Cross join should not have ON or USING expression";
166+
Ctx.IncrementMonCounter("sql_errors", "BadJoinExpr");
167+
return false;
168+
}
169+
170+
joinKeyExpr = JoinExpr(join, block.GetBlock4().GetRule_join_constraint1());
171+
if (!joinKeyExpr) {
172+
Ctx.IncrementMonCounter("sql_errors", "BadJoinExpr");
173+
return false;
174+
}
175+
}
176+
else {
177+
if (joinOp != "Cross") {
178+
Error() << "Expected ON or USING expression";
179+
Ctx.IncrementMonCounter("sql_errors", "BadJoinExpr");
180+
return false;
181+
}
182+
}
183+
184+
if (joinOp == "Cross" && anyPos) {
185+
Ctx.Error(*anyPos) << "ANY should not be used with Cross JOIN";
186+
Ctx.IncrementMonCounter("sql_errors", "BadJoinAny");
187+
return false;
188+
}
189+
190+
Y_DEBUG_ABORT_UNLESS(join->GetJoin());
191+
join->GetJoin()->SetupJoin(joinOp, joinKeyExpr, linkSettings);
192+
171193
return true;
172194
}
173195

ydb/library/yql/sql/v1/sql_ut.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,27 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) {
445445
UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlColumn"]);
446446
}
447447

448+
Y_UNIT_TEST(DisabledJoinCartesianProduct) {
449+
NYql::TAstParseResult res = SqlToYql("pragma DisableAnsiImplicitCrossJoin; use plato; select * from A,B,C");
450+
UNIT_ASSERT(!res.Root);
451+
UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:67: Error: Cartesian product of tables is disabled. Please use explicit CROSS JOIN or enable it via PRAGMA AnsiImplicitCrossJoin\n");
452+
}
453+
454+
Y_UNIT_TEST(JoinCartesianProduct) {
455+
NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from A,B,C");
456+
UNIT_ASSERT(res.Root);
457+
TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) {
458+
if (word == "EquiJoin") {
459+
auto pos = line.find("Cross");
460+
UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, pos);
461+
UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Cross", pos + 1));
462+
}
463+
};
464+
TWordCountHive elementStat = {{TString("EquiJoin"), 0}};
465+
VerifyProgram(res, elementStat, verifyLine);
466+
UNIT_ASSERT_VALUES_EQUAL(1, elementStat["EquiJoin"]);
467+
}
468+
448469
Y_UNIT_TEST(JoinWithoutConcreteColumns) {
449470
NYql::TAstParseResult res = SqlToYql(
450471
" use plato;"
@@ -541,6 +562,12 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) {
541562
UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:32: Warning: Non-default join strategy will not be used for CROSS JOIN, code: 4534\n");
542563
}
543564

565+
Y_UNIT_TEST(WarnCartesianProductStrategyHint) {
566+
NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; SELECT * FROM A, /*+ merge() */ B;");
567+
UNIT_ASSERT(res.Root);
568+
UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:74: Warning: Non-default join strategy will not be used for CROSS JOIN, code: 4534\n");
569+
}
570+
544571
Y_UNIT_TEST(WarnUnknownJoinStrategyHint) {
545572
NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ xmerge() */ plato.Input AS b USING (key);");
546573
UNIT_ASSERT(res.Root);
@@ -3926,6 +3953,16 @@ select FormatType($f());
39263953
UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:44: Error: ANY should not be used with Cross JOIN\n");
39273954
}
39283955

3956+
Y_UNIT_TEST(AnyWithCartesianProduct) {
3957+
NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from any Input1, Input2");
3958+
UNIT_ASSERT(!res.Root);
3959+
UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:56: Error: ANY should not be used with Cross JOIN\n");
3960+
3961+
res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from Input1, any Input2");
3962+
UNIT_ASSERT(!res.Root);
3963+
UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:64: Error: ANY should not be used with Cross JOIN\n");
3964+
}
3965+
39293966
Y_UNIT_TEST(ErrorPlainEndAsInlineActionTerminator) {
39303967
NYql::TAstParseResult res = SqlToYql(
39313968
"do begin\n"

0 commit comments

Comments
 (0)