be/src/format_v2/table/hudi_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/table/hudi_reader.h" |
19 | | |
20 | | #include <utility> |
21 | | |
22 | | #include "exprs/vexpr_context.h" |
23 | | #include "format_v2/column_mapper.h" |
24 | | #include "format_v2/jni/hudi_jni_reader.h" |
25 | | #include "format_v2/parquet/parquet_reader.h" |
26 | | #include "format_v2/table/schema_history_util.h" |
27 | | #include "gen_cpp/PlanNodes_types.h" |
28 | | |
29 | | namespace doris::format::hudi { |
30 | | |
31 | 7 | Status HudiReader::prepare_split(const format::SplitReadOptions& options) { |
32 | 7 | { |
33 | | // Derived schema selection is additive to, not nested around, the common base timers. |
34 | 7 | SCOPED_TIMER(_profile.total_timer); |
35 | 7 | SCOPED_TIMER(_profile.prepare_split_timer); |
36 | 7 | _split_schema_id = -1; |
37 | 7 | if (options.current_range.__isset.table_format_params && |
38 | 7 | options.current_range.table_format_params.__isset.hudi_params && |
39 | 7 | options.current_range.table_format_params.hudi_params.__isset.schema_id) { |
40 | 5 | _split_schema_id = options.current_range.table_format_params.hudi_params.schema_id; |
41 | 5 | } |
42 | 7 | } |
43 | 7 | RETURN_IF_ERROR(format::TableReader::prepare_split(options)); |
44 | 7 | SCOPED_TIMER(_profile.total_timer); |
45 | 7 | SCOPED_TIMER(_profile.prepare_split_timer); |
46 | 7 | if (current_split_pruned()) { |
47 | 0 | return Status::OK(); |
48 | 0 | } |
49 | | // This native reader only receives Hudi base-file splits that do not require merging delta |
50 | | // logs. Hudi creates a new versioned base file for updates and compaction instead of modifying |
51 | | // an existing Parquet/ORC base file in place, so missing mtime does not make its page-cache key |
52 | | // ambiguous. Merge-on-read log files remain on the JNI path because they may be appended and |
53 | | // must never be marked immutable here. |
54 | 7 | mark_current_data_file_immutable(); |
55 | 7 | return Status::OK(); |
56 | 7 | } |
57 | | |
58 | 8 | format::TableColumnMappingMode HudiReader::mapping_mode() const { |
59 | 8 | return format::can_map_by_history_schema(_scan_params, _split_schema_id) |
60 | 8 | ? format::TableColumnMappingMode::BY_FIELD_ID |
61 | 8 | : format::TableColumnMappingMode::BY_NAME; |
62 | 8 | } |
63 | | |
64 | 1 | Status HudiReader::create_file_reader(std::unique_ptr<format::FileReader>* reader) { |
65 | 1 | if (_format != format::FileFormat::PARQUET) { |
66 | 0 | return format::TableReader::create_file_reader(reader); |
67 | 0 | } |
68 | 1 | DORIS_CHECK(reader != nullptr); |
69 | 1 | const bool enable_mapping_timestamp_tz = _scan_params != nullptr && |
70 | 1 | _scan_params->__isset.enable_mapping_timestamp_tz && |
71 | 1 | _scan_params->enable_mapping_timestamp_tz; |
72 | 1 | const bool enable_mapping_varbinary = _scan_params != nullptr && |
73 | 1 | _scan_params->__isset.enable_mapping_varbinary && |
74 | 1 | _scan_params->enable_mapping_varbinary; |
75 | 1 | *reader = std::make_unique<format::parquet::ParquetReader>( |
76 | 1 | _system_properties, _current_task->data_file, _io_ctx, _scanner_profile, |
77 | 1 | _global_rowid_context, enable_mapping_timestamp_tz, enable_mapping_varbinary, |
78 | 1 | parquet_int96_time_zone()); |
79 | 1 | return Status::OK(); |
80 | 1 | } |
81 | | |
82 | 2 | std::string HudiReader::parquet_int96_time_zone() const { |
83 | 2 | DORIS_CHECK(_runtime_state != nullptr); |
84 | | // Hudi keeps its legacy session-timezone contract for native base files; Hive's INT96 |
85 | | // compatibility property must not make native and JNI Hudi splits diverge. |
86 | 2 | return _runtime_state->timezone(); |
87 | 2 | } |
88 | | |
89 | 3 | Status HudiReader::annotate_file_schema(std::vector<format::ColumnDefinition>* file_schema) { |
90 | 3 | DORIS_CHECK(file_schema != nullptr); |
91 | 3 | if (mapping_mode() != format::TableColumnMappingMode::BY_FIELD_ID) { |
92 | 2 | return Status::OK(); |
93 | 2 | } |
94 | 1 | return format::annotate_file_schema_from_history(_scan_params, _split_schema_id, file_schema); |
95 | 3 | } |
96 | | |
97 | 3 | Status HudiHybridReader::init(format::TableReadOptions&& options) { |
98 | 3 | return format::TableReader::init(std::move(options)); |
99 | 3 | } |
100 | | |
101 | 4 | Status HudiHybridReader::prepare_split(const format::SplitReadOptions& options) { |
102 | | // A newly selected child initializes against the same scanner profile. Keep hybrid dispatch |
103 | | // outside those shared counters so first-split initialization is counted exactly once. |
104 | 4 | RETURN_IF_ERROR(_ensure_current_split_reader(options)); |
105 | 4 | DORIS_CHECK(_current_split_reader != nullptr); |
106 | 4 | return _current_split_reader->prepare_split(options); |
107 | 4 | } |
108 | | |
109 | 1 | Status HudiHybridReader::refresh_conjuncts(VExprContextSPtrs conjuncts) { |
110 | 1 | RETURN_IF_ERROR(format::TableReader::refresh_conjuncts(std::move(conjuncts))); |
111 | 1 | if (_current_split_reader == nullptr) { |
112 | 0 | return Status::OK(); |
113 | 0 | } |
114 | 1 | VExprContextSPtrs child_conjuncts; |
115 | 1 | RETURN_IF_ERROR(_clone_conjuncts(&child_conjuncts)); |
116 | | // The hybrid wrapper owns no physical reader; forward a clone so the active child, rather than |
117 | | // only the wrapper snapshot, observes late predicates for the remainder of this split. |
118 | 1 | return _current_split_reader->refresh_conjuncts(std::move(child_conjuncts)); |
119 | 1 | } |
120 | | |
121 | 1 | Status HudiHybridReader::get_block(Block* block, bool* eos) { |
122 | 1 | DORIS_CHECK(_current_split_reader != nullptr); |
123 | 1 | return _current_split_reader->get_block(block, eos); |
124 | 1 | } |
125 | | |
126 | 0 | bool HudiHybridReader::current_split_pruned() const { |
127 | 0 | DORIS_CHECK(_current_split_reader != nullptr); |
128 | 0 | return _current_split_reader->current_split_pruned(); |
129 | 0 | } |
130 | | |
131 | 1 | bool HudiHybridReader::current_split_uses_metadata_count() const { |
132 | 1 | DORIS_CHECK(_current_split_reader != nullptr); |
133 | 1 | return _current_split_reader->current_split_uses_metadata_count(); |
134 | 1 | } |
135 | | |
136 | 0 | Status HudiHybridReader::abort_split() { |
137 | 0 | DORIS_CHECK(_current_split_reader != nullptr); |
138 | 0 | return _current_split_reader->abort_split(); |
139 | 0 | } |
140 | | |
141 | 1 | Status HudiHybridReader::close() { |
142 | 1 | Status close_status = Status::OK(); |
143 | 1 | if (_native_reader != nullptr) { |
144 | 1 | close_status = _native_reader->close(); |
145 | 1 | } |
146 | 1 | if (_jni_reader != nullptr) { |
147 | 0 | auto status = _jni_reader->close(); |
148 | 0 | if (!status.ok() && close_status.ok()) { |
149 | 0 | close_status = std::move(status); |
150 | 0 | } |
151 | 0 | } |
152 | 1 | _current_split_reader = nullptr; |
153 | 1 | return close_status; |
154 | 1 | } |
155 | | |
156 | 1 | void HudiHybridReader::set_batch_size(size_t batch_size) { |
157 | 1 | format::TableReader::set_batch_size(batch_size); |
158 | 1 | if (_native_reader != nullptr) { |
159 | 1 | _native_reader->set_batch_size(_batch_size); |
160 | 1 | } |
161 | 1 | if (_jni_reader != nullptr) { |
162 | 1 | _jni_reader->set_batch_size(_batch_size); |
163 | 1 | } |
164 | 1 | } |
165 | | |
166 | 1 | int64_t HudiHybridReader::condition_cache_hit_count() const { |
167 | | // Keep the wrapper count cumulative across native/JNI dispatch so scanner-level delta |
168 | | // accounting neither loses a child hit nor observes a counter reset on a split switch. |
169 | 1 | return (_native_reader == nullptr ? 0 : _native_reader->condition_cache_hit_count()) + |
170 | 1 | (_jni_reader == nullptr ? 0 : _jni_reader->condition_cache_hit_count()); |
171 | 1 | } |
172 | | |
173 | 4 | Status HudiHybridReader::_ensure_current_split_reader(const format::SplitReadOptions& options) { |
174 | 4 | DORIS_CHECK(_scan_params != nullptr); |
175 | 4 | if (_is_jni_split(*_scan_params, options.current_range)) { |
176 | 1 | if (_jni_reader == nullptr) { |
177 | 1 | #ifdef BE_TEST |
178 | 1 | if (_test_jni_reader_factory) { |
179 | 1 | _jni_reader = _test_jni_reader_factory(); |
180 | 1 | } else { |
181 | 0 | _jni_reader = std::make_unique<format::hudi::HudiJniReader>(); |
182 | 0 | } |
183 | | #else |
184 | | _jni_reader = std::make_unique<format::hudi::HudiJniReader>(); |
185 | | #endif |
186 | 1 | RETURN_IF_ERROR(_init_child_reader(_jni_reader.get(), format::FileFormat::JNI)); |
187 | 1 | } |
188 | 1 | _current_split_reader = _jni_reader.get(); |
189 | 3 | } else { |
190 | 3 | format::FileFormat file_format; |
191 | 3 | RETURN_IF_ERROR(_to_file_format(*_scan_params, options.current_range, &file_format)); |
192 | 3 | if (_native_reader == nullptr) { |
193 | 3 | #ifdef BE_TEST |
194 | 3 | if (_test_native_reader_factory) { |
195 | 2 | _native_reader = _test_native_reader_factory(); |
196 | 2 | } else { |
197 | 1 | _native_reader = format::hudi::HudiReader::create_unique(); |
198 | 1 | } |
199 | | #else |
200 | | _native_reader = format::hudi::HudiReader::create_unique(); |
201 | | #endif |
202 | 3 | RETURN_IF_ERROR(_init_child_reader(_native_reader.get(), file_format)); |
203 | 3 | } |
204 | 3 | _current_split_reader = _native_reader.get(); |
205 | 3 | } |
206 | 4 | return Status::OK(); |
207 | 4 | } |
208 | | |
209 | | Status HudiHybridReader::_init_child_reader(format::TableReader* reader, |
210 | 4 | format::FileFormat file_format) { |
211 | 4 | DORIS_CHECK(reader != nullptr); |
212 | 4 | VExprContextSPtrs conjuncts; |
213 | 4 | RETURN_IF_ERROR(_clone_conjuncts(&conjuncts)); |
214 | 4 | RETURN_IF_ERROR(reader->init({ |
215 | 4 | .projected_columns = _projected_columns, |
216 | 4 | .conjuncts = std::move(conjuncts), |
217 | 4 | .format = file_format, |
218 | 4 | .scan_params = _scan_params, |
219 | 4 | .io_ctx = _io_ctx, |
220 | 4 | .runtime_state = _runtime_state, |
221 | 4 | .scanner_profile = _scanner_profile, |
222 | 4 | .push_down_agg_type = _push_down_agg_type, |
223 | 4 | .push_down_count_columns = _push_down_count_columns, |
224 | 4 | .condition_cache_digest = _condition_cache_digest, |
225 | 4 | })); |
226 | | // Zero means no adaptive prediction has been produced yet. Preserve the child's normal |
227 | | // runtime default until FileScannerV2 supplies the first positive prediction. |
228 | 4 | if (_batch_size > 0) { |
229 | 0 | reader->set_batch_size(_batch_size); |
230 | 0 | } |
231 | 4 | return Status::OK(); |
232 | 4 | } |
233 | | |
234 | 5 | Status HudiHybridReader::_clone_conjuncts(VExprContextSPtrs* conjuncts) const { |
235 | 5 | DORIS_CHECK(conjuncts != nullptr); |
236 | 5 | conjuncts->clear(); |
237 | 5 | conjuncts->reserve(_conjuncts.size()); |
238 | 5 | for (const auto& conjunct : _conjuncts) { |
239 | 0 | VExprSPtr root; |
240 | 0 | RETURN_IF_ERROR(format::clone_table_expr_tree(conjunct->root(), &root)); |
241 | 0 | conjuncts->push_back(VExprContext::create_shared(std::move(root))); |
242 | 0 | } |
243 | 5 | return Status::OK(); |
244 | 5 | } |
245 | | |
246 | | TFileFormatType::type HudiHybridReader::_range_format_type(const TFileScanRangeParams& params, |
247 | 7 | const TFileRangeDesc& range) { |
248 | 7 | return range.__isset.format_type ? range.format_type : params.format_type; |
249 | 7 | } |
250 | | |
251 | | bool HudiHybridReader::_is_jni_split(const TFileScanRangeParams& params, |
252 | 4 | const TFileRangeDesc& range) { |
253 | 4 | return _range_format_type(params, range) == TFileFormatType::FORMAT_JNI; |
254 | 4 | } |
255 | | |
256 | | Status HudiHybridReader::_to_file_format(const TFileScanRangeParams& params, |
257 | | const TFileRangeDesc& range, |
258 | 3 | format::FileFormat* file_format) { |
259 | 3 | DORIS_CHECK(file_format != nullptr); |
260 | 3 | const auto format_type = _range_format_type(params, range); |
261 | 3 | switch (format_type) { |
262 | 3 | case TFileFormatType::FORMAT_PARQUET: |
263 | 3 | *file_format = format::FileFormat::PARQUET; |
264 | 3 | return Status::OK(); |
265 | 0 | case TFileFormatType::FORMAT_ORC: |
266 | 0 | *file_format = format::FileFormat::ORC; |
267 | 0 | return Status::OK(); |
268 | 0 | default: |
269 | 0 | return Status::NotSupported("Unsupported native Hudi file format {}", |
270 | 0 | to_string(format_type)); |
271 | 3 | } |
272 | 3 | } |
273 | | |
274 | | } // namespace doris::format::hudi |