diff --git a/dbms/src/Debug/astToExecutor.cpp b/dbms/src/Debug/astToExecutor.cpp index 48080f86461..e1d22b328ac 100644 --- a/dbms/src/Debug/astToExecutor.cpp +++ b/dbms/src/Debug/astToExecutor.cpp @@ -223,7 +223,6 @@ String getFunctionNameForConstantFolding(tipb::Expr * expr) } } - void foldConstant(tipb::Expr * expr, int32_t collator_id, const Context & context) { if (expr->tp() == tipb::ScalarFunc) @@ -286,7 +285,6 @@ void functionToPB(const DAGSchema & input, ASTFunction * func, tipb::Expr * expr void identifierToPB(const DAGSchema & input, ASTIdentifier * id, tipb::Expr * expr, int32_t collator_id); - void astToPB(const DAGSchema & input, ASTPtr ast, tipb::Expr * expr, int32_t collator_id, const Context & context) { if (auto * id = typeid_cast(ast.get())) @@ -307,17 +305,23 @@ void astToPB(const DAGSchema & input, ASTPtr ast, tipb::Expr * expr, int32_t col } } -void functionToPB(const DAGSchema & input, ASTFunction * func, tipb::Expr * expr, int32_t collator_id, const Context & context) +auto checkSchema(const DAGSchema & input, String checked_column) { - /// aggregation function is handled in Aggregation, so just treated as a column auto ft = std::find_if(input.begin(), input.end(), [&](const auto & field) { - auto column_name = splitQualifiedName(func->getColumnName()); - auto field_name = splitQualifiedName(field.first); - if (column_name.first.empty()) - return field_name.second == column_name.second; + auto [checked_db_name, checked_table_name, checked_column_name] = splitQualifiedName(checked_column); + auto [db_name, table_name, column_name] = splitQualifiedName(field.first); + if (checked_table_name.empty()) + return column_name == checked_column_name; else - return field_name.first == column_name.first && field_name.second == column_name.second; + return table_name == checked_table_name && column_name == checked_column_name; }); + return ft; +} + +void functionToPB(const DAGSchema & input, ASTFunction * func, tipb::Expr * expr, int32_t collator_id, const Context & context) +{ + /// aggregation function is handled in Aggregation, so just treated as a column + auto ft = checkSchema(input, func->getColumnName()); if (ft != input.end()) { expr->set_tp(tipb::ColumnRef); @@ -520,14 +524,7 @@ void functionToPB(const DAGSchema & input, ASTFunction * func, tipb::Expr * expr void identifierToPB(const DAGSchema & input, ASTIdentifier * id, tipb::Expr * expr, int32_t collator_id) { - auto ft = std::find_if(input.begin(), input.end(), [&](const auto & field) { - auto column_name = splitQualifiedName(id->getColumnName()); - auto field_name = splitQualifiedName(field.first); - if (column_name.first.empty()) - return field_name.second == column_name.second; - else - return field_name.first == column_name.first && field_name.second == column_name.second; - }); + auto ft = checkSchema(input, id->getColumnName()); if (ft == input.end()) throw Exception("No such column " + id->getColumnName(), ErrorCodes::NO_SUCH_COLUMN_IN_TABLE); expr->set_tp(tipb::ColumnRef); @@ -542,19 +539,18 @@ void collectUsedColumnsFromExpr(const DAGSchema & input, ASTPtr ast, std::unorde { if (auto * id = typeid_cast(ast.get())) { - auto column_name = splitQualifiedName(id->getColumnName()); - if (!column_name.first.empty()) + auto [db_name, table_name, column_name] = splitQualifiedName(id->getColumnName()); + if (!table_name.empty()) used_columns.emplace(id->getColumnName()); else { bool found = false; for (const auto & field : input) { - auto field_name = splitQualifiedName(field.first); - if (field_name.second == column_name.second) + if (splitQualifiedName(field.first).column_name == column_name) { if (found) - throw Exception("ambiguous column for " + column_name.second); + throw Exception("ambiguous column for " + column_name); found = true; used_columns.emplace(field.first); } @@ -570,14 +566,7 @@ void collectUsedColumnsFromExpr(const DAGSchema & input, ASTPtr ast, std::unorde else { /// check function - auto ft = std::find_if(input.begin(), input.end(), [&](const auto & field) { - auto column_name = splitQualifiedName(func->getColumnName()); - auto field_name = splitQualifiedName(field.first); - if (column_name.first.empty()) - return field_name.second == column_name.second; - else - return field_name.first == column_name.first && field_name.second == column_name.second; - }); + auto ft = checkSchema(input, func->getColumnName()); if (ft != input.end()) { used_columns.emplace(func->getColumnName()); @@ -597,14 +586,7 @@ TiDB::ColumnInfo compileExpr(const DAGSchema & input, ASTPtr ast) if (auto * id = typeid_cast(ast.get())) { /// check column - auto ft = std::find_if(input.begin(), input.end(), [&](const auto & field) { - auto column_name = splitQualifiedName(id->getColumnName()); - auto field_name = splitQualifiedName(field.first); - if (column_name.first.empty()) - return field_name.second == column_name.second; - else - return field_name.first == column_name.first && field_name.second == column_name.second; - }); + auto ft = checkSchema(input, id->getColumnName()); if (ft == input.end()) throw Exception("No such column " + id->getColumnName(), ErrorCodes::NO_SUCH_COLUMN_IN_TABLE); ci = ft->second; @@ -793,24 +775,28 @@ void setServiceAddr(const std::string & addr) } } // namespace Debug -std::pair splitQualifiedName(const String & s) +ColumnName splitQualifiedName(const String & s) { - std::pair ret; + ColumnName ret; Poco::StringTokenizer string_tokens(s, "."); switch (string_tokens.count()) { case 1: - ret.second = s; + ret.column_name = s; break; case 2: - ret.first = string_tokens[0]; - ret.second = string_tokens[1]; + ret.table_name = string_tokens[0]; + ret.column_name = string_tokens[1]; + break; + case 3: + ret.db_name = string_tokens[0]; + ret.table_name = string_tokens[1]; + ret.column_name = string_tokens[2]; break; default: throw Exception("Invalid identifier name " + s); } - return ret; } @@ -1217,7 +1203,7 @@ void Join::columnPrune(std::unordered_set & used_columns) auto col_name = identifier->getColumnName(); for (auto & field : children[0]->output_schema) { - if (col_name == splitQualifiedName(field.first).second) + if (col_name == splitQualifiedName(field.first).column_name) { left_used_columns.emplace(field.first); break; @@ -1225,7 +1211,7 @@ void Join::columnPrune(std::unordered_set & used_columns) } for (auto & field : children[1]->output_schema) { - if (col_name == splitQualifiedName(field.first).second) + if (col_name == splitQualifiedName(field.first).column_name) { right_used_columns.emplace(field.first); break; @@ -1272,7 +1258,7 @@ void Join::fillJoinKeyAndFieldType( { const auto & [col_name, col_info] = child_schema[index]; - if (splitQualifiedName(col_name).second == identifier->getColumnName()) + if (splitQualifiedName(col_name).column_name == identifier->getColumnName()) { auto tipb_type = TiDB::columnInfoToFieldType(col_info); tipb_type.set_collate(collator_id); @@ -1361,7 +1347,7 @@ void Join::toMPPSubPlan(size_t & executor_index, const DAGProperties & propertie auto push_back_partition_key = [](auto & partition_keys, const auto & child_schema, const auto & key) { for (size_t index = 0; index < child_schema.size(); ++index) { - if (splitQualifiedName(child_schema[index].first).second == key->getColumnName()) + if (splitQualifiedName(child_schema[index].first).column_name == key->getColumnName()) { partition_keys.push_back(index); break; @@ -1495,7 +1481,7 @@ bool Sort::toTiPBExecutor(tipb::Executor * tipb_executor, int32_t collator_id, c } // namespace mock -ExecutorPtr compileTableScan(size_t & executor_index, TableInfo & table_info, String & table_alias, bool append_pk_column) +ExecutorPtr compileTableScan(size_t & executor_index, TableInfo & table_info, const String & db, const String & table_name, bool append_pk_column) { DAGSchema ts_output; for (const auto & column_info : table_info.columns) @@ -1510,7 +1496,7 @@ ExecutorPtr compileTableScan(size_t & executor_index, TableInfo & table_info, St ci.origin_default_value = column_info.origin_default_value; /// use qualified name as the column name to handle multiple table queries, not very /// efficient but functionally enough for mock test - ts_output.emplace_back(std::make_pair(table_alias + "." + column_info.name, std::move(ci))); + ts_output.emplace_back(std::make_pair(db + "." + table_name + "." + column_info.name, std::move(ci))); } if (append_pk_column) { diff --git a/dbms/src/Debug/astToExecutor.h b/dbms/src/Debug/astToExecutor.h index 50eaeda5b07..b97577f1e55 100644 --- a/dbms/src/Debug/astToExecutor.h +++ b/dbms/src/Debug/astToExecutor.h @@ -50,7 +50,17 @@ extern String LOCAL_HOST; void setServiceAddr(const std::string & addr); } // namespace Debug -std::pair splitQualifiedName(const String & s); +// We use qualified format like "db_name.table_name.column_name" +// to identify one column of a table. +// We can split the qualified format into the ColumnName struct. +struct ColumnName +{ + String db_name; + String table_name; + String column_name; +}; + +ColumnName splitQualifiedName(const String & s); struct MPPCtx { @@ -165,11 +175,11 @@ struct TableScan : public Executor void setTipbColumnInfo(tipb::ColumnInfo * ci, const DAGColumnInfo & dag_column_info) const { - auto column_name = splitQualifiedName(dag_column_info.first).second; - if (column_name == MutableSupport::tidb_pk_column_name) + auto names = splitQualifiedName(dag_column_info.first); + if (names.column_name == MutableSupport::tidb_pk_column_name) ci->set_column_id(-1); else - ci->set_column_id(table_info.getColumnID(column_name)); + ci->set_column_id(table_info.getColumnID(names.column_name)); ci->set_tp(dag_column_info.second.tp); ci->set_flag(dag_column_info.second.flag); ci->set_columnlen(dag_column_info.second.flen); @@ -343,7 +353,7 @@ struct Sort : Executor using ExecutorPtr = std::shared_ptr; -ExecutorPtr compileTableScan(size_t & executor_index, TableInfo & table_info, String & table_alias, bool append_pk_column); +ExecutorPtr compileTableScan(size_t & executor_index, TableInfo & table_info, const String & db, const String & table_name, bool append_pk_column); ExecutorPtr compileSelection(ExecutorPtr input, size_t & executor_index, ASTPtr filter); diff --git a/dbms/src/Debug/dbgFuncCoprocessor.cpp b/dbms/src/Debug/dbgFuncCoprocessor.cpp index 72f3599ebc4..fd2f9606015 100644 --- a/dbms/src/Debug/dbgFuncCoprocessor.cpp +++ b/dbms/src/Debug/dbgFuncCoprocessor.cpp @@ -767,13 +767,13 @@ std::pair compileQueryBlock( } } } - root_executor = compileTableScan(executor_index, table_info, table_alias, append_pk_column); + root_executor = compileTableScan(executor_index, table_info, "", table_alias, append_pk_column); } } else { TableInfo left_table_info = table_info; - String left_table_alias = table_alias; + auto const & left_table_alias = table_alias; TableInfo right_table_info; String right_table_alias; { @@ -812,24 +812,24 @@ std::pair compileQueryBlock( { if (auto * identifier = typeid_cast(expr.get())) { - auto names = splitQualifiedName(identifier->getColumnName()); - if (names.second == MutableSupport::tidb_pk_column_name) + auto [db_name, table_name, column_name] = splitQualifiedName(identifier->getColumnName()); + if (column_name == MutableSupport::tidb_pk_column_name) { - if (names.first.empty()) + if (table_name.empty()) { throw Exception("tidb pk column must be qualified since there are more than one tables"); } - if (names.first == left_table_alias) + if (table_name == left_table_alias) left_append_pk_column = true; - else if (names.first == right_table_alias) + else if (table_name == right_table_alias) right_append_pk_column = true; else - throw Exception("Unknown table alias: " + names.first); + throw Exception("Unknown table alias: " + table_name); } } } - auto left_ts = compileTableScan(executor_index, left_table_info, left_table_alias, left_append_pk_column); - auto right_ts = compileTableScan(executor_index, right_table_info, right_table_alias, right_append_pk_column); + auto left_ts = compileTableScan(executor_index, left_table_info, "", left_table_alias, left_append_pk_column); + auto right_ts = compileTableScan(executor_index, right_table_info, "", right_table_alias, right_append_pk_column); root_executor = compileJoin(executor_index, left_ts, right_ts, joined_table->table_join); } diff --git a/dbms/src/Flash/tests/gtest_compute_server.cpp b/dbms/src/Flash/tests/gtest_compute_server.cpp index 7cd62f1ab66..5dac37d207f 100644 --- a/dbms/src/Flash/tests/gtest_compute_server.cpp +++ b/dbms/src/Flash/tests/gtest_compute_server.cpp @@ -83,15 +83,15 @@ try size_t task_size = tasks.size(); std::vector expected_strings = { - "exchange_sender_6 | type:Hash, {<0, String>}\n" - " table_scan_1 | {<0, String>}", + "exchange_sender_6 | type:Hash, {<0, String>, <1, String>}\n" + " table_scan_1 | {<0, String>, <1, String>}", "exchange_sender_5 | type:Hash, {<0, String>, <1, String>}\n" " table_scan_0 | {<0, String>, <1, String>}", - "exchange_sender_4 | type:PassThrough, {<0, String>, <1, String>, <2, String>}\n" + "exchange_sender_4 | type:PassThrough, {<0, String>, <1, String>, <2, String>, <3, String>}\n" " topn_3 | order_by: {(<1, String>, desc: false)}, limit: 2\n" " Join_2 | LeftOuterJoin, HashJoin. left_join_keys: {<0, String>}, right_join_keys: {<0, String>}\n" " exchange_receiver_7 | type:PassThrough, {<0, String>, <1, String>}\n" - " exchange_receiver_8 | type:PassThrough, {<0, String>}"}; + " exchange_receiver_8 | type:PassThrough, {<0, String>, <1, String>}"}; for (size_t i = 0; i < task_size; ++i) { ASSERT_DAGREQUEST_EQAUL(expected_strings[i], tasks[i].dag_request); @@ -100,7 +100,8 @@ try auto expected_cols = { toNullableVec({{}, "banana"}), toNullableVec({{}, "apple"}), - toNullableVec({{}, {}})}; + toNullableVec({{}, "banana"}), + toNullableVec({{}, "apple"})}; ASSERT_MPPTASK_EQUAL(tasks, expected_cols); } CATCH diff --git a/dbms/src/Flash/tests/gtest_executor.cpp b/dbms/src/Flash/tests/gtest_filter_executor.cpp similarity index 78% rename from dbms/src/Flash/tests/gtest_executor.cpp rename to dbms/src/Flash/tests/gtest_filter_executor.cpp index b8fac961832..55f2279c954 100644 --- a/dbms/src/Flash/tests/gtest_executor.cpp +++ b/dbms/src/Flash/tests/gtest_filter_executor.cpp @@ -19,7 +19,7 @@ namespace DB { namespace tests { -class ExecutorTestRunner : public DB::tests::ExecutorTest +class FilterExecutorTestRunner : public DB::tests::ExecutorTest { public: void initializeContext() override @@ -36,7 +36,7 @@ class ExecutorTestRunner : public DB::tests::ExecutorTest } }; -TEST_F(ExecutorTestRunner, Filter) +TEST_F(FilterExecutorTestRunner, equals) try { WRAP_FOR_DIS_ENABLE_PLANNER_BEGIN @@ -62,5 +62,22 @@ try } CATCH +TEST_F(FilterExecutorTestRunner, FilterWithQualifiedFormat) +try +{ + auto request = context + .scan("test_db", "test_table") + .filter(eq(col("test_table.s1"), col("test_table.s2"))) + .build(context); + { + ASSERT_COLUMNS_EQ_R(executeStreams(request), + createColumns({toNullableVec({"banana"}), + toNullableVec({"banana"})})); + } +} +CATCH + +/// TODO: more functions. + } // namespace tests } // namespace DB diff --git a/dbms/src/Flash/tests/gtest_split_tasks.cpp b/dbms/src/Flash/tests/gtest_split_tasks.cpp index 151e22baa5b..1155c450594 100644 --- a/dbms/src/Flash/tests/gtest_split_tasks.cpp +++ b/dbms/src/Flash/tests/gtest_split_tasks.cpp @@ -44,7 +44,7 @@ try .topN("s2", false, 10) .buildMPPTasks(context); - size_t task_size = tasks.size(); + const auto task_size = tasks.size(); std::vector executors = { "exchange_sender_7 | type:Hash, {<0, String>, <1, String>, <2, String>}\n" " aggregation_6 | group_by: {<1, String>, <2, String>}, agg_func: {max(<0, String>)}\n" @@ -93,17 +93,17 @@ try .topN("join_c", false, 2) .buildMPPTasks(context); - size_t task_size = tasks.size(); + const auto task_size = tasks.size(); std::vector executors = { - "exchange_sender_6 | type:Hash, {<0, String>}\n" - " table_scan_1 | {<0, String>}", + "exchange_sender_6 | type:Hash, {<0, String>, <1, String>}\n" + " table_scan_1 | {<0, String>, <1, String>}", "exchange_sender_5 | type:Hash, {<0, String>, <1, String>}\n" " table_scan_0 | {<0, String>, <1, String>}", - "exchange_sender_4 | type:PassThrough, {<0, String>, <1, String>, <2, String>}\n" + "exchange_sender_4 | type:PassThrough, {<0, String>, <1, String>, <2, String>, <3, String>}\n" " topn_3 | order_by: {(<1, String>, desc: false)}, limit: 2\n" " Join_2 | LeftOuterJoin, HashJoin. left_join_keys: {<0, String>}, right_join_keys: {<0, String>}\n" " exchange_receiver_7 | type:PassThrough, {<0, String>, <1, String>}\n" - " exchange_receiver_8 | type:PassThrough, {<0, String>}"}; + " exchange_receiver_8 | type:PassThrough, {<0, String>, <1, String>}"}; for (size_t i = 0; i < task_size; ++i) { ASSERT_DAGREQUEST_EQAUL(executors[i], tasks[i].dag_request); diff --git a/dbms/src/TestUtils/mockExecutor.cpp b/dbms/src/TestUtils/mockExecutor.cpp index e650553134d..2b12eeb9c18 100644 --- a/dbms/src/TestUtils/mockExecutor.cpp +++ b/dbms/src/TestUtils/mockExecutor.cpp @@ -138,8 +138,7 @@ DAGRequestBuilder & DAGRequestBuilder::mockTable(const String & db, const String ret.id = i++; table_info.columns.push_back(std::move(ret)); } - String empty_alias; - root = compileTableScan(getExecutorIndex(), table_info, empty_alias, false); + root = compileTableScan(getExecutorIndex(), table_info, db, table, false); return *this; } diff --git a/dbms/src/TestUtils/tests/gtest_mock_executors.cpp b/dbms/src/TestUtils/tests/gtest_mock_executors.cpp index b415632b72f..ad9c7790f9e 100644 --- a/dbms/src/TestUtils/tests/gtest_mock_executors.cpp +++ b/dbms/src/TestUtils/tests/gtest_mock_executors.cpp @@ -61,6 +61,7 @@ try " table_scan_0 | {<0, String>, <1, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + request = context.scan("test_db", "test_table_1") .filter(And(eq(col("s1"), col("s2")), lt(col("s2"), col("s2")))) // type in lt must be same .build(context); @@ -69,6 +70,15 @@ try " table_scan_0 | {<0, Long>, <1, String>, <2, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + + request = context.scan("test_db", "test_table_1") + .filter(And(eq(col("test_table_1.s1"), col("test_table_1.s2")), lt(col("test_table_1.s2"), col("test_table_1.s2")))) + .build(context); + { + String expected = "selection_1 | equals(<0, Long>, <1, String>) and less(<1, String>, <1, String>)}\n" + " table_scan_0 | {<0, Long>, <1, String>, <2, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + } } CATCH @@ -83,6 +93,7 @@ try " table_scan_0 | {<0, String>, <1, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + request = context.scan("test_db", "test_table_1") .project({col("s3"), eq(col("s1"), col("s2"))}) .build(context); @@ -91,6 +102,7 @@ try " table_scan_0 | {<0, Long>, <1, String>, <2, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + request = context.scan("test_db", "test_table_1") .project({"s1", "s2"}) .build(context); @@ -99,6 +111,15 @@ try " table_scan_0 | {<0, Long>, <1, String>, <2, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + + request = context.scan("test_db", "test_table_1") + .project({"test_table_1.s1", "test_table_1.s2"}) + .build(context); + { + String expected = "project_1 | {<0, Long>, <1, String>}\n" + " table_scan_0 | {<0, Long>, <1, String>, <2, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + } } CATCH @@ -113,6 +134,7 @@ try " table_scan_0 | {<0, String>, <1, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + request = context.scan("test_db", "test_table_1") .limit(lit(Field(static_cast(10)))) .build(context); @@ -135,6 +157,7 @@ try " table_scan_0 | {<0, String>, <1, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + request = context.scan("test_db", "test_table") .topN("s1", false, 10) .build(context); @@ -143,6 +166,15 @@ try " table_scan_0 | {<0, String>, <1, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + + request = context.scan("test_db", "test_table") + .topN("test_table.s1", false, 10) + .build(context); + { + String expected = "topn_1 | order_by: {(<0, String>, desc: false)}, limit: 10\n" + " table_scan_0 | {<0, String>, <1, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + } } CATCH @@ -166,6 +198,15 @@ try " table_scan_0 | {<0, String>, <1, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + + request = context.scan("test_db", "test_table") + .aggregation({Max(col("test_table.s1"))}, {col("test_table.s2"), lt(col("test_table.s1"), col("test_table.s2"))}) + .build(context); + { + String expected = "aggregation_1 | group_by: {<1, String>, less(<0, String>, <1, String>)}, agg_func: {max(<0, String>)}\n" + " table_scan_0 | {<0, String>, <1, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + } } CATCH @@ -195,6 +236,32 @@ try " table_scan_0 | {<0, Long>, <1, String>, <2, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + + { + auto right_builder = context.scan("test_db", "r_table") + .filter(And(eq(col("r_table.r_a"), col("r_table.r_b")), eq(col("r_table.r_a"), col("r_table.r_b")))) + .project({col("r_table.r_a"), col("r_table.r_b"), col("join_c")}) + .aggregation({Max(col("r_table.r_a"))}, {col("join_c"), col("r_table.r_b")}) + .topN({{"r_table.r_b", false}}, 10); + + auto left_builder = context.scan("test_db", "l_table") + .topN({{"l_table.l_a", false}}, 10) + .join(right_builder, tipb::JoinType::TypeLeftOuterJoin, {col("join_c")}) // todo ensure the join is legal. + .limit(10); + request = left_builder.build(context); + { + String expected = "limit_8 | 10\n" + " Join_7 | LeftOuterJoin, HashJoin. left_join_keys: {<0, String>}, right_join_keys: {<0, String>}\n" + " topn_6 | order_by: {(<0, Long>, desc: false)}, limit: 10\n" + " table_scan_5 | {<0, Long>, <1, String>, <2, String>}\n" + " topn_4 | order_by: {(<2, String>, desc: false)}, limit: 10\n" + " aggregation_3 | group_by: {<2, String>, <1, String>}, agg_func: {max(<0, Long>)}\n" + " project_2 | {<0, Long>, <1, String>, <2, String>}\n" + " selection_1 | equals(<0, Long>, <1, String>) and equals(<0, Long>, <1, String>)}\n" + " table_scan_0 | {<0, Long>, <1, String>, <2, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + } + } } CATCH @@ -262,6 +329,15 @@ try " table_scan_0 | {<0, String>, <1, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + + + request = context.scan("test_db", "test_table").sort({"test_table.s1", false}, true).window(RowNumber(), {"test_table.s1", true}, {"test_table.s2", false}, buildDefaultRowsFrame()).build(context); + { + String expected = "window_2 | partition_by: {(<1, String>, desc: false)}}, order_by: {(<0, String>, desc: true)}, func_desc: {row_number()}, frame: {start<2, false, 0>, end<2, false, 0>}\n" + " sort_1 | isPartialSort: true, partition_by: {(<0, String>, desc: false)}\n" + " table_scan_0 | {<0, String>, <1, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + } } CATCH } // namespace tests