Skip to content

Commit

Permalink
Add more cross join tests, refine CrossJoinAdder (#7321)
Browse files Browse the repository at this point in the history
ref #6233
  • Loading branch information
windtalker authored Apr 20, 2023
1 parent 68412d1 commit b2b976e
Show file tree
Hide file tree
Showing 3 changed files with 432 additions and 97 deletions.
2 changes: 0 additions & 2 deletions dbms/src/Debug/MockExecutor/JoinBinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ class JoinBinder : public ExecutorBinder
, is_null_aware_semi_join(is_null_aware_semi_join)
, inner_index(inner_index_)
{
if (!(join_cols.size() + left_conds.size() + right_conds.size() + other_conds.size() + other_eq_conds_from_in.size()))
throw Exception("No join condition found.");
}

void columnPrune(std::unordered_set<String> & used_columns) override;
Expand Down
291 changes: 229 additions & 62 deletions dbms/src/Flash/tests/gtest_join_executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,88 +472,255 @@ TEST_F(JoinExecutorTestRunner, CrossJoinWithCondition)
try
{
context.addMockTable("cross_join", "t1", {{"a", TiDB::TP::TypeString}, {"b", TiDB::TP::TypeString}}, {toNullableVec<String>("a", {"1", "2", {}, "1"}), toNullableVec<String>("b", {"3", "4", "3", {}})});
context.addMockTable("cross_join", "t2", {{"a", TiDB::TP::TypeString}, {"b", TiDB::TP::TypeString}}, {toNullableVec<String>("a", {"1", "3", {}, "2"}), toNullableVec<String>("b", {"3", "4", "3", {}})});

const auto cond = gt(col("a"), lit(Field("1", 1)));
context.addMockTable("cross_join", "t2", {{"c", TiDB::TP::TypeString}, {"d", TiDB::TP::TypeString}}, {toNullableVec<String>("c", {"1", "3", {}, "2"}), toNullableVec<String>("d", {"3", "4", "3", {}})});
context.addMockTable("cross_join", "empty_table_t1", {{"a", TiDB::TP::TypeString}, {"b", TiDB::TP::TypeString}}, {toNullableVec<String>("a", {}), toNullableVec<String>("b", {})});
context.addMockTable("cross_join", "empty_table_t2", {{"c", TiDB::TP::TypeString}, {"d", TiDB::TP::TypeString}}, {toNullableVec<String>("c", {}), toNullableVec<String>("d", {})});

const auto table_scan = [&]() -> std::tuple<DAGRequestBuilder, DAGRequestBuilder> {
return {context.scan("cross_join", "t1"), context.scan("cross_join", "t2")};
};

const ColumnsWithTypeAndName expected_cols[join_type_num] = {
// inner
const ColumnsWithTypeAndName expected_cols[join_type_num * 4] = {
// non-empty inner non-empty
{toNullableVec<String>({"2", "2", "2", "2"}), toNullableVec<String>({"4", "4", "4", "4"}), toNullableVec<String>({"1", "3", {}, "2"}), toNullableVec<String>({"3", "4", "3", {}})},
// left
// empty inner non-empty
{},
// non-empty inner empty
{toNullableVec<String>({}), toNullableVec<String>({}), toNullableVec<String>({}), toNullableVec<String>({})},
// empty inner empty
{},
// non-empty left non-empty
{toNullableVec<String>({"1", "2", "2", "2", "2", {}, "1"}), toNullableVec<String>({"3", "4", "4", "4", "4", "3", {}}), toNullableVec<String>({{}, "2", {}, "3", "1", {}, {}}), toNullableVec<String>({{}, {}, "3", "4", "3", {}, {}})},
// right
// empty left non-empty
{},
// non-empty left empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}}), toNullableVec<String>({{}, {}, {}, {}}), toNullableVec<String>({{}, {}, {}, {}})},
// empty left empty
{},
// non-empty right non-empty
{toNullableVec<String>({{}, "1", {}, "2", "1", {}, "1", {}, "2", "1"}), toNullableVec<String>({{}, {}, "3", "4", "3", {}, {}, "3", "4", "3"}), toNullableVec<String>({"1", "3", "3", "3", "3", {}, "2", "2", "2", "2"}), toNullableVec<String>({"3", "4", "4", "4", "4", "3", {}, {}, {}, {}})},
// semi
// empty right non-empty
{toNullableVec<String>({{}, {}, {}, {}}), toNullableVec<String>({{}, {}, {}, {}}), toNullableVec<String>({"1", "3", {}, "2"}), toNullableVec<String>({"3", "4", "3", {}})},
// non-empty right empty
{},
// empty right empty
{},
// non-empty semi non-empty
{toNullableVec<String>({"2"}), toNullableVec<String>({"4"})},
// anti semi
// empty semi non-empty
{},
// non-empty semi empty
{toNullableVec<String>({}), toNullableVec<String>({})},
// empty semi empty
{},
// non-empty anti semi non-empty
{toNullableVec<String>({"1", "1", {}}), toNullableVec<String>({{}, "3", "3"})},
// left outer semi
// empty anti semi non-empty
{},
// non-empty anti semi empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}})},
// empty anti semi empty
{},
// non-empty left outer semi non-empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}}), toNullableVec<Int8>({0, 1, 0, 0})},
// anti left outer semi
// empty left outer semi non-empty
{},
// non-empty left outer semi empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}}), toNullableVec<Int8>({0, 0, 0, 0})},
// empty left outer semi empty
{},
// non-empty anti left outer semi non-empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}}), toNullableVec<Int8>({1, 0, 1, 1})},
// empty anti left outer semi non-empty
{},
// non-empty anti left outer semi empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}}), toNullableVec<Int8>({1, 1, 1, 1})},
// empty anti left outer semi empty
{},
};

const auto cond = gt(col("a"), lit(Field("1", 1)));
const auto cond_right = gt(col("c"), lit(Field("1", 1)));
const auto gen_join_inputs = [&]() -> std::vector<std::pair<DAGRequestBuilder, DAGRequestBuilder>> {
return {
{context.scan("cross_join", "t1"), context.scan("cross_join", "t2")},
{context.scan("cross_join", "empty_table_t1"), context.scan("cross_join", "t2")},
{context.scan("cross_join", "t1"), context.scan("cross_join", "empty_table_t2")},
{context.scan("cross_join", "empty_table_t1"), context.scan("cross_join", "empty_table_t2")},
};
};

/// for cross join, there is no join columns
size_t i = 0;

for (const auto & join_type : join_types)
{
auto [t1, t2] = table_scan();
auto request = t1
.join(t2, tipb::JoinType::TypeInnerJoin, {}, {}, {}, {cond}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}

{
auto [t1, t2] = table_scan();
auto request = t1
.join(t2, tipb::JoinType::TypeLeftOuterJoin, {}, {cond}, {}, {}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}

{
auto [t1, t2] = table_scan();
auto request = t1
.join(t2, tipb::JoinType::TypeRightOuterJoin, {}, {}, {cond}, {}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
auto join_cond = cond;
if (join_type == tipb::TypeRightOuterJoin)
join_cond = cond_right;
auto join_inputs = gen_join_inputs();
for (auto & join_input : join_inputs)
{
auto request = join_input.first
.join(join_input.second, join_type, {}, {}, {}, {join_cond}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}
/// extra tests for outer join
if (join_type == tipb::TypeLeftOuterJoin)
{
/// left out join with left condition
join_inputs = gen_join_inputs();
i -= join_inputs.size();
for (auto & join_input : join_inputs)
{
auto request = join_input.first
.join(join_input.second, tipb::JoinType::TypeLeftOuterJoin, {}, {join_cond}, {}, {}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}
/// left out join with left condition and other condition
join_inputs = gen_join_inputs();
i -= join_inputs.size();
for (auto & join_input : join_inputs)
{
auto request = join_input.first
.join(join_input.second, tipb::JoinType::TypeLeftOuterJoin, {}, {join_cond}, {}, {join_cond}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}
}
else if (join_type == tipb::TypeRightOuterJoin)
{
/// right out join with right condition
join_inputs = gen_join_inputs();
i -= join_inputs.size();
for (auto & join_input : join_inputs)
{
auto request = join_input.first
.join(join_input.second, tipb::JoinType::TypeRightOuterJoin, {}, {}, {join_cond}, {}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}
/// right out join with right condition and other condition
join_inputs = gen_join_inputs();
i -= join_inputs.size();
for (auto & join_input : join_inputs)
{
auto request = join_input.first
.join(join_input.second, tipb::JoinType::TypeRightOuterJoin, {}, {}, {join_cond}, {join_cond}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}
}
}
}
CATCH

{
auto [t1, t2] = table_scan();
auto request = t1
.join(t2, tipb::JoinType::TypeSemiJoin, {}, {}, {}, {cond}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}
TEST_F(JoinExecutorTestRunner, CrossJoinWithoutCondition)
try
{
context.addMockTable("cross_join", "t1", {{"a", TiDB::TP::TypeString}, {"b", TiDB::TP::TypeString}}, {toNullableVec<String>("a", {"1", "2", {}, "1"}), toNullableVec<String>("b", {"3", "4", "3", {}})});
context.addMockTable("cross_join", "t2", {{"a", TiDB::TP::TypeString}, {"b", TiDB::TP::TypeString}}, {toNullableVec<String>("a", {"1", "3", {}, "2"}), toNullableVec<String>("b", {"3", "4", "3", {}})});
context.addMockTable("cross_join", "empty_table", {{"a", TiDB::TP::TypeString}, {"b", TiDB::TP::TypeString}}, {toNullableVec<String>("a", {}), toNullableVec<String>("b", {})});

const auto gen_join_inputs = [&]() -> std::vector<std::pair<DAGRequestBuilder, DAGRequestBuilder>> {
return {
{context.scan("cross_join", "t1"), context.scan("cross_join", "t2")},
{context.scan("cross_join", "empty_table"), context.scan("cross_join", "t2")},
{context.scan("cross_join", "t1"), context.scan("cross_join", "empty_table")},
{context.scan("cross_join", "empty_table"), context.scan("cross_join", "empty_table")},
};
};

{
auto [t1, t2] = table_scan();
auto request = t1
.join(t2, tipb::JoinType::TypeAntiSemiJoin, {}, {}, {}, {cond}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}
const ColumnsWithTypeAndName expected_cols[join_type_num * 4] = {
// non-empty inner non-empty
{
toNullableVec<String>({"1", "1", "1", "1", "2", "2", "2", "2", {}, {}, {}, {}, "1", "1", "1", "1"}),
toNullableVec<String>({"3", "3", "3", "3", "4", "4", "4", "4", "3", "3", "3", "3", {}, {}, {}, {}}),
toNullableVec<String>({"1", "3", {}, "2", "1", "3", {}, "2", "1", "3", {}, "2", "1", "3", {}, "2"}),
toNullableVec<String>({"3", "4", "3", {}, "3", "4", "3", {}, "3", "4", "3", {}, "3", "4", "3", {}})},
// empty inner non-empty
{},
// non-empty inner empty
{toNullableVec<String>({}), toNullableVec<String>({}), toNullableVec<String>({}), toNullableVec<String>({})},
// empty inner empty
{},
// non-empty left non-empty
{
toNullableVec<String>({"1", "1", "1", "1", "2", "2", "2", "2", {}, {}, {}, {}, "1", "1", "1", "1"}),
toNullableVec<String>({"3", "3", "3", "3", "4", "4", "4", "4", "3", "3", "3", "3", {}, {}, {}, {}}),
toNullableVec<String>({"1", "3", {}, "2", "1", "3", {}, "2", "1", "3", {}, "2", "1", "3", {}, "2"}),
toNullableVec<String>({"3", "4", "3", {}, "3", "4", "3", {}, "3", "4", "3", {}, "3", "4", "3", {}})},
// empty left non-empty
{},
// non-empty left empty
{
toNullableVec<String>({"1", "2", {}, "1"}),
toNullableVec<String>({"3", "4", "3", {}}),
toNullableVec<String>({{}, {}, {}, {}}),
toNullableVec<String>({{}, {}, {}, {}})},
// empty left empty
{},
// non-empty right non-empty
{
toNullableVec<String>({"1", "1", "1", "1", "2", "2", "2", "2", {}, {}, {}, {}, "1", "1", "1", "1"}),
toNullableVec<String>({"3", "3", "3", "3", "4", "4", "4", "4", "3", "3", "3", "3", {}, {}, {}, {}}),
toNullableVec<String>({"1", "3", {}, "2", "1", "3", {}, "2", "1", "3", {}, "2", "1", "3", {}, "2"}),
toNullableVec<String>({"3", "4", "3", {}, "3", "4", "3", {}, "3", "4", "3", {}, "3", "4", "3", {}})},
// empty right non-empty
{
toNullableVec<String>({{}, {}, {}, {}}),
toNullableVec<String>({{}, {}, {}, {}}),
toNullableVec<String>({"1", "3", {}, "2"}),
toNullableVec<String>({"3", "4", "3", {}})},
// non-empty right empty
{},
// empty right empty
{},
// non-empty semi non-empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}})},
// empty semi non-empty
{},
// non-empty semi empty
{toNullableVec<String>({}), toNullableVec<String>({})},
// empty semi empty
{},
// non-empty anti semi non-empty
{toNullableVec<String>({}), toNullableVec<String>({})},
// empty anti semi non-empty
{},
// non-empty anti semi empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}})},
// empty anti semi empty
{},
// non-empty left outer semi non-empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}}), toNullableVec<Int8>({1, 1, 1, 1})},
// empty left outer semi non-empty
{},
// non-empty left outer semi empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}}), toNullableVec<Int8>({0, 0, 0, 0})},
// empty left outer semi empty
{},
// non-empty anti left outer semi non-empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}}), toNullableVec<Int8>({0, 0, 0, 0})},
// empty anti left outer semi non-empty
{},
// non-empty anti left outer semi empty
{toNullableVec<String>({"1", "2", {}, "1"}), toNullableVec<String>({"3", "4", "3", {}}), toNullableVec<Int8>({1, 1, 1, 1})},
// empty anti left outer semi empty
{},
};

{
auto [t1, t2] = table_scan();
auto request = t1
.join(t2, tipb::JoinType::TypeLeftOuterSemiJoin, {}, {}, {}, {cond}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}
/// for cross join, there is no join columns
size_t i = 0;

for (const auto & join_type : join_types)
{
auto [t1, t2] = table_scan();
auto request = t1
.join(t2, tipb::JoinType::TypeAntiLeftOuterSemiJoin, {}, {}, {}, {cond}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
auto join_inputs = gen_join_inputs();
for (auto & join_input : join_inputs)
{
auto request = join_input.first
.join(join_input.second, join_type, {}, {}, {}, {}, {})
.build(context);
executeAndAssertColumnsEqual(request, expected_cols[i++]);
}
}
}
CATCH
Expand Down
Loading

0 comments on commit b2b976e

Please sign in to comment.