// Copyright 2023 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include namespace DB { namespace tests { class PhysicalPlanTestRunner : public DB::tests::ExecutorTest { public: void initializeContext() override { ExecutorTest::initializeContext(); context.addMockTable( {"test_db", "test_table"}, {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}}, {toNullableVec("s1", {"banana", {}, "banana"}), toNullableVec("s2", {"apple", {}, "banana"})}); context.addExchangeReceiver( "exchange1", {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}}, {toNullableVec("s1", {"banana", {}, "banana"}), toNullableVec("s2", {"apple", {}, "banana"})}); context.addExchangeReceiver( "exchange2", {{"partition", TiDB::TP::TypeLongLong}, {"order", TiDB::TP::TypeLongLong}}, {toNullableVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {1, 1, 2, 2, 1, 1, 2, 2})}, 1, {{"partition", TiDB::TP::TypeLongLong}}); context.addExchangeReceiver( "exchange3", {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}, {"s3", TiDB::TP::TypeLongLong}, {"s4", TiDB::TP::TypeLongLong}}, {toNullableVec("s1", {"banana", {}, "banana"}), toNullableVec("s2", {"apple", {}, "banana"}), toNullableVec("s3", {1, {}, 1}), toNullableVec("s4", {1, 1, {}})}); context.addExchangeReceiver( "exchange_r_table", {{"s", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}, {toNullableVec("s", {"banana", "banana"}), toNullableVec("join_c", {"apple", "banana"})}); context.addExchangeReceiver( "exchange_l_table", {{"s", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}, {toNullableVec("s", {"banana", "banana"}), toNullableVec("join_c", {"apple", "banana"})}); context.addMockTable( {"multi_test", "t1"}, {{"a", TiDB::TP::TypeLong}, {"b", TiDB::TP::TypeLong}, {"c", TiDB::TP::TypeLong}}, {toVec("a", {1, 3, 0}), toVec("b", {2, 2, 0}), toVec("c", {3, 2, 0})}); context.addMockTable( {"multi_test", "t2"}, {{"a", TiDB::TP::TypeLong}, {"b", TiDB::TP::TypeLong}, {"c", TiDB::TP::TypeLong}}, {toVec("a", {3, 3, 0}), toVec("b", {4, 2, 0}), toVec("c", {5, 3, 0})}); context.addMockTable( {"multi_test", "t3"}, {{"a", TiDB::TP::TypeLong}, {"b", TiDB::TP::TypeLong}}, {toVec("a", {1, 2, 0}), toVec("b", {2, 2, 0})}); context.addMockTable( {"multi_test", "t4"}, {{"a", TiDB::TP::TypeLong}, {"b", TiDB::TP::TypeLong}}, {toVec("a", {3, 2, 0}), toVec("b", {4, 2, 0})}); } void execute( const std::shared_ptr & request, const String & expected_physical_plan, const String & expected_streams, const ColumnsWithTypeAndName & expect_columns) { // TODO support multi-streams. size_t max_streams = 1; DAGContext dag_context(*request, "executor_test", max_streams); TiFlashTestEnv::setUpTestContext( *context.context, &dag_context, context.mockStorage(), TestType::EXECUTOR_TEST); PhysicalPlan physical_plan{*context.context, log->identifier()}; assert(request); physical_plan.build(request.get()); physical_plan.outputAndOptimize(); ASSERT_EQ(Poco::trim(expected_physical_plan), Poco::trim(physical_plan.toString())); BlockInputStreamPtr final_stream; { DAGPipeline pipeline; physical_plan.buildBlockInputStream(pipeline, *context.context, max_streams); executeCreatingSets(pipeline, *context.context, max_streams, log); final_stream = pipeline.firstStream(); FmtBuffer fb; final_stream->dumpTree(fb); ASSERT_EQ(Poco::trim(expected_streams), Poco::trim(fb.toString())); } ASSERT_COLUMNS_EQ_UR(expect_columns, readBlock(final_stream)); } std::tuple multiTestScan() { return { context.scan("multi_test", "t1"), context.scan("multi_test", "t2"), context.scan("multi_test", "t3"), context.scan("multi_test", "t4")}; } LoggerPtr log = Logger::get("PhysicalPlanTestRunner", "test_physical_plan"); }; TEST_F(PhysicalPlanTestRunner, Filter) try { auto request = context.receive("exchange1").filter(eq(col("s1"), col("s2"))).build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( Expression: Filter MockExchangeReceiver)", {toNullableVec({"banana"}), toNullableVec({"banana"})}); } CATCH TEST_F(PhysicalPlanTestRunner, Limit) try { auto request = context.receive("exchange1").limit(1).build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( Expression: Limit, limit = 1 MockExchangeReceiver)", {toNullableVec({"banana"}), toNullableVec({"apple"})}); } CATCH TEST_F(PhysicalPlanTestRunner, TopN) try { auto request = context.receive("exchange1").topN("s2", false, 1).build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( Expression: MergeSorting, limit = 1 PartialSorting: limit = 1 MockExchangeReceiver)", {toNullableVec({{}}), toNullableVec({{}})}); } CATCH // agg's schema = agg funcs + agg group bys TEST_F(PhysicalPlanTestRunner, Aggregation) try { auto request = context.receive("exchange1").aggregation(Max(col("s2")), col("s1")).build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( Expression: Expression: Aggregating MockExchangeReceiver)", {toNullableVec({{}, "banana"}), toNullableVec({{}, "banana"})}); } CATCH TEST_F(PhysicalPlanTestRunner, Projection) try { auto request = context.receive("exchange1").project({concat(col("s1"), col("s2"))}).build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: | is_tidb_operator: true, schema: | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( Expression: Expression: MockExchangeReceiver)", {toNullableVec({"bananaapple", {}, "bananabanana"})}); request = context.receive("exchange3") .project( {concat(col("s1"), col("s2")), concat(col("s1"), col("s2")), And(col("s3"), col("s4")), NOT(col("s3"))}) .build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , , , | is_tidb_operator: true, schema: , , , | is_tidb_operator: true, schema: , , , )", /*expected_streams=*/R"( Expression: Expression: MockExchangeReceiver)", {toNullableVec({"bananaapple", {}, "bananabanana"}), toNullableVec({"bananaapple", {}, "bananabanana"}), toNullableVec({1, {}, {}}), toNullableVec({0, {}, 0})}); } CATCH TEST_F(PhysicalPlanTestRunner, MockExchangeSender) try { auto request = context.receive("exchange1").exchangeSender(tipb::Hash).build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: true, schema: , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( MockExchangeSender Expression: MockExchangeReceiver)", {toNullableVec({"banana", {}, "banana"}), toNullableVec({"apple", {}, "banana"})}); } CATCH TEST_F(PhysicalPlanTestRunner, MockExchangeReceiver) try { auto request = context.receive("exchange1").build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( Expression: MockExchangeReceiver)", {toNullableVec({"banana", {}, "banana"}), toNullableVec({"apple", {}, "banana"})}); } CATCH TEST_F(PhysicalPlanTestRunner, WindowFunction) try { auto get_request = [&](bool enable_fine_grained_shuffle) { static const uint64_t enable = 8; static const uint64_t disable = 0; bool fine_grained_shuffle_stream_count = enable_fine_grained_shuffle ? enable : disable; return context.receive("exchange2", fine_grained_shuffle_stream_count) .sort( {{"partition", false}, {"order", false}, {"partition", false}, {"order", false}}, true, fine_grained_shuffle_stream_count) .window( RowNumber(), {"order", false}, {"partition", false}, buildDefaultRowsFrame(), fine_grained_shuffle_stream_count) .build(context); }; auto request = get_request(false); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , , | is_tidb_operator: true, schema: , , | is_tidb_operator: true, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( Expression: Expression: Window, function: {row_number}, frame: {type: Rows, boundary_begin: Current, boundary_end: Current} MergeSorting, limit = 0 PartialSorting: limit = 0 MockExchangeReceiver)", {toNullableVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {1, 1, 2, 2, 1, 1, 2, 2}), toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}); request = get_request(true); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , , | is_tidb_operator: true, schema: , , | is_tidb_operator: true, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( Expression: Expression: Window: , function: {row_number}, frame: {type: Rows, boundary_begin: Current, boundary_end: Current} MergeSorting: , limit = 0 PartialSorting: : limit = 0 MockExchangeReceiver)", {toNullableVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {1, 1, 2, 2, 1, 1, 2, 2}), toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}); } CATCH TEST_F(PhysicalPlanTestRunner, MockTableScan) try { auto request = context.scan("test_db", "test_table").build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( Expression: MockTableScan)", {toNullableVec({"banana", {}, "banana"}), toNullableVec({"apple", {}, "banana"})}); } CATCH TEST_F(PhysicalPlanTestRunner, Join) try { // Simple Join { auto get_request = [&](const tipb::JoinType & join_type) { return context.receive("exchange_l_table") .join(context.receive("exchange_r_table"), join_type, {col("join_c"), col("join_c")}) .build(context); }; auto request = get_request(tipb::JoinType::TypeInnerJoin); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , , , | is_tidb_operator: true, schema: , , , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( CreatingSets HashJoinBuild: , join_kind = Inner Expression: MockExchangeReceiver Expression: HashJoinProbe: Expression: MockExchangeReceiver)", {toNullableVec({"banana", "banana"}), toNullableVec({"apple", "banana"}), toNullableVec({"banana", "banana"}), toNullableVec({"apple", "banana"})}); request = get_request(tipb::JoinType::TypeLeftOuterJoin); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , , , | is_tidb_operator: true, schema: , , , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( CreatingSets HashJoinBuild: , join_kind = Left Expression: MockExchangeReceiver Expression: HashJoinProbe: Expression: MockExchangeReceiver)", {toNullableVec({"banana", "banana"}), toNullableVec({"apple", "banana"}), toNullableVec({"banana", "banana"}), toNullableVec({"apple", "banana"})}); request = get_request(tipb::JoinType::TypeRightOuterJoin); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , , , | is_tidb_operator: true, schema: , , , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( CreatingSets HashJoinBuild: , join_kind = Right Expression: MockExchangeReceiver Expression: HashJoinProbe: Expression: MockExchangeReceiver)", {toNullableVec({"banana", "banana"}), toNullableVec({"apple", "banana"}), toNullableVec({"banana", "banana"}), toNullableVec({"apple", "banana"})}); } // MultiRightInnerJoin { auto [t1, t2, t3, t4] = multiTestScan(); auto request = t1.join(t2, tipb::JoinType::TypeRightOuterJoin, {col("a")}) .join( t3.join(t4, tipb::JoinType::TypeRightOuterJoin, {col("a")}), tipb::JoinType::TypeInnerJoin, {col("b")}) .build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , , , , , , , , , | is_tidb_operator: true, schema: , , , , , , , , , | is_tidb_operator: false, schema: , , , , , | is_tidb_operator: true, schema: , , , , , | is_tidb_operator: false, schema: , , | is_tidb_operator: true, schema: , , | is_tidb_operator: false, schema: , , | is_tidb_operator: true, schema: , , | is_tidb_operator: false, schema: , , , | is_tidb_operator: true, schema: , , , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( CreatingSets HashJoinBuild x 2: , join_kind = Right Expression: MockTableScan HashJoinBuild: , join_kind = Inner Expression: HashJoinProbe: Expression: MockTableScan Expression: HashJoinProbe: Expression: HashJoinProbe: Expression: MockTableScan)", {toNullableVec({3, 3, 0}), toNullableVec({2, 2, 0}), toNullableVec({2, 2, 0}), toNullableVec({3, 3, 0}), toNullableVec({4, 2, 0}), toNullableVec({5, 3, 0}), toNullableVec({2, 2, 0}), toNullableVec({2, 2, 0}), toNullableVec({2, 2, 0}), toNullableVec({2, 2, 0})}); } // MultiRightLeftJoin { auto [t1, t2, t3, t4] = multiTestScan(); auto request = t1.join(t2, tipb::JoinType::TypeRightOuterJoin, {col("a")}) .join( t3.join(t4, tipb::JoinType::TypeRightOuterJoin, {col("a")}), tipb::JoinType::TypeLeftOuterJoin, {col("b")}) .build(context); execute( request, /*expected_physical_plan=*/R"( | is_tidb_operator: false, schema: , , , , , , , , , | is_tidb_operator: true, schema: , , , , , , , , , | is_tidb_operator: false, schema: , , , , , | is_tidb_operator: true, schema: , , , , , | is_tidb_operator: false, schema: , , | is_tidb_operator: true, schema: , , | is_tidb_operator: false, schema: , , | is_tidb_operator: true, schema: , , | is_tidb_operator: false, schema: , , , | is_tidb_operator: true, schema: , , , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , | is_tidb_operator: false, schema: , | is_tidb_operator: true, schema: , )", /*expected_streams=*/R"( CreatingSets HashJoinBuild x 2: , join_kind = Right Expression: MockTableScan HashJoinBuild: , join_kind = Left Expression: HashJoinProbe: Expression: MockTableScan Expression: HashJoinProbe: Expression: HashJoinProbe: Expression: MockTableScan)", {toNullableVec({3, 3, 0}), toNullableVec({2, 2, 0}), toNullableVec({2, 2, 0}), toNullableVec({3, 3, 0}), toNullableVec({4, 2, 0}), toNullableVec({5, 3, 0}), toNullableVec({2, 2, 0}), toNullableVec({2, 2, 0}), toNullableVec({2, 2, 0}), toNullableVec({2, 2, 0})}); } } CATCH } // namespace tests } // namespace DB