Coverage Report

Created: 2026-07-12 20:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/scan/file_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/file_scanner.h"
19
20
#include <fmt/format.h>
21
#include <gen_cpp/Exprs_types.h>
22
#include <gen_cpp/Metrics_types.h>
23
#include <gen_cpp/Opcodes_types.h>
24
#include <gen_cpp/PaloInternalService_types.h>
25
#include <gen_cpp/PlanNodes_types.h>
26
#include <glog/logging.h>
27
28
#include <algorithm>
29
#include <boost/iterator/iterator_facade.hpp>
30
#include <map>
31
#include <ranges>
32
#include <tuple>
33
#include <unordered_map>
34
#include <utility>
35
36
#include "common/cast_set.h"
37
#include "common/compiler_util.h" // IWYU pragma: keep
38
#include "common/config.h"
39
#include "common/consts.h"
40
#include "common/logging.h"
41
#include "common/status.h"
42
#include "core/block/column_with_type_and_name.h"
43
#include "core/block/columns_with_type_and_name.h"
44
#include "core/column/column.h"
45
#include "core/column/column_nullable.h"
46
#include "core/column/column_vector.h"
47
#include "core/data_type/data_type.h"
48
#include "core/data_type/data_type_nullable.h"
49
#include "core/data_type/data_type_string.h"
50
#include "core/string_ref.h"
51
#include "exec/common/stringop_substring.h"
52
#include "exec/rowid_fetcher.h"
53
#include "exec/scan/scan_node.h"
54
#include "exprs/aggregate/aggregate_function.h"
55
#include "exprs/function/function.h"
56
#include "exprs/function/simple_function_factory.h"
57
#include "exprs/vexpr.h"
58
#include "exprs/vexpr_context.h"
59
#include "exprs/vexpr_fwd.h"
60
#include "exprs/vslot_ref.h"
61
#include "format/arrow/arrow_stream_reader.h"
62
#include "format/count_reader.h"
63
#include "format/csv/csv_reader.h"
64
#include "format/json/new_json_reader.h"
65
#include "format/native/native_reader.h"
66
#include "format/orc/vorc_reader.h"
67
#include "format/parquet/vparquet_reader.h"
68
#include "format/table/es/es_http_reader.h"
69
#include "format/table/hive_reader.h"
70
#include "format/table/hudi_jni_reader.h"
71
#include "format/table/hudi_reader.h"
72
#include "format/table/iceberg_reader.h"
73
#include "format/table/iceberg_sys_table_jni_reader.h"
74
#include "format/table/jdbc_jni_reader.h"
75
#include "format/table/max_compute_jni_reader.h"
76
#include "format/table/paimon_cpp_reader.h"
77
#include "format/table/paimon_jni_reader.h"
78
#include "format/table/paimon_predicate_converter.h"
79
#include "format/table/paimon_reader.h"
80
#include "format/table/partition_column_filler.h"
81
#include "format/table/remote_doris_reader.h"
82
#include "format/table/transactional_hive_reader.h"
83
#include "format/table/trino_connector_jni_reader.h"
84
#include "format/text/text_reader.h"
85
#include "io/cache/block_file_cache_profile.h"
86
#include "load/group_commit/wal/wal_reader.h"
87
#include "runtime/descriptors.h"
88
#include "runtime/runtime_profile.h"
89
#include "runtime/runtime_state.h"
90
91
namespace cctz {
92
class time_zone;
93
} // namespace cctz
94
namespace doris {
95
class ShardedKVCache;
96
} // namespace doris
97
98
namespace doris {
99
using namespace ErrorCode;
100
101
const std::string FileScanner::FileReadBytesProfile = "FileReadBytes";
102
const std::string FileScanner::FileReadTimeProfile = "FileReadTime";
103
104
FileScanner::FileScanner(RuntimeState* state, FileScanLocalState* local_state, int64_t limit,
105
                         std::shared_ptr<SplitSourceConnector> split_source,
106
                         RuntimeProfile* profile, ShardedKVCache* kv_cache,
107
                         const std::unordered_map<std::string, int>* colname_to_slot_id)
108
6.22k
        : Scanner(state, local_state, limit, profile),
109
6.22k
          _split_source(split_source),
110
6.22k
          _cur_reader(nullptr),
111
6.22k
          _cur_reader_eof(false),
112
6.22k
          _kv_cache(kv_cache),
113
6.22k
          _strict_mode(false),
114
6.22k
          _col_name_to_slot_id(colname_to_slot_id) {
115
6.22k
    if (state->get_query_ctx() != nullptr &&
116
6.22k
        state->get_query_ctx()->file_scan_range_params_map.count(local_state->parent_id()) > 0) {
117
1.02k
        _params = &(state->get_query_ctx()->file_scan_range_params_map[local_state->parent_id()]);
118
5.20k
    } else {
119
        // old fe thrift protocol
120
5.20k
        _params = _split_source->get_params();
121
5.20k
    }
122
6.22k
    if (_params->__isset.strict_mode) {
123
5.19k
        _strict_mode = _params->strict_mode;
124
5.19k
    }
125
126
    // For load scanner, there are input and output tuple.
127
    // For query scanner, there is only output tuple
128
6.22k
    _input_tuple_desc = state->desc_tbl().get_tuple_descriptor(_params->src_tuple_id);
129
6.22k
    _real_tuple_desc = _input_tuple_desc == nullptr ? _output_tuple_desc : _input_tuple_desc;
130
6.22k
    _is_load = (_input_tuple_desc != nullptr);
131
6.22k
    _configure_file_scan_handlers();
132
6.22k
}
133
134
9.44k
void FileScanner::_configure_file_scan_handlers() {
135
9.44k
    if (_is_load) {
136
5.20k
        _init_src_block_handler = &FileScanner::_init_src_block_for_load;
137
5.20k
        _process_src_block_after_read_handler =
138
5.20k
                &FileScanner::_process_src_block_after_read_for_load;
139
5.20k
        _should_push_down_predicates_handler = &FileScanner::_should_push_down_predicates_for_load;
140
5.20k
        _should_enable_condition_cache_handler =
141
5.20k
                &FileScanner::_should_enable_condition_cache_for_load;
142
5.20k
    } else {
143
4.24k
        _init_src_block_handler = &FileScanner::_init_src_block_for_query;
144
4.24k
        _process_src_block_after_read_handler =
145
4.24k
                &FileScanner::_process_src_block_after_read_for_query;
146
4.24k
        _should_push_down_predicates_handler = &FileScanner::_should_push_down_predicates_for_query;
147
4.24k
        _should_enable_condition_cache_handler =
148
4.24k
                &FileScanner::_should_enable_condition_cache_for_query;
149
4.24k
    }
150
9.44k
}
151
152
6.22k
Status FileScanner::init(RuntimeState* state, const VExprContextSPtrs& conjuncts) {
153
6.22k
    RETURN_IF_ERROR(Scanner::init(state, conjuncts));
154
6.22k
    _get_block_timer =
155
6.22k
            ADD_TIMER_WITH_LEVEL(_local_state->scanner_profile(), "FileScannerGetBlockTime", 1);
156
6.22k
    _cast_to_input_block_timer = ADD_TIMER_WITH_LEVEL(_local_state->scanner_profile(),
157
6.22k
                                                      "FileScannerCastInputBlockTime", 1);
158
6.22k
    _fill_missing_columns_timer = ADD_TIMER_WITH_LEVEL(_local_state->scanner_profile(),
159
6.22k
                                                       "FileScannerFillMissingColumnTime", 1);
160
6.22k
    _pre_filter_timer =
161
6.22k
            ADD_TIMER_WITH_LEVEL(_local_state->scanner_profile(), "FileScannerPreFilterTimer", 1);
162
6.22k
    _convert_to_output_block_timer = ADD_TIMER_WITH_LEVEL(_local_state->scanner_profile(),
163
6.22k
                                                          "FileScannerConvertOuputBlockTime", 1);
164
6.22k
    _runtime_filter_partition_prune_timer = ADD_TIMER_WITH_LEVEL(
165
6.22k
            _local_state->scanner_profile(), "FileScannerRuntimeFilterPartitionPruningTime", 1);
166
6.22k
    _empty_file_counter =
167
6.22k
            ADD_COUNTER_WITH_LEVEL(_local_state->scanner_profile(), "EmptyFileNum", TUnit::UNIT, 1);
168
6.22k
    _not_found_file_counter = ADD_COUNTER_WITH_LEVEL(_local_state->scanner_profile(),
169
6.22k
                                                     "NotFoundFileNum", TUnit::UNIT, 1);
170
6.22k
    _fully_skipped_file_counter = ADD_COUNTER_WITH_LEVEL(_local_state->scanner_profile(),
171
6.22k
                                                         "FullySkippedFileNum", TUnit::UNIT, 1);
172
6.22k
    _file_counter =
173
6.22k
            ADD_COUNTER_WITH_LEVEL(_local_state->scanner_profile(), "FileNumber", TUnit::UNIT, 1);
174
175
6.22k
    _file_read_bytes_counter = ADD_COUNTER_WITH_LEVEL(_local_state->scanner_profile(),
176
6.22k
                                                      FileReadBytesProfile, TUnit::BYTES, 1);
177
6.22k
    _file_read_calls_counter = ADD_COUNTER_WITH_LEVEL(_local_state->scanner_profile(),
178
6.22k
                                                      "FileReadCalls", TUnit::UNIT, 1);
179
6.22k
    _file_read_time_counter =
180
6.22k
            ADD_TIMER_WITH_LEVEL(_local_state->scanner_profile(), FileReadTimeProfile, 1);
181
182
6.22k
    _runtime_filter_partition_pruned_range_counter =
183
6.22k
            ADD_COUNTER_WITH_LEVEL(_local_state->scanner_profile(),
184
6.22k
                                   "RuntimeFilterPartitionPrunedRangeNum", TUnit::UNIT, 1);
185
    // Keep the current file's adaptive state while also preserving the peak value across all
186
    // files handled by this scanner instance.
187
6.22k
    _adaptive_batch_predicted_rows_counter =
188
6.22k
            _local_state->scanner_profile()->AddHighWaterMarkCounter(
189
6.22k
                    "AdaptiveBatchPredictedRows", TUnit::UNIT, RuntimeProfile::ROOT_COUNTER, 1);
190
6.22k
    _adaptive_batch_actual_bytes_before_truncate_counter =
191
6.22k
            _local_state->scanner_profile()->AddHighWaterMarkCounter(
192
6.22k
                    "AdaptiveBatchActualBytesBeforeTruncate", TUnit::BYTES,
193
6.22k
                    RuntimeProfile::ROOT_COUNTER, 1);
194
6.22k
    _adaptive_batch_actual_bytes_after_truncate_counter =
195
6.22k
            _local_state->scanner_profile()->AddHighWaterMarkCounter(
196
6.22k
                    "AdaptiveBatchActualBytesAfterTruncate", TUnit::BYTES,
197
6.22k
                    RuntimeProfile::ROOT_COUNTER, 1);
198
6.22k
    _adaptive_batch_probe_count_counter = ADD_COUNTER_WITH_LEVEL(
199
6.22k
            _local_state->scanner_profile(), "AdaptiveBatchProbeCount", TUnit::UNIT, 1);
200
201
6.22k
    _file_cache_statistics.reset(new io::FileCacheStatistics());
202
6.22k
    _file_reader_stats.reset(new io::FileReaderStats());
203
204
6.22k
    RETURN_IF_ERROR(_init_io_ctx());
205
6.22k
    _io_ctx->file_cache_stats = _file_cache_statistics.get();
206
6.22k
    _io_ctx->file_reader_stats = _file_reader_stats.get();
207
6.22k
    _io_ctx->is_disposable = _state->query_options().disable_file_cache;
208
209
6.22k
    if (_is_load) {
210
5.19k
        _src_row_desc.reset(new RowDescriptor(_state->desc_tbl(),
211
5.19k
                                              std::vector<TupleId>({_input_tuple_desc->id()})));
212
        // prepare pre filters
213
5.19k
        if (_params->__isset.pre_filter_exprs_list) {
214
6
            RETURN_IF_ERROR(doris::VExpr::create_expr_trees(_params->pre_filter_exprs_list,
215
6
                                                            _pre_conjunct_ctxs));
216
5.19k
        } else if (_params->__isset.pre_filter_exprs) {
217
0
            VExprContextSPtr context;
218
0
            RETURN_IF_ERROR(doris::VExpr::create_expr_tree(_params->pre_filter_exprs, context));
219
0
            _pre_conjunct_ctxs.emplace_back(context);
220
0
        }
221
222
5.19k
        for (auto& conjunct : _pre_conjunct_ctxs) {
223
8
            RETURN_IF_ERROR(conjunct->prepare(_state, *_src_row_desc));
224
8
            RETURN_IF_ERROR(conjunct->open(_state));
225
8
        }
226
227
5.19k
        _dest_row_desc.reset(new RowDescriptor(_state->desc_tbl(),
228
5.19k
                                               std::vector<TupleId>({_output_tuple_desc->id()})));
229
5.19k
    }
230
231
6.22k
    _default_val_row_desc.reset(
232
6.22k
            new RowDescriptor(_state->desc_tbl(), std::vector<TupleId>({_real_tuple_desc->id()})));
233
234
6.22k
    return Status::OK();
235
6.22k
}
236
237
6.21k
bool FileScanner::_should_enable_adaptive_batch_size(TFileFormatType::type format_type) const {
238
    // Only enable for readers that support set_batch_size().
239
    // Table-format wrappers are covered because they delegate to native readers.
240
6.21k
    if (!config::enable_adaptive_batch_size) {
241
0
        return false;
242
0
    }
243
6.21k
    switch (format_type) {
244
91
    case TFileFormatType::FORMAT_PARQUET:
245
376
    case TFileFormatType::FORMAT_ORC:
246
2.69k
    case TFileFormatType::FORMAT_CSV_PLAIN:
247
2.69k
    case TFileFormatType::FORMAT_CSV_GZ:
248
2.69k
    case TFileFormatType::FORMAT_CSV_BZ2:
249
2.69k
    case TFileFormatType::FORMAT_CSV_LZ4FRAME:
250
2.69k
    case TFileFormatType::FORMAT_CSV_LZ4BLOCK:
251
2.69k
    case TFileFormatType::FORMAT_CSV_LZOP:
252
2.69k
    case TFileFormatType::FORMAT_CSV_DEFLATE:
253
2.69k
    case TFileFormatType::FORMAT_CSV_SNAPPYBLOCK:
254
2.76k
    case TFileFormatType::FORMAT_PROTO:
255
2.77k
    case TFileFormatType::FORMAT_TEXT:
256
5.53k
    case TFileFormatType::FORMAT_JSON:
257
5.53k
    case TFileFormatType::FORMAT_JNI:
258
5.53k
        return true;
259
683
    default:
260
683
        return false;
261
6.21k
    }
262
6.21k
}
263
264
68.2k
bool FileScanner::_should_run_adaptive_batch_size() const {
265
    // Skip adaptive batch sizing for pushed-down COUNT(*): the reader is wrapped by CountReader
266
    // and only emits a single aggregated row count instead of materializing real columns, so
267
    // there is no per-row byte cost to learn from and no benefit in tuning the batch size.
268
68.2k
    return _block_size_predictor != nullptr && _get_push_down_agg_type() != TPushAggOp::type::COUNT;
269
68.2k
}
270
271
18.5k
void FileScanner::_reset_adaptive_batch_size_state() {
272
18.5k
    _block_size_predictor.reset();
273
18.5k
    COUNTER_SET(_adaptive_batch_predicted_rows_counter, int64_t(0));
274
18.5k
    COUNTER_SET(_adaptive_batch_actual_bytes_before_truncate_counter, int64_t(0));
275
18.5k
    COUNTER_SET(_adaptive_batch_actual_bytes_after_truncate_counter, int64_t(0));
276
18.5k
}
277
278
6.20k
void FileScanner::_init_adaptive_batch_size_state(TFileFormatType::type format_type) {
279
6.20k
    _reset_adaptive_batch_size_state();
280
6.20k
    if (!_should_enable_adaptive_batch_size(format_type)) {
281
683
        return;
282
683
    }
283
284
    // External file readers do not provide reliable memory-size metadata hints. Use a small probe
285
    // batch so the predictor can learn from real FileScanner output quickly.
286
5.52k
    _block_size_predictor = std::make_unique<AdaptiveBlockSizePredictor>(
287
5.52k
            _state->preferred_block_size_bytes(), 0.0, ADAPTIVE_BATCH_INITIAL_PROBE_ROWS,
288
5.52k
            _state->batch_size());
289
5.52k
}
290
291
15.9k
size_t FileScanner::_predict_reader_batch_rows() {
292
15.9k
    DCHECK(_block_size_predictor != nullptr);
293
15.9k
    size_t predicted_rows = _block_size_predictor->predict_next_rows();
294
15.9k
    COUNTER_SET(_adaptive_batch_predicted_rows_counter, static_cast<int64_t>(predicted_rows));
295
15.9k
    return predicted_rows;
296
15.9k
}
297
298
19.7k
void FileScanner::_update_adaptive_batch_size_before_truncate(const Block& block) {
299
19.7k
    if (!_should_run_adaptive_batch_size()) {
300
9.86k
        return;
301
9.86k
    }
302
303
    // Learn from the logical bytes before CHAR/VARCHAR truncation. The truncated block can be
304
    // much smaller than the data the reader and FileScanner have already materialized.
305
9.86k
    COUNTER_SET(_adaptive_batch_actual_bytes_before_truncate_counter,
306
9.86k
                static_cast<int64_t>(block.bytes()));
307
9.86k
    if (block.rows() == 0) {
308
1.01k
        return;
309
1.01k
    }
310
311
    // Count a probe only when we actually obtain the first non-empty sample that seeds history.
312
8.85k
    if (!_block_size_predictor->has_history()) {
313
5.10k
        COUNTER_UPDATE(_adaptive_batch_probe_count_counter, 1);
314
5.10k
    }
315
8.85k
    _block_size_predictor->update(block);
316
8.85k
}
317
318
19.7k
void FileScanner::_update_adaptive_batch_size_after_truncate(const Block& block) {
319
19.7k
    if (!_should_run_adaptive_batch_size()) {
320
9.86k
        return;
321
9.86k
    }
322
323
    // Keep the post-truncate size only for observability. It should not affect the next batch
324
    // because truncation happens after the upstream memory cost has already been paid.
325
9.87k
    COUNTER_SET(_adaptive_batch_actual_bytes_after_truncate_counter,
326
9.87k
                static_cast<int64_t>(block.bytes()));
327
9.87k
}
328
329
// check if the expr is a partition pruning expr
330
260
bool FileScanner::_check_partition_prune_expr(const VExprSPtr& expr) {
331
260
    if (expr->is_slot_ref()) {
332
124
        auto* slot_ref = static_cast<VSlotRef*>(expr.get());
333
124
        return _partition_slot_index_map.find(slot_ref->slot_id()) !=
334
124
               _partition_slot_index_map.end();
335
124
    }
336
136
    if (expr->is_literal()) {
337
12
        return true;
338
12
    }
339
136
    return std::ranges::all_of(expr->children(), [this](const auto& child) {
340
136
        return _check_partition_prune_expr(child);
341
136
    });
342
136
}
343
344
752
bool FileScanner::_contains_runtime_filter(const VExprContextSPtrs& conjuncts) const {
345
752
    return std::ranges::any_of(
346
752
            conjuncts, [](const auto& conjunct) { return conjunct->root()->is_rf_wrapper(); });
347
752
}
348
349
156
void FileScanner::_init_runtime_filter_partition_prune_ctxs() {
350
156
    _runtime_filter_partition_prune_ctxs.clear();
351
156
    for (auto& conjunct : _conjuncts) {
352
124
        auto impl = conjunct->root()->get_impl();
353
        // If impl is not null, which means this a conjuncts from runtime filter.
354
124
        auto expr = impl ? impl : conjunct->root();
355
124
        if (_check_partition_prune_expr(expr)) {
356
12
            _runtime_filter_partition_prune_ctxs.emplace_back(conjunct);
357
12
        }
358
124
    }
359
156
}
360
361
94
void FileScanner::_init_runtime_filter_partition_prune_block() {
362
    // init block with empty column
363
286
    for (auto const* slot_desc : _real_tuple_desc->slots()) {
364
286
        _runtime_filter_partition_prune_block.insert(
365
286
                ColumnWithTypeAndName(slot_desc->get_empty_mutable_column(),
366
286
                                      slot_desc->get_data_type_ptr(), slot_desc->col_name()));
367
286
    }
368
94
}
369
370
94
Status FileScanner::_process_runtime_filters_partition_prune(bool& can_filter_all) {
371
94
    SCOPED_TIMER(_runtime_filter_partition_prune_timer);
372
94
    if (_runtime_filter_partition_prune_ctxs.empty() || _partition_col_descs.empty()) {
373
88
        return Status::OK();
374
88
    }
375
6
    size_t partition_value_column_size = 1;
376
377
    // 1. Get partition key values to string columns.
378
6
    std::unordered_map<SlotId, MutableColumnPtr> partition_slot_id_to_column;
379
6
    for (auto const& partition_col_desc : _partition_col_descs) {
380
6
        const auto& [partition_value, partition_slot_desc] = partition_col_desc.second;
381
6
        auto data_type = partition_slot_desc->get_data_type_ptr();
382
6
        auto partition_value_column = data_type->create_column();
383
6
        auto null_it = _partition_value_is_null.find(partition_slot_desc->col_name());
384
6
        DORIS_CHECK(null_it != _partition_value_is_null.end());
385
6
        RETURN_IF_ERROR(fill_partition_column_from_path_value(
386
6
                *partition_value_column, *partition_slot_desc, partition_value,
387
6
                partition_value_column_size, null_it->second));
388
389
6
        partition_slot_id_to_column[partition_slot_desc->id()] = std::move(partition_value_column);
390
6
    }
391
392
    // 2. Fill _runtime_filter_partition_prune_block from the partition column, then execute conjuncts and filter block.
393
    // 2.1 Fill _runtime_filter_partition_prune_block from the partition column to match the conjuncts executing.
394
6
    size_t index = 0;
395
6
    bool first_column_filled = false;
396
18
    for (auto const* slot_desc : _real_tuple_desc->slots()) {
397
18
        if (partition_slot_id_to_column.find(slot_desc->id()) !=
398
18
            partition_slot_id_to_column.end()) {
399
6
            auto partition_value_column = std::move(partition_slot_id_to_column[slot_desc->id()]);
400
6
            _runtime_filter_partition_prune_block.replace_by_position(
401
6
                    index, std::move(partition_value_column));
402
6
            if (index == 0) {
403
0
                first_column_filled = true;
404
0
            }
405
6
        }
406
18
        index++;
407
18
    }
408
409
    // 2.2 Execute conjuncts.
410
6
    if (!first_column_filled) {
411
        // VExprContext.execute has an optimization, the filtering is executed when block->rows() > 0
412
        // The following process may be tricky and time-consuming, but we have no other way.
413
6
        auto column = IColumn::mutate(
414
6
                std::move(_runtime_filter_partition_prune_block.get_by_position(0).column));
415
6
        column->resize(partition_value_column_size);
416
6
        _runtime_filter_partition_prune_block.replace_by_position(0, std::move(column));
417
6
    }
418
6
    IColumn::Filter result_filter(_runtime_filter_partition_prune_block.rows(), 1);
419
6
    RETURN_IF_ERROR(VExprContext::execute_conjuncts(_runtime_filter_partition_prune_ctxs, nullptr,
420
6
                                                    &_runtime_filter_partition_prune_block,
421
6
                                                    &result_filter, &can_filter_all));
422
6
    return Status::OK();
423
6
}
424
425
191
Status FileScanner::_process_conjuncts() {
426
191
    _slot_id_to_filter_conjuncts.clear();
427
191
    _not_single_slot_filter_conjuncts.clear();
428
217
    for (auto& conjunct : _push_down_conjuncts) {
429
217
        auto impl = conjunct->root()->get_impl();
430
        // If impl is not null, which means this a conjuncts from runtime filter.
431
217
        auto cur_expr = impl ? impl : conjunct->root();
432
433
217
        std::vector<int> slot_ids;
434
217
        _get_slot_ids(cur_expr.get(), &slot_ids);
435
217
        if (slot_ids.empty()) {
436
1
            _not_single_slot_filter_conjuncts.emplace_back(conjunct);
437
1
            continue;
438
1
        }
439
216
        bool single_slot = true;
440
216
        for (int i = 1; i < slot_ids.size(); i++) {
441
0
            if (slot_ids[i] != slot_ids[0]) {
442
0
                single_slot = false;
443
0
                break;
444
0
            }
445
0
        }
446
216
        if (single_slot) {
447
216
            SlotId slot_id = slot_ids[0];
448
216
            _slot_id_to_filter_conjuncts[slot_id].emplace_back(conjunct);
449
216
        } else {
450
0
            _not_single_slot_filter_conjuncts.emplace_back(conjunct);
451
0
        }
452
216
    }
453
191
    return Status::OK();
454
191
}
455
456
350
Status FileScanner::_process_late_arrival_conjuncts() {
457
350
    if (_push_down_conjuncts.size() < _conjuncts.size()) {
458
191
        _push_down_conjuncts = _conjuncts;
459
        // Do not clear _conjuncts here!
460
        // We must keep it for fallback filtering, especially when mixing
461
        // Native readers (which use _push_down_conjuncts) and JNI readers (which rely on _conjuncts).
462
        // _conjuncts.clear();
463
191
        RETURN_IF_ERROR(_process_conjuncts());
464
191
    }
465
350
    if (_applied_rf_num == _total_rf_num) {
466
350
        _local_state->scanner_profile()->add_info_string("ApplyAllRuntimeFilters", "True");
467
350
    }
468
350
    return Status::OK();
469
350
}
470
471
385
void FileScanner::_get_slot_ids(VExpr* expr, std::vector<int>* slot_ids) {
472
385
    for (auto& child_expr : expr->children()) {
473
384
        if (child_expr->is_slot_ref()) {
474
216
            VSlotRef* slot_ref = reinterpret_cast<VSlotRef*>(child_expr.get());
475
216
            SlotDescriptor* slot_desc = _state->desc_tbl().get_slot_descriptor(slot_ref->slot_id());
476
216
            slot_desc->set_is_predicate(true);
477
216
            slot_ids->emplace_back(slot_ref->slot_id());
478
216
        } else {
479
168
            _get_slot_ids(child_expr.get(), slot_ids);
480
168
        }
481
384
    }
482
385
}
483
484
6.22k
Status FileScanner::_open_impl(RuntimeState* state) {
485
6.22k
    RETURN_IF_CANCELLED(state);
486
6.22k
    RETURN_IF_ERROR(Scanner::_open_impl(state));
487
6.22k
    if (_local_state) {
488
6.22k
        _condition_cache_digest = _local_state->get_condition_cache_digest();
489
6.22k
    }
490
6.22k
    RETURN_IF_ERROR(_split_source->get_next(&_first_scan_range, &_current_range));
491
6.22k
    if (_first_scan_range) {
492
6.22k
        RETURN_IF_ERROR(_init_expr_ctxes());
493
6.22k
        if (_state->query_options().enable_runtime_filter_partition_prune &&
494
6.22k
            !_partition_slot_index_map.empty()) {
495
94
            _init_runtime_filter_partition_prune_ctxs();
496
94
            _init_runtime_filter_partition_prune_block();
497
94
        }
498
6.22k
    } else {
499
        // there's no scan range in split source. stop scanner directly.
500
0
        _scanner_eof = true;
501
0
    }
502
503
6.22k
    return Status::OK();
504
6.22k
}
505
506
34.9k
Status FileScanner::_get_block_impl(RuntimeState* state, Block* block, bool* eof) {
507
34.9k
    Status st = _get_block_wrapped(state, block, eof);
508
509
34.9k
    if (!st.ok()) {
510
        // add cur path in error msg for easy debugging
511
24
        return std::move(st.append(". cur path: " + get_current_scan_range_name()));
512
24
    }
513
34.8k
    return st;
514
34.9k
}
515
516
// For query:
517
//                              [exist cols]  [non-exist cols]  [col from path]  input  output
518
//                              A     B    C  D                 E
519
// _init_src_block              x     x    x  x                 x                -      x
520
// get_next_block               x     x    x  -                 -                -      x
521
// _cast_to_input_block         -     -    -  -                 -                -      -
522
// _fill_columns_from_path      -     -    -  -                 x                -      x
523
// _fill_missing_columns        -     -    -  x                 -                -      x
524
// _convert_to_output_block     -     -    -  -                 -                -      -
525
//
526
// For load:
527
//                              [exist cols]  [non-exist cols]  [col from path]  input  output
528
//                              A     B    C  D                 E
529
// _init_src_block              x     x    x  x                 x                x      -
530
// get_next_block               x     x    x  -                 -                x      -
531
// _cast_to_input_block         x     x    x  -                 -                x      -
532
// _fill_columns_from_path      -     -    -  -                 x                x      -
533
// _fill_missing_columns        -     -    -  x                 -                x      -
534
// _convert_to_output_block     -     -    -  -                 -                -      x
535
34.9k
Status FileScanner::_get_block_wrapped(RuntimeState* state, Block* block, bool* eof) {
536
34.9k
    do {
537
34.9k
        RETURN_IF_CANCELLED(state);
538
34.9k
        if (_cur_reader == nullptr || _cur_reader_eof) {
539
12.3k
            _finalize_reader_condition_cache();
540
            // The file may not exist because the file list is got from meta cache,
541
            // And the file may already be removed from storage.
542
            // Just ignore not found files.
543
12.3k
            Status st = _get_next_reader();
544
12.3k
            if (st.is<ErrorCode::NOT_FOUND>() && config::ignore_not_found_file_in_external_table) {
545
0
                _cur_reader_eof = true;
546
0
                COUNTER_UPDATE(_not_found_file_counter, 1);
547
0
                continue;
548
12.3k
            } else if (st.is<ErrorCode::END_OF_FILE>()) {
549
0
                _cur_reader_eof = true;
550
0
                COUNTER_UPDATE(_fully_skipped_file_counter, 1);
551
0
                continue;
552
12.3k
            } else if (!st) {
553
12
                return st;
554
12
            }
555
12.3k
            _init_reader_condition_cache();
556
12.3k
        }
557
558
34.8k
        if (_scanner_eof) {
559
6.13k
            *eof = true;
560
6.13k
            return Status::OK();
561
6.13k
        }
562
563
        // Init src block for load job based on the data file schema (e.g. parquet)
564
        // For query job, simply set _src_block_ptr to block.
565
28.7k
        size_t read_rows = 0;
566
28.7k
        RETURN_IF_ERROR(_init_src_block(block));
567
568
28.7k
        if (_should_run_adaptive_batch_size()) {
569
15.9k
            _cur_reader->set_batch_size(_predict_reader_batch_rows());
570
15.9k
        }
571
28.7k
        {
572
28.7k
            SCOPED_TIMER(_get_block_timer);
573
574
            // Read next block.
575
            // Some of column in block may not be filled (column not exist in file)
576
28.7k
            RETURN_IF_ERROR(
577
28.7k
                    _cur_reader->get_next_block(_src_block_ptr, &read_rows, &_cur_reader_eof));
578
28.7k
        }
579
        // use read_rows instead of _src_block_ptr->rows(), because the first column of _src_block_ptr
580
        // may not be filled after calling `get_next_block()`, so _src_block_ptr->rows() may return wrong result.
581
28.7k
        if (read_rows > 0) {
582
19.7k
            if ((!_cur_reader->count_read_rows()) && _io_ctx) {
583
9.67k
                _io_ctx->file_reader_stats->read_rows += read_rows;
584
9.67k
            }
585
            // If the push_down_agg_type is COUNT, no need to do the rest,
586
            // because we only save a number in block.
587
19.7k
            if (_get_push_down_agg_type() != TPushAggOp::type::COUNT) {
588
19.7k
                RETURN_IF_ERROR(_process_src_block_after_read(block));
589
19.7k
            }
590
19.7k
        }
591
28.7k
        break;
592
28.7k
    } while (true);
593
594
    // Update filtered rows and unselected rows for load, reset counter.
595
    // {
596
    //     state->update_num_rows_load_filtered(_counter.num_rows_filtered);
597
    //     state->update_num_rows_load_unselected(_counter.num_rows_unselected);
598
    //     _reset_counter();
599
    // }
600
28.7k
    return Status::OK();
601
34.9k
}
602
603
/**
604
 * Check whether there are complex types in parquet/orc reader in broker/stream load.
605
 * Broker/stream load will cast any type as string type, and complex types will be casted wrong.
606
 * This is a temporary method, and will be replaced by tvf.
607
 */
608
14.2k
Status FileScanner::_check_output_block_types() {
609
    // Only called from _init_src_block_for_load, so _is_load is always true.
610
14.2k
    TFileFormatType::type format_type = _params->format_type;
611
14.2k
    if (format_type == TFileFormatType::FORMAT_PARQUET ||
612
14.2k
        format_type == TFileFormatType::FORMAT_ORC) {
613
1.84k
        for (auto slot : _output_tuple_desc->slots()) {
614
1.84k
            if (is_complex_type(slot->type()->get_primitive_type())) {
615
0
                return Status::InternalError(
616
0
                        "Parquet/orc doesn't support complex types in broker/stream load, "
617
0
                        "please use tvf(table value function) to insert complex types.");
618
0
            }
619
1.84k
        }
620
64
    }
621
14.2k
    return Status::OK();
622
14.2k
}
623
624
28.7k
Status FileScanner::_init_src_block(Block* block) {
625
28.7k
    DCHECK(_init_src_block_handler != nullptr);
626
28.7k
    return (this->*_init_src_block_handler)(block);
627
28.7k
}
628
629
14.5k
Status FileScanner::_init_src_block_for_query(Block* block) {
630
14.5k
    _src_block_ptr = block;
631
632
    // Build name to index map only once on first call.
633
14.5k
    if (_src_block_name_to_idx.empty()) {
634
4.23k
        _src_block_name_to_idx = block->get_name_to_pos_map();
635
4.23k
    }
636
14.5k
    return Status::OK();
637
14.5k
}
638
639
14.2k
Status FileScanner::_init_src_block_for_load(Block* block) {
640
14.2k
    static_cast<void>(block);
641
14.2k
    RETURN_IF_ERROR(_check_output_block_types());
642
643
    // if (_src_block_init) {
644
    //     _src_block.clear_column_data();
645
    //     _src_block_ptr = &_src_block;
646
    //     return Status::OK();
647
    // }
648
649
14.2k
    _src_block.clear();
650
14.2k
    uint32_t idx = 0;
651
    // slots in _input_tuple_desc contains all slots describe in load statement, eg:
652
    // -H "columns: k1, k2, tmp1, k3 = tmp1 + 1"
653
    // _input_tuple_desc will contains: k1, k2, tmp1
654
    // and some of them are from file, such as k1 and k2, and some of them may not exist in file, such as tmp1
655
    // _input_tuple_desc also contains columns from path
656
134k
    for (auto& slot : _input_tuple_desc->slots()) {
657
134k
        DataTypePtr data_type;
658
134k
        auto it = _slot_lower_name_to_col_type.find(slot->col_name());
659
134k
        if (slot->is_skip_bitmap_col()) {
660
358
            _skip_bitmap_col_idx = idx;
661
358
        }
662
134k
        if (_params->__isset.sequence_map_col) {
663
1.29k
            if (_params->sequence_map_col == slot->col_name()) {
664
203
                _sequence_map_col_uid = slot->col_unique_id();
665
203
            }
666
1.29k
        }
667
134k
        data_type =
668
134k
                it == _slot_lower_name_to_col_type.end() ? slot->type() : make_nullable(it->second);
669
134k
        MutableColumnPtr data_column = data_type->create_column();
670
134k
        _src_block.insert(
671
134k
                ColumnWithTypeAndName(std::move(data_column), data_type, slot->col_name()));
672
134k
        _src_block_name_to_idx.emplace(slot->col_name(), idx++);
673
134k
    }
674
14.2k
    if (_params->__isset.sequence_map_col) {
675
1.81k
        for (const auto& slot : _output_tuple_desc->slots()) {
676
            // When the target table has seqeunce map column, _input_tuple_desc will not contains __DORIS_SEQUENCE_COL__,
677
            // so we should get its column unique id from _output_tuple_desc
678
1.81k
            if (slot->is_sequence_col()) {
679
211
                _sequence_col_uid = slot->col_unique_id();
680
211
            }
681
1.81k
        }
682
247
    }
683
14.2k
    _src_block_ptr = &_src_block;
684
14.2k
    _src_block_init = true;
685
14.2k
    return Status::OK();
686
14.2k
}
687
688
9.07k
Status FileScanner::_cast_to_input_block(Block* block) {
689
    // Only called from _process_src_block_after_read_for_load, so _is_load is always true.
690
9.07k
    SCOPED_TIMER(_cast_to_input_block_timer);
691
    // cast primitive type(PT0) to primitive type(PT1)
692
9.07k
    uint32_t idx = 0;
693
97.1k
    for (auto& slot_desc : _input_tuple_desc->slots()) {
694
97.1k
        if (_slot_lower_name_to_col_type.find(slot_desc->col_name()) ==
695
97.1k
            _slot_lower_name_to_col_type.end()) {
696
            // skip columns which does not exist in file
697
2.75k
            continue;
698
2.75k
        }
699
94.3k
        auto& arg = _src_block_ptr->get_by_position(_src_block_name_to_idx[slot_desc->col_name()]);
700
94.3k
        auto return_type = slot_desc->get_data_type_ptr();
701
        // remove nullable here, let the get_function decide whether nullable
702
94.3k
        auto data_type = get_data_type_with_default_argument(remove_nullable(return_type));
703
94.3k
        ColumnsWithTypeAndName arguments {
704
94.3k
                arg, {data_type->create_column(), data_type, slot_desc->col_name()}};
705
94.3k
        auto func_cast =
706
94.3k
                SimpleFunctionFactory::instance().get_function("CAST", arguments, return_type, {});
707
94.3k
        if (!func_cast) {
708
0
            return Status::InternalError("Function CAST[arg={}, col name={}, return={}] not found!",
709
0
                                         arg.type->get_name(), slot_desc->col_name(),
710
0
                                         return_type->get_name());
711
0
        }
712
94.3k
        idx = _src_block_name_to_idx[slot_desc->col_name()];
713
94.3k
        DCHECK(_state != nullptr);
714
94.3k
        auto ctx = FunctionContext::create_context(_state, {}, {});
715
94.3k
        RETURN_IF_ERROR(
716
94.3k
                func_cast->execute(ctx.get(), *_src_block_ptr, {idx}, idx, arg.column->size()));
717
94.3k
        _src_block_ptr->get_by_position(idx).type = std::move(return_type);
718
94.3k
    }
719
9.07k
    return Status::OK();
720
9.07k
}
721
722
9.07k
Status FileScanner::_pre_filter_src_block() {
723
    // Only called from _process_src_block_after_read_for_load, so _is_load is always true.
724
9.07k
    if (!_pre_conjunct_ctxs.empty()) {
725
56
        SCOPED_TIMER(_pre_filter_timer);
726
56
        auto origin_column_num = _src_block_ptr->columns();
727
56
        auto old_rows = _src_block_ptr->rows();
728
56
        RETURN_IF_ERROR(
729
56
                VExprContext::filter_block(_pre_conjunct_ctxs, _src_block_ptr, origin_column_num));
730
56
        _counter.num_rows_unselected += old_rows - _src_block_ptr->rows();
731
56
    }
732
9.07k
    return Status::OK();
733
9.07k
}
734
735
9.07k
Status FileScanner::_convert_to_output_block(Block* block) {
736
    // Only called from _process_src_block_after_read_for_load, so _is_load is always true.
737
9.07k
    SCOPED_TIMER(_convert_to_output_block_timer);
738
    // The block is passed from scanner context's free blocks,
739
    // which is initialized by output columns
740
    // so no need to clear it
741
    // block->clear();
742
743
9.07k
    int ctx_idx = 0;
744
9.07k
    size_t rows = _src_block_ptr->rows();
745
9.07k
    auto filter_column = ColumnUInt8::create(rows, 1);
746
9.07k
    auto& filter_map = filter_column->get_data();
747
748
    // After convert, the column_ptr should be copied into output block.
749
    // Can not use block->insert() because it may cause use_count() non-zero bug
750
9.07k
    auto scoped_mutable_output_block =
751
9.07k
            VectorizedUtils::build_scoped_mutable_mem_reuse_block(block, *_dest_row_desc);
752
9.07k
    auto& mutable_output_block = scoped_mutable_output_block.mutable_block();
753
9.07k
    auto& mutable_output_columns = mutable_output_block.mutable_columns();
754
755
9.07k
    std::vector<BitmapValue>* skip_bitmaps {nullptr};
756
9.07k
    MutableColumnPtr skip_bitmap_column;
757
9.07k
    if (_should_process_skip_bitmap_col()) {
758
201
        skip_bitmap_column = IColumn::mutate(
759
201
                std::move(_src_block_ptr->get_by_position(_skip_bitmap_col_idx).column));
760
201
        auto* skip_bitmap_nullable_col_ptr = assert_cast<ColumnNullable*>(skip_bitmap_column.get());
761
201
        skip_bitmaps = &(assert_cast<ColumnBitmap*>(
762
201
                                 skip_bitmap_nullable_col_ptr->get_nested_column_ptr().get())
763
201
                                 ->get_data());
764
        // NOTE:
765
        // - If the table has sequence type column, __DORIS_SEQUENCE_COL__ will be put in _input_tuple_desc, so whether
766
        //   __DORIS_SEQUENCE_COL__ will be marked in skip bitmap depends on whether it's specified in that row
767
        // - If the table has sequence map column, __DORIS_SEQUENCE_COL__ will not be put in _input_tuple_desc,
768
        //   so __DORIS_SEQUENCE_COL__ will be ommited if it't specified in a row and will not be marked in skip bitmap.
769
        //   So we should mark __DORIS_SEQUENCE_COL__ in skip bitmap here if the corresponding sequence map column us marked
770
201
        if (_sequence_map_col_uid != -1) {
771
352
            for (int j = 0; j < rows; ++j) {
772
328
                if ((*skip_bitmaps)[j].contains(_sequence_map_col_uid)) {
773
137
                    (*skip_bitmaps)[j].add(_sequence_col_uid);
774
137
                }
775
328
            }
776
24
        }
777
201
        _src_block_ptr->replace_by_position(_skip_bitmap_col_idx, std::move(skip_bitmap_column));
778
201
    }
779
780
    // for (auto slot_desc : _output_tuple_desc->slots()) {
781
114k
    for (int j = 0; j < mutable_output_columns.size(); ++j) {
782
105k
        auto* slot_desc = _output_tuple_desc->slots()[j];
783
105k
        int dest_index = ctx_idx;
784
105k
        ColumnPtr column_ptr;
785
786
105k
        auto& ctx = _dest_vexpr_ctx[dest_index];
787
        // PT1 => dest primitive type
788
105k
        RETURN_IF_ERROR(ctx->execute(_src_block_ptr, column_ptr));
789
        // column_ptr maybe a ColumnConst, convert it to a normal column
790
105k
        column_ptr = column_ptr->convert_to_full_column_if_const();
791
105k
        DCHECK(column_ptr);
792
793
        // because of src_slot_desc is always be nullable, so the column_ptr after do dest_expr
794
        // is likely to be nullable
795
105k
        if (LIKELY(is_column_nullable(*column_ptr))) {
796
98.0k
            const auto* nullable_column = reinterpret_cast<const ColumnNullable*>(column_ptr.get());
797
177M
            for (int i = 0; i < rows; ++i) {
798
177M
                if (filter_map[i] && nullable_column->is_null_at(i)) {
799
                    // skip checks for non-mentioned columns in flexible partial update
800
2.05M
                    if (skip_bitmaps == nullptr ||
801
2.05M
                        !skip_bitmaps->at(i).contains(slot_desc->col_unique_id())) {
802
                        // clang-format off
803
1.99M
                        if (_strict_mode && (_src_slot_descs_order_by_dest[dest_index]) &&
804
1.99M
                            !_src_block_ptr->get_by_position(_dest_slot_to_src_slot_index[dest_index]).column->is_null_at(i)) {
805
5.49k
                            filter_map[i] = false;
806
5.49k
                            RETURN_IF_ERROR(_state->append_error_msg_to_file(
807
5.49k
                                [&]() -> std::string {
808
5.49k
                                    return _src_block_ptr->dump_one_line(i, _num_of_columns_from_file);
809
5.49k
                                },
810
5.49k
                                [&]() -> std::string {
811
5.49k
                                    auto raw_value =
812
5.49k
                                            _src_block_ptr->get_by_position(_dest_slot_to_src_slot_index[dest_index]).column->get_data_at(i);
813
5.49k
                                    std::string raw_string = raw_value.to_string();
814
5.49k
                                    fmt::memory_buffer error_msg;
815
5.49k
                                    fmt::format_to(error_msg,"column({}) value is incorrect while strict mode is {}, src value is {}",
816
5.49k
                                            slot_desc->col_name(), _strict_mode, raw_string);
817
5.49k
                                    return fmt::to_string(error_msg);
818
5.49k
                                }));
819
1.99M
                        } else if (!slot_desc->is_nullable()) {
820
648
                            filter_map[i] = false;
821
648
                            RETURN_IF_ERROR(_state->append_error_msg_to_file(
822
648
                                [&]() -> std::string {
823
648
                                    return _src_block_ptr->dump_one_line(i, _num_of_columns_from_file);
824
648
                                },
825
648
                                [&]() -> std::string {
826
648
                                    fmt::memory_buffer error_msg;
827
648
                                    fmt::format_to(error_msg, "column({}) values is null while columns is not nullable", slot_desc->col_name());
828
648
                                    return fmt::to_string(error_msg);
829
648
                                }));
830
648
                        }
831
                        // clang-format on
832
1.99M
                    }
833
2.05M
                }
834
177M
            }
835
98.0k
            if (!slot_desc->is_nullable()) {
836
48.6k
                column_ptr = remove_nullable(column_ptr);
837
48.6k
            }
838
98.0k
        } else if (slot_desc->is_nullable()) {
839
115
            column_ptr = make_nullable(column_ptr);
840
115
        }
841
105k
        mutable_output_columns[j]->insert_range_from(*column_ptr, 0, rows);
842
105k
        ctx_idx++;
843
105k
    }
844
9.06k
    scoped_mutable_output_block.restore();
845
846
    // after do the dest block insert operation, clear _src_block to remove the reference of origin column
847
9.06k
    _src_block_ptr->clear();
848
849
9.06k
    size_t dest_size = block->columns();
850
    // do filter
851
9.06k
    block->insert(ColumnWithTypeAndName(std::move(filter_column), std::make_shared<DataTypeUInt8>(),
852
9.06k
                                        "filter column"));
853
9.06k
    RETURN_IF_ERROR(Block::filter_block(block, dest_size, dest_size));
854
855
9.06k
    _counter.num_rows_filtered += rows - block->rows();
856
9.06k
    return Status::OK();
857
9.06k
}
858
859
19.7k
Status FileScanner::_process_src_block_after_read(Block* block) {
860
19.7k
    DCHECK(_process_src_block_after_read_handler != nullptr);
861
19.7k
    return (this->*_process_src_block_after_read_handler)(block);
862
19.7k
}
863
864
10.6k
Status FileScanner::_process_src_block_after_read_for_query(Block* block) {
865
10.6k
    _update_adaptive_batch_size_before_truncate(*block);
866
867
    // Truncate CHAR/VARCHAR columns when target size is smaller than file schema.
868
    // This is needed for external table queries with truncate_char_or_varchar_columns=true.
869
10.6k
    RETURN_IF_ERROR(_truncate_char_or_varchar_columns(block));
870
871
10.6k
    _update_adaptive_batch_size_after_truncate(*block);
872
10.6k
    return Status::OK();
873
10.6k
}
874
875
9.07k
Status FileScanner::_process_src_block_after_read_for_load(Block* block) {
876
    // Convert the src block columns type in-place.
877
9.07k
    RETURN_IF_ERROR(_cast_to_input_block(block));
878
    // All readers fill partition and missing columns inside get_next_block:
879
    //   ORC/Parquet: in _do_get_next_block via on_fill_partition_columns/on_fill_missing_columns
880
    //   CSV/JSON/others: in on_after_read_block via fill_remaining_columns
881
    // Assert all columns have consistent row counts.
882
9.07k
    if (_src_block_ptr->columns() > 0) {
883
9.07k
        size_t rows = _src_block_ptr->get_by_position(0).column->size();
884
97.1k
        for (size_t i = 1; i < _src_block_ptr->columns(); ++i) {
885
88.0k
            DCHECK_EQ(_src_block_ptr->get_by_position(i).column->size(), rows)
886
0
                    << "Column " << _src_block_ptr->get_by_position(i).name << " has "
887
0
                    << _src_block_ptr->get_by_position(i).column->size() << " rows, expected "
888
0
                    << rows;
889
88.0k
        }
890
9.07k
    }
891
    // Apply _pre_conjunct_ctxs to filter src block.
892
9.07k
    RETURN_IF_ERROR(_pre_filter_src_block());
893
894
    // Convert src block to output block (dest block), then apply filters.
895
9.07k
    RETURN_IF_ERROR(_convert_to_output_block(block));
896
897
9.06k
    _update_adaptive_batch_size_before_truncate(*block);
898
899
    // Truncate CHAR/VARCHAR columns when target size is smaller than file schema.
900
9.06k
    RETURN_IF_ERROR(_truncate_char_or_varchar_columns(block));
901
902
9.06k
    _update_adaptive_batch_size_after_truncate(*block);
903
9.06k
    return Status::OK();
904
9.06k
}
905
906
19.7k
Status FileScanner::_truncate_char_or_varchar_columns(Block* block) {
907
    // Truncate char columns or varchar columns if size is smaller than file columns
908
    // or not found in the file column schema.
909
19.7k
    if (!_state->query_options().truncate_char_or_varchar_columns) {
910
19.7k
        return Status::OK();
911
19.7k
    }
912
18.4E
    int idx = 0;
913
18.4E
    for (auto* slot_desc : _real_tuple_desc->slots()) {
914
0
        const auto& type = slot_desc->type();
915
0
        if (type->get_primitive_type() != TYPE_VARCHAR && type->get_primitive_type() != TYPE_CHAR) {
916
0
            ++idx;
917
0
            continue;
918
0
        }
919
0
        auto iter = _source_file_col_name_types.find(slot_desc->col_name());
920
0
        if (iter != _source_file_col_name_types.end()) {
921
0
            const auto file_type_desc = _source_file_col_name_types[slot_desc->col_name()];
922
0
            int l = -1;
923
0
            if (auto* ftype = check_and_get_data_type<DataTypeString>(
924
0
                        remove_nullable(file_type_desc).get())) {
925
0
                l = ftype->len();
926
0
            }
927
0
            if ((assert_cast<const DataTypeString*>(remove_nullable(type).get())->len() > 0) &&
928
0
                (assert_cast<const DataTypeString*>(remove_nullable(type).get())->len() < l ||
929
0
                 l < 0)) {
930
0
                _truncate_char_or_varchar_column(
931
0
                        block, idx,
932
0
                        assert_cast<const DataTypeString*>(remove_nullable(type).get())->len());
933
0
            }
934
0
        } else {
935
0
            _truncate_char_or_varchar_column(
936
0
                    block, idx,
937
0
                    assert_cast<const DataTypeString*>(remove_nullable(type).get())->len());
938
0
        }
939
0
        ++idx;
940
0
    }
941
18.4E
    return Status::OK();
942
19.7k
}
943
944
// VARCHAR substring(VARCHAR str, INT pos[, INT len])
945
0
void FileScanner::_truncate_char_or_varchar_column(Block* block, int idx, int len) {
946
0
    auto int_type = std::make_shared<DataTypeInt32>();
947
0
    uint32_t num_columns_without_result = block->columns();
948
0
    const ColumnNullable* col_nullable =
949
0
            assert_cast<const ColumnNullable*>(block->get_by_position(idx).column.get());
950
0
    const ColumnPtr& string_column_ptr = col_nullable->get_nested_column_ptr();
951
0
    ColumnPtr null_map_column_ptr = col_nullable->get_null_map_column_ptr();
952
0
    block->replace_by_position(idx, std::move(string_column_ptr));
953
0
    block->insert({int_type->create_column_const(block->rows(), to_field<TYPE_INT>(1)), int_type,
954
0
                   "const 1"}); // pos is 1
955
0
    block->insert({int_type->create_column_const(block->rows(), to_field<TYPE_INT>(len)), int_type,
956
0
                   fmt::format("const {}", len)});                          // len
957
0
    block->insert({nullptr, std::make_shared<DataTypeString>(), "result"}); // result column
958
0
    ColumnNumbers temp_arguments(3);
959
0
    temp_arguments[0] = idx;                            // str column
960
0
    temp_arguments[1] = num_columns_without_result;     // pos
961
0
    temp_arguments[2] = num_columns_without_result + 1; // len
962
0
    uint32_t result_column_id = num_columns_without_result + 2;
963
964
0
    SubstringUtil::substring_execute(*block, temp_arguments, result_column_id, block->rows());
965
0
    auto res = ColumnNullable::create(block->get_by_position(result_column_id).column,
966
0
                                      null_map_column_ptr);
967
0
    block->replace_by_position(idx, std::move(res));
968
0
    Block::erase_useless_column(block, num_columns_without_result);
969
0
}
970
971
54
std::shared_ptr<segment_v2::RowIdColumnIteratorV2> FileScanner::_create_row_id_column_iterator() {
972
54
    auto& id_file_map = _state->get_id_file_map();
973
54
    auto file_id = id_file_map->get_file_mapping_id(
974
54
            std::make_shared<FileMapping>(((FileScanLocalState*)_local_state)->parent_id(),
975
54
                                          _current_range, _should_enable_file_meta_cache()));
976
54
    return std::make_shared<RowIdColumnIteratorV2>(IdManager::ID_VERSION,
977
54
                                                   BackendOptions::get_backend_id(), file_id);
978
54
}
979
980
8.78k
void FileScanner::_fill_base_init_context(ReaderInitContext* ctx) {
981
8.78k
    ctx->column_descs = &_column_descs;
982
8.78k
    ctx->col_name_to_block_idx = &_src_block_name_to_idx;
983
8.78k
    ctx->state = _state;
984
8.78k
    ctx->tuple_descriptor = _real_tuple_desc;
985
8.78k
    ctx->row_descriptor = _default_val_row_desc.get();
986
8.78k
    ctx->params = _params;
987
8.78k
    ctx->range = &_current_range;
988
8.78k
    ctx->table_info_node = TableSchemaChangeHelper::ConstNode::get_instance();
989
8.78k
    ctx->push_down_agg_type = _get_push_down_agg_type();
990
8.78k
}
991
992
12.3k
Status FileScanner::_get_next_reader() {
993
12.3k
    while (true) {
994
12.3k
        if (_cur_reader) {
995
6.14k
            _cur_reader->collect_profile_before_close();
996
6.14k
            RETURN_IF_ERROR(_cur_reader->close());
997
6.14k
            _state->update_num_finished_scan_range(1);
998
6.14k
        }
999
12.3k
        _cur_reader.reset(nullptr);
1000
12.3k
        _reset_adaptive_batch_size_state();
1001
12.3k
        _src_block_init = false;
1002
12.3k
        bool has_next = _first_scan_range;
1003
12.3k
        if (!_first_scan_range) {
1004
6.14k
            RETURN_IF_ERROR(_split_source->get_next(&has_next, &_current_range));
1005
6.14k
        }
1006
12.3k
        _first_scan_range = false;
1007
12.3k
        if (!has_next || _should_stop) {
1008
6.13k
            _scanner_eof = true;
1009
6.13k
            return Status::OK();
1010
6.13k
        }
1011
1012
6.23k
        const TFileRangeDesc& range = _current_range;
1013
6.23k
        _current_range_path = range.path;
1014
1015
6.23k
        if (!_partition_slot_index_map.empty()) {
1016
            // we need get partition columns first for runtime filter partition pruning
1017
94
            RETURN_IF_ERROR(_generate_partition_columns());
1018
1019
94
            if (_state->query_options().enable_runtime_filter_partition_prune) {
1020
                // if enable_runtime_filter_partition_prune is true, we need to check whether this range can be filtered out
1021
                // by runtime filter partition prune
1022
94
                if (_push_down_conjuncts.size() < _conjuncts.size()) {
1023
                    // there are new runtime filters, need to re-init runtime filter partition pruning ctxs
1024
62
                    _init_runtime_filter_partition_prune_ctxs();
1025
62
                }
1026
1027
94
                bool can_filter_all = false;
1028
94
                RETURN_IF_ERROR(_process_runtime_filters_partition_prune(can_filter_all));
1029
94
                if (can_filter_all) {
1030
                    // this range can be filtered out by runtime filter partition pruning
1031
                    // so we need to skip this range
1032
0
                    COUNTER_UPDATE(_runtime_filter_partition_pruned_range_counter, 1);
1033
0
                    continue;
1034
0
                }
1035
94
            }
1036
94
        }
1037
1038
        // create reader for specific format
1039
6.23k
        Status init_status = Status::OK();
1040
6.23k
        TFileFormatType::type format_type = _get_current_format_type();
1041
        // for compatibility, this logic is deprecated in 3.1
1042
6.23k
        if (format_type == TFileFormatType::FORMAT_JNI && range.__isset.table_format_params) {
1043
0
            if (range.table_format_params.table_format_type == "paimon" &&
1044
0
                !range.table_format_params.paimon_params.__isset.paimon_split) {
1045
                // use native reader
1046
0
                auto format = range.table_format_params.paimon_params.file_format;
1047
0
                if (format == "orc") {
1048
0
                    format_type = TFileFormatType::FORMAT_ORC;
1049
0
                } else if (format == "parquet") {
1050
0
                    format_type = TFileFormatType::FORMAT_PARQUET;
1051
0
                } else {
1052
0
                    return Status::InternalError("Not supported paimon file format: {}", format);
1053
0
                }
1054
0
            }
1055
0
        }
1056
1057
6.23k
        bool push_down_predicates = _should_push_down_predicates(format_type);
1058
6.23k
        bool need_to_get_parsed_schema = false;
1059
6.23k
        switch (format_type) {
1060
1
        case TFileFormatType::FORMAT_JNI: {
1061
1
            ReaderInitContext jni_ctx;
1062
1
            _fill_base_init_context(&jni_ctx);
1063
1064
1
            if (range.__isset.table_format_params &&
1065
1
                range.table_format_params.table_format_type == "max_compute") {
1066
0
                const auto* mc_desc = static_cast<const MaxComputeTableDescriptor*>(
1067
0
                        _real_tuple_desc->table_desc());
1068
0
                if (!mc_desc->init_status()) {
1069
0
                    return mc_desc->init_status();
1070
0
                }
1071
0
                std::unique_ptr<MaxComputeJniReader> mc_reader = MaxComputeJniReader::create_unique(
1072
0
                        mc_desc, range.table_format_params.max_compute_params, _file_slot_descs,
1073
0
                        range, _state, _profile);
1074
0
                init_status = static_cast<GenericReader*>(mc_reader.get())->init_reader(&jni_ctx);
1075
0
                _cur_reader = std::move(mc_reader);
1076
1
            } else if (range.__isset.table_format_params &&
1077
1
                       range.table_format_params.table_format_type == "paimon") {
1078
0
                const auto& paimon_params = range.table_format_params.paimon_params;
1079
0
                bool use_paimon_cpp_reader = false;
1080
0
                if (paimon_params.__isset.reader_type) {
1081
0
                    switch (paimon_params.reader_type) {
1082
0
                    case TPaimonReaderType::PAIMON_CPP:
1083
0
                        use_paimon_cpp_reader = true;
1084
0
                        break;
1085
0
                    case TPaimonReaderType::PAIMON_JNI:
1086
0
                        break;
1087
0
                    case TPaimonReaderType::PAIMON_NATIVE:
1088
0
                        return Status::InternalError(
1089
0
                                "invalid PAIMON_NATIVE reader_type for paimon FORMAT_JNI split, "
1090
0
                                "possibly caused by FE/BE protocol mismatch");
1091
0
                    default:
1092
0
                        return Status::InternalError(
1093
0
                                "unknown paimon reader_type for paimon FORMAT_JNI split, possibly "
1094
0
                                "caused by FE/BE protocol mismatch");
1095
0
                    }
1096
0
                } else {
1097
                    // TODO: Remove this fallback after all FE versions set TPaimonReaderType.
1098
0
                    use_paimon_cpp_reader =
1099
0
                            _state->query_options().__isset.enable_paimon_cpp_reader &&
1100
0
                            _state->query_options().enable_paimon_cpp_reader;
1101
0
                }
1102
0
                if (use_paimon_cpp_reader) {
1103
0
                    auto cpp_reader = PaimonCppReader::create_unique(_file_slot_descs, _state,
1104
0
                                                                     _profile, range, _params);
1105
0
                    if (!_is_load && !_push_down_conjuncts.empty()) {
1106
0
                        PaimonPredicateConverter predicate_converter(_file_slot_descs, _state);
1107
0
                        auto predicate = predicate_converter.build(_push_down_conjuncts);
1108
0
                        if (predicate) {
1109
0
                            cpp_reader->set_predicate(std::move(predicate));
1110
0
                        }
1111
0
                    }
1112
0
                    init_status =
1113
0
                            static_cast<GenericReader*>(cpp_reader.get())->init_reader(&jni_ctx);
1114
0
                    _cur_reader = std::move(cpp_reader);
1115
0
                } else {
1116
0
                    auto paimon_reader = PaimonJniReader::create_unique(_file_slot_descs, _state,
1117
0
                                                                        _profile, range, _params);
1118
0
                    init_status =
1119
0
                            static_cast<GenericReader*>(paimon_reader.get())->init_reader(&jni_ctx);
1120
0
                    _cur_reader = std::move(paimon_reader);
1121
0
                }
1122
1
            } else if (range.__isset.table_format_params &&
1123
1
                       range.table_format_params.table_format_type == "hudi") {
1124
0
                auto hudi_reader = HudiJniReader::create_unique(
1125
0
                        *_params, range.table_format_params.hudi_params, _file_slot_descs, _state,
1126
0
                        _profile);
1127
0
                init_status = static_cast<GenericReader*>(hudi_reader.get())->init_reader(&jni_ctx);
1128
0
                _cur_reader = std::move(hudi_reader);
1129
1
            } else if (range.__isset.table_format_params &&
1130
1
                       range.table_format_params.table_format_type == "trino_connector") {
1131
0
                auto trino_reader = TrinoConnectorJniReader::create_unique(_file_slot_descs, _state,
1132
0
                                                                           _profile, range);
1133
0
                init_status =
1134
0
                        static_cast<GenericReader*>(trino_reader.get())->init_reader(&jni_ctx);
1135
0
                _cur_reader = std::move(trino_reader);
1136
1
            } else if (range.__isset.table_format_params &&
1137
1
                       range.table_format_params.table_format_type == "jdbc") {
1138
                // Extract jdbc params from table_format_params
1139
0
                std::map<std::string, std::string> jdbc_params(
1140
0
                        range.table_format_params.jdbc_params.begin(),
1141
0
                        range.table_format_params.jdbc_params.end());
1142
0
                auto jdbc_reader = JdbcJniReader::create_unique(_file_slot_descs, _state, _profile,
1143
0
                                                                jdbc_params);
1144
0
                init_status = static_cast<GenericReader*>(jdbc_reader.get())->init_reader(&jni_ctx);
1145
0
                _cur_reader = std::move(jdbc_reader);
1146
1
            } else if (range.__isset.table_format_params &&
1147
1
                       range.table_format_params.table_format_type == "iceberg") {
1148
0
                auto iceberg_sys_reader = IcebergSysTableJniReader::create_unique(
1149
0
                        _file_slot_descs, _state, _profile, range, _params);
1150
0
                init_status = static_cast<GenericReader*>(iceberg_sys_reader.get())
1151
0
                                      ->init_reader(&jni_ctx);
1152
0
                _cur_reader = std::move(iceberg_sys_reader);
1153
0
            }
1154
            // Set col_name_to_block_idx for JNI readers to avoid repeated map creation
1155
1
            if (_cur_reader) {
1156
0
                if (auto* jni_reader = dynamic_cast<JniReader*>(_cur_reader.get())) {
1157
0
                    jni_reader->set_col_name_to_block_idx(&_src_block_name_to_idx);
1158
0
                }
1159
0
            }
1160
1
            break;
1161
1
        }
1162
101
        case TFileFormatType::FORMAT_PARQUET: {
1163
101
            auto file_meta_cache_ptr = _should_enable_file_meta_cache()
1164
101
                                               ? ExecEnv::GetInstance()->file_meta_cache()
1165
101
                                               : nullptr;
1166
101
            if (push_down_predicates) {
1167
77
                RETURN_IF_ERROR(_process_late_arrival_conjuncts());
1168
77
            }
1169
101
            RETURN_IF_ERROR(_init_parquet_reader(file_meta_cache_ptr));
1170
1171
91
            need_to_get_parsed_schema = true;
1172
91
            break;
1173
101
        }
1174
285
        case TFileFormatType::FORMAT_ORC: {
1175
285
            auto file_meta_cache_ptr = _should_enable_file_meta_cache()
1176
285
                                               ? ExecEnv::GetInstance()->file_meta_cache()
1177
285
                                               : nullptr;
1178
285
            if (push_down_predicates) {
1179
272
                RETURN_IF_ERROR(_process_late_arrival_conjuncts());
1180
272
            }
1181
285
            RETURN_IF_ERROR(_init_orc_reader(file_meta_cache_ptr));
1182
1183
285
            need_to_get_parsed_schema = true;
1184
285
            break;
1185
285
        }
1186
2.31k
        case TFileFormatType::FORMAT_CSV_PLAIN:
1187
2.31k
        case TFileFormatType::FORMAT_CSV_GZ:
1188
2.31k
        case TFileFormatType::FORMAT_CSV_BZ2:
1189
2.31k
        case TFileFormatType::FORMAT_CSV_LZ4FRAME:
1190
2.31k
        case TFileFormatType::FORMAT_CSV_LZ4BLOCK:
1191
2.31k
        case TFileFormatType::FORMAT_CSV_LZOP:
1192
2.31k
        case TFileFormatType::FORMAT_CSV_DEFLATE:
1193
2.31k
        case TFileFormatType::FORMAT_CSV_SNAPPYBLOCK:
1194
2.39k
        case TFileFormatType::FORMAT_PROTO: {
1195
2.39k
            auto reader = CsvReader::create_unique(_state, _profile, &_counter, *_params, range,
1196
2.39k
                                                   _file_slot_descs, _state->batch_size(), nullptr,
1197
2.39k
                                                   _io_ctx);
1198
2.39k
            CsvInitContext csv_ctx;
1199
2.39k
            _fill_base_init_context(&csv_ctx);
1200
2.39k
            csv_ctx.is_load = _is_load;
1201
2.39k
            init_status = static_cast<GenericReader*>(reader.get())->init_reader(&csv_ctx);
1202
2.39k
            _cur_reader = std::move(reader);
1203
2.39k
            break;
1204
2.31k
        }
1205
3
        case TFileFormatType::FORMAT_TEXT: {
1206
3
            auto reader = TextReader::create_unique(_state, _profile, &_counter, *_params, range,
1207
3
                                                    _file_slot_descs, _state->batch_size(), nullptr,
1208
3
                                                    _io_ctx);
1209
3
            CsvInitContext text_ctx;
1210
3
            _fill_base_init_context(&text_ctx);
1211
3
            text_ctx.is_load = _is_load;
1212
3
            init_status = static_cast<GenericReader*>(reader.get())->init_reader(&text_ctx);
1213
3
            _cur_reader = std::move(reader);
1214
3
            break;
1215
2.31k
        }
1216
2.76k
        case TFileFormatType::FORMAT_JSON: {
1217
2.76k
            _cur_reader = NewJsonReader::create_unique(_state, _profile, &_counter, *_params, range,
1218
2.76k
                                                       _file_slot_descs, &_scanner_eof,
1219
2.76k
                                                       _state->batch_size(), nullptr, _io_ctx);
1220
2.76k
            JsonInitContext json_ctx;
1221
2.76k
            _fill_base_init_context(&json_ctx);
1222
2.76k
            json_ctx.col_default_value_ctx = &_col_default_value_ctx;
1223
2.76k
            json_ctx.is_load = _is_load;
1224
2.76k
            init_status = _cur_reader->init_reader(&json_ctx);
1225
2.76k
            break;
1226
2.31k
        }
1227
1228
0
        case TFileFormatType::FORMAT_WAL: {
1229
0
            _cur_reader = WalReader::create_unique(_state);
1230
0
            WalInitContext wal_ctx;
1231
0
            _fill_base_init_context(&wal_ctx);
1232
0
            wal_ctx.output_tuple_descriptor = _output_tuple_desc;
1233
0
            init_status = _cur_reader->init_reader(&wal_ctx);
1234
0
            break;
1235
2.31k
        }
1236
1
        case TFileFormatType::FORMAT_NATIVE: {
1237
1
            auto reader = NativeReader::create_unique(_profile, *_params, range, _io_ctx, _state);
1238
1
            ReaderInitContext native_ctx;
1239
1
            _fill_base_init_context(&native_ctx);
1240
1
            init_status = static_cast<GenericReader*>(reader.get())->init_reader(&native_ctx);
1241
1
            _cur_reader = std::move(reader);
1242
1
            need_to_get_parsed_schema = false;
1243
1
            break;
1244
2.31k
        }
1245
10
        case TFileFormatType::FORMAT_ARROW: {
1246
10
            ReaderInitContext arrow_ctx;
1247
10
            _fill_base_init_context(&arrow_ctx);
1248
1249
10
            if (range.__isset.table_format_params &&
1250
10
                range.table_format_params.table_format_type == "remote_doris") {
1251
0
                auto doris_reader =
1252
0
                        RemoteDorisReader::create_unique(_file_slot_descs, _state, _profile, range);
1253
0
                init_status =
1254
0
                        static_cast<GenericReader*>(doris_reader.get())->init_reader(&arrow_ctx);
1255
0
                if (doris_reader) {
1256
0
                    doris_reader->set_col_name_to_block_idx(&_src_block_name_to_idx);
1257
0
                }
1258
0
                _cur_reader = std::move(doris_reader);
1259
10
            } else {
1260
10
                auto arrow_reader =
1261
10
                        ArrowStreamReader::create_unique(_state, _profile, &_counter, *_params,
1262
10
                                                         range, _file_slot_descs, _io_ctx.get());
1263
10
                init_status =
1264
10
                        static_cast<GenericReader*>(arrow_reader.get())->init_reader(&arrow_ctx);
1265
10
                _cur_reader = std::move(arrow_reader);
1266
10
            }
1267
10
            break;
1268
2.31k
        }
1269
672
        case TFileFormatType::FORMAT_ES_HTTP: {
1270
672
            _cur_reader = EsHttpReader::create_unique(_file_slot_descs, _state, _profile, range,
1271
672
                                                      *_params, _real_tuple_desc);
1272
672
            init_status = static_cast<EsHttpReader*>(_cur_reader.get())->init_reader();
1273
672
            break;
1274
2.31k
        }
1275
0
        default:
1276
0
            return Status::NotSupported("Not supported create reader for file format: {}.",
1277
0
                                        to_string(_params->format_type));
1278
6.23k
        }
1279
1280
6.21k
        if (_cur_reader == nullptr) {
1281
1
            return Status::NotSupported(
1282
1
                    "Not supported create reader for table format: {} / file format: {}.",
1283
1
                    range.__isset.table_format_params ? range.table_format_params.table_format_type
1284
1
                                                      : "NotSet",
1285
1
                    to_string(_params->format_type));
1286
1
        }
1287
6.21k
        COUNTER_UPDATE(_file_counter, 1);
1288
        // The FileScanner for external table may try to open not exist files,
1289
        // Because FE file cache for external table may out of date.
1290
        // So, NOT_FOUND for FileScanner is not a fail case.
1291
        // Will remove this after file reader refactor.
1292
6.21k
        if (init_status.is<END_OF_FILE>()) {
1293
0
            COUNTER_UPDATE(_empty_file_counter, 1);
1294
0
            continue;
1295
6.21k
        } else if (init_status.is<ErrorCode::NOT_FOUND>()) {
1296
0
            if (config::ignore_not_found_file_in_external_table) {
1297
0
                COUNTER_UPDATE(_not_found_file_counter, 1);
1298
0
                continue;
1299
0
            }
1300
0
            return Status::InternalError("failed to find reader, err: {}", init_status.to_string());
1301
6.21k
        } else if (!init_status.ok()) {
1302
1
            return Status::InternalError("failed to init reader, err: {}", init_status.to_string());
1303
1
        }
1304
1305
        // For table-level COUNT pushdown, offsets are undefined so we must skip
1306
        // _set_fill_or_truncate_columns (it uses [start_offset, end_offset] to
1307
        // filter row groups, which would produce incorrect empty results).
1308
6.21k
        bool is_table_level_count = _get_push_down_agg_type() == TPushAggOp::type::COUNT &&
1309
6.21k
                                    range.__isset.table_format_params &&
1310
6.21k
                                    range.table_format_params.table_level_row_count >= 0;
1311
6.21k
        if (!is_table_level_count) {
1312
6.21k
            Status status = _set_fill_or_truncate_columns(need_to_get_parsed_schema);
1313
6.21k
            if (status.is<END_OF_FILE>()) { // all parquet row groups are filtered
1314
0
                continue;
1315
6.21k
            } else if (!status.ok()) {
1316
0
                return Status::InternalError("failed to set_fill_or_truncate_columns, err: {}",
1317
0
                                             status.to_string());
1318
0
            }
1319
6.21k
        }
1320
1321
        // Unified COUNT(*) pushdown: replace the real reader with CountReader
1322
        // decorator if the reader accepts COUNT and can provide a total row count.
1323
6.21k
        if (_cur_reader->get_push_down_agg_type() == TPushAggOp::type::COUNT) {
1324
26
            int64_t total_rows = -1;
1325
26
            if (is_table_level_count) {
1326
                // FE-provided count (may account for table-format deletions)
1327
0
                total_rows = range.table_format_params.table_level_row_count;
1328
26
            } else if (_cur_reader->supports_count_pushdown()) {
1329
                // File metadata count (ORC footer / Parquet row groups)
1330
26
                total_rows = _cur_reader->get_total_rows();
1331
26
            }
1332
26
            if (total_rows >= 0) {
1333
26
                auto batch_size = _state->batch_size();
1334
26
                _cur_reader = std::make_unique<CountReader>(total_rows, batch_size,
1335
26
                                                            std::move(_cur_reader));
1336
26
            }
1337
26
        }
1338
6.21k
        _cur_reader_eof = false;
1339
6.21k
        _init_adaptive_batch_size_state(format_type);
1340
6.21k
        break;
1341
6.21k
    }
1342
6.21k
    return Status::OK();
1343
12.3k
}
1344
1345
Status FileScanner::_init_parquet_reader(FileMetaCache* file_meta_cache_ptr,
1346
1.44k
                                         std::unique_ptr<ParquetReader> parquet_reader) {
1347
1.44k
    const TFileRangeDesc& range = _current_range;
1348
1.44k
    Status init_status = Status::OK();
1349
    // Row-id fetch scanners read one mapped range without a split source and disable memory cache.
1350
1.44k
    const bool enable_file_meta_memory_cache =
1351
1.44k
            _split_source != nullptr && _should_enable_file_meta_memory_cache(file_meta_cache_ptr);
1352
1.44k
    auto configure_file_meta_memory_cache = [&](GenericReader* reader) {
1353
1.43k
        reader->set_enable_file_meta_memory_cache(enable_file_meta_memory_cache);
1354
1.43k
    };
1355
1356
1.44k
    phmap::flat_hash_map<int, std::vector<std::shared_ptr<ColumnPredicate>>> slot_id_to_predicates =
1357
1.44k
            _local_state
1358
1.44k
                    ? _local_state->cast<FileScanLocalState>()._slot_id_to_predicates
1359
1.44k
                    : phmap::flat_hash_map<int, std::vector<std::shared_ptr<ColumnPredicate>>> {};
1360
1361
    // Build unified ParquetInitContext (shared by all Parquet reader variants)
1362
1.44k
    ParquetInitContext pctx;
1363
1.44k
    _fill_base_init_context(&pctx);
1364
1.44k
    pctx.conjuncts = &_push_down_conjuncts;
1365
1.44k
    pctx.slot_id_to_predicates = &slot_id_to_predicates;
1366
1.44k
    pctx.colname_to_slot_id = _col_name_to_slot_id;
1367
1.44k
    pctx.not_single_slot_filter_conjuncts = &_not_single_slot_filter_conjuncts;
1368
1.44k
    pctx.slot_id_to_filter_conjuncts = &_slot_id_to_filter_conjuncts;
1369
1370
1.44k
    if (range.__isset.table_format_params &&
1371
1.44k
        range.table_format_params.table_format_type == "iceberg") {
1372
        // IcebergParquetReader IS-A ParquetReader (CRTP mixin), no wrapping needed
1373
126
        std::unique_ptr<IcebergParquetReader> iceberg_reader = IcebergParquetReader::create_unique(
1374
126
                _kv_cache, _profile, *_params, range, _state->batch_size(), &_state->timezone_obj(),
1375
126
                _io_ctx, _state, file_meta_cache_ptr);
1376
126
        iceberg_reader->set_create_row_id_column_iterator_func(
1377
126
                [this]() -> std::shared_ptr<segment_v2::RowIdColumnIteratorV2> {
1378
0
                    return _create_row_id_column_iterator();
1379
0
                });
1380
126
        configure_file_meta_memory_cache(iceberg_reader.get());
1381
126
        init_status = static_cast<GenericReader*>(iceberg_reader.get())->init_reader(&pctx);
1382
126
        _cur_reader = std::move(iceberg_reader);
1383
1.31k
    } else if (range.__isset.table_format_params &&
1384
1.31k
               range.table_format_params.table_format_type == "paimon") {
1385
        // PaimonParquetReader IS-A ParquetReader, no wrapping needed
1386
0
        auto paimon_reader = PaimonParquetReader::create_unique(
1387
0
                _profile, *_params, range, _state->batch_size(), &_state->timezone_obj(), _kv_cache,
1388
0
                _io_ctx, _state, file_meta_cache_ptr);
1389
0
        configure_file_meta_memory_cache(paimon_reader.get());
1390
0
        init_status = static_cast<GenericReader*>(paimon_reader.get())->init_reader(&pctx);
1391
0
        _cur_reader = std::move(paimon_reader);
1392
1.31k
    } else if (range.__isset.table_format_params &&
1393
1.31k
               range.table_format_params.table_format_type == "hudi") {
1394
        // HudiParquetReader IS-A ParquetReader, no wrapping needed
1395
0
        auto hudi_reader = HudiParquetReader::create_unique(
1396
0
                _profile, *_params, range, _state->batch_size(), &_state->timezone_obj(), _io_ctx,
1397
0
                _state, file_meta_cache_ptr);
1398
0
        configure_file_meta_memory_cache(hudi_reader.get());
1399
0
        init_status = static_cast<GenericReader*>(hudi_reader.get())->init_reader(&pctx);
1400
0
        _cur_reader = std::move(hudi_reader);
1401
1.31k
    } else if (range.table_format_params.table_format_type == "hive") {
1402
1.21k
        auto hive_reader = HiveParquetReader::create_unique(
1403
1.21k
                _profile, *_params, range, _state->batch_size(), &_state->timezone_obj(), _io_ctx,
1404
1.21k
                _state, &_is_file_slot, file_meta_cache_ptr,
1405
1.21k
                _state->query_options().enable_parquet_lazy_mat);
1406
1.21k
        hive_reader->set_create_row_id_column_iterator_func(
1407
1.21k
                [this]() -> std::shared_ptr<segment_v2::RowIdColumnIteratorV2> {
1408
0
                    return _create_row_id_column_iterator();
1409
0
                });
1410
1.21k
        configure_file_meta_memory_cache(hive_reader.get());
1411
1.21k
        init_status = static_cast<GenericReader*>(hive_reader.get())->init_reader(&pctx);
1412
1.21k
        _cur_reader = std::move(hive_reader);
1413
1.21k
    } else if (range.table_format_params.table_format_type == "tvf") {
1414
70
        if (!parquet_reader) {
1415
1
            parquet_reader = ParquetReader::create_unique(
1416
1
                    _profile, *_params, range, _state->batch_size(), &_state->timezone_obj(),
1417
1
                    _io_ctx, _state, file_meta_cache_ptr,
1418
1
                    _state->query_options().enable_parquet_lazy_mat);
1419
1
        }
1420
70
        parquet_reader->set_create_row_id_column_iterator_func(
1421
70
                [this]() -> std::shared_ptr<segment_v2::RowIdColumnIteratorV2> {
1422
0
                    return _create_row_id_column_iterator();
1423
0
                });
1424
70
        configure_file_meta_memory_cache(parquet_reader.get());
1425
70
        init_status = static_cast<GenericReader*>(parquet_reader.get())->init_reader(&pctx);
1426
70
        _cur_reader = std::move(parquet_reader);
1427
70
    } else if (_is_load) {
1428
24
        if (!parquet_reader) {
1429
24
            parquet_reader = ParquetReader::create_unique(
1430
24
                    _profile, *_params, range, _state->batch_size(), &_state->timezone_obj(),
1431
24
                    _io_ctx, _state, file_meta_cache_ptr,
1432
24
                    _state->query_options().enable_parquet_lazy_mat);
1433
24
        }
1434
24
        configure_file_meta_memory_cache(parquet_reader.get());
1435
24
        init_status = static_cast<GenericReader*>(parquet_reader.get())->init_reader(&pctx);
1436
24
        _cur_reader = std::move(parquet_reader);
1437
24
    }
1438
1439
1.44k
    return init_status;
1440
1.44k
}
1441
1442
Status FileScanner::_init_orc_reader(FileMetaCache* file_meta_cache_ptr,
1443
2.17k
                                     std::unique_ptr<OrcReader> orc_reader) {
1444
2.17k
    const TFileRangeDesc& range = _current_range;
1445
2.17k
    Status init_status = Status::OK();
1446
    // Row-id fetch scanners read one mapped range without a split source and disable memory cache.
1447
2.17k
    const bool enable_file_meta_memory_cache =
1448
2.17k
            _split_source != nullptr && _should_enable_file_meta_memory_cache(file_meta_cache_ptr);
1449
2.17k
    auto configure_file_meta_memory_cache = [&](GenericReader* reader) {
1450
2.17k
        reader->set_enable_file_meta_memory_cache(enable_file_meta_memory_cache);
1451
2.17k
    };
1452
1453
    // Build unified OrcInitContext (shared by all ORC reader variants)
1454
2.17k
    OrcInitContext octx;
1455
2.17k
    _fill_base_init_context(&octx);
1456
2.17k
    octx.conjuncts = &_push_down_conjuncts;
1457
2.17k
    octx.not_single_slot_filter_conjuncts = &_not_single_slot_filter_conjuncts;
1458
2.17k
    octx.slot_id_to_filter_conjuncts = &_slot_id_to_filter_conjuncts;
1459
1460
2.17k
    if (range.__isset.table_format_params &&
1461
2.17k
        range.table_format_params.table_format_type == "transactional_hive") {
1462
        // TransactionalHiveReader IS-A OrcReader, no wrapping needed
1463
292
        auto tran_orc_reader = TransactionalHiveReader::create_unique(
1464
292
                _profile, _state, *_params, range, _state->batch_size(), _state->timezone(),
1465
292
                _io_ctx, file_meta_cache_ptr);
1466
292
        tran_orc_reader->set_create_row_id_column_iterator_func(
1467
292
                [this]() -> std::shared_ptr<segment_v2::RowIdColumnIteratorV2> {
1468
54
                    return _create_row_id_column_iterator();
1469
54
                });
1470
292
        configure_file_meta_memory_cache(tran_orc_reader.get());
1471
292
        init_status = static_cast<GenericReader*>(tran_orc_reader.get())->init_reader(&octx);
1472
1473
292
        _cur_reader = std::move(tran_orc_reader);
1474
1.88k
    } else if (range.__isset.table_format_params &&
1475
1.88k
               range.table_format_params.table_format_type == "iceberg") {
1476
        // IcebergOrcReader IS-A OrcReader (CRTP mixin), no wrapping needed
1477
88
        std::unique_ptr<IcebergOrcReader> iceberg_reader = IcebergOrcReader::create_unique(
1478
88
                _kv_cache, _profile, _state, *_params, range, _state->batch_size(),
1479
88
                _state->timezone(), _io_ctx, file_meta_cache_ptr);
1480
88
        iceberg_reader->set_create_row_id_column_iterator_func(
1481
88
                [this]() -> std::shared_ptr<segment_v2::RowIdColumnIteratorV2> {
1482
0
                    return _create_row_id_column_iterator();
1483
0
                });
1484
88
        configure_file_meta_memory_cache(iceberg_reader.get());
1485
88
        init_status = static_cast<GenericReader*>(iceberg_reader.get())->init_reader(&octx);
1486
1487
88
        _cur_reader = std::move(iceberg_reader);
1488
1.79k
    } else if (range.__isset.table_format_params &&
1489
1.79k
               range.table_format_params.table_format_type == "paimon") {
1490
        // PaimonOrcReader IS-A OrcReader, no wrapping needed
1491
0
        auto paimon_reader = PaimonOrcReader::create_unique(
1492
0
                _profile, _state, *_params, range, _state->batch_size(), _state->timezone(),
1493
0
                _kv_cache, _io_ctx, file_meta_cache_ptr);
1494
0
        configure_file_meta_memory_cache(paimon_reader.get());
1495
0
        init_status = static_cast<GenericReader*>(paimon_reader.get())->init_reader(&octx);
1496
1497
0
        _cur_reader = std::move(paimon_reader);
1498
1.79k
    } else if (range.__isset.table_format_params &&
1499
1.79k
               range.table_format_params.table_format_type == "hudi") {
1500
        // HudiOrcReader IS-A OrcReader, no wrapping needed
1501
0
        auto hudi_reader = HudiOrcReader::create_unique(_profile, _state, *_params, range,
1502
0
                                                        _state->batch_size(), _state->timezone(),
1503
0
                                                        _io_ctx, file_meta_cache_ptr);
1504
0
        configure_file_meta_memory_cache(hudi_reader.get());
1505
0
        init_status = static_cast<GenericReader*>(hudi_reader.get())->init_reader(&octx);
1506
1507
0
        _cur_reader = std::move(hudi_reader);
1508
1.79k
    } else if (range.__isset.table_format_params &&
1509
1.79k
               range.table_format_params.table_format_type == "hive") {
1510
1.63k
        auto hive_reader = HiveOrcReader::create_unique(
1511
1.63k
                _profile, _state, *_params, range, _state->batch_size(), _state->timezone(),
1512
1.63k
                _io_ctx, &_is_file_slot, file_meta_cache_ptr,
1513
1.63k
                _state->query_options().enable_orc_lazy_mat);
1514
1.63k
        hive_reader->set_create_row_id_column_iterator_func(
1515
1.63k
                [this]() -> std::shared_ptr<segment_v2::RowIdColumnIteratorV2> {
1516
0
                    return _create_row_id_column_iterator();
1517
0
                });
1518
1.63k
        configure_file_meta_memory_cache(hive_reader.get());
1519
1.63k
        init_status = static_cast<GenericReader*>(hive_reader.get())->init_reader(&octx);
1520
1521
1.63k
        _cur_reader = std::move(hive_reader);
1522
1.63k
    } else if (range.__isset.table_format_params &&
1523
164
               range.table_format_params.table_format_type == "tvf") {
1524
135
        if (!orc_reader) {
1525
0
            orc_reader = OrcReader::create_unique(
1526
0
                    _profile, _state, *_params, range, _state->batch_size(), _state->timezone(),
1527
0
                    _io_ctx, file_meta_cache_ptr, _state->query_options().enable_orc_lazy_mat);
1528
0
        }
1529
135
        orc_reader->set_create_row_id_column_iterator_func(
1530
135
                [this]() -> std::shared_ptr<segment_v2::RowIdColumnIteratorV2> {
1531
0
                    return _create_row_id_column_iterator();
1532
0
                });
1533
135
        configure_file_meta_memory_cache(orc_reader.get());
1534
135
        init_status = static_cast<GenericReader*>(orc_reader.get())->init_reader(&octx);
1535
135
        _cur_reader = std::move(orc_reader);
1536
135
    } else if (_is_load) {
1537
13
        if (!orc_reader) {
1538
13
            orc_reader = OrcReader::create_unique(
1539
13
                    _profile, _state, *_params, range, _state->batch_size(), _state->timezone(),
1540
13
                    _io_ctx, file_meta_cache_ptr, _state->query_options().enable_orc_lazy_mat);
1541
13
        }
1542
13
        configure_file_meta_memory_cache(orc_reader.get());
1543
13
        init_status = static_cast<GenericReader*>(orc_reader.get())->init_reader(&octx);
1544
13
        _cur_reader = std::move(orc_reader);
1545
13
    }
1546
1547
2.17k
    return init_status;
1548
2.17k
}
1549
1550
9.45k
Status FileScanner::_set_fill_or_truncate_columns(bool need_to_get_parsed_schema) {
1551
9.45k
    _slot_lower_name_to_col_type.clear();
1552
1553
9.45k
    std::unordered_map<std::string, DataTypePtr> name_to_col_type;
1554
9.45k
    RETURN_IF_ERROR(_cur_reader->get_columns(&name_to_col_type));
1555
1556
82.8k
    for (const auto& [col_name, col_type] : name_to_col_type) {
1557
82.8k
        auto col_name_lower = to_lower(col_name);
1558
82.8k
        if (_partition_col_descs.contains(col_name_lower)) {
1559
            /*
1560
             * `_slot_lower_name_to_col_type` is used by `_init_src_block` and `_cast_to_input_block` during LOAD to
1561
             * generate columns of the corresponding type, which records the columns existing in the file.
1562
             *
1563
             * When a column in `COLUMNS FROM PATH` exists in a file column, the column type in the block will
1564
             * not match the slot type in `_output_tuple_desc`, causing an error when
1565
             * Serde `deserialize_one_cell_from_json` fills the partition values.
1566
             *
1567
             * So for partition column not need fill _slot_lower_name_to_col_type.
1568
             */
1569
44
            continue;
1570
44
        }
1571
82.7k
        _slot_lower_name_to_col_type.emplace(col_name_lower, col_type);
1572
82.7k
    }
1573
1574
9.45k
    RETURN_IF_ERROR(_generate_truncate_columns(need_to_get_parsed_schema));
1575
9.45k
    return Status::OK();
1576
9.45k
}
1577
1578
9.45k
Status FileScanner::_generate_truncate_columns(bool need_to_get_parsed_schema) {
1579
9.45k
    _source_file_col_name_types.clear();
1580
    //  The col names and types of source file, such as parquet, orc files.
1581
9.45k
    if (_state->query_options().truncate_char_or_varchar_columns && need_to_get_parsed_schema) {
1582
0
        std::vector<std::string> source_file_col_names;
1583
0
        std::vector<DataTypePtr> source_file_col_types;
1584
0
        Status status =
1585
0
                _cur_reader->get_parsed_schema(&source_file_col_names, &source_file_col_types);
1586
0
        if (!status.ok() && status.code() != TStatusCode::NOT_IMPLEMENTED_ERROR) {
1587
0
            return status;
1588
0
        }
1589
0
        DCHECK_EQ(source_file_col_names.size(), source_file_col_types.size());
1590
0
        for (int i = 0; i < source_file_col_names.size(); ++i) {
1591
0
            _source_file_col_name_types[to_lower(source_file_col_names[i])] =
1592
0
                    source_file_col_types[i];
1593
0
        }
1594
0
    }
1595
9.45k
    return Status::OK();
1596
9.45k
}
1597
1598
3.22k
Status FileScanner::prepare_for_read_lines(const TFileRangeDesc& range) {
1599
3.22k
    _current_range = range;
1600
1601
3.22k
    _file_cache_statistics.reset(new io::FileCacheStatistics());
1602
3.22k
    _file_reader_stats.reset(new io::FileReaderStats());
1603
1604
3.22k
    _file_read_bytes_counter =
1605
3.22k
            ADD_COUNTER_WITH_LEVEL(_profile, FileReadBytesProfile, TUnit::BYTES, 1);
1606
3.22k
    _file_read_time_counter = ADD_TIMER_WITH_LEVEL(_profile, FileReadTimeProfile, 1);
1607
1608
3.22k
    RETURN_IF_ERROR(_init_io_ctx());
1609
3.22k
    _io_ctx->file_cache_stats = _file_cache_statistics.get();
1610
3.22k
    _io_ctx->file_reader_stats = _file_reader_stats.get();
1611
3.22k
    _default_val_row_desc.reset(new RowDescriptor((TupleDescriptor*)_real_tuple_desc));
1612
3.22k
    RETURN_IF_ERROR(_init_expr_ctxes());
1613
1614
    // Since only one column is read from the file, there is no need to filter, so set these variables to empty.
1615
3.22k
    _push_down_conjuncts.clear();
1616
3.22k
    _not_single_slot_filter_conjuncts.clear();
1617
3.22k
    _slot_id_to_filter_conjuncts.clear();
1618
3.22k
    _kv_cache = nullptr;
1619
3.22k
    return Status::OK();
1620
3.22k
}
1621
1622
Status FileScanner::read_lines_from_range(const TFileRangeDesc& range,
1623
                                          const std::list<int64_t>& row_ids, Block* result_block,
1624
                                          const ExternalFileMappingInfo& external_info,
1625
3.22k
                                          int64_t* init_reader_ms, int64_t* get_block_ms) {
1626
3.22k
    _current_range = range;
1627
3.22k
    RETURN_IF_ERROR(_generate_partition_columns());
1628
1629
3.22k
    TFileFormatType::type format_type = _get_current_format_type();
1630
3.22k
    Status init_status = Status::OK();
1631
1632
3.22k
    auto file_meta_cache_ptr = external_info.enable_file_meta_cache
1633
3.22k
                                       ? ExecEnv::GetInstance()->file_meta_cache()
1634
3.22k
                                       : nullptr;
1635
1636
3.22k
    RETURN_IF_ERROR(scope_timer_run(
1637
3.22k
            [&]() -> Status {
1638
3.22k
                switch (format_type) {
1639
3.22k
                case TFileFormatType::FORMAT_PARQUET: {
1640
3.22k
                    std::unique_ptr<ParquetReader> parquet_reader = ParquetReader::create_unique(
1641
3.22k
                            _profile, *_params, range, 1, &_state->timezone_obj(), _io_ctx, _state,
1642
3.22k
                            file_meta_cache_ptr, false);
1643
3.22k
                    RETURN_IF_ERROR(
1644
3.22k
                            _init_parquet_reader(file_meta_cache_ptr, std::move(parquet_reader)));
1645
                    // _init_parquet_reader may create a new table-format specific reader
1646
                    // (e.g., HiveParquetReader) that replaces the original parquet_reader.
1647
                    // We need to re-apply read_by_rows to the actual _cur_reader.
1648
3.22k
                    RETURN_IF_ERROR(_cur_reader->read_by_rows(row_ids));
1649
3.22k
                    break;
1650
3.22k
                }
1651
3.22k
                case TFileFormatType::FORMAT_ORC: {
1652
3.22k
                    std::unique_ptr<OrcReader> orc_reader = OrcReader::create_unique(
1653
3.22k
                            _profile, _state, *_params, range, 1, _state->timezone(), _io_ctx,
1654
3.22k
                            file_meta_cache_ptr, false);
1655
3.22k
                    RETURN_IF_ERROR(_init_orc_reader(file_meta_cache_ptr, std::move(orc_reader)));
1656
                    // Same as above: re-apply read_by_rows to the actual _cur_reader.
1657
3.22k
                    RETURN_IF_ERROR(_cur_reader->read_by_rows(row_ids));
1658
3.22k
                    break;
1659
3.22k
                }
1660
3.22k
                default: {
1661
3.22k
                    return Status::NotSupported(
1662
3.22k
                            "Not support create lines reader for file format: {},"
1663
3.22k
                            "only support parquet and orc.",
1664
3.22k
                            to_string(_params->format_type));
1665
3.22k
                }
1666
3.22k
                }
1667
3.22k
                return Status::OK();
1668
3.22k
            },
1669
3.22k
            init_reader_ms));
1670
1671
3.22k
    RETURN_IF_ERROR(_set_fill_or_truncate_columns(true));
1672
3.22k
    _cur_reader_eof = false;
1673
1674
3.22k
    RETURN_IF_ERROR(scope_timer_run(
1675
3.22k
            [&]() -> Status {
1676
3.22k
                while (!_cur_reader_eof) {
1677
3.22k
                    bool eof = false;
1678
3.22k
                    RETURN_IF_ERROR(_get_block_impl(_state, result_block, &eof));
1679
3.22k
                }
1680
3.22k
                return Status::OK();
1681
3.22k
            },
1682
3.22k
            get_block_ms));
1683
1684
3.22k
    _cur_reader->collect_profile_before_close();
1685
3.22k
    RETURN_IF_ERROR(_cur_reader->close());
1686
1687
3.22k
    COUNTER_UPDATE(_file_read_bytes_counter, _file_reader_stats->read_bytes);
1688
3.22k
    COUNTER_UPDATE(_file_read_time_counter, _file_reader_stats->read_time_ns);
1689
3.22k
    return Status::OK();
1690
3.22k
}
1691
1692
3.31k
Status FileScanner::_generate_partition_columns() {
1693
3.31k
    _partition_col_descs.clear();
1694
3.31k
    _partition_value_is_null.clear();
1695
3.31k
    const TFileRangeDesc& range = _current_range;
1696
3.31k
    if (!range.__isset.columns_from_path_keys) {
1697
1.83k
        return Status::OK();
1698
1.83k
    }
1699
1.47k
    DORIS_CHECK(range.__isset.columns_from_path);
1700
1.47k
    DORIS_CHECK(range.columns_from_path.size() == range.columns_from_path_keys.size());
1701
1.47k
    const bool has_null_flags = range.__isset.columns_from_path_is_null;
1702
1.48k
    if (has_null_flags) {
1703
1.48k
        DORIS_CHECK(range.columns_from_path_is_null.size() == range.columns_from_path_keys.size());
1704
1.48k
    }
1705
1706
1.47k
    std::unordered_map<std::string, int> partition_name_to_key_index;
1707
1.47k
    int index = 0;
1708
2.54k
    for (const auto& key : range.columns_from_path_keys) {
1709
2.54k
        partition_name_to_key_index.emplace(key, index++);
1710
2.54k
    }
1711
1712
    // Iterate _column_descs to find PARTITION_KEY columns instead of _partition_slot_descs.
1713
18.8k
    for (const auto& col_desc : _column_descs) {
1714
18.8k
        if (col_desc.category != ColumnCategory::PARTITION_KEY) {
1715
16.6k
            continue;
1716
16.6k
        }
1717
2.24k
        auto pit = partition_name_to_key_index.find(col_desc.name);
1718
2.24k
        if (pit != partition_name_to_key_index.end()) {
1719
2.24k
            auto values_index = cast_set<size_t>(pit->second);
1720
2.24k
            _partition_col_descs.emplace(
1721
2.24k
                    col_desc.name,
1722
2.24k
                    std::make_tuple(range.columns_from_path[values_index], col_desc.slot_desc));
1723
2.24k
            _partition_value_is_null.emplace(
1724
2.24k
                    col_desc.name,
1725
18.4E
                    has_null_flags ? range.columns_from_path_is_null[values_index] : false);
1726
2.24k
        }
1727
2.24k
    }
1728
1.47k
    return Status::OK();
1729
3.31k
}
1730
1731
9.43k
Status FileScanner::_init_expr_ctxes() {
1732
9.43k
    std::map<SlotId, int> full_src_index_map;
1733
9.43k
    std::map<SlotId, SlotDescriptor*> full_src_slot_map;
1734
9.43k
    std::map<std::string, int> partition_name_to_key_index_map;
1735
9.43k
    int index = 0;
1736
69.0k
    for (const auto& slot_desc : _real_tuple_desc->slots()) {
1737
69.0k
        full_src_slot_map.emplace(slot_desc->id(), slot_desc);
1738
69.0k
        full_src_index_map.emplace(slot_desc->id(), index++);
1739
69.0k
    }
1740
1741
    // For external table query, find the index of column in path.
1742
    // Because query doesn't always search for all columns in a table
1743
    // and the order of selected columns is random.
1744
    // All ranges in _ranges vector should have identical columns_from_path_keys
1745
    // because they are all file splits for the same external table.
1746
    // So here use the first element of _ranges to fill the partition_name_to_key_index_map
1747
9.43k
    if (_current_range.__isset.columns_from_path_keys) {
1748
1.55k
        std::vector<std::string> key_map = _current_range.columns_from_path_keys;
1749
1.55k
        if (!key_map.empty()) {
1750
4.18k
            for (size_t i = 0; i < key_map.size(); i++) {
1751
2.62k
                partition_name_to_key_index_map.emplace(key_map[i], i);
1752
2.62k
            }
1753
1.55k
        }
1754
1.55k
    }
1755
1756
9.43k
    _num_of_columns_from_file = _params->num_of_columns_from_file;
1757
69.0k
    for (const auto& slot_info : _params->required_slots) {
1758
69.0k
        auto slot_id = slot_info.slot_id;
1759
69.0k
        auto it = full_src_slot_map.find(slot_id);
1760
69.0k
        if (it == std::end(full_src_slot_map)) {
1761
0
            return Status::InternalError(
1762
0
                    fmt::format("Unknown source slot descriptor, slot_id={}", slot_id));
1763
0
        }
1764
1765
69.0k
        ColumnDescriptor col_desc;
1766
69.0k
        col_desc.name = it->second->col_name();
1767
69.0k
        col_desc.slot_desc = it->second;
1768
1769
        // Read category from Thrift if available (new FE), otherwise fall back
1770
        // to slot_info.is_file_slot + partition_name_to_key_index_map for broker/stream load
1771
        // where the FE does not set TColumnCategory.
1772
69.0k
        if (slot_info.__isset.category) {
1773
7.30k
            switch (slot_info.category) {
1774
7.16k
            case TColumnCategory::REGULAR:
1775
7.16k
                col_desc.category = ColumnCategory::REGULAR;
1776
7.16k
                break;
1777
88
            case TColumnCategory::PARTITION_KEY:
1778
88
                col_desc.category = ColumnCategory::PARTITION_KEY;
1779
88
                break;
1780
54
            case TColumnCategory::SYNTHESIZED:
1781
54
                col_desc.category = ColumnCategory::SYNTHESIZED;
1782
54
                break;
1783
0
            case TColumnCategory::GENERATED:
1784
0
                col_desc.category = ColumnCategory::GENERATED;
1785
0
                break;
1786
7.30k
            }
1787
61.7k
        } else if (partition_name_to_key_index_map.contains(it->second->col_name()) &&
1788
61.7k
                   !slot_info.is_file_slot) {
1789
2.17k
            col_desc.category = ColumnCategory::PARTITION_KEY;
1790
2.17k
        }
1791
1792
        // Derive is_file_slot from category
1793
69.0k
        bool is_file_slot = (col_desc.category == ColumnCategory::REGULAR ||
1794
69.0k
                             col_desc.category == ColumnCategory::GENERATED);
1795
1796
69.0k
        if (partition_name_to_key_index_map.contains(it->second->col_name())) {
1797
2.26k
            if (_is_load) {
1798
11
                auto iti = full_src_index_map.find(slot_id);
1799
11
                _partition_slot_index_map.emplace(slot_id, iti->second - _num_of_columns_from_file);
1800
2.25k
            } else {
1801
2.25k
                auto kit = partition_name_to_key_index_map.find(it->second->col_name());
1802
2.25k
                _partition_slot_index_map.emplace(slot_id, kit->second);
1803
2.25k
            }
1804
2.26k
        }
1805
1806
69.0k
        if (is_file_slot) {
1807
66.7k
            _is_file_slot.emplace(slot_id);
1808
66.7k
            _file_slot_descs.emplace_back(it->second);
1809
66.7k
        }
1810
1811
69.0k
        _column_descs.push_back(col_desc);
1812
69.0k
    }
1813
1814
    // set column name to default value expr map
1815
    // new inline TFileScanSlotInfo.default_value_expr (preferred)
1816
69.0k
    for (const auto& slot_info : _params->required_slots) {
1817
69.0k
        auto slot_id = slot_info.slot_id;
1818
69.0k
        auto it = full_src_slot_map.find(slot_id);
1819
69.0k
        if (it == std::end(full_src_slot_map)) {
1820
0
            continue;
1821
0
        }
1822
69.0k
        const std::string& col_name = it->second->col_name();
1823
1824
69.0k
        VExprContextSPtr ctx;
1825
69.0k
        bool has_default = false;
1826
1827
        // Prefer inline default_value_expr from TFileScanSlotInfo (new FE)
1828
69.0k
        if (slot_info.__isset.default_value_expr && !slot_info.default_value_expr.nodes.empty()) {
1829
37.8k
            RETURN_IF_ERROR(VExpr::create_expr_tree(slot_info.default_value_expr, ctx));
1830
37.8k
            RETURN_IF_ERROR(ctx->prepare(_state, *_default_val_row_desc));
1831
37.8k
            RETURN_IF_ERROR(ctx->open(_state));
1832
37.8k
            has_default = true;
1833
37.8k
        } else if (slot_info.__isset.default_value_expr) {
1834
            // Empty nodes means null default (same as legacy empty TExpr)
1835
6.49k
            has_default = true;
1836
6.49k
        }
1837
1838
        // Fall back to legacy default_value_of_src_slot map for mixed-version FE/BE
1839
        // and callers that have not preserved inline default_value_expr yet.
1840
69.0k
        if (!has_default) {
1841
24.7k
            auto legacy_it = _params->default_value_of_src_slot.find(slot_id);
1842
24.7k
            if (legacy_it != std::end(_params->default_value_of_src_slot)) {
1843
24.1k
                if (!legacy_it->second.nodes.empty()) {
1844
0
                    RETURN_IF_ERROR(VExpr::create_expr_tree(legacy_it->second, ctx));
1845
0
                    RETURN_IF_ERROR(ctx->prepare(_state, *_default_val_row_desc));
1846
0
                    RETURN_IF_ERROR(ctx->open(_state));
1847
0
                }
1848
24.1k
                has_default = true;
1849
24.1k
            }
1850
24.7k
        }
1851
1852
69.0k
        if (has_default) {
1853
            // if expr is empty, the default value will be null
1854
68.5k
            _col_default_value_ctx.emplace(col_name, ctx);
1855
68.5k
        }
1856
69.0k
    }
1857
1858
    // Populate default_expr in each ColumnDescriptor from _col_default_value_ctx.
1859
    // This makes default values available to readers via column_descs, eliminating the
1860
    // need for the separate _generate_missing_columns roundtrip.
1861
69.0k
    for (auto& col_desc : _column_descs) {
1862
69.0k
        auto it = _col_default_value_ctx.find(col_desc.name);
1863
69.0k
        if (it != _col_default_value_ctx.end()) {
1864
68.4k
            col_desc.default_expr = it->second;
1865
68.4k
        }
1866
69.0k
    }
1867
1868
9.43k
    if (_is_load) {
1869
        // follow desc expr map is only for load task.
1870
5.20k
        bool has_slot_id_map = _params->__isset.dest_sid_to_src_sid_without_trans;
1871
5.20k
        int idx = 0;
1872
46.4k
        for (auto* slot_desc : _output_tuple_desc->slots()) {
1873
46.4k
            auto it = _params->expr_of_dest_slot.find(slot_desc->id());
1874
46.4k
            if (it == std::end(_params->expr_of_dest_slot)) {
1875
0
                return Status::InternalError("No expr for dest slot, id={}, name={}",
1876
0
                                             slot_desc->id(), slot_desc->col_name());
1877
0
            }
1878
1879
46.4k
            VExprContextSPtr ctx;
1880
46.4k
            if (!it->second.nodes.empty()) {
1881
46.4k
                RETURN_IF_ERROR(VExpr::create_expr_tree(it->second, ctx));
1882
46.4k
                RETURN_IF_ERROR(ctx->prepare(_state, *_src_row_desc));
1883
46.4k
                RETURN_IF_ERROR(ctx->open(_state));
1884
46.4k
            }
1885
46.4k
            _dest_vexpr_ctx.emplace_back(ctx);
1886
46.4k
            _dest_slot_name_to_idx[slot_desc->col_name()] = idx++;
1887
1888
46.4k
            if (has_slot_id_map) {
1889
46.3k
                auto it1 = _params->dest_sid_to_src_sid_without_trans.find(slot_desc->id());
1890
46.3k
                if (it1 == std::end(_params->dest_sid_to_src_sid_without_trans)) {
1891
9.21k
                    _src_slot_descs_order_by_dest.emplace_back(nullptr);
1892
37.1k
                } else {
1893
37.1k
                    auto _src_slot_it = full_src_slot_map.find(it1->second);
1894
37.1k
                    if (_src_slot_it == std::end(full_src_slot_map)) {
1895
0
                        return Status::InternalError("No src slot {} in src slot descs",
1896
0
                                                     it1->second);
1897
0
                    }
1898
37.1k
                    _dest_slot_to_src_slot_index.emplace(_src_slot_descs_order_by_dest.size(),
1899
37.1k
                                                         full_src_index_map[_src_slot_it->first]);
1900
37.1k
                    _src_slot_descs_order_by_dest.emplace_back(_src_slot_it->second);
1901
37.1k
                }
1902
46.3k
            }
1903
46.4k
        }
1904
5.20k
    }
1905
9.43k
    return Status::OK();
1906
9.43k
}
1907
1908
30.9k
bool FileScanner::_should_enable_condition_cache() {
1909
30.9k
    DCHECK(_should_enable_condition_cache_handler != nullptr);
1910
30.9k
    if (_condition_cache_digest == 0 || !(this->*_should_enable_condition_cache_handler)()) {
1911
26.4k
        return false;
1912
26.4k
    }
1913
1914
    // Condition cache starts as all-false and is turned true only by native readers when a
1915
    // row-level predicate leaves at least one row in the granule. COUNT pushdown may replace the
1916
    // native reader with CountReader, which only emits row counts and never runs that marking path.
1917
4.45k
    if (_get_push_down_agg_type() == TPushAggOp::type::COUNT) {
1918
264
        return false;
1919
264
    }
1920
1921
    // The cache is populated by native readers while evaluating pushed-down predicates.
1922
    // Scanner-only predicates cannot mark reader granules, so there is nothing useful to cache.
1923
4.19k
    if (_push_down_conjuncts.empty()) {
1924
3.82k
        return false;
1925
3.82k
    }
1926
1927
    // Runtime filters are query-local dynamic predicates. Some ready RF implementations can hash
1928
    // their payload into get_digest(), but FileScanner cannot rely on that for all RFs reaching the
1929
    // native reader. In particular, ScanLocalState computes _condition_cache_digest during open(),
1930
    // while FileScanner may append late-arrival RFs in _process_late_arrival_conjuncts()
1931
    // immediately before initializing Parquet/ORC readers.
1932
    //
1933
    // Reading a weaker cache entry would be safe by itself: if a cached bitmap only represented
1934
    // static predicate P, false granules for P are also false for P AND RF. The unsafe part is
1935
    // writing. On cache miss, native readers mark survivor granules using all pushed-down
1936
    // predicates, including late RFs. Without a read-only cache mode, this would insert a bitmap for
1937
    // P AND RF under a digest that only represents P.
1938
    //
1939
    // Example:
1940
    //   Q1 static predicate: k = 1, late RF payload: partition_key IN ('2024-02-01')
1941
    //   Q2 static predicate: k = 1, late RF payload: partition_key IN ('2024-03-01')
1942
    // If both scans share the same file/range/digest, reusing Q1's bitmap for Q2 can skip row
1943
    // ranges according to the wrong RF payload. Keep RF predicate pushdown enabled for reader-side
1944
    // filtering, but do not persist its result in condition cache.
1945
376
    return !_contains_runtime_filter(_conjuncts) && !_contains_runtime_filter(_push_down_conjuncts);
1946
4.19k
}
1947
1948
0
bool FileScanner::_should_enable_condition_cache_for_load() const {
1949
0
    return false;
1950
0
}
1951
1952
4.46k
bool FileScanner::_should_enable_condition_cache_for_query() const {
1953
4.46k
    return true;
1954
4.46k
}
1955
1956
6.22k
bool FileScanner::_should_push_down_predicates(TFileFormatType::type format_type) const {
1957
6.22k
    DCHECK(_should_push_down_predicates_handler != nullptr);
1958
6.22k
    return (this->*_should_push_down_predicates_handler)(format_type);
1959
6.22k
}
1960
1961
5.20k
bool FileScanner::_should_push_down_predicates_for_load(TFileFormatType::type format_type) const {
1962
5.20k
    static_cast<void>(format_type);
1963
5.20k
    return false;
1964
5.20k
}
1965
1966
1.02k
bool FileScanner::_should_push_down_predicates_for_query(TFileFormatType::type format_type) const {
1967
    // JNI readers handle predicate conversion in their own paths.
1968
1.02k
    return format_type != TFileFormatType::FORMAT_JNI;
1969
1.02k
}
1970
1971
12.3k
void FileScanner::_init_reader_condition_cache() {
1972
12.3k
    _condition_cache = nullptr;
1973
12.3k
    _condition_cache_ctx = nullptr;
1974
1975
12.3k
    if (!_should_enable_condition_cache() || !_cur_reader) {
1976
12.2k
        return;
1977
12.2k
    }
1978
1979
    // Disable condition cache when delete operations exist (e.g. Iceberg position/equality
1980
    // deletes, Hive ACID deletes). Cached granule results may become stale if delete files
1981
    // change between queries while the data file's cache key remains the same.
1982
90
    if (_cur_reader->has_delete_operations()) {
1983
36
        return;
1984
36
    }
1985
1986
54
    auto* cache = segment_v2::ConditionCache::instance();
1987
54
    _condition_cache_key = segment_v2::ConditionCache::ExternalCacheKey(
1988
54
            _current_range.path,
1989
18.4E
            _current_range.__isset.modification_time ? _current_range.modification_time : 0,
1990
18.4E
            _current_range.__isset.file_size ? _current_range.file_size : -1,
1991
54
            _condition_cache_digest,
1992
18.4E
            _current_range.__isset.start_offset ? _current_range.start_offset : 0,
1993
18.4E
            _current_range.__isset.size ? _current_range.size : -1);
1994
1995
54
    segment_v2::ConditionCacheHandle handle;
1996
54
    auto condition_cache_hit = cache->lookup(_condition_cache_key, &handle);
1997
54
    if (condition_cache_hit) {
1998
6
        _condition_cache = handle.get_filter_result();
1999
6
        _condition_cache_hit_count++;
2000
48
    } else {
2001
        // Allocate cache pre-sized to total number of granules.
2002
        // We add +1 as a safety margin: when a file is split across multiple scanners
2003
        // and the first row of this scanner's range is not aligned to a granule boundary,
2004
        // the data may span one more granule than ceil(total_rows / GRANULE_SIZE).
2005
        // The extra element costs only 1 bit and never affects correctness (an extra
2006
        // false-granule beyond the actual data range won't overlap any real row range).
2007
48
        int64_t total_rows = _cur_reader->get_total_rows();
2008
52
        if (total_rows > 0) {
2009
52
            size_t num_granules = (total_rows + ConditionCacheContext::GRANULE_SIZE - 1) /
2010
52
                                  ConditionCacheContext::GRANULE_SIZE;
2011
52
            _condition_cache = std::make_shared<std::vector<bool>>(num_granules + 1, false);
2012
52
        }
2013
48
    }
2014
2015
58
    if (_condition_cache) {
2016
        // Create context to pass to readers (native readers use it; non-native readers ignore it)
2017
58
        _condition_cache_ctx = std::make_shared<ConditionCacheContext>();
2018
58
        _condition_cache_ctx->is_hit = condition_cache_hit;
2019
58
        _condition_cache_ctx->filter_result = _condition_cache;
2020
58
        _cur_reader->set_condition_cache_context(_condition_cache_ctx);
2021
58
    }
2022
54
}
2023
2024
18.5k
void FileScanner::_finalize_reader_condition_cache() {
2025
18.5k
    if (!_should_enable_condition_cache() || !_condition_cache_ctx ||
2026
18.5k
        _condition_cache_ctx->is_hit) {
2027
18.5k
        _condition_cache = nullptr;
2028
18.5k
        _condition_cache_ctx = nullptr;
2029
18.5k
        return;
2030
18.5k
    }
2031
    // Only store the cache if the reader was fully consumed. If the scan was
2032
    // truncated early (e.g. by LIMIT), the cache is incomplete — unread granules
2033
    // would remain false and cause surviving rows to be incorrectly skipped on HIT.
2034
47
    if (!_cur_reader_eof) {
2035
0
        _condition_cache = nullptr;
2036
0
        _condition_cache_ctx = nullptr;
2037
0
        return;
2038
0
    }
2039
2040
47
    auto* cache = segment_v2::ConditionCache::instance();
2041
47
    cache->insert(_condition_cache_key, std::move(_condition_cache));
2042
47
    _condition_cache = nullptr;
2043
47
    _condition_cache_ctx = nullptr;
2044
47
}
2045
2046
6.22k
Status FileScanner::close(RuntimeState* state) {
2047
6.22k
    if (!_try_close()) {
2048
0
        return Status::OK();
2049
0
    }
2050
2051
6.22k
    _finalize_reader_condition_cache();
2052
2053
6.22k
    if (_cur_reader) {
2054
85
        RETURN_IF_ERROR(_cur_reader->close());
2055
85
    }
2056
2057
6.22k
    RETURN_IF_ERROR(Scanner::close(state));
2058
6.22k
    return Status::OK();
2059
6.22k
}
2060
2061
6.22k
void FileScanner::try_stop() {
2062
6.22k
    Scanner::try_stop();
2063
6.22k
    if (_io_ctx) {
2064
6.22k
        _io_ctx->should_stop = true;
2065
6.22k
    }
2066
6.22k
}
2067
2068
15.2k
void FileScanner::update_realtime_counters() {
2069
15.2k
    FileScanLocalState* local_state = static_cast<FileScanLocalState*>(_local_state);
2070
2071
15.2k
    COUNTER_UPDATE(local_state->_scan_bytes, _file_reader_stats->read_bytes);
2072
15.2k
    COUNTER_UPDATE(local_state->_scan_rows, _file_reader_stats->read_rows);
2073
2074
15.2k
    _state->get_query_ctx()->resource_ctx()->io_context()->update_scan_rows(
2075
15.2k
            _file_reader_stats->read_rows);
2076
15.2k
    _state->get_query_ctx()->resource_ctx()->io_context()->update_scan_bytes(
2077
15.2k
            _file_reader_stats->read_bytes);
2078
2079
15.2k
    int64_t delta_bytes_read_from_local =
2080
15.2k
            _file_cache_statistics->bytes_read_from_local - _last_bytes_read_from_local;
2081
15.2k
    int64_t delta_bytes_read_from_remote =
2082
15.2k
            _file_cache_statistics->bytes_read_from_remote - _last_bytes_read_from_remote;
2083
15.2k
    if (_file_cache_statistics->bytes_read_from_local == 0 &&
2084
15.2k
        _file_cache_statistics->bytes_read_from_remote == 0) {
2085
15.2k
        _state->get_query_ctx()
2086
15.2k
                ->resource_ctx()
2087
15.2k
                ->io_context()
2088
15.2k
                ->update_scan_bytes_from_remote_storage(_file_reader_stats->read_bytes);
2089
15.2k
        DorisMetrics::instance()->query_scan_bytes_from_local->increment(
2090
15.2k
                _file_reader_stats->read_bytes);
2091
15.2k
    } else {
2092
3
        _state->get_query_ctx()->resource_ctx()->io_context()->update_scan_bytes_from_local_storage(
2093
3
                delta_bytes_read_from_local);
2094
3
        _state->get_query_ctx()
2095
3
                ->resource_ctx()
2096
3
                ->io_context()
2097
3
                ->update_scan_bytes_from_remote_storage(delta_bytes_read_from_remote);
2098
3
        DorisMetrics::instance()->query_scan_bytes_from_local->increment(
2099
3
                delta_bytes_read_from_local);
2100
3
        DorisMetrics::instance()->query_scan_bytes_from_remote->increment(
2101
3
                delta_bytes_read_from_remote);
2102
3
    }
2103
2104
15.2k
    COUNTER_UPDATE(_file_read_bytes_counter, _file_reader_stats->read_bytes);
2105
2106
15.2k
    DorisMetrics::instance()->query_scan_bytes->increment(_file_reader_stats->read_bytes);
2107
15.2k
    DorisMetrics::instance()->query_scan_rows->increment(_file_reader_stats->read_rows);
2108
2109
15.2k
    _file_reader_stats->read_bytes = 0;
2110
15.2k
    _file_reader_stats->read_rows = 0;
2111
2112
15.2k
    _last_bytes_read_from_local = _file_cache_statistics->bytes_read_from_local;
2113
15.2k
    _last_bytes_read_from_remote = _file_cache_statistics->bytes_read_from_remote;
2114
15.2k
}
2115
2116
6.21k
bool FileScanner::_should_update_load_counters() const {
2117
6.21k
    if (_is_load) {
2118
5.20k
        return true;
2119
5.20k
    }
2120
    // TVF based loads (e.g. http_stream, group commit relay) plan the load source as a
2121
    // tvf query scan without src tuple desc, so _is_load is false. But rows filtered by
2122
    // the load's WHERE clause still need to be reported as unselected rows. FILE_STREAM
2123
    // is only reachable from such load entries, never from normal queries, so use it to
2124
    // identify these scanners.
2125
1.01k
    return (_params->__isset.file_type && _params->file_type == TFileType::FILE_STREAM) ||
2126
1.01k
           (_current_range.__isset.file_type && _current_range.file_type == TFileType::FILE_STREAM);
2127
6.21k
}
2128
2129
6.21k
void FileScanner::_collect_profile_before_close() {
2130
6.21k
    Scanner::_collect_profile_before_close();
2131
6.21k
    if (config::enable_file_cache && _state->query_options().enable_file_cache &&
2132
6.21k
        _profile != nullptr) {
2133
0
        io::FileCacheProfileReporter cache_profile(_profile);
2134
0
        cache_profile.update(_file_cache_statistics.get());
2135
0
        _state->get_query_ctx()->resource_ctx()->io_context()->update_bytes_write_into_cache(
2136
0
                _file_cache_statistics->bytes_write_into_cache);
2137
0
    }
2138
2139
6.21k
    if (_cur_reader != nullptr) {
2140
85
        _cur_reader->collect_profile_before_close();
2141
85
    }
2142
2143
6.21k
    FileScanLocalState* local_state = static_cast<FileScanLocalState*>(_local_state);
2144
6.21k
    COUNTER_UPDATE(local_state->_scan_bytes, _file_reader_stats->read_bytes);
2145
6.21k
    COUNTER_UPDATE(local_state->_scan_rows, _file_reader_stats->read_rows);
2146
2147
6.21k
    COUNTER_UPDATE(_file_read_bytes_counter, _file_reader_stats->read_bytes);
2148
6.21k
    COUNTER_UPDATE(_file_read_calls_counter, _file_reader_stats->read_calls);
2149
6.21k
    COUNTER_UPDATE(_file_read_time_counter, _file_reader_stats->read_time_ns);
2150
6.21k
    COUNTER_UPDATE(local_state->_condition_cache_hit_counter, _condition_cache_hit_count);
2151
6.22k
    if (_io_ctx) {
2152
6.22k
        COUNTER_UPDATE(local_state->_condition_cache_filtered_rows_counter,
2153
6.22k
                       _io_ctx->condition_cache_filtered_rows);
2154
6.22k
    }
2155
2156
6.21k
    DorisMetrics::instance()->query_scan_bytes->increment(_file_reader_stats->read_bytes);
2157
6.21k
    DorisMetrics::instance()->query_scan_rows->increment(_file_reader_stats->read_rows);
2158
6.21k
}
2159
2160
} // namespace doris