This is an automated email from the ASF dual-hosted git repository.
englefly 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 9911303ab67 [refactor](nereids)unify outputTupleDesc and projection be
part (#32439)
9911303ab67 is described below
commit 9911303ab676aa3e6b5521727d54dd445f49da8a
Author: Mryange <[email protected]>
AuthorDate: Fri Mar 22 08:55:18 2024 +0800
[refactor](nereids)unify outputTupleDesc and projection be part (#32439)
---
be/src/exec/exec_node.cpp | 26 +++++++++++++-----
be/src/exec/exec_node.h | 4 +++
be/src/pipeline/exec/hashjoin_probe_operator.cpp | 3 +++
be/src/pipeline/exec/join_probe_operator.cpp | 31 ++++++++++++++++++----
be/src/pipeline/exec/join_probe_operator.h | 12 ++++++++-
.../exec/nested_loop_join_probe_operator.cpp | 7 ++++-
.../exec/nested_loop_join_probe_operator.h | 2 +-
be/src/pipeline/pipeline_x/operator.cpp | 30 ++++++++++++++-------
be/src/pipeline/pipeline_x/operator.h | 4 +++
be/src/vec/exec/join/vhash_join_node.cpp | 3 +++
be/src/vec/exec/join/vjoin_node_base.cpp | 31 ++++++++++++++++++----
be/src/vec/exec/join/vjoin_node_base.h | 12 ++++++++-
be/src/vec/exec/join/vnested_loop_join_node.cpp | 4 +++
be/src/vec/exec/join/vnested_loop_join_node.h | 2 +-
gensrc/thrift/PlanNodes.thrift | 4 +++
15 files changed, 144 insertions(+), 31 deletions(-)
diff --git a/be/src/exec/exec_node.cpp b/be/src/exec/exec_node.cpp
index 368e94562a2..ed032d09767 100644
--- a/be/src/exec/exec_node.cpp
+++ b/be/src/exec/exec_node.cpp
@@ -514,6 +514,24 @@ std::string ExecNode::get_name() {
Status ExecNode::do_projections(vectorized::Block* origin_block,
vectorized::Block* output_block) {
SCOPED_TIMER(_exec_timer);
SCOPED_TIMER(_projection_timer);
+ auto insert_column_datas = [&](auto& to, vectorized::ColumnPtr& from,
size_t rows) {
+ if (to->is_nullable() && !from->is_nullable()) {
+ if (_keep_origin || !from->is_exclusive()) {
+ auto& null_column =
reinterpret_cast<vectorized::ColumnNullable&>(*to);
+ null_column.get_nested_column().insert_range_from(*from, 0,
rows);
+ null_column.get_null_map_column().get_data().resize_fill(rows,
0);
+ } else {
+ to = make_nullable(from, false)->assume_mutable();
+ }
+ } else {
+ if (_keep_origin || !from->is_exclusive()) {
+ to->insert_range_from(*from, 0, rows);
+ } else {
+ to = from->assume_mutable();
+ }
+ }
+ };
+
using namespace vectorized;
MutableBlock mutable_block =
VectorizedUtils::build_mutable_mem_reuse_block(output_block,
*_output_row_descriptor);
@@ -535,13 +553,7 @@ Status ExecNode::do_projections(vectorized::Block*
origin_block, vectorized::Blo
auto column_ptr = origin_block->get_by_position(result_column_id)
.column->convert_to_full_column_if_const();
//TODO: this is a quick fix, we need a new function like
"change_to_nullable" to do it
- if (mutable_columns[i]->is_nullable() xor
column_ptr->is_nullable()) {
- DCHECK(mutable_columns[i]->is_nullable() &&
!column_ptr->is_nullable());
- reinterpret_cast<ColumnNullable*>(mutable_columns[i].get())
- ->insert_range_from_not_nullable(*column_ptr, 0, rows);
- } else {
- mutable_columns[i]->insert_range_from(*column_ptr, 0, rows);
- }
+ insert_column_datas(mutable_columns[i], column_ptr, rows);
}
DCHECK(mutable_block.rows() == rows);
output_block->set_columns(std::move(mutable_columns));
diff --git a/be/src/exec/exec_node.h b/be/src/exec/exec_node.h
index 1dd8979f5b3..5d7b3a91651 100644
--- a/be/src/exec/exec_node.h
+++ b/be/src/exec/exec_node.h
@@ -325,6 +325,10 @@ protected:
std::shared_ptr<QueryStatistics> _query_statistics = nullptr;
+ //_keep_origin is used to avoid copying during projection,
+ // currently set to true only in the nestloop join.
+ bool _keep_origin = false;
+
private:
static Status create_tree_helper(RuntimeState* state, ObjectPool* pool,
const std::vector<TPlanNode>& tnodes,
diff --git a/be/src/pipeline/exec/hashjoin_probe_operator.cpp
b/be/src/pipeline/exec/hashjoin_probe_operator.cpp
index e6a605405a7..878a813ce06 100644
--- a/be/src/pipeline/exec/hashjoin_probe_operator.cpp
+++ b/be/src/pipeline/exec/hashjoin_probe_operator.cpp
@@ -173,6 +173,9 @@ void HashJoinProbeLocalState::init_for_probe(RuntimeState*
state) {
void HashJoinProbeLocalState::add_tuple_is_null_column(vectorized::Block*
block) {
DCHECK(_parent->cast<HashJoinProbeOperatorX>()._is_outer_join);
+ if (!_parent->cast<HashJoinProbeOperatorX>()._use_specific_projections) {
+ return;
+ }
auto p0 = _tuple_is_null_left_flag_column->assume_mutable();
auto p1 = _tuple_is_null_right_flag_column->assume_mutable();
auto& left_null_map = reinterpret_cast<vectorized::ColumnUInt8&>(*p0);
diff --git a/be/src/pipeline/exec/join_probe_operator.cpp
b/be/src/pipeline/exec/join_probe_operator.cpp
index 03b20fdb4d4..48607309eca 100644
--- a/be/src/pipeline/exec/join_probe_operator.cpp
+++ b/be/src/pipeline/exec/join_probe_operator.cpp
@@ -82,6 +82,13 @@ template <typename SharedStateArg, typename Derived>
Status JoinProbeLocalState<SharedStateArg, Derived>::_build_output_block(
vectorized::Block* origin_block, vectorized::Block* output_block, bool
keep_origin) {
auto& p = Base::_parent->template cast<typename Derived::Parent>();
+ if (!Base::_projections.empty()) {
+ // In previous versions, the join node had a separate set of project
structures,
+ // and you could see a 'todo' in the Thrift definition.
+ // Here, we have refactored it, but considering upgrade
compatibility, we still need to retain the old code.
+ *output_block = *origin_block;
+ return Status::OK();
+ }
SCOPED_TIMER(_build_output_block_timer);
auto is_mem_reuse = output_block->mem_reuse();
vectorized::MutableBlock mutable_block =
@@ -192,19 +199,33 @@
JoinProbeOperatorX<LocalStateType>::JoinProbeOperatorX(ObjectPool* pool, const T
: tnode.hash_join_node.__isset.is_mark ?
tnode.hash_join_node.is_mark
: false),
_short_circuit_for_null_in_build_side(_join_op ==
TJoinOp::NULL_AWARE_LEFT_ANTI_JOIN &&
- !_is_mark_join) {
+ !_is_mark_join),
+ _use_specific_projections(
+ tnode.__isset.hash_join_node
+ ?
(tnode.hash_join_node.__isset.use_specific_projections
+ ?
tnode.hash_join_node.use_specific_projections
+ : true)
+ :
(tnode.nested_loop_join_node.__isset.use_specific_projections
+ ?
tnode.nested_loop_join_node.use_specific_projections
+ : true)
+
+ ) {
if (tnode.__isset.hash_join_node) {
_intermediate_row_desc.reset(new RowDescriptor(
descs, tnode.hash_join_node.vintermediate_tuple_id_list,
std::vector<bool>(tnode.hash_join_node.vintermediate_tuple_id_list.size())));
- _output_row_desc.reset(
- new RowDescriptor(descs,
{tnode.hash_join_node.voutput_tuple_id}, {false}));
+ if (!Base::_output_row_descriptor) {
+ _output_row_desc.reset(
+ new RowDescriptor(descs,
{tnode.hash_join_node.voutput_tuple_id}, {false}));
+ }
} else if (tnode.__isset.nested_loop_join_node) {
_intermediate_row_desc.reset(new RowDescriptor(
descs, tnode.nested_loop_join_node.vintermediate_tuple_id_list,
std::vector<bool>(tnode.nested_loop_join_node.vintermediate_tuple_id_list.size())));
- _output_row_desc.reset(
- new RowDescriptor(descs,
{tnode.nested_loop_join_node.voutput_tuple_id}, {false}));
+ if (!Base::_output_row_descriptor) {
+ _output_row_desc.reset(new RowDescriptor(
+ descs, {tnode.nested_loop_join_node.voutput_tuple_id},
{false}));
+ }
} else {
// Iff BE has been upgraded and FE has not yet, we should keep origin
logics for CROSS JOIN.
DCHECK_EQ(_join_op, TJoinOp::CROSS_JOIN);
diff --git a/be/src/pipeline/exec/join_probe_operator.h
b/be/src/pipeline/exec/join_probe_operator.h
index 9bb716ff36d..4072baa72fc 100644
--- a/be/src/pipeline/exec/join_probe_operator.h
+++ b/be/src/pipeline/exec/join_probe_operator.h
@@ -70,7 +70,12 @@ public:
Status init(const TPlanNode& tnode, RuntimeState* state) override;
Status open(doris::RuntimeState* state) override;
- [[nodiscard]] const RowDescriptor& row_desc() const override { return
*_output_row_desc; }
+ [[nodiscard]] const RowDescriptor& row_desc() const override {
+ if (Base::_output_row_descriptor) {
+ return *Base::_output_row_descriptor;
+ }
+ return *_output_row_desc;
+ }
[[nodiscard]] const RowDescriptor& intermediate_row_desc() const override {
return *_intermediate_row_desc;
@@ -114,6 +119,11 @@ protected:
vectorized::VExprContextSPtrs _output_expr_ctxs;
OperatorXPtr _build_side_child = nullptr;
const bool _short_circuit_for_null_in_build_side;
+ // In the Old planner, there is a plan for two columns of tuple is null,
+ // but in the Nereids planner, this logic does not exist.
+ // Therefore, we should not insert these two columns under the Nereids
optimizer.
+ // use_specific_projections true, if output exprssions is denoted by
srcExprList represents, o.w. PlanNode.projections
+ const bool _use_specific_projections;
};
} // namespace pipeline
diff --git a/be/src/pipeline/exec/nested_loop_join_probe_operator.cpp
b/be/src/pipeline/exec/nested_loop_join_probe_operator.cpp
index 9272418ca0a..271891709b0 100644
--- a/be/src/pipeline/exec/nested_loop_join_probe_operator.cpp
+++ b/be/src/pipeline/exec/nested_loop_join_probe_operator.cpp
@@ -111,6 +111,9 @@ void
NestedLoopJoinProbeLocalState::_reset_with_next_probe_row() {
void
NestedLoopJoinProbeLocalState::add_tuple_is_null_column(vectorized::Block*
block) {
auto& p = _parent->cast<NestedLoopJoinProbeOperatorX>();
+ if (!p._use_specific_projections) {
+ return;
+ }
if (p._is_outer_join) {
auto p0 = _tuple_is_null_left_flag_column->assume_mutable();
auto p1 = _tuple_is_null_right_flag_column->assume_mutable();
@@ -436,7 +439,9 @@
NestedLoopJoinProbeOperatorX::NestedLoopJoinProbeOperatorX(ObjectPool* pool, con
: JoinProbeOperatorX<NestedLoopJoinProbeLocalState>(pool, tnode,
operator_id, descs),
_is_output_left_side_only(tnode.nested_loop_join_node.__isset.is_output_left_side_only
&&
tnode.nested_loop_join_node.is_output_left_side_only),
- _old_version_flag(!tnode.__isset.nested_loop_join_node) {}
+ _old_version_flag(!tnode.__isset.nested_loop_join_node) {
+ _keep_origin = _is_output_left_side_only;
+}
Status NestedLoopJoinProbeOperatorX::init(const TPlanNode& tnode,
RuntimeState* state) {
RETURN_IF_ERROR(JoinProbeOperatorX<NestedLoopJoinProbeLocalState>::init(tnode,
state));
diff --git a/be/src/pipeline/exec/nested_loop_join_probe_operator.h
b/be/src/pipeline/exec/nested_loop_join_probe_operator.h
index 770289f397f..7a8be87d922 100644
--- a/be/src/pipeline/exec/nested_loop_join_probe_operator.h
+++ b/be/src/pipeline/exec/nested_loop_join_probe_operator.h
@@ -228,7 +228,7 @@ public:
const RowDescriptor& row_desc() const override {
return _old_version_flag
? (_output_row_descriptor ? *_output_row_descriptor :
_row_descriptor)
- : *_output_row_desc;
+ : (_output_row_descriptor ? *_output_row_descriptor :
*_output_row_desc);
}
bool need_more_input_data(RuntimeState* state) const override;
diff --git a/be/src/pipeline/pipeline_x/operator.cpp
b/be/src/pipeline/pipeline_x/operator.cpp
index 093bf17f46b..6ee9ccb13c4 100644
--- a/be/src/pipeline/pipeline_x/operator.cpp
+++ b/be/src/pipeline/pipeline_x/operator.cpp
@@ -172,9 +172,28 @@ void PipelineXLocalStateBase::clear_origin_block() {
Status OperatorXBase::do_projections(RuntimeState* state, vectorized::Block*
origin_block,
vectorized::Block* output_block) const {
- auto local_state = state->get_local_state(operator_id());
+ auto* local_state = state->get_local_state(operator_id());
SCOPED_TIMER(local_state->exec_time_counter());
SCOPED_TIMER(local_state->_projection_timer);
+
+ auto insert_column_datas = [&](auto& to, vectorized::ColumnPtr& from,
size_t rows) {
+ if (to->is_nullable() && !from->is_nullable()) {
+ if (_keep_origin || !from->is_exclusive()) {
+ auto& null_column =
reinterpret_cast<vectorized::ColumnNullable&>(*to);
+ null_column.get_nested_column().insert_range_from(*from, 0,
rows);
+ null_column.get_null_map_column().get_data().resize_fill(rows,
0);
+ } else {
+ to = make_nullable(from, false)->assume_mutable();
+ }
+ } else {
+ if (_keep_origin || !from->is_exclusive()) {
+ to->insert_range_from(*from, 0, rows);
+ } else {
+ to = from->assume_mutable();
+ }
+ }
+ };
+
using namespace vectorized;
vectorized::MutableBlock mutable_block =
vectorized::VectorizedUtils::build_mutable_mem_reuse_block(output_block,
@@ -189,14 +208,7 @@ Status OperatorXBase::do_projections(RuntimeState* state,
vectorized::Block* ori
RETURN_IF_ERROR(local_state->_projections[i]->execute(origin_block,
&result_column_id));
auto column_ptr = origin_block->get_by_position(result_column_id)
.column->convert_to_full_column_if_const();
- //TODO: this is a quick fix, we need a new function like
"change_to_nullable" to do it
- if (mutable_columns[i]->is_nullable() xor
column_ptr->is_nullable()) {
- DCHECK(mutable_columns[i]->is_nullable() &&
!column_ptr->is_nullable());
- reinterpret_cast<ColumnNullable*>(mutable_columns[i].get())
- ->insert_range_from_not_nullable(*column_ptr, 0, rows);
- } else {
- mutable_columns[i]->insert_range_from(*column_ptr, 0, rows);
- }
+ insert_column_datas(mutable_columns[i], column_ptr, rows);
}
DCHECK(mutable_block.rows() == rows);
output_block->set_columns(std::move(mutable_columns));
diff --git a/be/src/pipeline/pipeline_x/operator.h
b/be/src/pipeline/pipeline_x/operator.h
index 06ba93a36f3..56991d43105 100644
--- a/be/src/pipeline/pipeline_x/operator.h
+++ b/be/src/pipeline/pipeline_x/operator.h
@@ -326,6 +326,10 @@ protected:
std::string _op_name;
bool _ignore_data_distribution = false;
int _parallel_tasks = 0;
+
+ //_keep_origin is used to avoid copying during projection,
+ // currently set to true only in the nestloop join.
+ bool _keep_origin = false;
};
template <typename LocalStateType>
diff --git a/be/src/vec/exec/join/vhash_join_node.cpp
b/be/src/vec/exec/join/vhash_join_node.cpp
index ea4fd924180..7f820eafea5 100644
--- a/be/src/vec/exec/join/vhash_join_node.cpp
+++ b/be/src/vec/exec/join/vhash_join_node.cpp
@@ -563,6 +563,9 @@ Status HashJoinNode::get_next(RuntimeState* state, Block*
output_block, bool* eo
void HashJoinNode::_add_tuple_is_null_column(Block* block) {
DCHECK(_is_outer_join);
+ if (!_use_specific_projections) {
+ return;
+ }
auto p0 = _tuple_is_null_left_flag_column->assume_mutable();
auto p1 = _tuple_is_null_right_flag_column->assume_mutable();
auto& left_null_map = reinterpret_cast<ColumnUInt8&>(*p0);
diff --git a/be/src/vec/exec/join/vjoin_node_base.cpp
b/be/src/vec/exec/join/vjoin_node_base.cpp
index 6fab6b8b91f..4697be19b84 100644
--- a/be/src/vec/exec/join/vjoin_node_base.cpp
+++ b/be/src/vec/exec/join/vjoin_node_base.cpp
@@ -80,7 +80,17 @@ VJoinNodeBase::VJoinNodeBase(ObjectPool* pool, const
TPlanNode& tnode, const Des
tnode.hash_join_node.is_mark),
_short_circuit_for_null_in_build_side(_join_op ==
TJoinOp::NULL_AWARE_LEFT_ANTI_JOIN &&
!_is_mark_join),
- _runtime_filter_descs(tnode.runtime_filters) {
+ _runtime_filter_descs(tnode.runtime_filters),
+ _use_specific_projections(
+ tnode.__isset.hash_join_node
+ ?
(tnode.hash_join_node.__isset.use_specific_projections
+ ?
tnode.hash_join_node.use_specific_projections
+ : true)
+ :
(tnode.nested_loop_join_node.__isset.use_specific_projections
+ ?
tnode.nested_loop_join_node.use_specific_projections
+ : true)
+
+ ) {
_runtime_filters.resize(_runtime_filter_descs.size());
_init_join_op();
if (_is_mark_join) {
@@ -95,14 +105,18 @@ VJoinNodeBase::VJoinNodeBase(ObjectPool* pool, const
TPlanNode& tnode, const Des
}
if (tnode.__isset.hash_join_node) {
- _output_row_desc.reset(
- new RowDescriptor(descs,
{tnode.hash_join_node.voutput_tuple_id}, {false}));
+ if (!_output_row_descriptor) {
+ _output_row_desc.reset(
+ new RowDescriptor(descs,
{tnode.hash_join_node.voutput_tuple_id}, {false}));
+ }
_intermediate_row_desc.reset(new RowDescriptor(
descs, tnode.hash_join_node.vintermediate_tuple_id_list,
std::vector<bool>(tnode.hash_join_node.vintermediate_tuple_id_list.size())));
} else if (tnode.__isset.nested_loop_join_node) {
- _output_row_desc.reset(
- new RowDescriptor(descs,
{tnode.nested_loop_join_node.voutput_tuple_id}, {false}));
+ if (!_output_row_descriptor) {
+ _output_row_desc.reset(new RowDescriptor(
+ descs, {tnode.nested_loop_join_node.voutput_tuple_id},
{false}));
+ }
_intermediate_row_desc.reset(new RowDescriptor(
descs, tnode.nested_loop_join_node.vintermediate_tuple_id_list,
std::vector<bool>(tnode.nested_loop_join_node.vintermediate_tuple_id_list.size())));
@@ -166,6 +180,13 @@ void VJoinNodeBase::_construct_mutable_join_block() {
Status VJoinNodeBase::_build_output_block(Block* origin_block, Block*
output_block,
bool keep_origin) {
SCOPED_TIMER(_build_output_block_timer);
+ if (!_projections.empty()) {
+ // In previous versions, the join node had a separate set of project
structures,
+ // and you could see a 'todo' in the Thrift definition.
+ // Here, we have refactored it, but considering upgrade
compatibility, we still need to retain the old code.
+ *output_block = *origin_block;
+ return Status::OK();
+ }
auto is_mem_reuse = output_block->mem_reuse();
MutableBlock mutable_block =
is_mem_reuse
diff --git a/be/src/vec/exec/join/vjoin_node_base.h
b/be/src/vec/exec/join/vjoin_node_base.h
index 0e6ac3c9837..63a8bb20ba6 100644
--- a/be/src/vec/exec/join/vjoin_node_base.h
+++ b/be/src/vec/exec/join/vjoin_node_base.h
@@ -63,7 +63,12 @@ public:
Status open(RuntimeState* state) override;
- const RowDescriptor& row_desc() const override { return *_output_row_desc;
}
+ const RowDescriptor& row_desc() const override {
+ if (_output_row_descriptor) {
+ return *_output_row_descriptor;
+ }
+ return *_output_row_desc;
+ }
const RowDescriptor& intermediate_row_desc() const override { return
*_intermediate_row_desc; }
@@ -152,6 +157,11 @@ protected:
std::vector<TRuntimeFilterDesc> _runtime_filter_descs;
std::vector<IRuntimeFilter*> _runtime_filters;
+ // In the Old planner, there is a plan for two columns of tuple is null,
+ // but in the Nereids planner, this logic does not exist.
+ // Therefore, we should not insert these two columns under the Nereids
optimizer.
+ // use_specific_projections true, if output exprssions is denoted by
srcExprList represents, o.w. PlanNode.projections
+ const bool _use_specific_projections = false;
};
} // namespace doris::vectorized
diff --git a/be/src/vec/exec/join/vnested_loop_join_node.cpp
b/be/src/vec/exec/join/vnested_loop_join_node.cpp
index 3548680bf49..d50171485af 100644
--- a/be/src/vec/exec/join/vnested_loop_join_node.cpp
+++ b/be/src/vec/exec/join/vnested_loop_join_node.cpp
@@ -102,6 +102,7 @@ Status VNestedLoopJoinNode::init(const TPlanNode& tnode,
RuntimeState* state) {
if (tnode.nested_loop_join_node.__isset.is_output_left_side_only) {
_is_output_left_side_only =
tnode.nested_loop_join_node.is_output_left_side_only;
+ _keep_origin = _is_output_left_side_only;
}
if (tnode.nested_loop_join_node.__isset.join_conjuncts &&
@@ -382,6 +383,9 @@ void
VNestedLoopJoinNode::_resize_fill_tuple_is_null_column(size_t new_size, int
}
void VNestedLoopJoinNode::_add_tuple_is_null_column(Block* block) {
+ if (!_use_specific_projections) {
+ return;
+ }
if (_is_outer_join) {
auto p0 = _tuple_is_null_left_flag_column->assume_mutable();
auto p1 = _tuple_is_null_right_flag_column->assume_mutable();
diff --git a/be/src/vec/exec/join/vnested_loop_join_node.h
b/be/src/vec/exec/join/vnested_loop_join_node.h
index fd31b651bd3..18bc901222f 100644
--- a/be/src/vec/exec/join/vnested_loop_join_node.h
+++ b/be/src/vec/exec/join/vnested_loop_join_node.h
@@ -92,7 +92,7 @@ public:
const RowDescriptor& row_desc() const override {
return _old_version_flag
? (_output_row_descriptor ? *_output_row_descriptor :
_row_descriptor)
- : *_output_row_desc;
+ : (_output_row_descriptor ? *_output_row_descriptor :
*_output_row_desc);
}
std::shared_ptr<Block> get_left_block() { return _left_block; }
diff --git a/gensrc/thrift/PlanNodes.thrift b/gensrc/thrift/PlanNodes.thrift
index 2d5e8bb7bb9..148db2b9a17 100644
--- a/gensrc/thrift/PlanNodes.thrift
+++ b/gensrc/thrift/PlanNodes.thrift
@@ -794,6 +794,8 @@ struct THashJoinNode {
11: optional bool is_mark
12: optional TJoinDistributionType dist_type
13: optional list<Exprs.TExpr> mark_join_conjuncts
+ // use_specific_projections true, if output exprssions is denoted by
srcExprList represents, o.w. PlanNode.projections
+ 14: optional bool use_specific_projections
}
struct TNestedLoopJoinNode {
@@ -815,6 +817,8 @@ struct TNestedLoopJoinNode {
8: optional list<Exprs.TExpr> join_conjuncts
9: optional list<Exprs.TExpr> mark_join_conjuncts
+ // use_specific_projections true, if output exprssions is denoted by
srcExprList represents, o.w. PlanNode.projections
+ 10: optional bool use_specific_projections
}
struct TMergeJoinNode {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]