Coverage Report

Created: 2026-07-07 12:34

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