Copilot commented on code in PR #61613:
URL: https://github.com/apache/doris/pull/61613#discussion_r2973013335
##########
be/src/exec/operator/table_function_operator.cpp:
##########
@@ -177,15 +332,33 @@ Status
TableFunctionLocalState::get_expanded_block(RuntimeState* state, Block* o
_fns[i]->set_nullable();
}
}
- while (columns[p._child_slots.size()]->size() < state->batch_size()) {
- RETURN_IF_CANCELLED(state);
- if (_child_block->rows() == 0) {
- break;
+ // Try the block fast path first. If the table function reports
NOT_IMPLEMENTED (e.g. non-
+ // contiguous nested range), fall back to the original row-wise path.
+ bool fallback_to_slow_path = false;
+ if (_can_use_block_fast_path()) {
+ RETURN_IF_CANCELLED(state);
+ auto st = _get_expanded_block_block_fast_path(state, columns);
+ if (!st.ok()) {
+ if (st.is<ErrorCode::NOT_IMPLEMENTED_ERROR>()) {
+ fallback_to_slow_path = true;
+ _block_fast_path_prepared = false;
+ } else {
Review Comment:
If the fast path returns NOT_IMPLEMENTED and you fall back to the slow path,
subsequent `pull()` calls will try the fast path again (because
`_can_use_block_fast_path()` stays true) even though the slow path may have
already advanced the table function’s internal cursor within the current child
row. Since the fast path always restarts from `in_row_offset = 0` for
`_cur_child_offset`, this can duplicate or reorder output (e.g. fallback occurs
after producing part of a later row in the batch, leaving `_cur_offset > 0`,
then next pull fast-path replays from the start of that row). Consider
disabling the fast path for the remainder of the current child block once a
fallback happens (or otherwise synchronizing the fast-path starting row/offset
with the table function’s cursor).
##########
be/src/exprs/table_function/vexplode_v2.cpp:
##########
@@ -107,6 +107,29 @@ Status VExplodeV2TableFunction::process_init(Block* block,
RuntimeState* state)
return Status::OK();
}
+bool VExplodeV2TableFunction::support_block_fast_path() const {
+ return !_is_outer && !_generate_row_index;
+}
+
+Status VExplodeV2TableFunction::prepare_block_fast_path(Block* /*block*/,
RuntimeState* /*state*/,
+ BlockFastPathContext*
ctx) {
+ if (!support_block_fast_path()) {
+ return Status::NotSupported("vexplode doesn't support block fast path
in current mode");
+ }
+ if (_multi_detail.size() != 1) {
+ return Status::NotSupported("vexplode block fast path only supports
single parameter");
+ }
Review Comment:
`support_block_fast_path()` returns true for any non-outer/non-row-index
mode, but `prepare_block_fast_path()` immediately rejects `_multi_detail.size()
!= 1`. This means multi-parameter vexplode_v2 will still be probed for the fast
path on every `pull()` and pay the NotSupported→fallback overhead. Consider
including the single-parameter requirement in `support_block_fast_path()` (or
otherwise ensuring `_can_use_block_fast_path()` can short-circuit) so the
operator can skip attempting the fast path when it can never succeed.
##########
be/test/exec/operator/table_function_operator_test.cpp:
##########
@@ -218,6 +306,618 @@ TEST_F(TableFunctionOperatorTest, single_fn_test2) {
}
}
+TEST_F(TableFunctionOperatorTest, block_fast_path_explode) {
+ bool get_value_called = false;
+ {
+ op->_vfn_ctxs =
+ MockSlotRef::create_mock_contexts(DataTypes
{std::make_shared<DataTypeInt32>()});
+ auto fn =
std::make_shared<MockFastExplodeTableFunction>(&get_value_called);
+ fns.push_back(fn);
+ op->_fns.push_back(fn.get());
+ op->_output_slot_ids = {true, false, true};
+
+ child_op->_mock_row_desc.reset(
+ new MockRowDescriptor {{std::make_shared<DataTypeInt32>(),
+ std::make_shared<DataTypeArray>(
+
std::make_shared<DataTypeInt32>())},
+ &pool});
+ op->_mock_row_descriptor.reset(
+ new MockRowDescriptor {{std::make_shared<DataTypeInt32>(),
+ std::make_shared<DataTypeArray>(
+
std::make_shared<DataTypeInt32>()),
+ std::make_shared<DataTypeInt32>()},
+ &pool});
+
+ op->_fn_num = 1;
+ EXPECT_TRUE(op->prepare(state.get()));
+
+ local_state_uptr =
std::make_unique<MockTableFunctionLocalState>(state.get(), op.get());
+ local_state = local_state_uptr.get();
+ LocalStateInfo info {.parent_profile = &profile,
+ .scan_ranges = {},
+ .shared_state = nullptr,
+ .shared_state_map = {},
+ .task_idx = 0};
+ EXPECT_TRUE(local_state->init(state.get(), info));
+ state->resize_op_id_to_local_state(-100);
+ state->emplace_local_state(op->operator_id(),
std::move(local_state_uptr));
+ EXPECT_TRUE(local_state->open(state.get()));
+ }
+
+ {
+ auto id_col = ColumnHelper::create_column<DataTypeInt32>({10, 20, 30,
40});
+ auto nested = ColumnInt32::create();
+ nested->insert_value(1);
+ nested->insert_value(2);
+ nested->insert_value(3);
+ nested->insert_value(4);
+ auto offsets = ColumnArray::ColumnOffsets::create();
+ offsets->insert_value(1);
+ offsets->insert_value(3);
+ offsets->insert_value(3);
+ offsets->insert_value(4);
+ auto arr_col = ColumnArray::create(std::move(nested),
std::move(offsets));
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type = std::make_shared<DataTypeArray>(int_type);
+ *local_state->_child_block =
+ Block({ColumnWithTypeAndName(id_col, int_type, "id"),
+ ColumnWithTypeAndName(ColumnPtr(std::move(arr_col)),
arr_type, "arr")});
+ auto st = op->push(state.get(), local_state->_child_block.get(), true);
+ EXPECT_TRUE(st) << st.msg();
+ }
+
+ {
+ Block block;
+ bool eos = false;
+ auto st = op->pull(state.get(), &block, &eos);
+ EXPECT_TRUE(st) << st.msg();
+ EXPECT_FALSE(get_value_called);
+
+ auto expected_id = ColumnHelper::create_column<DataTypeInt32>({10, 20,
20, 40});
+
+ auto expected_nested = ColumnInt32::create();
+ expected_nested->insert_value(1);
+ expected_nested->insert_value(2);
+ expected_nested->insert_value(3);
+ expected_nested->insert_value(2);
+ expected_nested->insert_value(3);
+ expected_nested->insert_value(4);
+ auto expected_offsets = ColumnArray::ColumnOffsets::create();
+ expected_offsets->insert_value(1);
+ expected_offsets->insert_value(3);
+ expected_offsets->insert_value(5);
+ expected_offsets->insert_value(6);
+ auto expected_arr =
+ ColumnArray::create(std::move(expected_nested),
std::move(expected_offsets));
+
+ auto expected_out = ColumnHelper::create_column<DataTypeInt32>({1, 2,
3, 4});
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type = std::make_shared<DataTypeArray>(int_type);
+ Block expected({ColumnWithTypeAndName(expected_id, int_type, "id"),
+
ColumnWithTypeAndName(ColumnPtr(std::move(expected_arr)), arr_type, "arr"),
+ ColumnWithTypeAndName(expected_out, int_type, "x")});
+ EXPECT_TRUE(ColumnHelper::block_equal(block, expected));
+ }
+}
+
+TEST_F(TableFunctionOperatorTest, block_fast_path_explode_batch_truncate) {
+ state->batsh_size = 2;
+ bool get_value_called = false;
+ {
+ op->_vfn_ctxs =
+ MockSlotRef::create_mock_contexts(DataTypes
{std::make_shared<DataTypeInt32>()});
+ auto fn =
std::make_shared<MockFastExplodeTableFunction>(&get_value_called);
+ fns.push_back(fn);
+ op->_fns.push_back(fn.get());
+ op->_output_slot_ids = {true, true, true};
+
+ child_op->_mock_row_desc.reset(
+ new MockRowDescriptor {{std::make_shared<DataTypeInt32>(),
+ std::make_shared<DataTypeArray>(
+
std::make_shared<DataTypeInt32>())},
+ &pool});
+ op->_mock_row_descriptor.reset(
+ new MockRowDescriptor {{std::make_shared<DataTypeInt32>(),
+ std::make_shared<DataTypeArray>(
+
std::make_shared<DataTypeInt32>()),
+ std::make_shared<DataTypeInt32>()},
+ &pool});
+
+ op->_fn_num = 1;
+ EXPECT_TRUE(op->prepare(state.get()));
+
+ local_state_uptr =
std::make_unique<MockTableFunctionLocalState>(state.get(), op.get());
+ local_state = local_state_uptr.get();
+ LocalStateInfo info {.parent_profile = &profile,
+ .scan_ranges = {},
+ .shared_state = nullptr,
+ .shared_state_map = {},
+ .task_idx = 0};
+ EXPECT_TRUE(local_state->init(state.get(), info));
+ state->resize_op_id_to_local_state(-100);
+ state->emplace_local_state(op->operator_id(),
std::move(local_state_uptr));
+ EXPECT_TRUE(local_state->open(state.get()));
+ }
+
+ {
+ auto id_col = ColumnHelper::create_column<DataTypeInt32>({10, 20, 30,
40});
+ auto nested = ColumnInt32::create();
+ nested->insert_value(1);
+ nested->insert_value(2);
+ nested->insert_value(3);
+ nested->insert_value(4);
+ auto offsets = ColumnArray::ColumnOffsets::create();
+ offsets->insert_value(1);
+ offsets->insert_value(3);
+ offsets->insert_value(3);
+ offsets->insert_value(4);
+ auto arr_col = ColumnArray::create(std::move(nested),
std::move(offsets));
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type = std::make_shared<DataTypeArray>(int_type);
+ *local_state->_child_block =
+ Block({ColumnWithTypeAndName(id_col, int_type, "id"),
+ ColumnWithTypeAndName(ColumnPtr(std::move(arr_col)),
arr_type, "arr")});
+ auto st = op->push(state.get(), local_state->_child_block.get(), true);
+ EXPECT_TRUE(st) << st.msg();
+ }
+
+ {
+ Block block;
+ bool eos = false;
+ auto st = op->pull(state.get(), &block, &eos);
+ EXPECT_TRUE(st) << st.msg();
+ EXPECT_FALSE(get_value_called);
+
+ auto expected_id = ColumnHelper::create_column<DataTypeInt32>({10,
20});
+ auto expected_nested = ColumnInt32::create();
+ expected_nested->insert_value(1);
+ expected_nested->insert_value(2);
+ expected_nested->insert_value(3);
+ auto expected_offsets = ColumnArray::ColumnOffsets::create();
+ expected_offsets->insert_value(1);
+ expected_offsets->insert_value(3);
+ auto expected_arr =
+ ColumnArray::create(std::move(expected_nested),
std::move(expected_offsets));
+ auto expected_out = ColumnHelper::create_column<DataTypeInt32>({1, 2});
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type = std::make_shared<DataTypeArray>(int_type);
+ Block expected({ColumnWithTypeAndName(expected_id, int_type, "id"),
+
ColumnWithTypeAndName(ColumnPtr(std::move(expected_arr)), arr_type, "arr"),
+ ColumnWithTypeAndName(expected_out, int_type, "x")});
+ EXPECT_TRUE(ColumnHelper::block_equal(block, expected));
+ }
+
+ {
+ Block block;
+ bool eos = false;
+ auto st = op->pull(state.get(), &block, &eos);
+ EXPECT_TRUE(st) << st.msg();
+
+ auto expected_id = ColumnHelper::create_column<DataTypeInt32>({20,
40});
+ auto expected_nested = ColumnInt32::create();
+ expected_nested->insert_value(2);
+ expected_nested->insert_value(3);
+ expected_nested->insert_value(4);
+ auto expected_offsets = ColumnArray::ColumnOffsets::create();
+ expected_offsets->insert_value(2);
+ expected_offsets->insert_value(3);
+ auto expected_arr =
+ ColumnArray::create(std::move(expected_nested),
std::move(expected_offsets));
+ auto expected_out = ColumnHelper::create_column<DataTypeInt32>({3, 4});
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type = std::make_shared<DataTypeArray>(int_type);
+ Block expected({ColumnWithTypeAndName(expected_id, int_type, "id"),
+
ColumnWithTypeAndName(ColumnPtr(std::move(expected_arr)), arr_type, "arr"),
+ ColumnWithTypeAndName(expected_out, int_type, "x")});
+ EXPECT_TRUE(ColumnHelper::block_equal(block, expected));
+ }
+}
+
+TEST_F(TableFunctionOperatorTest, block_fast_path_explode_nullable_array_skip)
{
+ bool get_value_called = false;
+ {
+ op->_vfn_ctxs =
+ MockSlotRef::create_mock_contexts(DataTypes
{std::make_shared<DataTypeInt32>()});
+ auto fn =
std::make_shared<MockFastExplodeTableFunction>(&get_value_called);
+ fns.push_back(fn);
+ op->_fns.push_back(fn.get());
+ op->_output_slot_ids = {true, true, true};
+
+ child_op->_mock_row_desc.reset(
+ new MockRowDescriptor {{std::make_shared<DataTypeInt32>(),
+ std::make_shared<DataTypeArray>(
+
std::make_shared<DataTypeInt32>())},
+ &pool});
+ op->_mock_row_descriptor.reset(
+ new MockRowDescriptor {{std::make_shared<DataTypeInt32>(),
+ std::make_shared<DataTypeArray>(
+
std::make_shared<DataTypeInt32>()),
+ std::make_shared<DataTypeInt32>()},
+ &pool});
+
+ op->_fn_num = 1;
+ EXPECT_TRUE(op->prepare(state.get()));
+
+ local_state_uptr =
std::make_unique<MockTableFunctionLocalState>(state.get(), op.get());
+ local_state = local_state_uptr.get();
+ LocalStateInfo info {.parent_profile = &profile,
+ .scan_ranges = {},
+ .shared_state = nullptr,
+ .shared_state_map = {},
+ .task_idx = 0};
+ EXPECT_TRUE(local_state->init(state.get(), info));
+ state->resize_op_id_to_local_state(-100);
+ state->emplace_local_state(op->operator_id(),
std::move(local_state_uptr));
+ EXPECT_TRUE(local_state->open(state.get()));
+ }
+
+ {
+ auto id_col = ColumnHelper::create_column<DataTypeInt32>({10, 20, 30});
+ auto nested = ColumnInt32::create();
+ nested->insert_value(1);
+ nested->insert_value(2);
+ auto offsets = ColumnArray::ColumnOffsets::create();
+ offsets->insert_value(1);
+ offsets->insert_value(1);
+ offsets->insert_value(2);
+ auto arr_col = ColumnArray::create(std::move(nested),
std::move(offsets));
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type = std::make_shared<DataTypeArray>(int_type);
+ *local_state->_child_block =
+ Block({ColumnWithTypeAndName(id_col, int_type, "id"),
+ ColumnWithTypeAndName(ColumnPtr(std::move(arr_col)),
arr_type, "arr")});
+ auto st = op->push(state.get(), local_state->_child_block.get(), true);
+ EXPECT_TRUE(st) << st.msg();
+ }
+
+ {
+ Block block;
+ bool eos = false;
+ auto st = op->pull(state.get(), &block, &eos);
+ EXPECT_TRUE(st) << st.msg();
+ EXPECT_FALSE(get_value_called);
+
+ auto expected_id = ColumnHelper::create_column<DataTypeInt32>({10,
30});
+
+ auto expected_nested = ColumnInt32::create();
+ expected_nested->insert_value(1);
+ expected_nested->insert_value(2);
+ auto expected_offsets = ColumnArray::ColumnOffsets::create();
+ expected_offsets->insert_value(1);
+ expected_offsets->insert_value(2);
+ auto expected_arr =
+ ColumnArray::create(std::move(expected_nested),
std::move(expected_offsets));
+
+ auto expected_out = ColumnHelper::create_column<DataTypeInt32>({1, 2});
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type = std::make_shared<DataTypeArray>(int_type);
+ Block expected({ColumnWithTypeAndName(expected_id, int_type, "id"),
+
ColumnWithTypeAndName(ColumnPtr(std::move(expected_arr)), arr_type, "arr"),
+ ColumnWithTypeAndName(expected_out, int_type, "x")});
+ EXPECT_TRUE(ColumnHelper::block_equal(block, expected));
+ }
+}
+
+TEST_F(TableFunctionOperatorTest,
block_fast_path_explode_nullable_array_null_row) {
+ bool get_value_called = false;
+ {
+ op->_vfn_ctxs =
+ MockSlotRef::create_mock_contexts(DataTypes
{std::make_shared<DataTypeInt32>()});
+ auto fn =
std::make_shared<MockFastExplodeTableFunction>(&get_value_called);
+ fns.push_back(fn);
+ op->_fns.push_back(fn.get());
+ op->_output_slot_ids = {true, true, true};
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto child_arr_type = std::make_shared<DataTypeNullable>(
+ std::make_shared<DataTypeArray>(int_type));
+ child_op->_mock_row_desc.reset(new MockRowDescriptor {{int_type,
child_arr_type}, &pool});
+ op->_mock_row_descriptor.reset(
+ new MockRowDescriptor {{int_type, child_arr_type, int_type},
&pool});
+
+ op->_fn_num = 1;
+ EXPECT_TRUE(op->prepare(state.get()));
+
+ local_state_uptr =
std::make_unique<MockTableFunctionLocalState>(state.get(), op.get());
+ local_state = local_state_uptr.get();
+ LocalStateInfo info {.parent_profile = &profile,
+ .scan_ranges = {},
+ .shared_state = nullptr,
+ .shared_state_map = {},
+ .task_idx = 0};
+ EXPECT_TRUE(local_state->init(state.get(), info));
+ state->resize_op_id_to_local_state(-100);
+ state->emplace_local_state(op->operator_id(),
std::move(local_state_uptr));
+ EXPECT_TRUE(local_state->open(state.get()));
+ }
+
+ {
+ auto id_col = ColumnHelper::create_column<DataTypeInt32>({10, 20, 30});
+ auto nested = ColumnInt32::create();
+ nested->insert_value(1);
+ nested->insert_value(2);
+ auto offsets = ColumnArray::ColumnOffsets::create();
+ offsets->insert_value(1);
+ offsets->insert_value(1);
+ offsets->insert_value(2);
+ auto arr_data = ColumnArray::create(std::move(nested),
std::move(offsets));
+ auto null_map = ColumnUInt8::create();
+ null_map->insert_value(0);
+ null_map->insert_value(1);
+ null_map->insert_value(0);
+ auto arr_col = ColumnNullable::create(std::move(arr_data),
std::move(null_map));
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type =
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeArray>(int_type));
+ *local_state->_child_block =
+ Block({ColumnWithTypeAndName(id_col, int_type, "id"),
+ ColumnWithTypeAndName(ColumnPtr(std::move(arr_col)),
arr_type, "arr")});
+ auto st = op->push(state.get(), local_state->_child_block.get(), true);
+ EXPECT_TRUE(st) << st.msg();
+ }
+
+ {
+ Block block;
+ bool eos = false;
+ auto st = op->pull(state.get(), &block, &eos);
+ EXPECT_TRUE(st) << st.msg();
+ EXPECT_FALSE(get_value_called);
+
+ auto expected_id = ColumnHelper::create_column<DataTypeInt32>({10,
30});
+ auto expected_nested = ColumnInt32::create();
+ expected_nested->insert_value(1);
+ expected_nested->insert_value(2);
+ auto expected_offsets = ColumnArray::ColumnOffsets::create();
+ expected_offsets->insert_value(1);
+ expected_offsets->insert_value(2);
+ auto expected_arr_data =
+ ColumnArray::create(std::move(expected_nested),
std::move(expected_offsets));
+ auto expected_arr_null = ColumnUInt8::create();
+ expected_arr_null->insert_value(0);
+ expected_arr_null->insert_value(0);
+ auto expected_arr =
+ ColumnNullable::create(std::move(expected_arr_data),
std::move(expected_arr_null));
+ auto expected_out = ColumnHelper::create_column<DataTypeInt32>({1, 2});
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type =
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeArray>(int_type));
+ Block expected({ColumnWithTypeAndName(expected_id, int_type, "id"),
+
ColumnWithTypeAndName(ColumnPtr(std::move(expected_arr)), arr_type, "arr"),
+ ColumnWithTypeAndName(expected_out, int_type, "x")});
+ EXPECT_TRUE(ColumnHelper::block_equal(block, expected));
+ }
+}
+
+TEST_F(TableFunctionOperatorTest,
block_fast_path_explode_nullable_array_misaligned_fallback) {
+ bool get_value_called = false;
+ {
+ op->_vfn_ctxs =
+ MockSlotRef::create_mock_contexts(DataTypes
{std::make_shared<DataTypeInt32>()});
+ auto fn =
std::make_shared<MockFastExplodeTableFunction>(&get_value_called);
+ fns.push_back(fn);
+ op->_fns.push_back(fn.get());
+ op->_output_slot_ids = {true, true, true};
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto child_arr_type = std::make_shared<DataTypeNullable>(
+ std::make_shared<DataTypeArray>(int_type));
+ child_op->_mock_row_desc.reset(new MockRowDescriptor {{int_type,
child_arr_type}, &pool});
+ op->_mock_row_descriptor.reset(
+ new MockRowDescriptor {{int_type, child_arr_type, int_type},
&pool});
+
+ op->_fn_num = 1;
+ EXPECT_TRUE(op->prepare(state.get()));
+
+ local_state_uptr =
std::make_unique<MockTableFunctionLocalState>(state.get(), op.get());
+ local_state = local_state_uptr.get();
+ LocalStateInfo info {.parent_profile = &profile,
+ .scan_ranges = {},
+ .shared_state = nullptr,
+ .shared_state_map = {},
+ .task_idx = 0};
+ EXPECT_TRUE(local_state->init(state.get(), info));
+ state->resize_op_id_to_local_state(-100);
+ state->emplace_local_state(op->operator_id(),
std::move(local_state_uptr));
+ EXPECT_TRUE(local_state->open(state.get()));
+ }
+
+ {
+ auto id_col = ColumnHelper::create_column<DataTypeInt32>({10, 20, 30});
+ auto nested = ColumnInt32::create();
+ nested->insert_value(1);
+ nested->insert_value(9);
+ nested->insert_value(2);
+ auto offsets = ColumnArray::ColumnOffsets::create();
+ offsets->insert_value(1);
+ offsets->insert_value(2);
+ offsets->insert_value(3);
+ auto arr_data = ColumnArray::create(std::move(nested),
std::move(offsets));
+ auto null_map = ColumnUInt8::create();
+ null_map->insert_value(0);
+ null_map->insert_value(1);
+ null_map->insert_value(0);
+ auto arr_col = ColumnNullable::create(std::move(arr_data),
std::move(null_map));
+
+ auto int_type = std::make_shared<DataTypeInt32>();
+ auto arr_type =
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeArray>(int_type));
+ *local_state->_child_block =
+ Block({ColumnWithTypeAndName(id_col, int_type, "id"),
+ ColumnWithTypeAndName(ColumnPtr(std::move(arr_col)),
arr_type, "arr")});
+ auto st = op->push(state.get(), local_state->_child_block.get(), true);
+ EXPECT_TRUE(st) << st.msg();
+ }
+
+ {
+ Block block;
+ bool eos = false;
+ auto st = op->pull(state.get(), &block, &eos);
+ EXPECT_TRUE(st) << st.msg();
+ EXPECT_TRUE(get_value_called);
+
Review Comment:
Current fallback test
(`block_fast_path_explode_nullable_array_misaligned_fallback`) only exercises a
single `pull()` where the slow path finishes the whole child block. It doesn’t
cover the critical scenario where fast path fails, slow path produces a partial
batch leaving the table function cursor mid-row, and then a subsequent `pull()`
re-attempts the fast path (risking duplicated output unless fast path is
disabled after fallback). Adding a unit test with a small `state->batsh_size`
and larger arrays that require multiple `pull()` calls would catch regressions
here.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]