Coverage Report

Created: 2026-07-03 18:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/scan/scanner.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "exec/scan/scanner.h"
19
20
#include <glog/logging.h>
21
22
#include <algorithm>
23
24
#include "common/config.h"
25
#include "common/status.h"
26
#include "core/block/column_with_type_and_name.h"
27
#include "core/column/column_nothing.h"
28
#include "exec/common/util.hpp"
29
#include "exec/operator/scan_operator.h"
30
#include "exec/scan/scan_node.h"
31
#include "exprs/vexpr_context.h"
32
#include "exprs/vslot_ref.h"
33
#include "runtime/descriptors.h"
34
#include "runtime/runtime_profile.h"
35
#include "util/concurrency_stats.h"
36
#include "util/defer_op.h"
37
38
namespace doris {
39
40
Scanner::Scanner(RuntimeState* state, ScanLocalStateBase* local_state, int64_t limit,
41
                 RuntimeProfile* profile)
42
1.32M
        : _state(state),
43
1.32M
          _local_state(local_state),
44
1.32M
          _limit(limit),
45
1.32M
          _profile(profile),
46
1.32M
          _output_tuple_desc(_local_state->output_tuple_desc()),
47
1.32M
          _output_row_descriptor(_local_state->_parent->output_row_descriptor()),
48
1.32M
          _has_prepared(false) {
49
1.32M
    _total_rf_num = cast_set<int>(_local_state->_helper.runtime_filter_nums());
50
1.32M
    DorisMetrics::instance()->scanner_cnt->increment(1);
51
1.32M
}
52
53
1.33M
Status Scanner::init(RuntimeState* state, const VExprContextSPtrs& conjuncts) {
54
    // All scanners share a remaining-limit counter so a LIMIT query can
55
    // stop once enough rows have been collected across scanners.
56
    // Key TopN scans have no ordinary scan LIMIT, so each scanner can
57
    // independently produce its full local top-N.
58
1.33M
    _shared_scan_limit = _local_state->shared_scan_limit_ptr();
59
60
1.33M
    if (!conjuncts.empty()) {
61
40.2k
        _conjuncts.resize(conjuncts.size());
62
94.6k
        for (size_t i = 0; i != conjuncts.size(); ++i) {
63
54.4k
            RETURN_IF_ERROR(conjuncts[i]->clone(state, _conjuncts[i]));
64
54.4k
        }
65
40.2k
    }
66
67
1.33M
    const auto& projections = _local_state->_projections;
68
1.33M
    if (!projections.empty()) {
69
1.23M
        _projections.resize(projections.size());
70
13.2M
        for (size_t i = 0; i != projections.size(); ++i) {
71
12.0M
            RETURN_IF_ERROR(projections[i]->clone(state, _projections[i]));
72
12.0M
        }
73
1.23M
    }
74
75
1.33M
    const auto& intermediate_projections = _local_state->_intermediate_projections;
76
1.33M
    if (!intermediate_projections.empty()) {
77
13.5k
        _intermediate_projections.resize(intermediate_projections.size());
78
41.1k
        for (int i = 0; i < intermediate_projections.size(); i++) {
79
27.5k
            _intermediate_projections[i].resize(intermediate_projections[i].size());
80
164k
            for (int j = 0; j < intermediate_projections[i].size(); j++) {
81
137k
                RETURN_IF_ERROR(intermediate_projections[i][j]->clone(
82
137k
                        state, _intermediate_projections[i][j]));
83
137k
            }
84
27.5k
        }
85
13.5k
    }
86
87
    // Pure slot-ref projections do not compute new values; they only reorder or forward scan
88
    // columns. Record the source column ids once here so the hot projection path can move the
89
    // existing ColumnPtr instead of executing VSlotRef and then mutating it, which would clone
90
    // the column when the input block still shares the same pointer. Conjuncts are safe for this
91
    // path because Scanner filters the source block before running output projections.
92
1.33M
    if (!_projections.empty() && _intermediate_projections.empty() && _limit <= 0 &&
93
1.33M
        _shared_scan_limit == nullptr && _output_row_descriptor != nullptr &&
94
1.33M
        _output_row_descriptor->num_materialized_slots() == _projections.size()) {
95
1.22M
        std::vector<int> direct_slot_ref_projection_column_ids;
96
1.22M
        direct_slot_ref_projection_column_ids.reserve(_projections.size());
97
11.9M
        for (const auto& projection : _projections) {
98
11.9M
            auto slot_ref = std::dynamic_pointer_cast<VSlotRef>(projection->root());
99
11.9M
            if (slot_ref == nullptr) {
100
224k
                direct_slot_ref_projection_column_ids.clear();
101
224k
                break;
102
224k
            }
103
11.7M
            direct_slot_ref_projection_column_ids.push_back(slot_ref->column_id());
104
11.7M
        }
105
1.22M
        if (!direct_slot_ref_projection_column_ids.empty()) {
106
997k
            _direct_slot_ref_projection_column_ids =
107
997k
                    std::move(direct_slot_ref_projection_column_ids);
108
997k
        }
109
1.22M
    }
110
111
1.33M
    return Status::OK();
112
1.33M
}
113
114
1.44M
Status Scanner::get_block_after_projects(RuntimeState* state, Block* block, bool* eos) {
115
1.44M
    SCOPED_CONCURRENCY_COUNT(ConcurrencyStatsManager::instance().vscanner_get_block);
116
1.44M
    auto& row_descriptor = _local_state->_parent->row_descriptor();
117
1.44M
    if (_output_row_descriptor) {
118
1.27M
        _origin_block.clear_column_data(row_descriptor.num_materialized_slots());
119
1.27M
        const auto min_batch_size = std::max(state->batch_size() / 2, 1);
120
1.27M
        const auto block_max_bytes = state->preferred_block_size_bytes();
121
1.63M
        while (_padding_block.rows() < min_batch_size && _padding_block.bytes() < block_max_bytes &&
122
1.63M
               !*eos) {
123
1.63M
            RETURN_IF_ERROR(get_block(state, &_origin_block, eos));
124
1.63M
            if (*eos) {
125
                // For the final block, merge any padding directly and return eos in this call.
126
                // The merged tail can be larger than the target batch, but each source block is
127
                // already bounded by the lower scanner.
128
1.23M
                RETURN_IF_ERROR(_merge_padding_block());
129
1.23M
                _origin_block.clear_column_data(row_descriptor.num_materialized_slots());
130
1.23M
                break;
131
1.23M
            }
132
399k
            if (_origin_block.rows() >= min_batch_size) {
133
34.8k
                break;
134
34.8k
            }
135
136
364k
            if (_origin_block.rows() + _padding_block.rows() <= state->batch_size() &&
137
364k
                _origin_block.bytes() + _padding_block.bytes() <= block_max_bytes) {
138
364k
                RETURN_IF_ERROR(_merge_padding_block());
139
364k
                _origin_block.clear_column_data(row_descriptor.num_materialized_slots());
140
18.4E
            } else {
141
18.4E
                if (_origin_block.rows() < _padding_block.rows()) {
142
0
                    _padding_block.swap(_origin_block);
143
0
                }
144
18.4E
                break;
145
18.4E
            }
146
364k
        }
147
148
1.27M
        if (_origin_block.empty() && !_padding_block.empty()) {
149
251k
            _padding_block.swap(_origin_block);
150
251k
        }
151
1.27M
        return _do_projections(&_origin_block, block);
152
1.27M
    } else {
153
168k
        return get_block(state, block, eos);
154
168k
    }
155
1.44M
}
156
157
1.80M
Status Scanner::get_block(RuntimeState* state, Block* block, bool* eof) {
158
    // only empty block should be here
159
1.80M
    DCHECK(block->rows() == 0);
160
161
    // Stop early if other scanners have already collected enough rows
162
    // for the SQL LIMIT. Skipped when _shared_scan_limit is null (topn
163
    // path or no LIMIT).
164
1.80M
    if (_shared_scan_limit && _shared_scan_limit->load(std::memory_order_acquire) <= 0) {
165
26
        *eof = true;
166
26
        return Status::OK();
167
26
    }
168
169
    // scanner running time
170
1.80M
    SCOPED_RAW_TIMER(&_per_scanner_timer);
171
1.80M
    int64_t rows_read_threshold = _num_rows_read + config::doris_scanner_row_num;
172
1.80M
    if (!block->mem_reuse()) {
173
13.6M
        for (auto* const slot_desc : _output_tuple_desc->slots()) {
174
13.6M
            block->insert(ColumnWithTypeAndName(slot_desc->get_empty_mutable_column(),
175
13.6M
                                                slot_desc->get_data_type_ptr(),
176
13.6M
                                                slot_desc->col_name()));
177
13.6M
        }
178
1.47M
    }
179
180
1.80M
    {
181
1.88M
        do {
182
            // 1. Get input block from scanner
183
1.88M
            {
184
                // get block time
185
1.88M
                SCOPED_TIMER(_local_state->_scan_timer);
186
1.88M
                RETURN_IF_ERROR(_get_block_impl(state, block, eof));
187
1.88M
                if (*eof) {
188
1.32M
                    DCHECK(block->rows() == 0);
189
1.32M
                    break;
190
1.32M
                }
191
563k
                _num_rows_read += block->rows();
192
563k
                _num_byte_read += block->allocated_bytes();
193
563k
            }
194
195
            // 2. Filter the output block finally.
196
0
            {
197
563k
                SCOPED_TIMER(_local_state->_filter_timer);
198
563k
                RETURN_IF_ERROR(_filter_output_block(block));
199
563k
            }
200
            // record rows return (after filter) for _limit check
201
563k
            _num_rows_return += block->rows();
202
            // Publish progress to the shared counter so peer scanners can
203
            // observe it. The counter may go negative when several scanners
204
            // subtract concurrently; that is harmless because the operator's
205
            // reached_limit() makes the final cut.
206
563k
            if (_shared_scan_limit && block->rows() > 0) {
207
3.21k
                _shared_scan_limit->fetch_sub(block->rows(), std::memory_order_acq_rel);
208
3.21k
            }
209
563k
        } while (!_should_stop && !state->is_cancelled() && block->rows() == 0 && !(*eof) &&
210
563k
                 _num_rows_read < rows_read_threshold);
211
1.80M
    }
212
213
1.80M
    if (state->is_cancelled()) {
214
        // TODO: Should return the specific ErrorStatus instead of just Cancelled.
215
19
        return Status::Cancelled("cancelled");
216
19
    }
217
1.80M
    *eof = *eof || _should_stop;
218
    // set eof to true if per scanner limit is reached
219
    // currently for query: ORDER BY key LIMIT n
220
1.80M
    *eof = *eof || (_limit > 0 && _num_rows_return >= _limit);
221
1.80M
    *eof = *eof || (_shared_scan_limit && _shared_scan_limit->load(std::memory_order_acquire) <= 0);
222
223
1.80M
    return Status::OK();
224
1.80M
}
225
226
562k
Status Scanner::_filter_output_block(Block* block) {
227
562k
    auto old_rows = block->rows();
228
562k
    Status st = VExprContext::filter_block(_conjuncts, block, block->columns());
229
562k
    _counter.num_rows_unselected += old_rows - block->rows();
230
562k
    return st;
231
562k
}
232
233
1.27M
Status Scanner::_do_projections(Block* origin_block, Block* output_block) {
234
1.27M
    SCOPED_RAW_TIMER(&_per_scanner_timer);
235
1.27M
    SCOPED_RAW_TIMER(&_projection_timer);
236
237
1.27M
    const size_t rows = origin_block->rows();
238
1.27M
    if (rows == 0) {
239
987k
        return Status::OK();
240
987k
    }
241
285k
    Block input_block = *origin_block;
242
243
285k
    std::vector<int> result_column_ids;
244
285k
    for (auto& projections : _intermediate_projections) {
245
41.8k
        result_column_ids.resize(projections.size());
246
297k
        for (int i = 0; i < projections.size(); i++) {
247
255k
            RETURN_IF_ERROR(projections[i]->execute(&input_block, &result_column_ids[i]));
248
255k
        }
249
41.8k
        input_block.shuffle_columns(result_column_ids);
250
41.8k
    }
251
252
285k
    DCHECK_EQ(rows, input_block.rows());
253
    // Fast path for the common scan shape: PhysicalProject only forwards slot refs and the
254
    // output slot type is exactly the same as the scan column type. The exact type check keeps
255
    // casts/nullability changes on the generic expression path; only a pure column ownership
256
    // transfer is handled here.
257
285k
    if (!_direct_slot_ref_projection_column_ids.empty() &&
258
285k
        _direct_slot_ref_projection_column_ids.size() == _projections.size()) {
259
150k
        bool can_use_direct_projection = true;
260
150k
        auto direct_projection_columns =
261
150k
                VectorizedUtils::create_columns_with_type_and_name(*_output_row_descriptor);
262
150k
        if (direct_projection_columns.size() != _direct_slot_ref_projection_column_ids.size()) {
263
0
            return Status::InternalError(
264
0
                    "direct slot ref projection size mismatch, output columns {}, projections {}",
265
0
                    direct_projection_columns.size(),
266
0
                    _direct_slot_ref_projection_column_ids.size());
267
0
        }
268
582k
        for (size_t i = 0; i < _direct_slot_ref_projection_column_ids.size(); ++i) {
269
432k
            auto column_id = _direct_slot_ref_projection_column_ids[i];
270
432k
            if (column_id < 0 || column_id >= origin_block->columns()) {
271
0
                return Status::InternalError(
272
0
                        "slot ref projection column id {} is out of range, origin block columns {}",
273
0
                        column_id, origin_block->columns());
274
0
            }
275
432k
            const auto& src = origin_block->get_by_position(column_id);
276
432k
            if (!direct_projection_columns[i].type->equals(*src.type)) {
277
0
                can_use_direct_projection = false;
278
0
                break;
279
0
            }
280
432k
        }
281
150k
        if (can_use_direct_projection) {
282
150k
            return _do_direct_slot_ref_projection(origin_block, output_block,
283
150k
                                                  std::move(direct_projection_columns));
284
150k
        }
285
150k
    }
286
287
135k
    auto scoped_mutable_block = VectorizedUtils::build_scoped_mutable_mem_reuse_block(
288
135k
            output_block, *_output_row_descriptor);
289
135k
    auto& mutable_block = scoped_mutable_block.mutable_block();
290
291
135k
    auto& mutable_columns = mutable_block.mutable_columns();
292
293
135k
    DCHECK_EQ(mutable_columns.size(), _projections.size());
294
295
470k
    for (int i = 0; i < mutable_columns.size(); ++i) {
296
336k
        ColumnPtr column_ptr;
297
336k
        RETURN_IF_ERROR(_projections[i]->execute(&input_block, column_ptr));
298
335k
        column_ptr = column_ptr->convert_to_full_column_if_const();
299
335k
        if (mutable_columns[i]->is_nullable() != column_ptr->is_nullable()) {
300
0
            throw Exception(ErrorCode::INTERNAL_ERROR, "Nullable mismatch");
301
0
        }
302
335k
        mutable_columns[i] = IColumn::mutate(std::move(column_ptr));
303
335k
    }
304
305
133k
    scoped_mutable_block.restore();
306
307
    // origin columns was moved into output_block, so we need to set origin_block to empty columns
308
133k
    auto empty_columns = origin_block->clone_empty_columns();
309
133k
    origin_block->set_columns(std::move(empty_columns));
310
133k
    DCHECK_EQ(output_block->rows(), rows);
311
312
133k
    return Status::OK();
313
135k
}
314
315
Status Scanner::_do_direct_slot_ref_projection(Block* origin_block, Block* output_block,
316
150k
                                               ColumnsWithTypeAndName&& output_columns) {
317
150k
    const size_t rows = origin_block->rows();
318
319
582k
    for (size_t i = 0; i < _direct_slot_ref_projection_column_ids.size(); ++i) {
320
431k
        auto column_id = _direct_slot_ref_projection_column_ids[i];
321
431k
        if (column_id < 0 || column_id >= origin_block->columns()) {
322
0
            return Status::InternalError(
323
0
                    "slot ref projection column id {} is out of range, origin block columns {}",
324
0
                    column_id, origin_block->columns());
325
0
        }
326
327
431k
        auto column =
328
431k
                origin_block->get_by_position(column_id).column->convert_to_full_column_if_const();
329
431k
        if (column->size() != rows) {
330
0
            return Status::InternalError(
331
0
                    "direct slot ref projection result column size {} not equal input rows {}",
332
0
                    column->size(), rows);
333
0
        }
334
431k
        output_columns[i].column = std::move(column);
335
431k
    }
336
337
150k
    Block projected_block(std::move(output_columns));
338
150k
    output_block->swap(projected_block);
339
340
    // The output block now owns the scan columns. Replace the origin block with empty columns so
341
    // later block reuse/destruction does not keep an extra shared reference that would make later
342
    // mutable paths clone the forwarded columns.
343
150k
    auto empty_columns = origin_block->clone_empty_columns();
344
150k
    origin_block->set_columns(std::move(empty_columns));
345
150k
    DCHECK_EQ(output_block->rows(), rows);
346
347
150k
    return Status::OK();
348
150k
}
349
350
1.44M
Status Scanner::try_append_late_arrival_runtime_filter() {
351
1.44M
    if (_applied_rf_num == _total_rf_num) {
352
1.40M
        return Status::OK();
353
1.40M
    }
354
1.44M
    DCHECK(_applied_rf_num < _total_rf_num);
355
40.4k
    int arrived_rf_num = 0;
356
40.4k
    RETURN_IF_ERROR(_local_state->update_late_arrival_runtime_filter(_state, arrived_rf_num));
357
358
40.4k
    if (arrived_rf_num == _applied_rf_num) {
359
        // No newly arrived runtime filters, just return;
360
4.99k
        return Status::OK();
361
4.99k
    }
362
363
    // avoid conjunct destroy in used by storage layer
364
35.4k
    _conjuncts.clear();
365
35.4k
    RETURN_IF_ERROR(_local_state->clone_conjunct_ctxs(_conjuncts));
366
35.4k
    _applied_rf_num = arrived_rf_num;
367
35.4k
    return Status::OK();
368
35.4k
}
369
370
1.33M
Status Scanner::close(RuntimeState* state) {
371
1.33M
#ifndef BE_TEST
372
1.33M
    COUNTER_UPDATE(_local_state->_scanner_wait_worker_timer, _scanner_wait_worker_timer);
373
1.33M
#endif
374
1.33M
    return Status::OK();
375
1.33M
}
376
377
1.33M
bool Scanner::_try_close() {
378
1.33M
    bool expected = false;
379
1.33M
    return _is_closed.compare_exchange_strong(expected, true);
380
1.33M
}
381
382
1.32M
void Scanner::_collect_profile_before_close() {
383
1.32M
    COUNTER_UPDATE(_local_state->_scan_cpu_timer, _scan_cpu_timer);
384
1.32M
    COUNTER_UPDATE(_local_state->_rows_read_counter, _num_rows_read);
385
386
    // Update stats for load. See _should_update_load_counters() for why this is gated.
387
1.32M
    if (_should_update_load_counters()) {
388
5.20k
        _state->update_num_rows_load_filtered(_counter.num_rows_filtered);
389
5.20k
        _state->update_num_rows_load_unselected(_counter.num_rows_unselected);
390
5.20k
    }
391
1.32M
}
392
393
1.44M
void Scanner::_update_scan_cpu_timer() {
394
1.44M
    int64_t cpu_time = _cpu_watch.elapsed_time();
395
1.44M
    _scan_cpu_timer += cpu_time;
396
1.44M
    if (_state && _state->get_query_ctx()) {
397
1.44M
        _state->get_query_ctx()->resource_ctx()->cpu_context()->update_cpu_cost_ms(cpu_time);
398
1.44M
    }
399
1.44M
}
400
401
} // namespace doris