This is an automated email from the ASF dual-hosted git repository.
Gabriel39 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 1f10685c0c5 [fix](be) Keep external scanner schema in projection order
(#68231)
1f10685c0c5 is described below
commit 1f10685c0c504a89859e36399442798f20236eb5
Author: Gabriel <[email protected]>
AuthorDate: Sun Sep 20 14:29:36 2026 +0800
[fix](be) Keep external scanner schema in projection order (#68231)
### What problem does this PR solve?
Issue Number: None
Related PR: None
Problem Summary:
External scanner metadata was returned in tuple-slot order while Arrow
batches from the memory scratch sink followed fragment output expression
order. Reordered projections could therefore bind values to the wrong
columns.
Build the selected-column schema from the fragment output slot
references so metadata and data use the same order. Add a BE unit test
that projects two slots in reverse descriptor order and verifies the
returned names and types follow the projection.
### Release note
Fix external scanner reads with reordered projected columns.
### Check List (For Author)
- Test
- [ ] Regression test
- [x] Unit Test
-
`FragmentMgrExternalScanTest.SelectedColumnsFollowOutputExpressionOrder`
- [ ] Manual test
- [ ] No need to test or manual test.
- Behavior changed:
- [ ] No.
- [x] Yes. External scanner schema now follows projection order.
- Does this need documentation?
- [x] No.
- [ ] Yes.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
---
be/src/runtime/fragment_mgr.cpp | 48 +++++++++++++------
be/src/runtime/fragment_mgr.h | 5 ++
.../fragment_mgr_cross_cluster_cancel_test.cpp | 54 ++++++++++++++++++++++
3 files changed, 92 insertions(+), 15 deletions(-)
diff --git a/be/src/runtime/fragment_mgr.cpp b/be/src/runtime/fragment_mgr.cpp
index 72dd4a6d7c7..a26e2c157d8 100644
--- a/be/src/runtime/fragment_mgr.cpp
+++ b/be/src/runtime/fragment_mgr.cpp
@@ -1012,6 +1012,37 @@ void FragmentMgr::_check_brpc_available(const
std::shared_ptr<PBackendService_St
}
void FragmentMgr::debug(std::stringstream& ss) {}
+
+Status FragmentMgr::_build_external_scan_selected_columns(
+ const TPlanFragment& plan_fragment, const DescriptorTbl& desc_tbl,
+ std::vector<TScanColumnDesc>* selected_columns) {
+ // The memory scratch sink emits Arrow columns in output expression order,
so the returned
+ // schema must use the same order to prevent positional column misbinding.
+ for (const auto& expr : plan_fragment.output_exprs) {
+ if (expr.nodes.empty() || expr.nodes[0].node_type !=
TExprNodeType::SLOT_REF) {
+ LOG(WARNING) << "output expr is not slot ref";
+ return Status::InvalidArgument("output expr is not slot ref");
+ }
+
+ const auto& slot_ref = expr.nodes[0].slot_ref;
+ if (desc_tbl.get_tuple_descriptor(slot_ref.tuple_id) == nullptr) {
+ LOG(WARNING) << "tuple descriptor is null. id: " <<
slot_ref.tuple_id;
+ return Status::InvalidArgument("tuple descriptor is null");
+ }
+ const auto* slot_desc = desc_tbl.get_slot_descriptor(slot_ref.slot_id);
+ if (slot_desc == nullptr) {
+ LOG(WARNING) << "slot descriptor is null. id: " <<
slot_ref.slot_id;
+ return Status::InvalidArgument("slot descriptor is null");
+ }
+
+ TScanColumnDesc column;
+ column.__set_name(slot_desc->col_name());
+ column.__set_type(to_thrift(slot_desc->type()->get_primitive_type()));
+ selected_columns->emplace_back(std::move(column));
+ }
+ return Status::OK();
+}
+
/*
* 1. resolve opaqued_query_plan to thrift structure
* 2. build TPipelineFragmentParams
@@ -1032,21 +1063,8 @@ Status FragmentMgr::exec_external_plan_fragment(const
TScanOpenParams& params,
"processed";
return Status::InvalidArgument(msg.str());
}
- TupleDescriptor* tuple_desc = desc_tbl->get_tuple_descriptor(0);
- if (tuple_desc == nullptr) {
- LOG(WARNING) << "open context error: extract TupleDescriptor failure";
- std::stringstream msg;
- msg << " get TupleDescriptor error, should not be modified after
returned Doris FE "
- "processed";
- return Status::InvalidArgument(msg.str());
- }
- // process selected columns form slots
- for (const SlotDescriptor* slot : tuple_desc->slots()) {
- TScanColumnDesc col;
- col.__set_name(slot->col_name());
- col.__set_type(to_thrift(slot->type()->get_primitive_type()));
- selected_columns->emplace_back(std::move(col));
- }
+
RETURN_IF_ERROR(_build_external_scan_selected_columns(t_query_plan_info.plan_fragment,
+ *desc_tbl,
selected_columns));
VLOG_QUERY << "BackendService execute open() TQueryPlanInfo: "
<< apache::thrift::ThriftDebugString(t_query_plan_info);
diff --git a/be/src/runtime/fragment_mgr.h b/be/src/runtime/fragment_mgr.h
index 426e395bc32..5a5bf64fd8b 100644
--- a/be/src/runtime/fragment_mgr.h
+++ b/be/src/runtime/fragment_mgr.h
@@ -53,6 +53,7 @@ extern bvar::Status<uint64_t> g_fragment_last_active_time;
class PipelineFragmentContext;
class QueryContext;
+class DescriptorTbl;
class ExecEnv;
struct FrontendInfo;
class ThreadPool;
@@ -219,6 +220,10 @@ private:
void _check_brpc_available(const std::shared_ptr<PBackendService_Stub>&
brpc_stub,
const BrpcItem& brpc_item);
+ static Status _build_external_scan_selected_columns(
+ const TPlanFragment& plan_fragment, const DescriptorTbl& desc_tbl,
+ std::vector<TScanColumnDesc>* selected_columns);
+
// This is input params
ExecEnv* _exec_env = nullptr;
diff --git a/be/test/runtime/fragment_mgr_cross_cluster_cancel_test.cpp
b/be/test/runtime/fragment_mgr_cross_cluster_cancel_test.cpp
index c178728712d..dd705635b6e 100644
--- a/be/test/runtime/fragment_mgr_cross_cluster_cancel_test.cpp
+++ b/be/test/runtime/fragment_mgr_cross_cluster_cancel_test.cpp
@@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.
+#include <gen_cpp/DorisExternalService_types.h>
#include <gen_cpp/PaloInternalService_types.h>
#include <gtest/gtest.h>
@@ -225,4 +226,57 @@ TEST(FragmentMgrRerunnableParamsTest,
StopReleasesLastQueryContextRefOutsideLock
delete fragment_mgr;
}
+TEST(FragmentMgrExternalScanTest, SelectedColumnsFollowOutputExpressionOrder) {
+ TDescriptorTableBuilder desc_tbl_builder;
+ TTupleDescriptorBuilder tuple_builder;
+ tuple_builder
+ .add_slot(TSlotDescriptorBuilder()
+ .type(TYPE_INT)
+ .nullable(true)
+ .column_name("k1")
+ .column_pos(0)
+ .build())
+ .add_slot(TSlotDescriptorBuilder()
+ .type(TYPE_BIGINT)
+ .nullable(true)
+ .column_name("v2")
+ .column_pos(1)
+ .build());
+ tuple_builder.build(&desc_tbl_builder);
+
+ ObjectPool pool;
+ DescriptorTbl* desc_tbl = nullptr;
+ ASSERT_TRUE(DescriptorTbl::create(&pool, desc_tbl_builder.desc_tbl(),
&desc_tbl).ok());
+
+ auto make_slot_ref_expr = [](TSlotId slot_id, TPrimitiveType::type type) {
+ TSlotRef slot_ref;
+ slot_ref.__set_slot_id(slot_id);
+ slot_ref.__set_tuple_id(0);
+
+ TExprNode node;
+ node.__set_node_type(TExprNodeType::SLOT_REF);
+ node.__set_type(TSlotDescriptorBuilder().get_common_type(type));
+ node.__set_num_children(0);
+ node.__set_slot_ref(slot_ref);
+
+ TExpr expr;
+ expr.nodes.emplace_back(std::move(node));
+ return expr;
+ };
+
+ TPlanFragment plan_fragment;
+ plan_fragment.__set_output_exprs({make_slot_ref_expr(1,
TPrimitiveType::BIGINT),
+ make_slot_ref_expr(0,
TPrimitiveType::INT)});
+
+ std::vector<TScanColumnDesc> selected_columns;
+
ASSERT_TRUE(FragmentMgr::_build_external_scan_selected_columns(plan_fragment,
*desc_tbl,
+
&selected_columns)
+ .ok());
+ ASSERT_EQ(selected_columns.size(), 2);
+ EXPECT_EQ(selected_columns[0].name, "v2");
+ EXPECT_EQ(selected_columns[0].type, TPrimitiveType::BIGINT);
+ EXPECT_EQ(selected_columns[1].name, "k1");
+ EXPECT_EQ(selected_columns[1].type, TPrimitiveType::INT);
+}
+
} // namespace doris
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]