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 |