be/src/format_v2/parquet/parquet_scan.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 | | // http://www.apache.org/licenses/LICENSE-2.0 |
9 | | // Unless required by applicable law or agreed to in writing, |
10 | | // software distributed under the License is distributed on an |
11 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
12 | | // KIND, either express or implied. See the License for the |
13 | | // specific language governing permissions and limitations |
14 | | // under the License. |
15 | | |
16 | | #include "format_v2/parquet/parquet_scan.h" |
17 | | |
18 | | #include <algorithm> |
19 | | #include <limits> |
20 | | #include <memory> |
21 | | #include <ranges> |
22 | | #include <unordered_set> |
23 | | #include <utility> |
24 | | |
25 | | #include "common/exception.h" |
26 | | #include "common/status.h" |
27 | | #include "core/assert_cast.h" |
28 | | #include "core/block/block.h" |
29 | | #include "core/column/column_vector.h" |
30 | | #include "exprs/vexpr_context.h" |
31 | | #include "format_v2/parquet/parquet_column_schema.h" |
32 | | #include "format_v2/parquet/parquet_file_context.h" |
33 | | #include "format_v2/parquet/parquet_statistics.h" |
34 | | |
35 | | namespace doris::format::parquet { |
36 | | |
37 | | namespace { |
38 | | |
39 | 200 | int64_t column_start_offset(const ::parquet::ColumnChunkMetaData& column_metadata) { |
40 | 200 | return column_metadata.has_dictionary_page() |
41 | 200 | ? cast_set<int64_t>(column_metadata.dictionary_page_offset()) |
42 | 200 | : cast_set<int64_t>(column_metadata.data_page_offset()); |
43 | 200 | } |
44 | | |
45 | | void collect_all_leaf_column_ids(const ParquetColumnSchema& column_schema, |
46 | 148 | std::unordered_set<int>* leaf_column_ids) { |
47 | 148 | DORIS_CHECK(leaf_column_ids != nullptr); |
48 | 148 | if (column_schema.kind == ParquetColumnSchemaKind::PRIMITIVE) { |
49 | 140 | if (column_schema.leaf_column_id >= 0) { |
50 | 140 | leaf_column_ids->insert(column_schema.leaf_column_id); |
51 | 140 | } |
52 | 140 | return; |
53 | 140 | } |
54 | 12 | for (const auto& child : column_schema.children) { |
55 | 12 | DORIS_CHECK(child != nullptr); |
56 | 12 | collect_all_leaf_column_ids(*child, leaf_column_ids); |
57 | 12 | } |
58 | 8 | } |
59 | | |
60 | | void collect_projected_leaf_column_ids(const ParquetColumnSchema& column_schema, |
61 | | const format::LocalColumnIndex& projection, |
62 | 142 | std::unordered_set<int>* leaf_column_ids) { |
63 | 142 | DORIS_CHECK(leaf_column_ids != nullptr); |
64 | 142 | if (projection.project_all_children || projection.children.empty()) { |
65 | 136 | collect_all_leaf_column_ids(column_schema, leaf_column_ids); |
66 | 136 | return; |
67 | 136 | } |
68 | 7 | for (const auto& child_projection : projection.children) { |
69 | 7 | const auto child_it = |
70 | 11 | std::ranges::find_if(column_schema.children, [&](const auto& child_schema) { |
71 | 11 | return child_schema->local_id == child_projection.local_id(); |
72 | 11 | }); |
73 | 7 | DORIS_CHECK(child_it != column_schema.children.end()); |
74 | 7 | collect_projected_leaf_column_ids(**child_it, child_projection, leaf_column_ids); |
75 | 7 | } |
76 | 6 | } |
77 | | |
78 | | bool is_row_group_outside_range(const ::parquet::FileMetaData& metadata, |
79 | 204 | const ParquetScanRange& scan_range, int row_group_idx) { |
80 | 204 | if (scan_range.size < 0) { |
81 | 174 | return false; |
82 | 174 | } |
83 | 30 | const int64_t range_start_offset = scan_range.start_offset; |
84 | 30 | const int64_t range_end_offset = range_start_offset + scan_range.size; |
85 | 30 | DORIS_CHECK(range_start_offset >= 0); |
86 | 30 | DORIS_CHECK(range_end_offset >= range_start_offset); |
87 | 30 | if (range_start_offset == 0 && |
88 | 30 | (scan_range.file_size < 0 || range_end_offset >= scan_range.file_size)) { |
89 | 0 | return false; |
90 | 0 | } |
91 | | |
92 | 30 | auto row_group_metadata = metadata.RowGroup(row_group_idx); |
93 | 30 | DORIS_CHECK(row_group_metadata != nullptr); |
94 | 30 | DORIS_CHECK(row_group_metadata->num_columns() > 0); |
95 | 30 | const auto first_column = row_group_metadata->ColumnChunk(0); |
96 | 30 | const auto last_column = row_group_metadata->ColumnChunk(row_group_metadata->num_columns() - 1); |
97 | 30 | DORIS_CHECK(first_column != nullptr); |
98 | 30 | DORIS_CHECK(last_column != nullptr); |
99 | 30 | const int64_t row_group_start_offset = column_start_offset(*first_column); |
100 | 30 | const int64_t row_group_end_offset = |
101 | 30 | column_start_offset(*last_column) + last_column->total_compressed_size(); |
102 | 30 | const int64_t row_group_mid_offset = |
103 | 30 | row_group_start_offset + (row_group_end_offset - row_group_start_offset) / 2; |
104 | 30 | return row_group_mid_offset < range_start_offset || row_group_mid_offset >= range_end_offset; |
105 | 30 | } |
106 | | |
107 | 98 | std::vector<format::LocalColumnIndex> request_scan_columns(const format::FileScanRequest& request) { |
108 | 98 | std::vector<format::LocalColumnIndex> scan_columns; |
109 | 98 | scan_columns.reserve(request.predicate_columns.size() + request.non_predicate_columns.size()); |
110 | 98 | scan_columns.insert(scan_columns.end(), request.predicate_columns.begin(), |
111 | 98 | request.predicate_columns.end()); |
112 | 98 | scan_columns.insert(scan_columns.end(), request.non_predicate_columns.begin(), |
113 | 98 | request.non_predicate_columns.end()); |
114 | 98 | return scan_columns; |
115 | 98 | } |
116 | | |
117 | | std::vector<ParquetPageCacheRange> build_row_group_prefetch_ranges( |
118 | | const ::parquet::FileMetaData& metadata, |
119 | | const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema, |
120 | 98 | const std::vector<format::LocalColumnIndex>& scan_columns, int row_group_idx) { |
121 | 98 | std::unordered_set<int> leaf_column_ids; |
122 | 162 | for (const auto& projection : scan_columns) { |
123 | 162 | const auto local_id = projection.local_id(); |
124 | 162 | if (local_id == format::ROW_POSITION_COLUMN_ID || |
125 | 162 | local_id == format::GLOBAL_ROWID_COLUMN_ID) { |
126 | 27 | continue; |
127 | 27 | } |
128 | 135 | DORIS_CHECK(local_id >= 0 && local_id < static_cast<int32_t>(file_schema.size())); |
129 | 135 | DORIS_CHECK(file_schema[local_id] != nullptr); |
130 | | // Prefetch and merge-reader ranges must be physical leaf chunks, not Doris logical slots. |
131 | | // Example: for a struct column s<a:int,b:string>, projecting only s.a should include only |
132 | | // the Parquet leaf chunk of a. Projecting the whole struct includes both a and b. |
133 | 135 | collect_projected_leaf_column_ids(*file_schema[local_id], projection, &leaf_column_ids); |
134 | 135 | } |
135 | | |
136 | 98 | auto row_group_metadata = metadata.RowGroup(row_group_idx); |
137 | 98 | DORIS_CHECK(row_group_metadata != nullptr); |
138 | 98 | std::vector<int> ordered_leaf_column_ids(leaf_column_ids.begin(), leaf_column_ids.end()); |
139 | 98 | std::ranges::sort(ordered_leaf_column_ids); |
140 | | |
141 | 98 | std::vector<ParquetPageCacheRange> ranges; |
142 | 98 | ranges.reserve(ordered_leaf_column_ids.size()); |
143 | 140 | for (const auto leaf_column_id : ordered_leaf_column_ids) { |
144 | 140 | DORIS_CHECK(leaf_column_id >= 0 && leaf_column_id < row_group_metadata->num_columns()); |
145 | 140 | auto column_metadata = row_group_metadata->ColumnChunk(leaf_column_id); |
146 | 140 | DORIS_CHECK(column_metadata != nullptr); |
147 | 140 | const int64_t offset = column_start_offset(*column_metadata); |
148 | 140 | const int64_t size = column_metadata->total_compressed_size(); |
149 | 140 | DORIS_CHECK(offset >= 0); |
150 | 140 | if (size > 0) { |
151 | 140 | ranges.push_back(ParquetPageCacheRange {.offset = offset, .size = size}); |
152 | 140 | } |
153 | 140 | } |
154 | 98 | return ranges; |
155 | 98 | } |
156 | | |
157 | | Status select_row_groups_by_scan_range(const ::parquet::FileMetaData& metadata, |
158 | | const ParquetScanRange& scan_range, |
159 | | std::vector<int64_t>* row_group_first_rows, |
160 | 118 | std::vector<int>* selected_row_groups) { |
161 | 118 | DORIS_CHECK(row_group_first_rows != nullptr); |
162 | 118 | DORIS_CHECK(selected_row_groups != nullptr); |
163 | 118 | row_group_first_rows->assign(metadata.num_row_groups(), 0); |
164 | 118 | selected_row_groups->clear(); |
165 | 118 | selected_row_groups->reserve(metadata.num_row_groups()); |
166 | 118 | int64_t next_row_group_first_row = 0; |
167 | 322 | for (int row_group_idx = 0; row_group_idx < metadata.num_row_groups(); ++row_group_idx) { |
168 | 204 | (*row_group_first_rows)[row_group_idx] = next_row_group_first_row; |
169 | 204 | auto row_group_metadata = metadata.RowGroup(row_group_idx); |
170 | 204 | DORIS_CHECK(row_group_metadata != nullptr); |
171 | 204 | const int64_t row_group_rows = row_group_metadata->num_rows(); |
172 | 204 | if (row_group_rows < 0) { |
173 | 0 | return Status::Corruption("Invalid negative row count in parquet row group {}", |
174 | 0 | row_group_idx); |
175 | 0 | } |
176 | 204 | next_row_group_first_row += row_group_rows; |
177 | 204 | if (!is_row_group_outside_range(metadata, scan_range, row_group_idx)) { |
178 | 183 | selected_row_groups->push_back(row_group_idx); |
179 | 183 | } |
180 | 204 | } |
181 | 118 | return Status::OK(); |
182 | 118 | } |
183 | | |
184 | | Status build_row_group_read_plans( |
185 | | const ::parquet::FileMetaData& metadata, ::parquet::ParquetFileReader* file_reader, |
186 | | const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema, |
187 | | const format::FileScanRequest& request, const std::vector<int>& selected_row_groups, |
188 | | const std::vector<int64_t>& row_group_first_rows, RowGroupScanPlan* plan, |
189 | 118 | const cctz::time_zone* timezone, const RuntimeState* runtime_state) { |
190 | 118 | DORIS_CHECK(plan != nullptr); |
191 | 118 | plan->row_groups.reserve(selected_row_groups.size()); |
192 | 147 | for (const auto row_group_idx : selected_row_groups) { |
193 | 147 | DORIS_CHECK(row_group_idx >= 0); |
194 | 147 | DORIS_CHECK(static_cast<size_t>(row_group_idx) < row_group_first_rows.size()); |
195 | 147 | auto row_group_metadata = metadata.RowGroup(row_group_idx); |
196 | 147 | DORIS_CHECK(row_group_metadata != nullptr); |
197 | 147 | const int64_t row_group_rows = row_group_metadata->num_rows(); |
198 | 147 | if (row_group_rows == 0) { |
199 | 0 | continue; |
200 | 0 | } |
201 | | |
202 | 147 | RowGroupReadPlan row_group_plan; |
203 | 147 | row_group_plan.row_group_id = row_group_idx; |
204 | 147 | row_group_plan.first_file_row = row_group_first_rows[row_group_idx]; |
205 | 147 | row_group_plan.row_group_rows = row_group_rows; |
206 | 147 | RETURN_IF_ERROR(select_row_group_ranges_by_page_index( |
207 | 147 | file_reader, file_schema, request, row_group_idx, row_group_rows, |
208 | 147 | &row_group_plan.selected_ranges, &row_group_plan.page_skip_plans, |
209 | 147 | &plan->pruning_stats, timezone, runtime_state)); |
210 | 147 | if (row_group_plan.selected_ranges.empty()) { |
211 | 1 | continue; |
212 | 1 | } |
213 | 146 | plan->pruning_stats.selected_row_ranges += row_group_plan.selected_ranges.size(); |
214 | 146 | plan->row_groups.push_back(std::move(row_group_plan)); |
215 | 146 | } |
216 | 118 | return Status::OK(); |
217 | 118 | } |
218 | | |
219 | | } // namespace |
220 | | |
221 | | Status plan_parquet_row_groups(const ::parquet::FileMetaData& metadata, |
222 | | ::parquet::ParquetFileReader* file_reader, |
223 | | const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema, |
224 | | const format::FileScanRequest& request, |
225 | | const ParquetScanRange& scan_range, bool enable_bloom_filter, |
226 | | RowGroupScanPlan* plan, const cctz::time_zone* timezone, |
227 | 118 | const RuntimeState* runtime_state) { |
228 | 118 | DORIS_CHECK(plan != nullptr); |
229 | 118 | plan->row_groups.clear(); |
230 | 118 | plan->pruning_stats = ParquetPruningStats {}; |
231 | | |
232 | | // Row-group planning flow: |
233 | | // |
234 | | // parquet footer row groups |
235 | | // | |
236 | | // v |
237 | | // split byte-range candidates |
238 | | // | |
239 | | // v |
240 | | // row-group metadata pruning |
241 | | // statistics/ZoneMap -> dictionary -> bloom filter |
242 | | // | |
243 | | // v |
244 | | // page-index pruning per selected row group |
245 | | // | |
246 | | // v |
247 | | // RowGroupReadPlan with selected row ranges |
248 | | // |
249 | | // Metadata pruning removes whole row groups before readers are opened. Page index pruning runs |
250 | | // only for remaining row groups and produces selected row ranges; the scan scheduler later skips |
251 | | // gaps between those ranges, while row-level VExpr conjuncts still run on loaded batches for |
252 | | // correctness. |
253 | 118 | std::vector<int64_t> row_group_first_rows; |
254 | 118 | std::vector<int> scan_range_selected_row_groups; |
255 | 118 | RETURN_IF_ERROR(select_row_groups_by_scan_range(metadata, scan_range, &row_group_first_rows, |
256 | 118 | &scan_range_selected_row_groups)); |
257 | | |
258 | 118 | std::vector<int> metadata_selected_row_groups; |
259 | 118 | RETURN_IF_ERROR(select_row_groups_by_metadata( |
260 | 118 | metadata, file_reader, file_schema, request, &scan_range_selected_row_groups, |
261 | 118 | &metadata_selected_row_groups, enable_bloom_filter, &plan->pruning_stats, timezone, |
262 | 118 | runtime_state)); |
263 | | |
264 | 118 | RETURN_IF_ERROR(build_row_group_read_plans(metadata, file_reader, file_schema, request, |
265 | 118 | metadata_selected_row_groups, row_group_first_rows, |
266 | 118 | plan, timezone, runtime_state)); |
267 | 118 | plan->pruning_stats.selected_row_groups = plan->row_groups.size(); |
268 | 118 | return Status::OK(); |
269 | 118 | } |
270 | | |
271 | | namespace { |
272 | | |
273 | | uint16_t apply_filter_to_selection(const IColumn::Filter& filter, SelectionVector* selection, |
274 | 48 | uint16_t selected_rows) { |
275 | 48 | uint16_t new_selected_rows = 0; |
276 | 6.44k | for (uint16_t selection_idx = 0; selection_idx < selected_rows; ++selection_idx) { |
277 | 6.39k | const auto row_idx = selection->get_index(selection_idx); |
278 | 6.39k | if (filter[row_idx] != 0) { |
279 | 4.30k | selection->set_index(new_selected_rows++, static_cast<SelectionVector::Index>(row_idx)); |
280 | 4.30k | } |
281 | 6.39k | } |
282 | 48 | return new_selected_rows; |
283 | 48 | } |
284 | | |
285 | | Status execute_filter_conjuncts(const format::FileScanRequest& request, int64_t batch_rows, |
286 | | Block* file_block, SelectionVector* selection, |
287 | 48 | uint16_t* selected_rows) { |
288 | 48 | for (const auto& conjunct : request.conjuncts) { |
289 | 36 | if (*selected_rows == 0) { |
290 | 0 | break; |
291 | 0 | } |
292 | 36 | DORIS_CHECK(conjunct != nullptr); |
293 | 36 | IColumn::Filter filter(static_cast<size_t>(batch_rows), 1); |
294 | 36 | bool can_filter_all = false; |
295 | 36 | RETURN_IF_ERROR(conjunct->execute_filter(file_block, filter.data(), |
296 | 36 | static_cast<size_t>(batch_rows), false, |
297 | 36 | &can_filter_all)); |
298 | 36 | *selected_rows = |
299 | 36 | can_filter_all ? 0 : apply_filter_to_selection(filter, selection, *selected_rows); |
300 | 36 | } |
301 | 48 | return Status::OK(); |
302 | 48 | } |
303 | | |
304 | | Status execute_delete_conjuncts(const format::FileScanRequest& request, int64_t batch_rows, |
305 | | Block* file_block, SelectionVector* selection, |
306 | 47 | uint16_t* selected_rows) { |
307 | 47 | for (const auto& delete_conjunct : request.delete_conjuncts) { |
308 | 13 | if (*selected_rows == 0) { |
309 | 0 | break; |
310 | 0 | } |
311 | 13 | DORIS_CHECK(delete_conjunct != nullptr); |
312 | 13 | int result_column_id = -1; |
313 | 13 | RETURN_IF_ERROR(delete_conjunct->root()->execute(delete_conjunct.get(), file_block, |
314 | 13 | &result_column_id)); |
315 | 13 | DORIS_CHECK(result_column_id >= 0 && |
316 | 13 | result_column_id < static_cast<int>(file_block->columns())); |
317 | 13 | const auto& delete_filter = assert_cast<const ColumnUInt8&>( |
318 | 13 | *file_block->get_by_position(result_column_id).column) |
319 | 13 | .get_data(); |
320 | 13 | DORIS_CHECK(delete_filter.size() == static_cast<size_t>(batch_rows)); |
321 | 13 | IColumn::Filter keep_filter(static_cast<size_t>(batch_rows), 1); |
322 | 13 | bool has_kept_row = false; |
323 | 64 | for (size_t row = 0; row < static_cast<size_t>(batch_rows); ++row) { |
324 | 51 | keep_filter[row] = !delete_filter[row]; |
325 | 51 | has_kept_row |= keep_filter[row] != 0; |
326 | 51 | } |
327 | 13 | file_block->erase(result_column_id); |
328 | 13 | *selected_rows = |
329 | 13 | !has_kept_row ? 0 |
330 | 13 | : apply_filter_to_selection(keep_filter, selection, *selected_rows); |
331 | 13 | } |
332 | 47 | return Status::OK(); |
333 | 47 | } |
334 | | |
335 | | } // namespace |
336 | | |
337 | | IColumn::Filter selection_to_filter(const SelectionVector& selection, uint16_t selected_rows, |
338 | 28 | int64_t batch_rows) { |
339 | 28 | IColumn::Filter filter(static_cast<size_t>(batch_rows), 0); |
340 | 2.13k | for (uint16_t selection_idx = 0; selection_idx < selected_rows; ++selection_idx) { |
341 | 2.10k | filter[selection.get_index(selection_idx)] = 1; |
342 | 2.10k | } |
343 | 28 | return filter; |
344 | 28 | } |
345 | | |
346 | | Status execute_batch_filters(const format::FileScanRequest& request, int64_t batch_rows, |
347 | | Block* file_block, SelectionVector* selection, uint16_t* selected_rows, |
348 | 91 | int64_t* conjunct_filtered_rows) { |
349 | 91 | if (request.conjuncts.empty() && request.delete_conjuncts.empty()) { |
350 | 43 | return Status::OK(); |
351 | 43 | } |
352 | 48 | const auto selected_rows_before_conjunct = *selected_rows; |
353 | 48 | RETURN_IF_ERROR( |
354 | 48 | execute_filter_conjuncts(request, batch_rows, file_block, selection, selected_rows)); |
355 | 48 | if (conjunct_filtered_rows != nullptr) { |
356 | 48 | *conjunct_filtered_rows += static_cast<int64_t>(selected_rows_before_conjunct) - |
357 | 48 | static_cast<int64_t>(*selected_rows); |
358 | 48 | } |
359 | 48 | if (*selected_rows == 0) { |
360 | 1 | return Status::OK(); |
361 | 1 | } |
362 | 47 | return execute_delete_conjuncts(request, batch_rows, file_block, selection, selected_rows); |
363 | 48 | } |
364 | | |
365 | | namespace { |
366 | 2 | int64_t count_range_rows(const std::vector<RowRange>& ranges) { |
367 | 2 | int64_t rows = 0; |
368 | 2 | for (const auto& range : ranges) { |
369 | 2 | rows += range.length; |
370 | 2 | } |
371 | 2 | return rows; |
372 | 2 | } |
373 | | |
374 | | void append_intersection(const RowRange& left, const RowRange& right, |
375 | 1 | std::vector<RowRange>* result) { |
376 | 1 | const int64_t start = std::max(left.start, right.start); |
377 | 1 | const int64_t end = std::min(left.start + left.length, right.start + right.length); |
378 | 1 | if (start < end) { |
379 | 1 | result->push_back(RowRange {.start = start, .length = end - start}); |
380 | 1 | } |
381 | 1 | } |
382 | | |
383 | | std::vector<RowRange> filter_ranges_by_condition_cache(const std::vector<RowRange>& ranges, |
384 | | const std::vector<bool>& cache, |
385 | | int64_t row_group_first_row, |
386 | 1 | int64_t base_granule) { |
387 | 1 | std::vector<RowRange> result; |
388 | 1 | if (cache.empty()) { |
389 | 0 | return ranges; |
390 | 0 | } |
391 | | |
392 | | // Cache coordinates are file-global granules; RowRange coordinates are row-group-relative. |
393 | | // Walk every selected range in order and split it by granule. Granules covered by the bitmap |
394 | | // are kept only when the bit is true. Granules outside the bitmap are kept conservatively, so |
395 | | // an undersized or old-format cache entry cannot skip valid rows. |
396 | 1 | for (const auto& range : ranges) { |
397 | 1 | const int64_t global_start = row_group_first_row + range.start; |
398 | 1 | const int64_t global_end = global_start + range.length; |
399 | 1 | for (int64_t granule = global_start / ConditionCacheContext::GRANULE_SIZE; |
400 | 3 | granule <= (global_end - 1) / ConditionCacheContext::GRANULE_SIZE; ++granule) { |
401 | 2 | const int64_t cache_idx = granule - base_granule; |
402 | 2 | const bool keep = cache_idx < 0 || static_cast<size_t>(cache_idx) >= cache.size() || |
403 | 2 | cache[static_cast<size_t>(cache_idx)]; |
404 | 2 | if (!keep) { |
405 | 1 | continue; |
406 | 1 | } |
407 | 1 | const int64_t granule_start = granule * ConditionCacheContext::GRANULE_SIZE; |
408 | 1 | const int64_t granule_end = granule_start + ConditionCacheContext::GRANULE_SIZE; |
409 | 1 | const RowRange file_granule_range {.start = granule_start - row_group_first_row, |
410 | 1 | .length = granule_end - granule_start}; |
411 | 1 | append_intersection(range, file_granule_range, &result); |
412 | 1 | } |
413 | 1 | } |
414 | 1 | return result; |
415 | 1 | } |
416 | | |
417 | | } // namespace |
418 | | |
419 | 106 | void ParquetScanScheduler::set_plan(RowGroupScanPlan plan) { |
420 | 106 | _row_group_plans = std::move(plan.row_groups); |
421 | 106 | _condition_cache_filtered_rows = 0; |
422 | 106 | _predicate_filtered_rows = 0; |
423 | 106 | reset(); |
424 | 106 | } |
425 | | |
426 | 2 | void ParquetScanScheduler::set_condition_cache_context(std::shared_ptr<ConditionCacheContext> ctx) { |
427 | 2 | _condition_cache_ctx = std::move(ctx); |
428 | 2 | if (!_condition_cache_ctx || !_condition_cache_ctx->filter_result || _row_group_plans.empty()) { |
429 | 0 | return; |
430 | 0 | } |
431 | | |
432 | 2 | _condition_cache_ctx->base_granule = |
433 | 2 | _row_group_plans.front().first_file_row / ConditionCacheContext::GRANULE_SIZE; |
434 | 2 | if (!_condition_cache_ctx->is_hit) { |
435 | 1 | return; |
436 | 1 | } |
437 | | |
438 | 1 | std::vector<RowGroupReadPlan> filtered_plans; |
439 | 1 | filtered_plans.reserve(_row_group_plans.size()); |
440 | 1 | for (auto& plan : _row_group_plans) { |
441 | 1 | const int64_t old_rows = count_range_rows(plan.selected_ranges); |
442 | 1 | plan.selected_ranges = filter_ranges_by_condition_cache( |
443 | 1 | plan.selected_ranges, *_condition_cache_ctx->filter_result, plan.first_file_row, |
444 | 1 | _condition_cache_ctx->base_granule); |
445 | 1 | const int64_t new_rows = count_range_rows(plan.selected_ranges); |
446 | 1 | _condition_cache_filtered_rows += old_rows - new_rows; |
447 | 1 | if (!plan.selected_ranges.empty()) { |
448 | 1 | filtered_plans.push_back(std::move(plan)); |
449 | 1 | } |
450 | 1 | } |
451 | 1 | _row_group_plans = std::move(filtered_plans); |
452 | 1 | reset(); |
453 | 1 | } |
454 | | |
455 | 107 | void ParquetScanScheduler::reset() { |
456 | 107 | _next_row_group_plan_idx = 0; |
457 | 107 | _raw_rows_read = 0; |
458 | 107 | reset_current_row_group(); |
459 | 107 | } |
460 | | |
461 | 157 | void ParquetScanScheduler::reset_current_row_group() { |
462 | 157 | _current_row_group.reset(); |
463 | 157 | _current_predicate_columns.clear(); |
464 | 157 | _current_non_predicate_columns.clear(); |
465 | 157 | _current_row_group_rows = 0; |
466 | 157 | _current_row_group_id = -1; |
467 | 157 | _current_row_group_rows_read = 0; |
468 | 157 | _current_row_group_first_row = 0; |
469 | 157 | _current_selected_ranges.clear(); |
470 | 157 | _current_range_idx = 0; |
471 | 157 | _current_range_rows_read = 0; |
472 | 157 | _current_predicate_prefetched = false; |
473 | 157 | _current_non_predicate_prefetched = false; |
474 | 157 | _current_merge_range_active = false; |
475 | 157 | } |
476 | | |
477 | | Status ParquetScanScheduler::open_next_row_group( |
478 | | ParquetFileContext& file_context, |
479 | | const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema, |
480 | 137 | const format::FileScanRequest& request, bool* has_row_group) { |
481 | 137 | *has_row_group = false; |
482 | 137 | if (_next_row_group_plan_idx >= _row_group_plans.size()) { |
483 | 39 | return Status::OK(); |
484 | 39 | } |
485 | 98 | const RowGroupReadPlan& row_group_plan = _row_group_plans[_next_row_group_plan_idx++]; |
486 | 98 | const int row_group_idx = row_group_plan.row_group_id; |
487 | 98 | _current_merge_range_active = |
488 | 98 | prepare_current_row_group_reader(file_context, file_schema, request, row_group_idx); |
489 | 98 | try { |
490 | 98 | _current_row_group = file_context.file_reader->RowGroup(row_group_idx); |
491 | 98 | } catch (const ::parquet::ParquetException& e) { |
492 | 0 | return Status::Corruption("Failed to open parquet row group {}: {}", row_group_idx, |
493 | 0 | e.what()); |
494 | 0 | } catch (const std::exception& e) { |
495 | 0 | return Status::InternalError("Failed to open parquet row group {}: {}", row_group_idx, |
496 | 0 | e.what()); |
497 | 0 | } |
498 | | |
499 | 98 | auto row_group_metadata = file_context.metadata->RowGroup(row_group_idx); |
500 | 98 | DORIS_CHECK(row_group_metadata != nullptr); |
501 | 98 | _current_row_group_rows = row_group_metadata->num_rows(); |
502 | 98 | DORIS_CHECK(_current_row_group_rows == row_group_plan.row_group_rows); |
503 | 98 | DORIS_CHECK(_current_row_group_rows > 0); |
504 | 98 | _current_row_group_id = row_group_idx; |
505 | 98 | DORIS_CHECK(!row_group_plan.selected_ranges.empty()); |
506 | 98 | _current_row_group_first_row = row_group_plan.first_file_row; |
507 | 98 | _current_row_group_rows_read = 0; |
508 | 98 | _current_selected_ranges = row_group_plan.selected_ranges; |
509 | 98 | _current_range_idx = 0; |
510 | 98 | _current_range_rows_read = 0; |
511 | 98 | _current_predicate_columns.clear(); |
512 | 98 | _current_non_predicate_columns.clear(); |
513 | | |
514 | 98 | ParquetColumnReaderFactory column_reader_factory( |
515 | 98 | _current_row_group, file_context.schema->num_columns(), &row_group_plan.page_skip_plans, |
516 | 98 | _page_skip_profile, _timezone, _enable_strict_mode, |
517 | 98 | _scan_profile.column_reader_profile); |
518 | 98 | for (const auto& col : request.predicate_columns) { |
519 | 52 | const auto local_id = col.local_id(); |
520 | 52 | if (local_id == format::ROW_POSITION_COLUMN_ID) { |
521 | 11 | _current_predicate_columns[local_id] = |
522 | 11 | column_reader_factory.create_row_position_column_reader( |
523 | 11 | _current_row_group_first_row); |
524 | 11 | continue; |
525 | 11 | } |
526 | 41 | if (local_id == format::GLOBAL_ROWID_COLUMN_ID) { |
527 | 0 | DORIS_CHECK(_global_rowid_context.has_value()); |
528 | 0 | _current_predicate_columns[local_id] = |
529 | 0 | column_reader_factory.create_global_rowid_column_reader( |
530 | 0 | *_global_rowid_context, _current_row_group_first_row); |
531 | 0 | continue; |
532 | 0 | } |
533 | | |
534 | 41 | DORIS_CHECK(local_id >= 0 && local_id < static_cast<int32_t>(file_schema.size())); |
535 | 41 | const auto& column_schema = file_schema[local_id]; |
536 | 41 | DORIS_CHECK(column_schema != nullptr); |
537 | 41 | std::unique_ptr<ParquetColumnReader> column_reader; |
538 | 41 | RETURN_IF_ERROR(column_reader_factory.create(*column_schema, &col, &column_reader)); |
539 | 41 | _current_predicate_columns[local_id] = std::move(column_reader); |
540 | 41 | } |
541 | | // Start warming filter-column chunks as soon as their row group is selected. Parquet v2 still |
542 | | // reads through Arrow's random-access reader; this prefetch only warms Doris file cache blocks |
543 | | // in the background and never changes the row/column materialization order. |
544 | 98 | if (!_current_merge_range_active) { |
545 | 11 | prefetch_current_row_group_columns(file_context, file_schema, request.predicate_columns, |
546 | 11 | &_current_predicate_prefetched); |
547 | 11 | } |
548 | 110 | for (const auto& col : request.non_predicate_columns) { |
549 | 110 | const auto local_id = col.local_id(); |
550 | 110 | if (local_id == format::ROW_POSITION_COLUMN_ID) { |
551 | 14 | _current_non_predicate_columns[local_id] = |
552 | 14 | column_reader_factory.create_row_position_column_reader( |
553 | 14 | _current_row_group_first_row); |
554 | 14 | continue; |
555 | 14 | } |
556 | 96 | if (local_id == format::GLOBAL_ROWID_COLUMN_ID) { |
557 | 2 | DORIS_CHECK(_global_rowid_context.has_value()); |
558 | 2 | _current_non_predicate_columns[local_id] = |
559 | 2 | column_reader_factory.create_global_rowid_column_reader( |
560 | 2 | *_global_rowid_context, _current_row_group_first_row); |
561 | 2 | continue; |
562 | 2 | } |
563 | 94 | DORIS_CHECK(local_id >= 0 && local_id < static_cast<int32_t>(file_schema.size())); |
564 | 94 | const auto& column_schema = file_schema[local_id]; |
565 | 94 | DORIS_CHECK(column_schema != nullptr); |
566 | 94 | std::unique_ptr<ParquetColumnReader> column_reader; |
567 | 94 | RETURN_IF_ERROR(column_reader_factory.create(*column_schema, &col, &column_reader)); |
568 | 94 | _current_non_predicate_columns[local_id] = std::move(column_reader); |
569 | 94 | } |
570 | 98 | if (!_current_merge_range_active && request.conjuncts.empty() && |
571 | 98 | request.delete_conjuncts.empty()) { |
572 | | // With no row-level filters there is no lazy-read decision to wait for, so start warming |
573 | | // output chunks immediately after their readers are created. Filtered scans still defer |
574 | | // this until at least one row survives the predicate phase. |
575 | 11 | prefetch_current_row_group_columns(file_context, file_schema, request.non_predicate_columns, |
576 | 11 | &_current_non_predicate_prefetched); |
577 | 11 | } |
578 | 98 | *has_row_group = true; |
579 | 98 | return Status::OK(); |
580 | 98 | } |
581 | | |
582 | 3 | Status ParquetScanScheduler::skip_current_row_group_rows(int64_t rows) { |
583 | 3 | DORIS_CHECK(rows >= 0); |
584 | 3 | if (rows == 0) { |
585 | 0 | return Status::OK(); |
586 | 0 | } |
587 | 3 | if (_scan_profile.range_gap_skipped_rows != nullptr) { |
588 | 2 | COUNTER_UPDATE(_scan_profile.range_gap_skipped_rows, rows); |
589 | 2 | } |
590 | 3 | for (const auto& column_reader : _current_predicate_columns | std::views::values) { |
591 | 3 | RETURN_IF_ERROR(column_reader->skip(rows)); |
592 | 3 | } |
593 | 3 | for (const auto& column_reader : _current_non_predicate_columns | std::views::values) { |
594 | 1 | RETURN_IF_ERROR(column_reader->skip(rows)); |
595 | 1 | } |
596 | 3 | _current_row_group_rows_read += rows; |
597 | 3 | return Status::OK(); |
598 | 3 | } |
599 | | |
600 | | Status ParquetScanScheduler::read_filter_columns(int64_t batch_rows, |
601 | | const format::FileScanRequest& request, |
602 | | Block* file_block, SelectionVector* selection, |
603 | | uint16_t* selected_rows, |
604 | 91 | int64_t* conjunct_filtered_rows) { |
605 | 91 | if (!request.conjuncts.empty() || !request.delete_conjuncts.empty()) { |
606 | 48 | selection->resize(static_cast<size_t>(batch_rows)); |
607 | 48 | } |
608 | 91 | for (const auto& [fid, column_reader] : _current_predicate_columns) { |
609 | 52 | auto position_it = request.local_positions.find(format::LocalColumnId(fid)); |
610 | 52 | DORIS_CHECK(position_it != request.local_positions.end()); |
611 | 52 | const auto block_position = position_it->second.value(); |
612 | 52 | DCHECK(remove_nullable(column_reader->type()) |
613 | 0 | ->equals(*remove_nullable(file_block->get_by_position(block_position).type))) |
614 | 0 | << column_reader->type()->get_name() << " " |
615 | 0 | << file_block->get_by_position(block_position).type->get_name() << " " |
616 | 0 | << column_reader->name() << " " << file_block->get_by_position(block_position).name; |
617 | 52 | auto column = file_block->get_by_position(block_position).column->assert_mutable(); |
618 | 52 | int64_t column_rows = 0; |
619 | 52 | { |
620 | 52 | SCOPED_TIMER(_scan_profile.column_read_time); |
621 | 52 | RETURN_IF_ERROR(column_reader->read(batch_rows, column, &column_rows)); |
622 | 52 | } |
623 | 52 | if (column_rows != batch_rows) { |
624 | 0 | return Status::Corruption("Parquet filter column {} returned {} rows, expected {} rows", |
625 | 0 | column_reader->name(), column_rows, batch_rows); |
626 | 0 | } |
627 | 52 | file_block->replace_by_position(block_position, std::move(column)); |
628 | 52 | } |
629 | 91 | if (_scan_profile.predicate_filter_time == nullptr) { |
630 | 66 | return execute_batch_filters(request, batch_rows, file_block, selection, selected_rows, |
631 | 66 | conjunct_filtered_rows); |
632 | 66 | } |
633 | 25 | SCOPED_TIMER(_scan_profile.predicate_filter_time); |
634 | 25 | return execute_batch_filters(request, batch_rows, file_block, selection, selected_rows, |
635 | 25 | conjunct_filtered_rows); |
636 | 91 | } |
637 | | |
638 | | bool ParquetScanScheduler::prepare_current_row_group_reader( |
639 | | ParquetFileContext& file_context, |
640 | | const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema, |
641 | 98 | const format::FileScanRequest& request, int row_group_idx) { |
642 | 98 | if (file_context.metadata == nullptr) { |
643 | 0 | return false; |
644 | 0 | } |
645 | 98 | const auto ranges = build_row_group_prefetch_ranges( |
646 | 98 | *file_context.metadata, file_schema, request_scan_columns(request), row_group_idx); |
647 | 98 | const size_t avg_io_size = detail::average_prefetch_range_size(ranges); |
648 | 98 | return file_context.set_random_access_ranges(ranges, avg_io_size, _profile, |
649 | 98 | _merge_read_slice_size); |
650 | 98 | } |
651 | | |
652 | | void ParquetScanScheduler::prefetch_current_row_group_columns( |
653 | | ParquetFileContext& file_context, |
654 | | const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema, |
655 | 22 | const std::vector<format::LocalColumnIndex>& scan_columns, bool* prefetched) { |
656 | 22 | DORIS_CHECK(prefetched != nullptr); |
657 | 22 | if (_current_merge_range_active || *prefetched || scan_columns.empty() || |
658 | 22 | _current_row_group_id < 0 || file_context.metadata == nullptr) { |
659 | 22 | return; |
660 | 22 | } |
661 | 0 | *prefetched = true; |
662 | | // The scanner request separates predicate and non-predicate columns so Parquet can read |
663 | | // predicate columns first and lazily materialize the rest. Keep the same contract for |
664 | | // prefetch: callers decide which side to warm, and this helper only translates that selected |
665 | | // projection into physical column-chunk byte ranges for the current row group. |
666 | 0 | file_context.prefetch_ranges( |
667 | 0 | build_row_group_prefetch_ranges(*file_context.metadata, file_schema, scan_columns, |
668 | 0 | _current_row_group_id), |
669 | 0 | nullptr); |
670 | 0 | } |
671 | | |
672 | | Status ParquetScanScheduler::read_current_row_group_batch( |
673 | | ParquetFileContext& file_context, |
674 | | const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema, int64_t batch_rows, |
675 | | const format::FileScanRequest& request, int64_t batch_first_file_row, Block* file_block, |
676 | 102 | size_t* rows) { |
677 | 102 | if (_scan_profile.total_batches != nullptr) { |
678 | 25 | COUNTER_UPDATE(_scan_profile.total_batches, 1); |
679 | 25 | } |
680 | 102 | if (_scan_profile.raw_rows_read != nullptr) { |
681 | 25 | COUNTER_UPDATE(_scan_profile.raw_rows_read, batch_rows); |
682 | 25 | } |
683 | 102 | _raw_rows_read += batch_rows; |
684 | 102 | if (_current_predicate_columns.empty() && _current_non_predicate_columns.empty()) { |
685 | 11 | *rows = static_cast<size_t>(batch_rows); |
686 | 11 | if (_scan_profile.selected_rows != nullptr) { |
687 | 0 | COUNTER_UPDATE(_scan_profile.selected_rows, batch_rows); |
688 | 0 | } |
689 | 11 | return Status::OK(); |
690 | 11 | } |
691 | 91 | SelectionVector selection; |
692 | 91 | DORIS_CHECK(batch_rows <= std::numeric_limits<uint16_t>::max()); |
693 | 91 | uint16_t selected_rows = static_cast<uint16_t>(batch_rows); |
694 | 91 | int64_t conjunct_filtered_rows = 0; |
695 | 91 | RETURN_IF_ERROR(read_filter_columns(batch_rows, request, file_block, &selection, &selected_rows, |
696 | 91 | &conjunct_filtered_rows)); |
697 | 91 | _predicate_filtered_rows += conjunct_filtered_rows; |
698 | 91 | mark_condition_cache_granules(selection, selected_rows, batch_first_file_row); |
699 | | |
700 | 91 | const bool need_filter_output = selected_rows != batch_rows; |
701 | 91 | if (_scan_profile.selected_rows != nullptr) { |
702 | 25 | COUNTER_UPDATE(_scan_profile.selected_rows, selected_rows); |
703 | 25 | } |
704 | 91 | if (_scan_profile.rows_filtered_by_conjunct != nullptr) { |
705 | 25 | COUNTER_UPDATE(_scan_profile.rows_filtered_by_conjunct, conjunct_filtered_rows); |
706 | 25 | } |
707 | 91 | if (!_current_non_predicate_columns.empty() && |
708 | 91 | _scan_profile.lazy_read_filtered_rows != nullptr) { |
709 | 22 | COUNTER_UPDATE(_scan_profile.lazy_read_filtered_rows, batch_rows - selected_rows); |
710 | 22 | } |
711 | 91 | if (selected_rows == 0 && _scan_profile.empty_selection_batches != nullptr) { |
712 | 1 | COUNTER_UPDATE(_scan_profile.empty_selection_batches, 1); |
713 | 1 | } |
714 | 91 | if (need_filter_output) { |
715 | 28 | IColumn::Filter output_filter = selection_to_filter(selection, selected_rows, batch_rows); |
716 | 31 | for (const auto& col : request.predicate_columns) { |
717 | 31 | auto position_it = request.local_positions.find(col.column_id()); |
718 | 31 | DORIS_CHECK(position_it != request.local_positions.end()); |
719 | 31 | const auto block_position = position_it->second.value(); |
720 | 31 | RETURN_IF_CATCH_EXCEPTION(file_block->replace_by_position( |
721 | 31 | block_position, file_block->get_by_position(block_position) |
722 | 31 | .column->filter(output_filter, selected_rows))); |
723 | 31 | } |
724 | 28 | } |
725 | 91 | if (!_current_merge_range_active && selected_rows > 0 && |
726 | 91 | !_current_non_predicate_columns.empty()) { |
727 | | // Do not prefetch lazy output columns until at least one row survives filtering. This is |
728 | | // the same decision point where the v2 reader switches from predicate-only reads to |
729 | | // materializing non-predicate columns, so fully filtered batches avoid unnecessary IO. |
730 | 0 | prefetch_current_row_group_columns(file_context, file_schema, request.non_predicate_columns, |
731 | 0 | &_current_non_predicate_prefetched); |
732 | 0 | } |
733 | | |
734 | 91 | { |
735 | 91 | SCOPED_TIMER(_scan_profile.column_read_time); |
736 | 118 | for (const auto& [fid, column_reader] : _current_non_predicate_columns) { |
737 | 118 | auto position_it = request.local_positions.find(format::LocalColumnId(fid)); |
738 | 118 | DORIS_CHECK(position_it != request.local_positions.end()); |
739 | 118 | const auto block_position = position_it->second.value(); |
740 | 118 | auto column = file_block->get_by_position(block_position).column->assert_mutable(); |
741 | 118 | DCHECK_EQ(file_block->get_by_position(block_position).type->get_primitive_type(), |
742 | 0 | column_reader->type()->get_primitive_type()) |
743 | 0 | << type_to_string(file_block->get_by_position(block_position) |
744 | 0 | .type->get_primitive_type()) |
745 | 0 | << " " << type_to_string(column_reader->type()->get_primitive_type()) << " " |
746 | 0 | << column_reader->name() << " " << fid << " " << block_position; |
747 | 118 | if (need_filter_output) { |
748 | 23 | [[maybe_unused]] auto old_size = column->size(); |
749 | 23 | RETURN_IF_ERROR( |
750 | 23 | column_reader->select(selection, selected_rows, batch_rows, column)); |
751 | 23 | if (column->size() != old_size + selected_rows) { |
752 | 0 | return Status::Corruption( |
753 | 0 | "Parquet selected output column {} returned {} rows, expected {} rows", |
754 | 0 | column_reader->name(), column->size(), old_size + selected_rows); |
755 | 0 | } |
756 | 95 | } else { |
757 | 95 | int64_t column_rows = 0; |
758 | 95 | RETURN_IF_ERROR(column_reader->read(batch_rows, column, &column_rows)); |
759 | 95 | if (column_rows != batch_rows) { |
760 | 0 | return Status::Corruption( |
761 | 0 | "Parquet output column {} returned {} rows, expected {} rows", |
762 | 0 | column_reader->name(), column_rows, batch_rows); |
763 | 0 | } |
764 | 95 | } |
765 | 118 | file_block->replace_by_position(block_position, std::move(column)); |
766 | 118 | } |
767 | 91 | } |
768 | 91 | *rows = static_cast<size_t>(selected_rows); |
769 | 91 | return Status::OK(); |
770 | 91 | } |
771 | | |
772 | | void ParquetScanScheduler::mark_condition_cache_granules(const SelectionVector& selection, |
773 | | uint16_t selected_rows, |
774 | 91 | int64_t batch_first_file_row) { |
775 | 91 | if (!_condition_cache_ctx || _condition_cache_ctx->is_hit || |
776 | 91 | !_condition_cache_ctx->filter_result) { |
777 | 90 | return; |
778 | 90 | } |
779 | 1 | auto& cache = *_condition_cache_ctx->filter_result; |
780 | 2.04k | for (uint16_t selection_idx = 0; selection_idx < selected_rows; ++selection_idx) { |
781 | 2.04k | const int64_t file_row = batch_first_file_row + selection.get_index(selection_idx); |
782 | 2.04k | const int64_t granule = file_row / ConditionCacheContext::GRANULE_SIZE; |
783 | 2.04k | const int64_t cache_idx = granule - _condition_cache_ctx->base_granule; |
784 | 2.04k | if (cache_idx >= 0 && static_cast<size_t>(cache_idx) < cache.size()) { |
785 | 2.04k | cache[static_cast<size_t>(cache_idx)] = true; |
786 | 2.04k | } |
787 | 2.04k | } |
788 | 1 | } |
789 | | |
790 | | Status ParquetScanScheduler::read_next_batch( |
791 | | ParquetFileContext& file_context, |
792 | | const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema, |
793 | 140 | const format::FileScanRequest& request, Block* file_block, size_t* rows, bool* eof) { |
794 | 140 | *rows = 0; |
795 | 191 | while (true) { |
796 | 191 | if (_current_row_group == nullptr) { |
797 | 137 | bool has_row_group = false; |
798 | 137 | RETURN_IF_ERROR( |
799 | 137 | open_next_row_group(file_context, file_schema, request, &has_row_group)); |
800 | 137 | if (!has_row_group) { |
801 | 39 | *eof = true; |
802 | 39 | return Status::OK(); |
803 | 39 | } |
804 | 137 | } |
805 | | |
806 | 152 | if (_current_range_idx >= _current_selected_ranges.size()) { |
807 | | // Current row group finished, try next row group. |
808 | 50 | reset_current_row_group(); |
809 | 50 | continue; |
810 | 50 | } |
811 | | |
812 | 102 | const RowRange& current_range = _current_selected_ranges[_current_range_idx]; |
813 | 102 | DORIS_CHECK(current_range.start >= 0); |
814 | 102 | DORIS_CHECK(current_range.length > 0); |
815 | 102 | DORIS_CHECK(current_range.start + current_range.length <= _current_row_group_rows); |
816 | | |
817 | 102 | if (_current_row_group_rows_read < current_range.start) { |
818 | | // Skip filtered rows according to row group level pruning. |
819 | 3 | RETURN_IF_ERROR(skip_current_row_group_rows(current_range.start - |
820 | 3 | _current_row_group_rows_read)); |
821 | 3 | } |
822 | 102 | DORIS_CHECK(_current_row_group_rows_read == current_range.start + _current_range_rows_read); |
823 | 102 | const int64_t remaining_rows = current_range.length - _current_range_rows_read; |
824 | 102 | if (remaining_rows <= 0) { |
825 | | // Current range finished, try next range in the same row group. |
826 | 0 | ++_current_range_idx; |
827 | 0 | _current_range_rows_read = 0; |
828 | 0 | continue; |
829 | 0 | } |
830 | | |
831 | 102 | const int64_t batch_rows = std::min<int64_t>(_batch_size, remaining_rows); |
832 | 102 | const int64_t physical_rows_read = batch_rows; |
833 | 102 | const int64_t batch_first_file_row = |
834 | 102 | _current_row_group_first_row + _current_row_group_rows_read; |
835 | 102 | RETURN_IF_ERROR(read_current_row_group_batch(file_context, file_schema, batch_rows, request, |
836 | 102 | batch_first_file_row, file_block, rows)); |
837 | 102 | _current_row_group_rows_read += physical_rows_read; |
838 | 102 | _current_range_rows_read += physical_rows_read; |
839 | 102 | if (_current_range_rows_read >= current_range.length) { |
840 | 98 | ++_current_range_idx; |
841 | 98 | _current_range_rows_read = 0; |
842 | 98 | } |
843 | 102 | if (*rows == 0) { |
844 | 1 | continue; |
845 | 1 | } |
846 | 101 | *eof = false; |
847 | 101 | return Status::OK(); |
848 | 102 | } |
849 | 140 | } |
850 | | |
851 | | } // namespace doris::format::parquet |