Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 33 additions & 25 deletions axiom/optimizer/tests/PlanTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ namespace {
using namespace facebook::velox;
namespace lp = facebook::axiom::logical_plan;

class PlanTest : public test::HiveQueriesTestBase {
class PlanTest : public test::HiveQueriesTestBase,
public ::testing::WithParamInterface<bool> {
protected:
static void SetUpTestCase() {
test::HiveQueriesTestBase::SetUpTestCase();
Expand All @@ -43,6 +44,11 @@ class PlanTest : public test::HiveQueriesTestBase {
test::registerDfFunctions();
}

void SetUp() override {
useV2_ = GetParam();
test::HiveQueriesTestBase::SetUp();
}

lp::PlanBuilder::Context makeContext() const {
return lp::PlanBuilder::Context{kTestConnectorId, kDefaultSchema};
}
Expand All @@ -54,7 +60,7 @@ class PlanTest : public test::HiveQueriesTestBase {
}
};

TEST_F(PlanTest, dedupEmptyArrays) {
TEST_P(PlanTest, dedupEmptyArrays) {
auto logicalPlan =
lp::PlanBuilder()
.values(ROW({}), {variant::row({})})
Expand All @@ -80,7 +86,7 @@ TEST_F(PlanTest, dedupEmptyArrays) {

// Verify that optimizer can handle connectors that do not support filter
// pushdown.
TEST_F(PlanTest, rejectedFilters) {
TEST_P(PlanTest, rejectedFilters) {
const auto mapType = MAP(BIGINT(), DOUBLE());
testConnector_->addTable(
"t", ROW({"a", "b", "c"}, {BIGINT(), DOUBLE(), mapType}));
Expand Down Expand Up @@ -141,7 +147,7 @@ TEST_F(PlanTest, rejectedFilters) {
.project() // project c.x + 1, c.y + 2, c.z + 3
.build();

AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}

// SELECT 1 FROM t WHERE c.x > 10.
Expand All @@ -162,7 +168,7 @@ TEST_F(PlanTest, rejectedFilters) {
.project({"1"})
.build();

AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}

// SELECT c.y + 1 FROM t WHERE c.x > 10.
Expand All @@ -184,11 +190,11 @@ TEST_F(PlanTest, rejectedFilters) {
.project() // project c.y + 1
.build();

AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}
}

TEST_F(PlanTest, specialFormConstantFold) {
TEST_P(PlanTest, specialFormConstantFold) {
testConnector_->addTable("numbers", ROW({"a", "b", "c"}, BIGINT()));

struct TestCase {
Expand Down Expand Up @@ -234,7 +240,7 @@ TEST_F(PlanTest, specialFormConstantFold) {
}

auto plan = toSingleNodePlan(logicalPlan);
AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}

std::vector<TestCase> projectTestCases = {
Expand Down Expand Up @@ -284,7 +290,7 @@ TEST_F(PlanTest, specialFormConstantFold) {
// Verifies that func(..., null, ...) is folded to null for
// default-null-behavior functions and is not folded for
// non-default-null-behavior functions.
TEST_F(PlanTest, nullPropagation) {
TEST_P(PlanTest, nullPropagation) {
testConnector_->addTable("t", ROW({"a", "b", "c"}, BIGINT()));

struct TestCase {
Expand Down Expand Up @@ -314,11 +320,11 @@ TEST_F(PlanTest, nullPropagation) {
auto matcher = matchScan("t").project({expected, "a", "b"}).build();

auto plan = toSingleNodePlan(logicalPlan);
AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}
}

TEST_F(PlanTest, inList) {
TEST_P(PlanTest, inList) {
testConnector_->addTable(
"numbers", ROW({"a", "b", "c"}, {BIGINT(), DOUBLE(), VARCHAR()}));

Expand All @@ -344,7 +350,7 @@ TEST_F(PlanTest, inList) {
auto matcher = scanMatcher().filter("false").project().build();

auto plan = toSingleNodePlan(logicalPlan);
AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}
{
auto logicalPlan =
Expand All @@ -367,7 +373,7 @@ TEST_F(PlanTest, inList) {
}
}

TEST_F(PlanTest, multipleConnectors) {
TEST_P(PlanTest, multipleConnectors) {
auto extraConnector = std::make_shared<connector::TestConnector>("extra");
velox::connector::registerConnector(extraConnector);
connector::ConnectorMetadataRegistry::global().insert(
Expand Down Expand Up @@ -397,7 +403,7 @@ TEST_F(PlanTest, multipleConnectors) {
AXIOM_ASSERT_PLAN(plan, matcher);
}

TEST_F(PlanTest, filterToJoinEdge) {
TEST_P(PlanTest, filterToJoinEdge) {
auto nationType = ROW({"n_regionkey"}, BIGINT());
auto regionType = ROW({"r_regionkey"}, BIGINT());

Expand Down Expand Up @@ -458,13 +464,13 @@ TEST_F(PlanTest, filterToJoinEdge) {
.project()
.build();

AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}

checkSame(logicalPlan, referencePlan);
}

TEST_F(PlanTest, filterBreakup) {
TEST_P(PlanTest, filterBreakup) {
const char* filterText =
" (\n"
" l_partkey = p_partkey\n"
Expand Down Expand Up @@ -531,7 +537,7 @@ TEST_F(PlanTest, filterBreakup) {
.singleAggregation()
.build();

AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}

auto referenceBuilder =
Expand All @@ -543,7 +549,7 @@ TEST_F(PlanTest, filterBreakup) {
checkSame(logicalPlan, referencePlan);
}

TEST_F(PlanTest, values) {
TEST_P(PlanTest, values) {
auto nationType =
ROW({"n_nationkey", "n_regionkey", "n_name", "n_comment"},
{BIGINT(), BIGINT(), VARCHAR(), VARCHAR()});
Expand Down Expand Up @@ -779,7 +785,7 @@ TEST_F(PlanTest, values) {
}
}

TEST_F(PlanTest, parallelCse) {
TEST_P(PlanTest, parallelCse) {
testConnector_->addTable("t", ROW({"a", "b", "c"}, INTEGER()));

{
Expand All @@ -801,7 +807,7 @@ TEST_F(PlanTest, parallelCse) {
.project()
.build();

AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}

{
Expand All @@ -823,11 +829,11 @@ TEST_F(PlanTest, parallelCse) {
.project()
.build();

AXIOM_ASSERT_PLAN(plan, matcher);
AXIOM_ASSERT_PLAN_V1(plan, matcher);
}
}

TEST_F(PlanTest, lastProjection) {
TEST_P(PlanTest, lastProjection) {
testConnector_->addTable(
"numbers", ROW({"a", "b", "c"}, {BIGINT(), DOUBLE(), VARCHAR()}));

Expand All @@ -848,7 +854,7 @@ TEST_F(PlanTest, lastProjection) {
AXIOM_ASSERT_PLAN(plan, matcher);
}

TEST_F(PlanTest, orderByDuplicateKeys) {
TEST_P(PlanTest, orderByDuplicateKeys) {
testConnector_->addTable("t", ROW({"a"}, {BIGINT()}));

auto logicalPlan = lp::PlanBuilder(makeContext())
Expand All @@ -867,7 +873,7 @@ TEST_F(PlanTest, orderByDuplicateKeys) {
AXIOM_ASSERT_PLAN(plan, matcher);
}

TEST_F(PlanTest, lambdaArgs) {
TEST_P(PlanTest, lambdaArgs) {
testConnector_->addTable(
"t", ROW({"a", "b"}, {ARRAY(ARRAY(REAL())), BIGINT()}));

Expand All @@ -886,7 +892,7 @@ TEST_F(PlanTest, lambdaArgs) {
AXIOM_ASSERT_PLAN(plan, matcher);
}

TEST_F(PlanTest, outputNames) {
TEST_P(PlanTest, outputNames) {
testConnector_->addTable("t", ROW({"a", "b"}, {BIGINT(), BIGINT()}));

auto test = [&](std::string_view sql,
Expand All @@ -907,6 +913,8 @@ TEST_F(PlanTest, outputNames) {
test(R"(SELECT a AS "", b FROM t)", {"", "b"});
}

AXIOM_INSTANTIATE_V1_V2(PlanTest);

} // namespace
} // namespace facebook::axiom::optimizer

Expand Down
34 changes: 21 additions & 13 deletions axiom/optimizer/tests/UnknownStatsJoinTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ using namespace velox;
// When a join-key NDV is missing the join cost is unknown, so the optimizer
// falls back to the query's syntactic join order instead of a cost-based one.
// 't' is large and 'u' is small; 'k' is the join key.
class UnknownStatsJoinTest : public test::QueryTestBase {
class UnknownStatsJoinTest : public test::QueryTestBase,
public ::testing::WithParamInterface<bool> {
protected:
void SetUp() override {
useV2_ = GetParam();
test::QueryTestBase::SetUp();
}

velox::core::PlanNodePtr plan(const std::string& sql) {
return toSingleNodePlan(parseSelect(sql, kTestConnectorId));
}
Expand All @@ -43,7 +49,7 @@ class UnknownStatsJoinTest : public test::QueryTestBase {
}
};

TEST_F(UnknownStatsJoinTest, singleJoin) {
TEST_P(UnknownStatsJoinTest, singleJoin) {
testConnector_->addTable("t", ROW({"a", "k"}, BIGINT()))
->setStats(1'000'000, {{"k", {.numDistinct = 1'000'000}}});
testConnector_->addTable("u", ROW({"b", "k"}, BIGINT()))
Expand Down Expand Up @@ -76,7 +82,7 @@ TEST_F(UnknownStatsJoinTest, singleJoin) {
// cost-based ordering of an independent join elsewhere in the query. A
// non-deterministic filter between (u JOIN t) and the join with 'v' keeps the
// two joins in separate derived tables.
TEST_F(UnknownStatsJoinTest, twoJoins) {
TEST_P(UnknownStatsJoinTest, twoJoins) {
const auto query =
"SELECT count(*) "
"FROM (SELECT u.k AS k FROM u JOIN t ON u.k = t.k WHERE rand() < 0.1) AS s "
Expand All @@ -93,7 +99,7 @@ TEST_F(UnknownStatsJoinTest, twoJoins) {
const std::string& innerBuild) {
return matchScan(innerProbe)
.hashJoinInner(matchScan(innerBuild))
.filter()
.filterIf(!useV2_)
.hashJoinInner(matchScan("v"))
.aggregation()
.build();
Expand All @@ -119,7 +125,7 @@ TEST_F(UnknownStatsJoinTest, twoJoins) {
}
// A base table with no statistics at all must fall back to syntactic join
// order, not crash on the unknown cardinality.
TEST_F(UnknownStatsJoinTest, joinWithUnknownTableCardinality) {
TEST_P(UnknownStatsJoinTest, joinWithUnknownTableCardinality) {
testConnector_->addTable("t", ROW({"a", "k"}, BIGINT()))
->setStats(1'000'000, {{"k", {.numDistinct = 1'000'000}}});
testConnector_->addTable("u", ROW({"b", "k"}, BIGINT()));
Expand All @@ -140,15 +146,15 @@ TEST_F(UnknownStatsJoinTest, joinWithUnknownTableCardinality) {

// Two large tables join only through 'v'; the fallback must hash-join through
// it rather than cross-join the two.
TEST_F(UnknownStatsJoinTest, sharedTableJoinAvoidsCrossJoin) {
TEST_P(UnknownStatsJoinTest, sharedTableJoinAvoidsCrossJoin) {
addSharedJoinTableSchema();

const auto query =
"SELECT count(*) FROM t, u, v WHERE t.k = v.x AND u.k = v.y";

// Fallback on: hash-join 't' and 'u' through 'v'.
optimizerOptions_.syntacticJoinOrder = false;
AXIOM_ASSERT_PLAN(
AXIOM_ASSERT_PLAN_V1(
plan(query),
matchScan("t")
.hashJoinInner(matchScan("v"))
Expand All @@ -168,15 +174,15 @@ TEST_F(UnknownStatsJoinTest, sharedTableJoinAvoidsCrossJoin) {
}

// An expression equi-key ('t.k + 0') behaves the same under the flag toggle.
TEST_F(UnknownStatsJoinTest, sharedTableJoinAvoidsCrossJoinExpressionKey) {
TEST_P(UnknownStatsJoinTest, sharedTableJoinAvoidsCrossJoinExpressionKey) {
addSharedJoinTableSchema();

const auto query =
"SELECT count(*) FROM t, u, v WHERE t.k + 0 = v.x AND u.k = v.y";

// Fallback on: hash-join through 'v' on the projected key.
optimizerOptions_.syntacticJoinOrder = false;
AXIOM_ASSERT_PLAN(
AXIOM_ASSERT_PLAN_V1(
plan(query),
matchScan("t")
.project()
Expand All @@ -199,7 +205,7 @@ TEST_F(UnknownStatsJoinTest, sharedTableJoinAvoidsCrossJoinExpressionKey) {

// Guard: 'w' has no equi-join to any table, so it still cross-joins even while
// 't' and 'u' hash-join through the shared table 'v'.
TEST_F(UnknownStatsJoinTest, crossJoinWhenNoEquiPartner) {
TEST_P(UnknownStatsJoinTest, crossJoinWhenNoEquiPartner) {
addSharedJoinTableSchema();
testConnector_->addTable("w", ROW({"c", "k"}, BIGINT()));

Expand All @@ -209,7 +215,7 @@ TEST_F(UnknownStatsJoinTest, crossJoinWhenNoEquiPartner) {
// Fallback on: 't'/'u' hash-join through 'v'; 'w' has no partner, so it
// crosses.
optimizerOptions_.syntacticJoinOrder = false;
AXIOM_ASSERT_PLAN(
AXIOM_ASSERT_PLAN_V1(
plan(query),
matchScan("t")
.hashJoinInner(matchScan("v"))
Expand All @@ -231,7 +237,7 @@ TEST_F(UnknownStatsJoinTest, crossJoinWhenNoEquiPartner) {
}

// The join sampler must tolerate an unknown build-side cardinality.
TEST_F(UnknownStatsJoinTest, sampledJoinWithUnknownCardinality) {
TEST_P(UnknownStatsJoinTest, sampledJoinWithUnknownCardinality) {
optimizerOptions_.sampleJoins = true;

testConnector_->addTable("t", ROW({"a", "k"}, BIGINT()))
Expand All @@ -251,7 +257,7 @@ TEST_F(UnknownStatsJoinTest, sampledJoinWithUnknownCardinality) {

// Enabling sampleJoins must not change the chosen plan when a side has
// unknown cardinality.
TEST_F(UnknownStatsJoinTest, sampledJoinMatchesUnsampledOnUnknownCardinality) {
TEST_P(UnknownStatsJoinTest, sampledJoinMatchesUnsampledOnUnknownCardinality) {
testConnector_->addTable("t", ROW({"a", "k"}, BIGINT()))
->setStats(1'000'000, {{"k", {.numDistinct = 1'000'000}}});
testConnector_->addTable("u", ROW({"b", "k"}, BIGINT()));
Expand All @@ -275,5 +281,7 @@ TEST_F(UnknownStatsJoinTest, sampledJoinMatchesUnsampledOnUnknownCardinality) {
AXIOM_ASSERT_PLAN(plan(altQuery), matchJoin("t", "u"));
}

AXIOM_INSTANTIATE_V1_V2(UnknownStatsJoinTest);

} // namespace
} // namespace facebook::axiom::optimizer
Loading