Coverage Report

Created: 2026-07-19 11:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format_v2/native/native_reader.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 "format_v2/native/native_reader.h"
19
20
#include <cstring>
21
#include <utility>
22
23
#include "common/cast_set.h"
24
#include "core/block/block.h"
25
#include "core/data_type/data_type_factory.hpp"
26
#include "core/data_type/data_type_nullable.h"
27
#include "format/native/native_format.h"
28
#include "format_v2/column_mapper.h"
29
#include "format_v2/materialized_reader_util.h"
30
#include "io/file_factory.h"
31
#include "io/fs/tracing_file_reader.h"
32
#include "runtime/file_scan_profile.h"
33
#include "runtime/runtime_state.h"
34
#include "util/slice.h"
35
36
namespace doris::format::native {
37
namespace {
38
39
7
Status parse_native_pblock(const std::string& buffer, const std::string& path, PBlock* pblock) {
40
7
    DORIS_CHECK(pblock != nullptr);
41
7
    if (!pblock->ParseFromArray(buffer.data(), cast_set<int>(buffer.size()))) {
42
1
        return Status::InternalError("Failed to parse native PBlock from file {}", path);
43
1
    }
44
6
    return Status::OK();
45
7
}
46
47
} // namespace
48
49
NativeReader::NativeReader(std::shared_ptr<io::FileSystemProperties>& system_properties,
50
                           std::unique_ptr<io::FileDescription>& file_description,
51
                           std::shared_ptr<io::IOContext> io_ctx, RuntimeProfile* profile)
52
10
        : FileReader(system_properties, file_description, std::move(io_ctx), profile) {}
53
54
10
NativeReader::~NativeReader() {
55
10
    static_cast<void>(close());
56
10
}
57
58
20
void NativeReader::_init_profile() {
59
20
    if (_profile == nullptr) {
60
0
        return;
61
0
    }
62
20
    file_scan_profile::ensure_hierarchy(_profile);
63
20
    static const char* native_profile = "NativeReader";
64
20
    _total_time =
65
20
            ADD_CHILD_TIMER_WITH_LEVEL(_profile, native_profile, file_scan_profile::FILE_READER, 1);
66
20
    _read_block_time =
67
20
            ADD_CHILD_TIMER_WITH_LEVEL(_profile, "NativeReadBlockTime", native_profile, 1);
68
20
    _deserialize_time =
69
20
            ADD_CHILD_TIMER_WITH_LEVEL(_profile, "NativeDeserializeTime", native_profile, 1);
70
20
    _materialize_time =
71
20
            ADD_CHILD_TIMER_WITH_LEVEL(_profile, "NativeMaterializeTime", native_profile, 1);
72
20
    _filter_time = ADD_CHILD_TIMER_WITH_LEVEL(_profile, "NativeFilterTime", native_profile, 1);
73
20
}
74
75
10
Status NativeReader::init(RuntimeState* state) {
76
10
    _init_profile();
77
10
    SCOPED_TIMER(_total_time);
78
10
    _runtime_state = state;
79
10
    if (_file_description == nullptr) {
80
0
        return Status::InvalidArgument("Native v2 reader requires file description");
81
0
    }
82
10
    RETURN_IF_ERROR(FileReader::init(state));
83
10
    RETURN_IF_ERROR(_validate_and_consume_header());
84
7
    return Status::OK();
85
10
}
86
87
7
Status NativeReader::get_schema(std::vector<ColumnDefinition>* file_schema) const {
88
7
    SCOPED_TIMER(_total_time);
89
7
    if (file_schema == nullptr) {
90
0
        return Status::InvalidArgument("Native v2 file_schema is null");
91
0
    }
92
7
    RETURN_IF_ERROR(_ensure_schema_loaded());
93
3
    *file_schema = _file_schema;
94
3
    return Status::OK();
95
7
}
96
97
std::unique_ptr<TableColumnMapper> NativeReader::create_column_mapper(
98
0
        TableColumnMapperOptions options) const {
99
0
    return std::make_unique<MaterializedColumnMapper>(std::move(options));
100
0
}
101
102
3
Status NativeReader::open(std::shared_ptr<FileScanRequest> request) {
103
3
    SCOPED_TIMER(_total_time);
104
3
    RETURN_IF_ERROR(FileReader::open(std::move(request)));
105
3
    DORIS_CHECK(_request != nullptr);
106
3
    _first_block_consumed = false;
107
3
    _reader_eof = false;
108
3
    _eof = false;
109
3
    return Status::OK();
110
3
}
111
112
4
Status NativeReader::get_block(Block* file_block, size_t* rows, bool* eof) {
113
4
    SCOPED_TIMER(_total_time);
114
4
    DORIS_CHECK(file_block != nullptr);
115
4
    DORIS_CHECK(rows != nullptr);
116
4
    DORIS_CHECK(eof != nullptr);
117
4
    if (_request == nullptr) {
118
0
        return Status::InternalError("Native v2 reader is not open");
119
0
    }
120
121
4
    *rows = 0;
122
4
    *eof = false;
123
4
    if (_reader_eof) {
124
1
        *eof = true;
125
1
        _eof = true;
126
1
        return Status::OK();
127
1
    }
128
129
3
    std::string buffer;
130
3
    bool local_eof = false;
131
3
    if (_first_block_loaded && !_first_block_consumed) {
132
3
        buffer = _first_block_buffer;
133
3
    } else {
134
0
        SCOPED_TIMER(_read_block_time);
135
0
        RETURN_IF_ERROR(_read_next_pblock(&buffer, &local_eof));
136
0
    }
137
138
3
    if (local_eof && buffer.empty()) {
139
0
        _reader_eof = true;
140
0
        *eof = true;
141
0
        _eof = true;
142
0
        return Status::OK();
143
0
    }
144
3
    if (buffer.empty()) {
145
0
        return Status::InternalError("read empty native block from file {}",
146
0
                                     _file_description->path);
147
0
    }
148
149
3
    PBlock pblock;
150
3
    RETURN_IF_ERROR(parse_native_pblock(buffer, _file_description->path, &pblock));
151
3
    if (!_schema_inited) {
152
0
        RETURN_IF_ERROR(_init_schema_from_pblock(pblock));
153
0
    }
154
155
3
    Block source_block;
156
3
    size_t uncompressed_bytes = 0;
157
3
    int64_t decompress_time = 0;
158
3
    {
159
3
        SCOPED_TIMER(_deserialize_time);
160
3
        RETURN_IF_ERROR(source_block.deserialize(pblock, &uncompressed_bytes, &decompress_time));
161
3
    }
162
3
    {
163
3
        SCOPED_TIMER(_materialize_time);
164
3
        RETURN_IF_ERROR(_materialize_requested_columns(source_block, file_block));
165
3
    }
166
2
    *rows = file_block->rows();
167
2
    _record_scan_rows(cast_set<int64_t>(*rows));
168
2
    {
169
2
        SCOPED_TIMER(_filter_time);
170
2
        RETURN_IF_ERROR(_apply_filters(file_block, rows));
171
2
    }
172
173
2
    if (_first_block_loaded && !_first_block_consumed) {
174
2
        _first_block_consumed = true;
175
2
    }
176
2
    if (_current_offset >= _file_size) {
177
2
        _reader_eof = true;
178
2
    }
179
2
    *eof = _reader_eof && *rows == 0;
180
2
    _eof = *eof;
181
2
    return Status::OK();
182
2
}
183
184
12
Status NativeReader::close() {
185
12
    SCOPED_TIMER(_total_time);
186
12
    _file_reader.reset();
187
12
    _tracing_file_reader.reset();
188
12
    _request.reset();
189
12
    _reader_eof = true;
190
12
    _eof = true;
191
12
    return Status::OK();
192
12
}
193
194
10
Status NativeReader::_validate_and_consume_header() {
195
10
    DORIS_CHECK(_tracing_file_reader != nullptr);
196
10
    _file_size = _tracing_file_reader->size();
197
10
    _current_offset = 0;
198
10
    _reader_eof = (_file_size == 0);
199
200
10
    static constexpr size_t HEADER_SIZE = sizeof(DORIS_NATIVE_MAGIC) + sizeof(uint32_t);
201
10
    if (_reader_eof || _file_size < cast_set<int64_t>(HEADER_SIZE)) {
202
1
        return Status::InternalError(
203
1
                "invalid Doris Native file {}, file size {} is smaller than header size {}",
204
1
                _file_description->path, _file_size, HEADER_SIZE);
205
1
    }
206
207
9
    char header[HEADER_SIZE];
208
9
    Slice header_slice(header, sizeof(header));
209
9
    size_t bytes_read = 0;
210
9
    RETURN_IF_ERROR(_tracing_file_reader->read_at(0, header_slice, &bytes_read, _io_ctx.get()));
211
9
    if (bytes_read != sizeof(header)) {
212
0
        return Status::InternalError(
213
0
                "failed to read Doris Native header from file {}, expect {} bytes, got {} bytes",
214
0
                _file_description->path, sizeof(header), bytes_read);
215
0
    }
216
9
    if (std::memcmp(header, DORIS_NATIVE_MAGIC, sizeof(DORIS_NATIVE_MAGIC)) != 0) {
217
1
        return Status::InternalError("invalid Doris Native magic header in file {}",
218
1
                                     _file_description->path);
219
1
    }
220
221
8
    uint32_t version = 0;
222
8
    std::memcpy(&version, header + sizeof(DORIS_NATIVE_MAGIC), sizeof(uint32_t));
223
8
    if (version != DORIS_NATIVE_FORMAT_VERSION) {
224
1
        return Status::InternalError(
225
1
                "unsupported Doris Native format version {} in file {}, expect {}", version,
226
1
                _file_description->path, DORIS_NATIVE_FORMAT_VERSION);
227
1
    }
228
229
7
    _current_offset = sizeof(header);
230
7
    _reader_eof = (_file_size == _current_offset);
231
7
    return Status::OK();
232
8
}
233
234
7
Status NativeReader::_ensure_schema_loaded() const {
235
7
    if (_schema_inited) {
236
0
        return Status::OK();
237
0
    }
238
7
    if (!_first_block_loaded) {
239
7
        bool local_eof = false;
240
7
        RETURN_IF_ERROR(_read_next_pblock(&_first_block_buffer, &local_eof));
241
6
        if (local_eof && _first_block_buffer.empty()) {
242
2
            return Status::EndOfFile("empty native file {}", _file_description->path);
243
2
        }
244
4
        if (_first_block_buffer.empty()) {
245
0
            return Status::InternalError("first native block is empty {}", _file_description->path);
246
0
        }
247
4
        _first_block_loaded = true;
248
4
    }
249
250
4
    PBlock pblock;
251
4
    RETURN_IF_ERROR(parse_native_pblock(_first_block_buffer, _file_description->path, &pblock));
252
3
    RETURN_IF_ERROR(_init_schema_from_pblock(pblock));
253
3
    return Status::OK();
254
3
}
255
256
7
Status NativeReader::_read_next_pblock(std::string* buffer, bool* eof) const {
257
7
    DORIS_CHECK(buffer != nullptr);
258
7
    DORIS_CHECK(eof != nullptr);
259
7
    DORIS_CHECK(_tracing_file_reader != nullptr);
260
7
    buffer->clear();
261
7
    *eof = false;
262
263
7
    if (_current_offset >= _file_size) {
264
1
        *eof = true;
265
1
        return Status::OK();
266
1
    }
267
268
6
    uint64_t block_len = 0;
269
6
    Slice len_slice(reinterpret_cast<char*>(&block_len), sizeof(block_len));
270
6
    size_t bytes_read = 0;
271
6
    RETURN_IF_ERROR(
272
6
            _tracing_file_reader->read_at(_current_offset, len_slice, &bytes_read, _io_ctx.get()));
273
6
    if (bytes_read == 0) {
274
0
        *eof = true;
275
0
        return Status::OK();
276
0
    }
277
6
    if (bytes_read != sizeof(block_len)) {
278
0
        return Status::InternalError(
279
0
                "Failed to read native block length from file {}, expect {}, actual {}",
280
0
                _file_description->path, sizeof(block_len), bytes_read);
281
0
    }
282
6
    _current_offset += sizeof(block_len);
283
6
    if (block_len == 0) {
284
1
        *eof = (_current_offset >= _file_size);
285
1
        return Status::OK();
286
1
    }
287
288
5
    buffer->assign(block_len, '\0');
289
5
    Slice data_slice(buffer->data(), block_len);
290
5
    bytes_read = 0;
291
5
    RETURN_IF_ERROR(
292
5
            _tracing_file_reader->read_at(_current_offset, data_slice, &bytes_read, _io_ctx.get()));
293
5
    if (bytes_read != block_len) {
294
1
        return Status::InternalError(
295
1
                "Failed to read native block body from file {}, expect {}, actual {}",
296
1
                _file_description->path, block_len, bytes_read);
297
1
    }
298
4
    _current_offset += block_len;
299
4
    *eof = (_current_offset >= _file_size);
300
4
    return Status::OK();
301
5
}
302
303
3
Status NativeReader::_init_schema_from_pblock(const PBlock& pblock) const {
304
3
    _file_schema.clear();
305
3
    _file_schema.reserve(pblock.column_metas_size());
306
9
    for (int idx = 0; idx < pblock.column_metas_size(); ++idx) {
307
6
        const auto& meta = pblock.column_metas(idx);
308
6
        ColumnDefinition field;
309
6
        field.identifier = Field::create_field<TYPE_STRING>(meta.name());
310
6
        field.local_id = idx;
311
6
        field.name = meta.name();
312
6
        field.type = make_nullable(DataTypeFactory::instance().create_data_type(meta));
313
6
        _file_schema.push_back(std::move(field));
314
6
    }
315
3
    _schema_inited = true;
316
3
    return Status::OK();
317
3
}
318
319
Status NativeReader::_materialize_requested_columns(const Block& source_block,
320
3
                                                    Block* file_block) const {
321
3
    DORIS_CHECK(file_block != nullptr);
322
3
    DORIS_CHECK(_request != nullptr);
323
5
    for (const auto& [file_column_id, block_position] : _request->local_positions) {
324
5
        const auto source_idx = file_column_id.value();
325
5
        if (source_idx < 0 || cast_set<size_t>(source_idx) >= source_block.columns()) {
326
1
            return Status::InternalError("native file {} does not contain local column id {}",
327
1
                                         _file_description->path, source_idx);
328
1
        }
329
4
        if (block_position.value() >= file_block->columns()) {
330
0
            return Status::InternalError("native v2 request has invalid block position {}",
331
0
                                         block_position.value());
332
0
        }
333
4
        const auto& target = file_block->get_by_position(block_position.value());
334
4
        auto column = source_block.get_by_position(source_idx).column;
335
4
        column = make_column_nullable_if_needed(std::move(column), target.type);
336
4
        file_block->replace_by_position(block_position.value(), IColumn::mutate(std::move(column)));
337
4
    }
338
2
    return Status::OK();
339
3
}
340
341
2
Status NativeReader::_apply_filters(Block* file_block, size_t* rows) const {
342
2
    return apply_materialized_reader_filters(_request.get(), _io_ctx.get(), file_block, rows);
343
2
}
344
345
} // namespace doris::format::native