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]

Reply via email to